diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f56f222..8c8ceca 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,8 +1,12 @@ -github: raxod502 -patreon: riju -ko_fi: riju_codes -liberapay: riju +github: radian-software +patreon: radiansoftware +ko_fi: radiansoftware +liberapay: radian-software custom: - - https://paypal.me/rijucodes - - https://cash.app/$RijuCodes - - https://venmo.com/code?user_id=3335527067549696598 + - https://www.paypal.com/donate/?hosted_button_id=SYF48KFJ95FPA + - https://cash.app/$RadianSoftware + # Venmo is not currently supported because it is impossible to + # create a new business account when one has been created at any + # point in the past, even if it has been deleted. I have reached out + # to Venmo support and they have confirmed there is no supported way + # to use Venmo going forward, and suggested I use PayPal instead. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index a30b7ff..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Build and deploy -on: - push: - branches: - - master -concurrency: deploy -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - # - name: Build and deploy - # env: - # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - # AWS_REGION: us-west-1 - # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - # run: tools/ci-ec2.bash diff --git a/.gitignore b/.gitignore index 9bc9590..3934bba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ *.log +*.out +*.out.* *.pem .env .lsp-repl-history .terraform +bin build +env.yaml node_modules out sentinel.h -financials/????-??/* -!financials/????-??/breakdown.txt +agent/agent diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6009098..a274323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,6 @@ * [Deploying your own instance of Riju](doc/selfhosting.md) If you'd like to request a new language, head to the [language support -meta-issue](https://github.com/raxod502/riju/issues/24) and add a -comment. Of course, if you actually want it to be added anytime soon, -you should submit a pull request :) +meta-issue](https://github.com/radian-software/riju/issues/24) and add +a comment. Of course, if you actually want it to be added anytime +soon, you should submit a pull request :) diff --git a/LICENSE.md b/LICENSE.md index ad907d4..bac17b1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,7 @@ # MIT License -Copyright (c) 2020 Radon Rosborough +Copyright (c) 2020–2022 [Radian LLC](https://radian.codes) and +contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 819a0b4..23a3914 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,9 @@ export BUILD := build/$(T)/$(L) DEB := riju-$(T)-$(L).deb S3 := s3://$(S3_BUCKET) +S3_CONFIG_PATH ?= config.json S3_DEB := $(S3)/debs/$(DEB) -S3_HASH := $(S3)/hashes/riju-$(T)-$(L) -S3_CONFIG := $(S3)/config.json +S3_CONFIG := $(S3)/$(S3_CONFIG_PATH) ifneq ($(CMD),) C_CMD := -c '$(CMD)' @@ -51,24 +51,25 @@ image: # I= [L=] [NC=1] : Build a Docker image ifeq ($(I),lang) @: $${L} node tools/build-lang-image.js --lang $(L) -else ifeq ($(I),ubuntu) - docker pull ubuntu:rolling - hash="$$(docker inspect ubuntu:rolling -f '{{ .Id }}' | sha1sum | awk '{ print $$1 }')"; echo "FROM ubuntu:rolling" | docker build --label riju.image-hash="$${hash}" -t riju:$(I) - else ifneq (,$(filter $(I),admin ci)) docker build . -f docker/$(I)/Dockerfile -t riju:$(I) $(NO_CACHE) else - hash="$$(node tools/hash-dockerfile.js $(I) | grep .)"; docker build . -f docker/$(I)/Dockerfile -t riju:$(I) --label riju.image-hash="$${hash}" $(NO_CACHE) + docker build . -f docker/$(I)/Dockerfile -t riju:$(I) $(NO_CACHE) endif VOLUME_MOUNT ?= $(PWD) +# http P1 ?= 6119 +# https P2 ?= 6120 +# metrics +P3 ?= 6121 ifneq (,$(EE)) -SHELL_PORTS := -p 0.0.0.0:$(P1):6119 -p 0.0.0.0:$(P2):6120 +SHELL_PORTS := -p 0.0.0.0:$(P1):6119 -p 0.0.0.0:$(P2):6120 -p 0.0.0.0:$(P3):6121 else ifneq (,$(E)) -SHELL_PORTS := -p 127.0.0.1:$(P1):6119 -p 127.0.0.1:$(P2):6120 +SHELL_PORTS := -p 127.0.0.1:$(P1):6119 -p 127.0.0.1:$(P2):6120 -p 127.0.0.1:$(P3):6121 else SHELL_PORTS := endif @@ -81,25 +82,22 @@ else LANG_TAG := $(I) endif -IMAGE_HASH := "$$(docker inspect riju:$(LANG_TAG) -f '{{ index .Config.Labels "riju.image-hash" }}')" -WITH_IMAGE_HASH := -e RIJU_IMAGE_HASH=$(IMAGE_HASH) - shell: # I= [L=] [E[E]=1] [P1|P2=] [CMD="..."] : Launch Docker image with shell @: $${I} ifneq (,$(filter $(I),admin ci)) @mkdir -p $(HOME)/.aws $(HOME)/.docker $(HOME)/.ssh $(HOME)/.terraform.d - docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock -v $(HOME)/.aws:/var/cache/riju/.aws -v $(HOME)/.docker:/var/cache/riju/.docker -v $(HOME)/.ssh:/var/cache/riju/.ssh -v $(HOME)/.terraform.d:/var/cache/riju/.terraform.d -e NI -e AWS_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e DOCKER_REPO -e PUBLIC_DOCKER_REPO -e S3_BUCKET -e DOMAIN -e VOLUME_MOUNT=$(VOLUME_MOUNT) $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) --network host riju:$(I) $(BASH_CMD) + docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock -v $(HOME)/.aws:/var/cache/riju/.aws -v $(HOME)/.docker:/var/cache/riju/.docker -v $(HOME)/.ssh:/var/cache/riju/.ssh -v $(HOME)/.terraform.d:/var/cache/riju/.terraform.d -e NI -e AWS_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e DOCKER_REPO -e PUBLIC_DOCKER_REPO -e S3_BUCKET -e DOMAIN -e VOLUME_MOUNT=$(VOLUME_MOUNT) $(SHELL_PORTS) $(SHELL_ENV) --network host riju:$(I) $(BASH_CMD) else ifeq ($(I),app) - docker run $(IT_ARG) --rm --hostname $(I) -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(I) $(BASH_CMD) + docker run $(IT_ARG) --rm --hostname $(I) -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD) else ifneq (,$(filter $(I),base lang)) ifeq ($(I),lang) @: $${L} endif - docker run $(IT_ARG) --rm --hostname $(LANG_TAG) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(LANG_TAG) $(BASH_CMD) + docker run $(IT_ARG) --rm --hostname $(LANG_TAG) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) riju:$(LANG_TAG) $(BASH_CMD) else ifeq ($(I),runtime) - docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(I) $(BASH_CMD) + docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src -v /var/cache/riju:/var/cache/riju -v /var/run/docker.sock:/var/run/docker.sock $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD) else - docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) $(WITH_IMAGE_HASH) riju:$(I) $(BASH_CMD) + docker run $(IT_ARG) --rm --hostname $(I) -v $(VOLUME_MOUNT):/src $(SHELL_PORTS) $(SHELL_ENV) riju:$(I) $(BASH_CMD) endif ecr: # Authenticate to ECR (temporary credentials) @@ -213,7 +211,7 @@ lsp: # L= : Run LSP REPL for language or custom command line ### Fetch artifacts from registries -PUBLIC_DOCKER_REPO_PULL ?= public.ecr.aws/raxod502/riju +PUBLIC_DOCKER_REPO_PULL ?= public.ecr.aws/radian-software/riju sync-ubuntu: # Pull Riju Ubuntu image from public Docker registry docker pull $(PUBLIC_DOCKER_REPO_PULL):ubuntu @@ -237,21 +235,13 @@ undeploy: # Pull latest deployment config from S3 push: # I= : Push Riju image to Docker registry @: $${I} $${DOCKER_REPO} - docker tag riju:$(I) $(DOCKER_REPO):$(I)-$(IMAGE_HASH) - docker push $(DOCKER_REPO):$(I)-$(IMAGE_HASH) -ifeq ($(I),ubuntu) - docker tag riju:$(I) $(PUBLIC_DOCKER_REPO):$(I) - docker push $(PUBLIC_DOCKER_REPO):$(I) -endif docker tag riju:$(I) $(DOCKER_REPO):$(I) docker push $(DOCKER_REPO):$(I) upload: # L= T= : Upload .deb to S3 @: $${L} $${T} $${S3_BUCKET} tools/ensure-deb-compressed.bash - aws s3 rm --recursive $(S3_HASH) aws s3 cp $(BUILD)/$(DEB) $(S3_DEB) - hash="$$(dpkg-deb -f $(BUILD)/$(DEB) Riju-Script-Hash | grep .)"; aws s3 cp - "$(S3_HASH)/$${hash}" < /dev/null deploy-config: # Generate deployment config file node tools/generate-deploy-config.js @@ -282,11 +272,13 @@ fmt: fmt-c fmt-go fmt-python fmt-terraform fmt-web # Format all code ### Infrastructure -packer-web: supervisor # Build and publish a new webserver AMI - tools/packer-build-web.bash +packer: supervisor # Build and publish a new webserver AMI + tools/packer-build.bash -packer-ci: # Build and publish a new CI AMI - tools/packer-build-ci.bash +deploy-alerts: # Deploy alerting configuration to Grafana Cloud + envsubst < grafana/alertmanager.yaml > grafana/alertmanager.yaml.out + cortextool rules load grafana/alerts.yaml --address=https://$(GRAFANA_PROMETHEUS_HOSTNAME) --id=$(GRAFANA_PROMETHEUS_USERNAME) --key=$(GRAFANA_API_KEY) + cortextool alertmanager load grafana/alertmanager.yaml.out --address=https://alertmanager-us-central1.grafana.net --id=$(GRAFANA_ALERTMANAGER_USERNAME) --key=$(GRAFANA_API_KEY) ### Miscellaneous diff --git a/README.md b/README.md index 1a74c56..f75870e 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,18 @@ or compiling [INTERCAL](https://en.wikipedia.org/wiki/INTERCAL) code. Check it out at ! -Service uptime available at . +Service uptime available at . ## Is it free? -Riju will always be free for everyone. I pay for the hosting costs -myself. +Riju will always be free for everyone. I pay for the hosting costs out +of the business account of Radian LLC, which is funded by donations +and my personal savings. If you would like to help keep Riju online +and see more projects like it, there are a few donation methods +available in the "Sponsor this project" sidebar on GitHub. -A number of people have asked me if they can donate to help keep Riju -online. In response, I have set up a few methods, which you can see in -the "Sponsor this project" sidebar on GitHub. All donations will be -used solely to cover hosting costs, and any surplus will be donated to -the [Electronic Frontier Foundation](https://www.eff.org/). +All financial records for Radian LLC are made [publicly +available](https://github.com/radian-software/financials). ## Is it safe? diff --git a/SECURITY.md b/SECURITY.md index 7d94501..12d2165 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,8 @@ # Reporting a security issue Please contact me at -[radon.neon@gmail.com](mailto:radon.neon@gmail.com) if you find any -way to: +[security+riju@radian.codes](mailto:security+riju@radian.codes) if you +find any way to: * Take down Riju without using a large number of concurrent sessions. * View or interfere with another user's session. diff --git a/agent/go.mod b/agent/go.mod new file mode 100644 index 0000000..bccdbc0 --- /dev/null +++ b/agent/go.mod @@ -0,0 +1,8 @@ +module github.com/radian-software/riju/agent + +go 1.18 + +require ( + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gorilla/websocket v1.5.0 // indirect +) diff --git a/agent/go.sum b/agent/go.sum new file mode 100644 index 0000000..3b8bc42 --- /dev/null +++ b/agent/go.sum @@ -0,0 +1,4 @@ +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/agent/logging.go b/agent/logging.go new file mode 100644 index 0000000..7906905 --- /dev/null +++ b/agent/logging.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "io" + "log" +) + +func logWarn(err error) { + log.Println(err.Error()) +} + +func logWarnf(format string, arg ...interface{}) { + logWarn(fmt.Errorf(format, arg...)) +} + +func logError(err error) { + log.Println(err.Error()) +} + +func logErrorf(format string, arg ...interface{}) { + logError(fmt.Errorf(format, arg...)) +} + +func tryClose(obj io.Closer, objName string) { + err := obj.Close() + if err != nil { + logErrorf("error closing %s: %w", objName, err) + } +} diff --git a/agent/main.go b/agent/main.go new file mode 100644 index 0000000..1eda23e --- /dev/null +++ b/agent/main.go @@ -0,0 +1,225 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "os/exec" + "time" + + "github.com/google/shlex" + "github.com/gorilla/websocket" +) + +type clientMessage struct { + // "stdin" + Event string `json:"event"` + // contents of stdin + Data []byte `json:"data,omitempty"` +} + +type serverMessage struct { + // "start", "stdout", "stderr", "exit", "warn", "error" + Event string `json:"event"` + // contents of stdout/stderr + Data []byte `json:"data,omitempty"` + // error message + Text string `json:"text,omitempty"` + // exit status + ExitStatus *int `json:"exitStatus,omitempty"` +} + +var upgrader = websocket.Upgrader{} + +func closeWs(ms *ManagedWebsocket) { + ms.CloseChan <- struct{}{} +} + +func send(ms *ManagedWebsocket, msg *serverMessage) { + data, err := json.Marshal(msg) + if err != nil { + logErrorf("marshaling message: %w", err) + closeWs(ms) + return + } + ms.OutgoingChan <- data +} + +func fatal(ms *ManagedWebsocket, err error) { + send(ms, &serverMessage{ + Event: "fatal", + Text: err.Error(), + }) +} + +func fatalf(ms *ManagedWebsocket, format string, arg ...interface{}) { + fatal(ms, fmt.Errorf(format, arg...)) +} + +func warn(ms *ManagedWebsocket, err error) { + send(ms, &serverMessage{ + Event: "warn", + Text: err.Error(), + }) +} + +func warnf(ms *ManagedWebsocket, format string, arg ...interface{}) { + warn(ms, fmt.Errorf(format, arg...)) +} + +func getCommandPrefix() []string { + prefix := os.Getenv("RIJU_AGENT_COMMAND_PREFIX") + if prefix == "" { + logErrorf("must specify RIJU_AGENT_COMMAND_PREFIX for security reasons") + os.Exit(1) + } + if prefix == "0" { + return []string{} + } + list, err := shlex.Split(prefix) + if err != nil { + logErrorf("parsing RIJU_AGENT_COMMAND_PREFIX: %w", err) + os.Exit(1) + } + return list +} + +var CommandPrefix = getCommandPrefix() + +// https://github.com/gorilla/websocket/blob/76ecc29eff79f0cedf70c530605e486fc32131d1/examples/command/main.go +func handler(w http.ResponseWriter, r *http.Request) { + // Upgrade http connection to websocket + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + logErrorf("upgrading connection: %w", err) + return + } + // Set up channels to handle incoming and outgoing websocket + // messages more conveniently, and also to handle closing the + // websocket on error or when we ask. + ms := &ManagedWebsocket{ + Socket: ws, + + MessageType: websocket.TextMessage, + PingPeriod: 5 * time.Second, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + ms.Init() + // Ensure that websocket will be closed eventually when we + // exit. + defer closeWs(ms) + // Parse request query parameters; do this after upgrading to + // websocket so that we can send errors back on the websocket + // which is easier for clients to parse + cmdline := r.URL.Query()["cmdline"] + if len(cmdline) == 0 { + fatalf(ms, "cmdline query parameter missing") + return + } + cmdline = append(CommandPrefix, cmdline...) + binary, err := exec.LookPath(cmdline[0]) + if err != nil { + fatalf(ms, "searching for executable: %w", err) + return + } + // Spawn subprocess + mp, err := NewManagedProcess(binary, cmdline, nil) + if err != nil { + fatalf(ms, "spawning process: %w", err) + return + } + // Ensure eventual process termination + defer func() { + mp.CloseChan <- struct{}{} + }() + // Handle received messages from client + go func() { + for data := range ms.IncomingChan { + msg := clientMessage{} + err := json.Unmarshal(data, &msg) + if err != nil { + warnf(ms, "parsing json: %w", err) + continue + } + switch msg.Event { + case "stdin": + mp.StdinChan <- msg.Data + default: + logWarnf("received unknown event type %s", msg.Event) + } + } + }() + // Proxy stdout and stderr from subprocess + go func() { + for data := range mp.StdoutChan { + msg, err := json.Marshal(&serverMessage{ + Event: "stdout", + Data: data, + }) + if err != nil { + warnf(ms, "wrapping stdout in json: %w", err) + return + } + ms.OutgoingChan <- msg + } + }() + go func() { + for data := range mp.StderrChan { + msg, err := json.Marshal(&serverMessage{ + Event: "stderr", + Data: data, + }) + if err != nil { + warnf(ms, "wrapping stderr in json: %w", err) + return + } + ms.OutgoingChan <- msg + } + }() + // Send info about process exit status + exitChan2 := make(chan struct{}, 16) + go func() { + for status := range mp.ExitChan { + exitChan2 <- struct{}{} + code := status.ExitCode() + send(ms, &serverMessage{ + Event: "exit", + ExitStatus: &code, + }) + } + }() + // Wait until one of subprocess or websocket exits. The other + // one will be cleaned up on return. + select { + case <-exitChan2: + case <-ms.ClosedChan: + } + // Wait a bit to send any pending messages before closing the + // connection. + time.Sleep(1 * time.Second) + return +} + +func main() { + port := os.Getenv("RIJU_AGENT_PORT") + if port == "" { + port = "869" + } + host := os.Getenv("RIJU_AGENT_HOST") + if host == "" { + host = "0.0.0.0" + } + fmt.Printf("Listening on http://%s:%s\n", host, port) + mux := http.NewServeMux() + mux.HandleFunc("/exec", handler) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + err := http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), mux) + if err != nil { + logError(err) + os.Exit(1) + } +} diff --git a/agent/process.go b/agent/process.go new file mode 100644 index 0000000..5739e9e --- /dev/null +++ b/agent/process.go @@ -0,0 +1,164 @@ +package main + +import ( + "fmt" + "os" + "syscall" + "time" +) + +type managedProcess struct { + proc *os.Process + + stdinRead *os.File + stdinWrite *os.File + stdoutRead *os.File + stdoutWrite *os.File + stderrRead *os.File + stderrWrite *os.File + + internalExitChan chan struct{} + + StdinChan chan []byte + StdoutChan chan []byte + StderrChan chan []byte + ExitChan chan *os.ProcessState + CloseChan chan struct{} +} + +func NewManagedProcess(name string, argv []string, attr *os.ProcAttr) (*managedProcess, error) { + mp := &managedProcess{ + internalExitChan: make(chan struct{}, 16), + + StdinChan: make(chan []byte, 16), + StdoutChan: make(chan []byte, 16), + StderrChan: make(chan []byte, 16), + ExitChan: make(chan *os.ProcessState, 16), + CloseChan: make(chan struct{}, 16), + } + done := false + go mp.handleClose() + defer func() { + if !done { + mp.CloseChan <- struct{}{} + } + }() + var err error + mp.stdinRead, mp.stdinWrite, err = os.Pipe() + if err != nil { + return mp, fmt.Errorf("creating stdin pipe: %w", err) + } + mp.stdoutRead, mp.stdoutWrite, err = os.Pipe() + if err != nil { + return mp, fmt.Errorf("creating stdout pipe: %w", err) + } + mp.stderrRead, mp.stderrWrite, err = os.Pipe() + if err != nil { + return mp, fmt.Errorf("creating stderr pipe: %w", err) + } + newAttr := &os.ProcAttr{} + if attr != nil { + *newAttr = *attr + } + if len(newAttr.Files) < 3 { + newAttr.Files = append(newAttr.Files, make([]*os.File, 3-len(newAttr.Files))...) + newAttr.Files[0] = mp.stdinRead + newAttr.Files[1] = mp.stdoutWrite + newAttr.Files[2] = mp.stderrWrite + } + mp.proc, err = os.StartProcess(name, argv, newAttr) + if err != nil { + return mp, fmt.Errorf("spawning process: %w", err) + } + go mp.handleWait() + go mp.handleInput(mp.StdinChan, mp.stdinWrite, "stdin") + go mp.handleOutput(mp.StdoutChan, mp.stdoutRead, "stdout") + go mp.handleOutput(mp.StderrChan, mp.stderrRead, "stderr") + done = true + return mp, nil +} + +func (mp *managedProcess) handleInput(ch chan []byte, f *os.File, name string) { + for data := range ch { + _, err := f.Write(data) + if err != nil { + // Likely stdin closed by subprocess, this is normal + return + } + } +} + +func (mp *managedProcess) handleOutput(ch chan []byte, f *os.File, name string) { + for { + buf := make([]byte, 1024) + nr, err := f.Read(buf) + if err != nil { + // Likely stdout/stderr closed by subprocess, + // this is normal + return + } + if nr == 0 { + continue + } + ch <- buf[:nr] + } +} + +func (mp *managedProcess) handleWait() { + s, err := mp.proc.Wait() + if err != nil { + logErrorf("waiting on process: %w", err) + } + mp.internalExitChan <- struct{}{} + mp.ExitChan <- s +} + +func (mp *managedProcess) killProc() { + // See if process has already exited or is about to + select { + case <-mp.internalExitChan: + return + case <-time.NewTimer(500 * time.Millisecond).C: + // + } + // Try killing the process by closing stdin + mp.stdinWrite.Close() + select { + case <-mp.internalExitChan: + return + case <-time.NewTimer(500 * time.Millisecond).C: + // + } + // Try killing the process with SIGTERM, SIGINT, then + // finally SIGKILL + for _, sig := range []os.Signal{syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL} { + err := mp.proc.Signal(sig) + if err != nil { + logErrorf("sending %s to child: %w", sig.String(), err) + } + select { + case <-mp.internalExitChan: + return + case <-time.NewTimer(500 * time.Millisecond).C: + // + } + } + // We are unable to kill the process + logErrorf("unable to kill child process (pid %d)", mp.proc.Pid) +} + +func (mp *managedProcess) handleClose() { + <-mp.CloseChan + for _, p := range []*os.File{ + mp.stdinRead, mp.stdinWrite, + mp.stdoutRead, mp.stdoutWrite, + mp.stderrRead, mp.stderrWrite, + } { + if p != nil { + p.Close() + } + } + if mp.proc != nil { + // + } +} diff --git a/agent/websocket.go b/agent/websocket.go new file mode 100644 index 0000000..15f19f6 --- /dev/null +++ b/agent/websocket.go @@ -0,0 +1,107 @@ +package main + +import ( + "time" + + "github.com/gorilla/websocket" +) + +type ManagedWebsocket struct { + Socket *websocket.Conn + + MessageType int + PingPeriod time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + + IncomingChan chan []byte + OutgoingChan chan []byte + CloseChan chan struct{} + ClosedChan chan struct{} +} + +func (m *ManagedWebsocket) handleIncoming() { + pongChan := make(chan struct{}, 16) + m.Socket.SetPongHandler(func(string) error { + pongChan <- struct{}{} + return nil + }) + msgChan := make(chan []byte, 16) + go func() { + defer close(msgChan) + for { + msgtype, data, err := m.Socket.ReadMessage() + if err != nil { + m.Socket.Close() + return + } + if msgtype != m.MessageType { + logWarnf("ignoring message of unexpected type %d", msgtype) + continue + } + msgChan <- data + + } + }() + for { + m.Socket.SetReadDeadline(time.Now().Add(m.ReadTimeout)) + var msgtype int + var msgdata []byte + select { + case <-pongChan: + msgtype = websocket.PongMessage + case data := <-msgChan: + msgtype = m.MessageType + msgdata = data + } + if msgtype != m.MessageType { + continue + } + m.IncomingChan <- msgdata + } +} + +func (m *ManagedWebsocket) handleOutgoing() { + pingTicker := time.NewTicker(m.PingPeriod) + defer pingTicker.Stop() + defer func() { + m.ClosedChan <- struct{}{} + }() + for { + var msgtype int + var msgdata []byte + select { + case <-pingTicker.C: + msgtype = websocket.PingMessage + msgdata = []byte{} + case data := <-m.OutgoingChan: + msgtype = m.MessageType + msgdata = data + case <-m.CloseChan: + msgtype = websocket.CloseMessage + msgdata = websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") + } + wd := time.Now().Add(m.WriteTimeout) + m.Socket.SetWriteDeadline(wd) + err := m.Socket.WriteMessage(msgtype, msgdata) + if err != nil { + m.Socket.Close() + return + } + if msgtype == websocket.CloseMessage { + time.Sleep(wd.Sub(time.Now())) + m.Socket.Close() + return + } + } +} + +func (m *ManagedWebsocket) Init() { + m.IncomingChan = make(chan []byte, 16) + m.OutgoingChan = make(chan []byte, 16) + m.CloseChan = make(chan struct{}, 16) + m.ClosedChan = make(chan struct{}, 16) + + go m.handleIncoming() + go m.handleOutgoing() +} diff --git a/backend/k8s.js b/backend/k8s.js new file mode 100644 index 0000000..116e283 --- /dev/null +++ b/backend/k8s.js @@ -0,0 +1,209 @@ +import * as k8sClient from "@kubernetes/client-node"; +import lodash from "lodash"; + +const kubeconfig = new k8sClient.KubeConfig(); +kubeconfig.loadFromDefault(); + +const k8s = kubeconfig.makeApiClient(k8sClient.CoreV1Api); + +export function watchPods() { + const callbacks = {}; + const pods = {}; + + // https://github.com/kubernetes-client/javascript/blob/1f76ee10c54e33a998abb4686488ccff4285366a/examples/typescript/informer/informer.ts + // + // The watch functionality seems to be wholly undocumented. Copy, + // paste, and pray. + const informer = k8sClient.makeInformer( + kubeconfig, + "/api/v1/namespaces/riju-user/pods", + () => k8s.listNamespacedPod("riju-user") + ); + + for (const event of ["add", "update", "delete"]) { + informer.on(event, (pod) => { + if (pod.metadata.name in callbacks) { + callbacks[pod.metadata.name](event, pod); + } + pods[pod.metadata.name] = pod; + if (event == "delete") { + delete callbacks[pod.metadata.name]; + delete pods[pod.metadata.name]; + } + }); + } + + informer.on("error", (err) => { + console.error(err); + setTimeout(() => informer.start(), 5000); + }); + informer.start(); + + return { + setCallback: (podName, callback) => { + callbacks[podName] = callback; + if (podName in pods) { + callback("add", pods[podName]); + } + }, + }; +} + +export async function listUserSessions() { + return (await k8s.listNamespacedPod("riju-user")).body.items.map((pod) => ({ + podName: pod.metadata.name, + sessionID: pod.metadata.labels["riju.codes/user-session-id"], + })); +} + +export async function createUserSession({ + watcher, + sessionID, + langConfig, + revisions, +}) { + const pod = ( + await k8s.createNamespacedPod("riju-user", { + metadata: { + name: `riju-user-session-${sessionID}`, + labels: { + "riju.codes/user-session-id": sessionID, + }, + }, + spec: { + volumes: [ + { + name: "minio-config", + secret: { + secretName: "minio-user-login", + }, + }, + { + name: "riju-bin", + emptyDir: {}, + }, + ], + imagePullSecrets: [ + { + name: "registry-user-login", + }, + ], + initContainers: [ + { + name: "download", + image: "minio/mc:RELEASE.2022-12-13T00-23-28Z", + resources: {}, + command: ["sh", "-c"], + args: [ + `mkdir -p /root/.mc && cp -LT /mc/config.json /root/.mc/config.json &&` + + `mc cp riju/agent/${revisions.agent} /riju-bin/agent && chmod +x /riju-bin/agent &&` + + `mc cp riju/ptyify/${revisions.ptyify} /riju-bin/ptyify && chmod +x /riju-bin/ptyify`, + ], + volumeMounts: [ + { + name: "minio-config", + mountPath: "/mc", + readOnly: true, + }, + { + name: "riju-bin", + mountPath: "/riju-bin", + }, + ], + }, + ], + containers: [ + { + name: "session", + image: `localhost:30999/riju-lang:${langConfig.id}-${revisions.langImage}`, + resources: { + requests: {}, + limits: { + cpu: "1000m", + memory: "4Gi", + }, + }, + command: ["/riju-bin/agent"], + env: [ + { + name: "RIJU_AGENT_COMMAND_PREFIX", + value: "runuser -u riju --", + }, + ], + securityContext: { + runAsUser: 0, + }, + startupProbe: { + httpGet: { + path: "/health", + port: 869, + scheme: "HTTP", + }, + failureThreshold: 30, + initialDelaySeconds: 0, + periodSeconds: 1, + successThreshold: 1, + timeoutSeconds: 2, + }, + readinessProbe: { + httpGet: { + path: "/health", + port: 869, + scheme: "HTTP", + }, + failureThreshold: 1, + initialDelaySeconds: 2, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 2, + }, + livenessProbe: { + httpGet: { + path: "/health", + port: 869, + scheme: "HTTP", + }, + failureThreshold: 3, + initialDelaySeconds: 2, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 2, + }, + volumeMounts: [ + { + name: "riju-bin", + mountPath: "/riju-bin", + readOnly: true, + }, + ], + }, + ], + restartPolicy: "Never", + }, + }) + ).body; + const podIP = await new Promise((resolve, reject) => { + setTimeout(() => reject("timed out"), 5 * 60 * 1000); + watcher.setCallback(pod.metadata.name, (event, pod) => { + if (event == "delete") { + reject(new Error("pod was deleted")); + } else if (pod.status.phase === "Failed") { + reject(new Error("pod status became Failed")); + } else if ( + pod.status.podIP && + lodash.every(pod.status.containerStatuses, (status) => status.ready) + ) { + resolve(pod.status.podIP); + } else { + console.log(event, JSON.stringify(pod.status, null, 2)); + } + }); + }); + return podIP; +} + +export async function deleteUserSessions(sessionsToDelete) { + for (const { podName } of sessionsToDelete) { + await k8s.deleteNamespacedPod(podName, "riju-user"); + } +} diff --git a/backend/langs.js b/backend/langs.js index c18af5c..8a0594e 100644 --- a/backend/langs.js +++ b/backend/langs.js @@ -1,5 +1,4 @@ -import fsOrig, { promises as fs } from "fs"; -import path from "path"; +import fsOrig from "fs"; import debounce from "debounce"; diff --git a/backend/sandbox-k8s.js b/backend/sandbox-k8s.js new file mode 100644 index 0000000..def9a81 --- /dev/null +++ b/backend/sandbox-k8s.js @@ -0,0 +1,80 @@ +import { spawn } from "child_process"; +import { promises as fs } from "fs"; +import process from "process"; + +import { readLangConfig } from "../lib/yaml.js"; +import * as k8s from "./k8s.js"; +import { getUUID, quote } from "./util.js"; + +function die(msg) { + console.error(msg); + process.exit(1); +} + +async function main() { + const sandboxScript = await fs.readFile("backend/sandbox.bash", "utf-8"); + const lang = process.env.L; + if (!lang) { + die("environment variable unset: $L"); + } + const langConfig = await readLangConfig(lang); + console.log(`Checking for existing sessions`); + const existingSessions = await k8s.listUserSessions(); + if (existingSessions.length > 0) { + console.log(`Killing ${existingSessions.length} existing session(s)`); + await k8s.deleteUserSessions(existingSessions); + } + const sessionID = getUUID(); + console.log(`Starting session with UUID ${sessionID}`); + const watcher = k8s.watchPods(); + await k8s.createUserSession({ + watcher, + sessionID, + langConfig, + revisions: { + agent: "20221229-002450-semantic-moccasin-albatross", + ptyify: "20221228-023645-clean-white-gorilla", + langImage: "20221227-195753-forward-harlequin-wolverine", + }, + }); + // let buffer = ""; + // await new Promise((resolve) => { + // session.stdout.on("data", (data) => { + // buffer += data.toString(); + // let idx; + // while ((idx = buffer.indexOf("\n")) !== -1) { + // const line = buffer.slice(0, idx); + // buffer = buffer.slice(idx + 1); + // if (line === "riju: container ready") { + // resolve(); + // } else { + // console.error(line); + // } + // } + // }); + // }); + // const args = [].concat.apply( + // ["riju-pty", "-f"], + // privilegedPty( + // { uuid }, + // bash( + // `env L='${lang}' LANG_CONFIG=${quote( + // JSON.stringify(langConfig) + // )} bash --rcfile <(cat <<< ${quote(sandboxScript)})` + // ) + // ) + // ); + // const proc = spawn(args[0], args.slice(1), { + // stdio: "inherit", + // }); + // try { + // await new Promise((resolve, reject) => { + // proc.on("error", reject); + // proc.on("close", resolve); + // }); + // } finally { + // session.kill(); + // } +} + +main().catch(die); diff --git a/backend/sandbox.bash b/backend/sandbox.bash index 8c6f6ec..6bc61a3 100644 --- a/backend/sandbox.bash +++ b/backend/sandbox.bash @@ -1,17 +1,18 @@ +#!/usr/bin/env bash # This script is sourced by Bash within 'make sandbox'. if [[ -z "$L" ]]; then - echo 'environment variable unset: $L' >&2 + echo "environment variable unset: \$L" >&2 exit 1 fi if [[ -z "$LANG_CONFIG" ]]; then - echo 'environment variable unset: $LANG_CONFIG' >&2 + echo "environment variable unset: \$LANG_CONFIG" >&2 exit 1 fi function get { - jq -r ".$1" <<< "${LANG_CONFIG}" + jq -r ".$1" <<<"${LANG_CONFIG}" } function has { @@ -24,21 +25,21 @@ function riju-exec { function daemon { if has daemon; then - echo "$(get daemon)" + get daemon riju-exec "$(get daemon)" fi } function setup { if has setup; then - echo "$(get setup)" + get setup riju-exec "$(get setup)" fi } function repl { if has repl; then - echo "$(get repl)" + get repl riju-exec "$(get repl)" fi } @@ -47,22 +48,22 @@ function main { if get main | grep -q /; then mkdir -p "$(dirname "$(get main)")" fi - : > "$(get main)" - has prefix && get prefix >> "$(get main)" - get template >> "$(get main)" - has suffix && get suffix >> "$(get main)" + : >"$(get main)" + has prefix && get prefix >>"$(get main)" + get template >>"$(get main)" + has suffix && get suffix >>"$(get main)" } function compile { if has compile; then - echo "$(get compile)" + get compile riju-exec "$(get compile)" fi } function run-only { if has run; then - echo "$(get run)" + get run riju-exec "$(get run)" fi } @@ -73,18 +74,18 @@ function run { function format { if has format; then - echo "$(get format.run)" + get format.run riju-exec "( $(get format.run) ) < $(get main)" fi } function lsp { if has lsp.setup; then - echo "$(get lsp.setup)" + get lsp.setup riju-exec "$(get lsp.setup)" fi if has lsp; then - echo "$(get lsp.start)" + get lsp.start riju-exec "$(get lsp.start)" fi } diff --git a/backend/sandbox.js b/backend/sandbox.js index aef507c..fd3b693 100644 --- a/backend/sandbox.js +++ b/backend/sandbox.js @@ -6,11 +6,9 @@ import { readLangConfig } from "../lib/yaml.js"; import { bash, getUUID, - privilegedExec, privilegedPty, privilegedSession, quote, - run, } from "./util.js"; function die(msg) { @@ -18,10 +16,6 @@ function die(msg) { process.exit(1); } -function log(msg) { - console.log(msg); -} - async function main() { const sandboxScript = await fs.readFile("backend/sandbox.bash", "utf-8"); const lang = process.env.L; diff --git a/backend/server.js b/backend/server.js index 4643aca..b6f19bc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -6,6 +6,7 @@ import cors from "cors" import express from "express"; import ws from "express-ws"; import _ from "lodash"; +import * as promClient from "prom-client"; import * as api from "./api.js"; import { aliases, langsPromise } from "./langs.js"; @@ -15,12 +16,21 @@ import { log, privilegedTeardown } from "./util.js"; const host = process.env.HOST || "localhost"; const port = parseInt(process.env.PORT || "") || 6119; const tlsPort = parseInt(process.env.TLS_PORT || "") || 6120; +const metricsPort = parseInt(process.env.METRICS_PORT || "") || 6121; const useTLS = process.env.TLS ? true : false; const analyticsTag = (process.env.ANALYTICS_TAG || "").replace( /^'(.+)'$/, "$1" ); +promClient.collectDefaultMetrics(); + +const metricsApp = express(); +metricsApp.get("/metrics", async (_, res) => { + res.contentType("text/plain; version=0.0.4"); + res.send(await promClient.register.metrics()); +}); + const langs = await langsPromise; const app = express(); @@ -142,3 +152,7 @@ if (useTLS) { console.log(`Listening on http://${host}:${port}`) ); } + +metricsApp.listen(metricsPort, host, () => + console.log(`Listening on http://${host}:${metricsPort}/metrics`) +); diff --git a/backend/test-runner.js b/backend/test-runner.js index 01962c7..75aa9cb 100644 --- a/backend/test-runner.js +++ b/backend/test-runner.js @@ -10,7 +10,7 @@ import { getTestHash } from "../lib/hash-test.js"; import * as api from "./api.js"; import { langsPromise } from "./langs.js"; import { shutdown } from "./shutdown.js"; -import { getUUID, run } from "./util.js"; +import { run } from "./util.js"; let langs = {}; @@ -525,7 +525,7 @@ const testTypes = { ensure: { pred: ({ ensure }) => (ensure ? true : false), }, - run: { pred: (config) => true }, + run: { pred: (_config) => true }, repl: { pred: ({ repl }) => (repl ? true : false), }, diff --git a/backend/util.js b/backend/util.js index 17805c8..db0512d 100644 --- a/backend/util.js +++ b/backend/util.js @@ -1,5 +1,4 @@ import { spawn } from "child_process"; -import os from "os"; import process from "process"; import * as Sentry from "@sentry/node"; @@ -89,6 +88,14 @@ export async function run(args, log, options) { }); } +export function privilegedList() { + return [rijuSystemPrivileged, "list"]; +} + +export function privilegedPull({ repo, tag }) { + return [rijuSystemPrivileged, "pull", repo, tag]; +} + export function privilegedSession({ uuid, lang }) { const cmdline = [rijuSystemPrivileged, "session", uuid, lang]; if (imageHashes[lang]) { diff --git a/cli/go.mod b/cli/go.mod index 3aa8505..8586943 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,4 +1,4 @@ -module github.com/raxod502/riju/cli +module github.com/radian-software/riju/cli go 1.16 diff --git a/doc/local.md b/doc/local.md index 2218ed9..f1bf43b 100644 --- a/doc/local.md +++ b/doc/local.md @@ -13,7 +13,7 @@ hesitate to open an issue! Clone locally: ``` -$ git clone https://github.com/raxod502/riju.git +$ git clone https://github.com/radian-software/riju.git $ cd riju ``` diff --git a/doc/selfhosting.md b/doc/selfhosting.md index 43bb580..78f7df1 100644 --- a/doc/selfhosting.md +++ b/doc/selfhosting.md @@ -111,8 +111,8 @@ API. Generate one randomly with `pwgen -s 30 1`. ## Build web AMI You'll want to run `set -a; . .env` to load in the new variables from -`.env`. Now run `make packer-web`. This will take up to 10 minutes to -build a timestamped AMI with a name like `riju-web-20210711223158`. +`.env`. Now run `make packer`. This will take up to 10 minutes to +build a timestamped AMI with a name like `riju-20210711223158`. ## Create local configuration (part 2 of 3) @@ -120,15 +120,16 @@ Add to `.env` the following contents: ``` # Terraform -AMI_NAME=riju-web-20210711223158 +AMI_NAME=riju-20210711223158 AWS_REGION=us-west-1 DOMAIN=your.domain S3_BUCKET=yourname-riju +S3_CONFIG_PATH=config.json ``` ### AMI\_NAME -This is the AMI name from `make packer-web`. +This is the AMI name from `make packer`. ### AWS\_REGION @@ -168,7 +169,7 @@ infrastructure. follow these steps:* 1. Update `.env` and make sure it is sourced (`set -a; . .env`). -2. Run `make packer-web` and get the name of the new AMI. +2. Run `make packer` and get the name of the new AMI. 3. Update it in `.env` under `AMI_NAME` and make sure the update is sourced (`set -a; . .env`). 4. Run `terraform apply`. @@ -231,7 +232,7 @@ from the load balancer). to upload the finished build artifacts to ECR, which amount to about 40 GB of data transfer. If you don't have a symmetric Internet plan at home, you may need to do this on an EC2 instance instead. You can -provision one manually with at least 256 GB of disk space, install +provision one manually with at least 128 GB of disk space, install Docker, clone down Riju, copy over your `.env` file, and proceed as if you were running locally.)* diff --git a/doc/what-languages.md b/doc/what-languages.md index 50cb764..5180217 100644 --- a/doc/what-languages.md +++ b/doc/what-languages.md @@ -49,9 +49,9 @@ requirements: because it only runs on macOS, and [Docker](https://www.docker.com/) is out because it can't be run inside Docker (without the `--privileged` flag, which has unacceptable security drawbacks; see - [#29](https://github.com/raxod502/riju/issues/29)). Note, however, - that many Windows-based languages can be used successfully via - [Mono](https://www.mono-project.com/) or + [#29](https://github.com/radian-software/riju/issues/29)). Note, + however, that many Windows-based languages can be used successfully + via [Mono](https://www.mono-project.com/) or [Wine](https://www.winehq.org/), such as [Cmd](https://en.wikipedia.org/wiki/Cmd.exe), [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)), @@ -60,7 +60,8 @@ requirements: Here are some explicit *non-requirements*: * *Language must be well-known.* Nope, I'll be happy to add your pet - project; after all, [Kalyn](https://github.com/raxod502/kalyn) and + project; after all, + [Kalyn](https://github.com/radian-software/kalyn) and [Ink](https://github.com/thesephist/ink) are already supported. * *Language must be useful.* I have no objection to adding everything on the esolangs wiki, if there are interpreters/compilers available. diff --git a/docker/admin/install.bash b/docker/admin/install.bash index 50d9a2b..4cffd38 100755 --- a/docker/admin/install.bash +++ b/docker/admin/install.bash @@ -2,7 +2,12 @@ set -euxo pipefail -pushd /tmp +latest_release() { + curl -sSL "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name +} + +mkdir /tmp/riju-work +pushd /tmp/riju-work export DEBIAN_FRONTEND=noninteractive @@ -39,6 +44,7 @@ dctrl-tools docker-ce-cli file g++ +gettext git golang htop @@ -57,8 +63,9 @@ skopeo ssh strace sudo -tmux terraform +tmux +tree unzip uuid-runtime vim @@ -77,7 +84,11 @@ npm install -g prettier wget -nv https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -O awscli.zip unzip -q awscli.zip ./aws/install -rm -rf aws awscli.zip + +ver="$(latest_release grafana/cortex-tools | sed 's/^v//')" +wget -nv "https://github.com/grafana/cortex-tools/releases/download/v${ver}/cortextool_${ver}_linux_amd64.tar.gz" -O cortextool.tar.gz +tar -xf cortextool.tar.gz +cp cortextool /usr/local/bin/ rm -rf /var/lib/apt/lists/* @@ -86,5 +97,6 @@ tee /etc/sudoers.d/90-riju >/dev/null <<"EOF" EOF popd +rm -rf /tmp/riju-work rm "$0" diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 9a80730..7e919a0 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -1,4 +1,5 @@ -FROM riju:ubuntu AS build +# EOL: April 2027 +FROM ubuntu:22.04 AS build COPY docker/app/install-build.bash /tmp/ RUN /tmp/install-build.bash diff --git a/docker/app/install-build.bash b/docker/app/install-build.bash index 5b80794..42281fc 100755 --- a/docker/app/install-build.bash +++ b/docker/app/install-build.bash @@ -15,7 +15,7 @@ curl -sSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - ubuntu_ver="$(lsb_release -rs)" ubuntu_name="$(lsb_release -cs)" -node_repo="$(curl -sS https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)" +node_repo="$(curl -sS https://deb.nodesource.com/setup_16.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)" tee -a /etc/apt/sources.list.d/custom.list >/dev/null </dev/null < 1: - die(f"multiple matching report prefixes: {repr(matching_month_prefixes)}") - (month_prefix,) = matching_month_prefixes - stream = io.BytesIO() - manifest_path = f"{month_prefix}{report_name}-Manifest.json" - logging.info(f"Download s3://{bucket}/{manifest_path} in-memory") - s3.download_fileobj(bucket, manifest_path, stream) - manifest = json.loads(stream.getvalue()) - (report_path,) = manifest["reportKeys"] - if not report_path.endswith(".csv.gz"): - die(f"unexpected report extension in {report_path}") - logging.info(f"Get metadata for s3://{bucket}/{report_path}") - basename = s3.head_object(Bucket=bucket, Key=report_path)[ - "LastModified" - ].strftime("%Y-%m-%d") - logging.info( - f"Download s3://{bucket}/{report_path} to {target_dir.relative_to(ROOT)}/{basename}.csv.gz" - ) - s3.download_file(bucket, report_path, f"{target_dir}/{basename}.csv.gz") - logging.info(f"Decompress {basename}.csv.gz") - with gzip.open(f"{target_dir}/{basename}.csv.gz") as f_read: - with open(f"{target_dir}/{basename}.csv", "wb") as f_write: - while chunk := f_read.read(1024): - f_write.write(chunk) - latest_csv.symlink_to(f"{basename}.csv") - return latest_csv - - -def read_csv(csv_path): - rows = [] - with open(csv_path) as f: - reader = csv.reader(f) - header = next(reader) - for row in reader: - rows.append(dict((key, val) for (key, val) in zip(header, row) if val)) - return rows - - -def get_tax_key(item): - service = item["lineItem/ProductCode"] - usage_type = item["lineItem/UsageType"] - if "DataTransfer" in usage_type: - service = "AWSDataTransfer" - return (service, usage_type) - - -def embed_taxes(items): - tax_items = collections.defaultdict(list) - usage_items = collections.defaultdict(list) - for item in items: - item_type = item["lineItem/LineItemType"] - if item_type == "Tax": - tax_items[get_tax_key(item)].append(item) - elif item_type == "Usage": - usage_items[get_tax_key(item)].append(item) - else: - die(f"unexpected line item type {repr(item_type)}") - for key in tax_items: - if key not in usage_items: - die(f"tax for {repr(key)} but no usage for that key") - tax_cost = sum(item["lineItem/UnblendedCost"] for item in tax_items[key]) - usage_cost = sum(item["lineItem/UnblendedCost"] for item in usage_items[key]) - tax_multiplier = (tax_cost + usage_cost) / usage_cost - for item in usage_items[key]: - item["lineItem/UnblendedCost"] *= tax_multiplier - return [item for group in usage_items.values() for item in group] - - -def classify_line_item(item, billing_month=None, full=False): - service = item["lineItem/ProductCode"] - usage_type = item["lineItem/UsageType"] - operation = item.get("lineItem/Operation") - resource = item.get("lineItem/ResourceId") - project = item.get("resourceTags/user:BillingCategory") - # In 2021-07, the first month that I was using AWS resources for - # Riju in a nontrivial capacity, I had subpar billing - # observability, so a lot of the resources aren't tagged - # correctly. So for that month specifically, I'm hacking in a - # couple of heuristics to tag the resources after the fact based - # on what I know about my usage of AWS. - if billing_month == "2021-07": - if resource and "riju" in resource.lower(): - project = "Riju" - elif resource and "shallan" in resource.lower(): - project = "Shallan" - elif resource and "veidt" in resource.lower(): - project = "Veidt" - elif service == "AmazonCloudWatch": - project = "Riju" - elif ( - service == "AmazonEC2" - and resource != "i-077884b74aba86bac" - and "ElasticIP:IdleAddress" not in usage_type - and "EBS:SnapshotUsage" not in usage_type - ): - project = "Riju" - # AWS does not let you put tags on a public ECR repository, - # yippee. - if service == "AmazonECRPublic" and resource.endswith("repository/riju"): - project = "Riju" - category = [ - "Uncategorized", - service, - usage_type, - operation or "(no operation)", - resource or "(no resource)", - ] - if not full: - if service == "AmazonS3": - category = ["S3"] - elif service == "AmazonSNS": - category = ["SNS"] - elif service in ("AmazonECR", "AmazonECRPublic"): - category = ["ECR"] - if "DataTransfer" in usage_type: - category.append("Data Transfer") - elif "TimedStorage" in usage_type: - category.append("Storage") - else: - category.extend( - [ - "Uncategorized", - usage_type, - operation or "(no operation)", - resource or "(no resource)", - ] - ) - elif service == "AmazonEC2": - category = ["EC2"] - if "ElasticIP:IdleAddress" in usage_type: - category.append("EIP") - # Apparently tags on EIPs are ignored for billing - # purposes, so we just have to know what we were using - # them for. (Leaving them uncategorized for 2021-07 - # though.) - if billing_month != "2021-07": - project = "Corona" - elif "EBS:VolumeUsage" in usage_type: - category.append("EBS Volume") - category.extend(["EBS Volume", re.sub(r"^.+\.", "", usage_type)]) - elif "EBS:SnapshotUsage" in usage_type: - category.append("EBS Snapshot") - elif ( - "DataTransfer" in usage_type - or "In-Bytes" in usage_type - or "Out-Bytes" in usage_type - ): - category.append("Data Transfer") - elif "BoxUsage" in usage_type or "CPUCredits" in usage_type: - category.extend(["Instance", re.sub(r"^.+:", "", usage_type)]) - else: - category.extend( - [ - "Uncategorized", - usage_type, - operation or "(no operation)", - resource or "(no resource)", - ] - ) - elif service == "AWSELB": - category = ["ELB"] - if "DataTransfer" in usage_type: - category.append("Data Transfer") - elif "LCUUsage" in usage_type: - category.append("LCUs") - elif "LoadBalancerUsage": - category.append("Load Balancer") - else: - category.extend( - [ - "Uncategorized", - usage_type, - operation or "(no operation)", - resource or "(no resource)", - ] - ) - elif service == "AmazonCloudWatch": - category = ["CloudWatch"] - elif service == "awskms": - category = ["KMS"] - if not project: - category.extend( - [ - usage_type, - operation or "(no operation)", - resource or "(no resource)", - ] - ) - return [project or "Uncategorized", *category] - - -def add_to_taxonomy(taxonomy, category, item): - if category: - categories = taxonomy.setdefault("categories", {}) - add_to_taxonomy(categories.setdefault(category[0], {}), category[1:], item) - else: - taxonomy.setdefault("items", []).append(item) - taxonomy.setdefault("cost", 0) - taxonomy["cost"] += float(item["lineItem/UnblendedCost"]) - - -def uncategorized_last(key): - return (key == "Uncategorized", key) - - -def print_taxonomy(taxonomy, indent="", file=sys.stdout): - cost = taxonomy["cost"] - categories = taxonomy.get("categories", {}) - for category in sorted(categories, key=uncategorized_last): - subtaxonomy = categories[category] - cost = subtaxonomy["cost"] - if cost < 0.01: - continue - print(f"{indent}{category} :: ${cost:.2f}", file=file) - print_taxonomy(subtaxonomy, indent=indent + " ", file=file) - - -def classify_costs(csv_path, **kwargs): - all_items = [item for item in read_csv(csv_path)] - items = [] - for item in all_items: - cost = item["lineItem/UnblendedCost"] - if cost and float(cost): - items.append({**item, "lineItem/UnblendedCost": float(cost)}) - taxonomy = {} - for item in embed_taxes(items): - add_to_taxonomy(taxonomy, ["AWS", *classify_line_item(item, **kwargs)], item) - return taxonomy - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("date") - parser.add_argument("-f", "--force-download", action="store_true") - parser.add_argument("-w", "--write", action="store_true") - args = parser.parse_args() - year, month = map(int, args.date.split("-")) - billing_month = f"{year}-{month:02d}" - csv_path = get_csv(year, month, force_download=args.force_download) - taxonomy = classify_costs(csv_path, billing_month=billing_month) - print_taxonomy(taxonomy) - if args.write: - riju_taxonomy = taxonomy["categories"]["AWS"] - riju_taxonomy["categories"] = {"Riju": riju_taxonomy["categories"]["Riju"]} - target_dir = ROOT / f"{year}-{month:02d}" - with open(target_dir / "breakdown.txt", "w") as f: - print_taxonomy(riju_taxonomy, file=f) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/financials/poetry.lock b/financials/poetry.lock deleted file mode 100644 index c9ec56f..0000000 --- a/financials/poetry.lock +++ /dev/null @@ -1,135 +0,0 @@ -[[package]] -name = "boto3" -version = "1.18.23" -description = "The AWS SDK for Python" -category = "main" -optional = false -python-versions = ">= 3.6" - -[package.dependencies] -botocore = ">=1.21.23,<1.22.0" -jmespath = ">=0.7.1,<1.0.0" -s3transfer = ">=0.5.0,<0.6.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.21.23" -description = "Low-level, data-driven core of boto 3." -category = "main" -optional = false -python-versions = ">= 3.6" - -[package.dependencies] -jmespath = ">=0.7.1,<1.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = ">=1.25.4,<1.27" - -[package.extras] -crt = ["awscrt (==0.11.24)"] - -[[package]] -name = "jmespath" -version = "0.10.0" -description = "JSON Matching Expressions" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "0.19.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "s3transfer" -version = "0.5.0" -description = "An Amazon S3 Transfer Manager" -category = "main" -optional = false -python-versions = ">= 3.6" - -[package.dependencies] -botocore = ">=1.12.36,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "urllib3" -version = "1.26.6" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.9" -content-hash = "170b0bcf9f0ae12c4c9e1daa195ecdb39585494414b88e53e3da72916eb52c51" - -[metadata.files] -boto3 = [ - {file = "boto3-1.18.23-py3-none-any.whl", hash = "sha256:1b08ace99e7b92965780e5ce759430ad62b7b7e037560bc772f9a8789f4f36d2"}, - {file = "boto3-1.18.23.tar.gz", hash = "sha256:31cc69e665f773390c4c17ce340d2420e45fbac51d46d945cc4a58d483ec5da6"}, -] -botocore = [ - {file = "botocore-1.21.23-py3-none-any.whl", hash = "sha256:3877d69e0b718b786f1696cd04ddbdb3a57aef6adb0239a29aa88754489849a4"}, - {file = "botocore-1.21.23.tar.gz", hash = "sha256:d0146d31dbc475942b578b47dd5bcf94d18fbce8c6d2ce5f12195e005de9b754"}, -] -jmespath = [ - {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, - {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -python-dotenv = [ - {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, - {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, -] -s3transfer = [ - {file = "s3transfer-0.5.0-py3-none-any.whl", hash = "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"}, - {file = "s3transfer-0.5.0.tar.gz", hash = "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -urllib3 = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, -] diff --git a/financials/pyproject.toml b/financials/pyproject.toml deleted file mode 100644 index cb626d9..0000000 --- a/financials/pyproject.toml +++ /dev/null @@ -1,16 +0,0 @@ -[tool.poetry] -name = "riju-financials" -version = "0.1.0" -description = "Financial data for Riju hosting" -authors = ["Radon Rosborough "] - -[tool.poetry.dependencies] -python = "^3.9" -boto3 = "^1.18.23" -python-dotenv = "^0.19.0" - -[tool.poetry.dev-dependencies] - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/frontend/pages/index.ejs b/frontend/pages/index.ejs index ac45a26..fd38f97 100644 --- a/frontend/pages/index.ejs +++ b/frontend/pages/index.ejs @@ -25,9 +25,11 @@

Created by - Radon Rosborough. + Radon Rosborough + and maintained by + Radian LLC. Check out the project - on GitHub. + on GitHub.

<% } else { %> diff --git a/grafana/alertmanager.yaml b/grafana/alertmanager.yaml new file mode 100644 index 0000000..a106b1b --- /dev/null +++ b/grafana/alertmanager.yaml @@ -0,0 +1,6 @@ +receivers: + - name: pagerduty + pagerduty_configs: + - routing_key: "$PAGERDUTY_INTEGRATION_KEY" +route: + receiver: pagerduty diff --git a/grafana/alerts.yaml b/grafana/alerts.yaml new file mode 100644 index 0000000..4f62f00 --- /dev/null +++ b/grafana/alerts.yaml @@ -0,0 +1,28 @@ +namespace: riju +groups: + - name: riju + rules: + - alert: NodeCPUHigh + annotations: + message: "Instance {{ $labels.node }} is running close to max CPU" + expr: | + sum(1 - rate(node_cpu_seconds_total{mode="idle"}[1m])) by (node) / count(sum(node_cpu_seconds_total{mode="idle"}) by (node, cpu)) by (node) * 100 >= 80 + for: 30m + - alert: NodeMemoryHigh + annotations: + message: "Instance {{ $labels.node }} is running close to max memory" + expr: | + sum(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) by (node) * 100 >= 80 + for: 30m + - alert: RootVolumeFilling + annotations: + message: "Root volume on instance {{ $labels.node }} is close to full" + expr: | + (1 - sum (node_filesystem_free_bytes{mountpoint="/"}) by (node) / sum (node_filesystem_size_bytes{mountpoint="/"}) by (node)) * 100 >= 80 + for: 30m + - alert: DataVolumeFilling + annotations: + message: "Data volume on instance {{ $labels.node }} is close to full" + expr: | + (1 - sum (node_filesystem_free_bytes{mountpoint="/mnt/riju"}) by (node) / sum (node_filesystem_size_bytes{mountpoint="/mnt/riju"}) by (node)) * 100 >= 80 + for: 30m diff --git a/grafana/dashboard.json b/grafana/dashboard.json new file mode 100644 index 0000000..1fd06c4 --- /dev/null +++ b/grafana/dashboard.json @@ -0,0 +1,902 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 4, + "iteration": 1644689175462, + "links": [], + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 15, + "title": "Server metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "sum(rate(process_cpu_seconds_total{node=~\"$node\",job=\"server\"}[1m])) by (node) / count(sum(node_cpu_seconds_total{node=~\"$node\",mode=\"idle\"}) by (node, cpu)) by (node) * 100", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "CPU Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 17, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "sum(process_resident_memory_bytes{node=~\"$node\",job=\"server\"} / node_memory_MemTotal_bytes{node=~\"$node\"}) by (node) * 100", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Memory Utilization", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 6, + "panels": [], + "title": "Instance metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "sum(1 - rate(node_cpu_seconds_total{node=~\"$node\",mode=\"idle\"}[1m])) by (node) / count(sum(node_cpu_seconds_total{node=~\"$node\",mode=\"idle\"}) by (node, cpu)) by (node) * 100", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "CPU Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "sum(1 - node_memory_MemAvailable_bytes{node=~\"$node\"} / node_memory_MemTotal_bytes{node=~\"$node\"}) by (node) * 100", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Memory Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "MBs" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "sum (rate(node_network_receive_bytes_total{node=~\"$node\"}[1m])) by (node) / 1e6", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Network Traffic Received", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "KBs" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "sum (rate(node_network_transmit_bytes_total{node=~\"$node\"}[1m])) by (node) / 1e3", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Network Traffic Sent", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "(1 - sum (node_filesystem_free_bytes{node=~\"$node\",mountpoint=\"/\"}) by (node) / sum (node_filesystem_size_bytes{node=~\"$node\",mountpoint=\"/\"}) by (node)) * 100", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Root Volume Disk Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 13, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "exemplar": true, + "expr": "(1 - sum (node_filesystem_free_bytes{node=~\"$node\",mountpoint=\"/mnt/riju\"}) by (node) / sum (node_filesystem_size_bytes{node=~\"$node\",mountpoint=\"/mnt/riju\"}) by (node)) * 100", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Data Volume Disk Utilization", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 2, + "panels": [], + "title": "Logs", + "type": "row" + }, + { + "datasource": { + "type": "loki", + "uid": "grafanacloud-logs" + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 4, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Ascending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "grafanacloud-logs" + }, + "expr": "{source=~\"$log_source\",node=~\"$node\"} | regexp \"(?P.+)\" | line_format \"{{ .node }} {{ .log }}\"", + "maxLines": 50, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Logs", + "type": "logs" + } + ], + "refresh": "5s", + "schemaVersion": 34, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + }, + "datasource": { + "type": "loki", + "uid": "grafanacloud-logs" + }, + "definition": "label_values(node)", + "hide": 0, + "includeAll": true, + "label": "", + "multi": true, + "name": "node", + "options": [], + "query": "label_values(node)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + }, + "datasource": { + "type": "loki", + "uid": "grafanacloud-logs" + }, + "definition": "label_values(source)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "log_source", + "options": [], + "query": "label_values(source)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Riju", + "uid": "mx3ZlzMnk", + "version": 30, + "weekStart": "" +} diff --git a/k8s/cluster.yaml b/k8s/cluster.yaml new file mode 100644 index 0000000..29c7bb2 --- /dev/null +++ b/k8s/cluster.yaml @@ -0,0 +1,105 @@ +apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +metadata: + creationTimestamp: null + name: k0s +spec: + api: + address: 192.168.0.216 + k0sApiPort: 9443 + port: 6443 + sans: + - 192.168.0.216 + - 192.168.122.1 + - 172.21.0.1 + - 172.17.0.1 + - 172.23.0.1 + - 10.88.0.1 + - 10.244.0.1 + - 2601:646:4000:3060::be49 + - 2601:646:4000:3060:8b13:5b76:2703:28f + - 2601:646:4000:3060:683c:7a51:eee8:2eb + - fe80::1fbd:2949:a12e:cedf + - fe80::42:d5ff:fe58:a84f + - fe80::844c:59ff:fe46:20dc + - fe80::20e5:9dff:fe7a:4698 + - fe80::f86c:22ff:feb0:59ac + - fe80::b0d0:51ff:fe45:ce31 + - fe80::3cd5:9eff:fed1:5f72 + tunneledNetworkingMode: false + controllerManager: {} + extensions: + helm: + charts: null + repositories: null + storage: + type: openebs_local_storage + images: + calico: + cni: + image: docker.io/calico/cni + version: v3.24.5 + kubecontrollers: + image: docker.io/calico/kube-controllers + version: v3.24.5 + node: + image: docker.io/calico/node + version: v3.24.5 + coredns: + image: docker.io/coredns/coredns + version: 1.9.4 + default_pull_policy: IfNotPresent + konnectivity: + image: quay.io/k0sproject/apiserver-network-proxy-agent + version: 0.0.32-k0s1 + kubeproxy: + image: registry.k8s.io/kube-proxy + version: v1.25.4 + kuberouter: + cni: + image: docker.io/cloudnativelabs/kube-router + version: v1.5.1 + cniInstaller: + image: quay.io/k0sproject/cni-node + version: 1.1.1-k0s.0 + metricsserver: + image: registry.k8s.io/metrics-server/metrics-server + version: v0.6.1 + pushgateway: + image: quay.io/k0sproject/pushgateway-ttl + version: edge@sha256:7031f6bf6c957e2fdb496161fe3bea0a5bde3de800deeba7b2155187196ecbd9 + installConfig: + users: + etcdUser: etcd + kineUser: kube-apiserver + konnectivityUser: konnectivity-server + kubeAPIserverUser: kube-apiserver + kubeSchedulerUser: kube-scheduler + konnectivity: + adminPort: 8133 + agentPort: 8132 + network: + calico: null + clusterDomain: cluster.local + dualStack: {} + kubeProxy: + mode: iptables + kuberouter: + autoMTU: true + hairpinMode: false + metricsPort: 8080 + mtu: 0 + peerRouterASNs: "" + peerRouterIPs: "" + podCIDR: 10.244.0.0/16 + provider: kuberouter + serviceCIDR: 10.96.0.0/12 + scheduler: {} + storage: + etcd: + externalCluster: null + peerAddress: 192.168.0.216 + type: etcd + telemetry: + enabled: true +status: {} diff --git a/k8s/metallb-config.in.yaml b/k8s/metallb-config.in.yaml new file mode 100644 index 0000000..1a8542d --- /dev/null +++ b/k8s/metallb-config.in.yaml @@ -0,0 +1,9 @@ +--- +kind: IPAddressPool +apiVersion: metallb.io/v1beta1 +metadata: + namespace: metallb + name: self +spec: + addresses: + - "{{ .networking.ip }}/32" diff --git a/k8s/metallb-crds.yaml b/k8s/metallb-crds.yaml new file mode 100644 index 0000000..a3a5f61 --- /dev/null +++ b/k8s/metallb-crds.yaml @@ -0,0 +1,1108 @@ +# Taken from bitnami/metallb helm chart 4.1.12 for metallb 0.13.7 + +--- +# Source: metallb/templates/controller/crds.yaml +# Based on: https://github.com/metallb/metallb/blob/v0.13.4/charts/metallb/charts/crds/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: addresspools.metallb.io +spec: + group: metallb.io + names: + kind: AddressPool + listKind: AddressPoolList + plural: addresspools + singular: addresspool + scope: Namespaced + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: ["v1alpha1", "v1beta1"] + clientConfig: + # this is a valid pem format, otherwise the apiserver will reject the deletion of the crds + # with "unable to parse bytes as PEM block", The controller will patch it with the right content after it starts + caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + service: + namespace: "default" + name: metallb-webhook-service + path: /convert + versions: + - deprecated: true + deprecationWarning: metallb.io v1alpha1 AddressPool is deprecated + name: v1alpha1 + schema: + openAPIV3Schema: + description: AddressPool is the Schema for the addresspools API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AddressPoolSpec defines the desired state of AddressPool. + properties: + addresses: + description: A list of IP address ranges over which MetalLB has authority. + You can list multiple ranges in a single pool, they will all share + the same settings. Each range can be either a CIDR prefix, or an + explicit start-end range of IPs. + items: + type: string + type: array + autoAssign: + default: true + description: AutoAssign flag used to prevent MetallB from automatic + allocation for a pool. + type: boolean + bgpAdvertisements: + description: When an IP is allocated from this pool, how should it + be translated into BGP announcements? + items: + properties: + aggregationLength: + default: 32 + description: The aggregation-length advertisement option lets + you “roll up” the /32s into a larger prefix. + format: int32 + minimum: 1 + type: integer + aggregationLengthV6: + default: 128 + description: Optional, defaults to 128 (i.e. no aggregation) + if not specified. + format: int32 + type: integer + communities: + description: BGP communities + items: + type: string + type: array + localPref: + description: BGP LOCAL_PREF attribute which is used by BGP best + path algorithm, Path with higher localpref is preferred over + one with lower localpref. + format: int32 + type: integer + type: object + type: array + protocol: + description: Protocol can be used to select how the announcement is + done. + enum: + - layer2 + - bgp + type: string + required: + - addresses + - protocol + type: object + status: + description: AddressPoolStatus defines the observed state of AddressPool. + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - deprecated: true + deprecationWarning: metallb.io v1beta1 AddressPool is deprecated, consider using + IPAddressPool + name: v1beta1 + schema: + openAPIV3Schema: + description: AddressPool represents a pool of IP addresses that can be allocated + to LoadBalancer services. AddressPool is deprecated and being replaced by + IPAddressPool. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AddressPoolSpec defines the desired state of AddressPool. + properties: + addresses: + description: A list of IP address ranges over which MetalLB has authority. + You can list multiple ranges in a single pool, they will all share + the same settings. Each range can be either a CIDR prefix, or an + explicit start-end range of IPs. + items: + type: string + type: array + autoAssign: + default: true + description: AutoAssign flag used to prevent MetallB from automatic + allocation for a pool. + type: boolean + bgpAdvertisements: + description: Drives how an IP allocated from this pool should translated + into BGP announcements. + items: + properties: + aggregationLength: + default: 32 + description: The aggregation-length advertisement option lets + you “roll up” the /32s into a larger prefix. + format: int32 + minimum: 1 + type: integer + aggregationLengthV6: + default: 128 + description: Optional, defaults to 128 (i.e. no aggregation) + if not specified. + format: int32 + type: integer + communities: + description: BGP communities to be associated with the given + advertisement. + items: + type: string + type: array + localPref: + description: BGP LOCAL_PREF attribute which is used by BGP best + path algorithm, Path with higher localpref is preferred over + one with lower localpref. + format: int32 + type: integer + type: object + type: array + protocol: + description: Protocol can be used to select how the announcement is + done. + enum: + - layer2 + - bgp + type: string + required: + - addresses + - protocol + type: object + status: + description: AddressPoolStatus defines the observed state of AddressPool. + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: metallb/templates/controller/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: bfdprofiles.metallb.io +spec: + group: metallb.io + names: + kind: BFDProfile + listKind: BFDProfileList + plural: bfdprofiles + singular: bfdprofile + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: BFDProfile represents the settings of the bfd session that can + be optionally associated with a BGP session. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BFDProfileSpec defines the desired state of BFDProfile. + properties: + detectMultiplier: + description: Configures the detection multiplier to determine packet + loss. The remote transmission interval will be multiplied by this + value to determine the connection loss detection timer. + format: int32 + maximum: 255 + minimum: 2 + type: integer + echoInterval: + description: Configures the minimal echo receive transmission interval + that this system is capable of handling in milliseconds. Defaults + to 50ms + format: int32 + maximum: 60000 + minimum: 10 + type: integer + echoMode: + description: Enables or disables the echo transmission mode. This + mode is disabled by default, and not supported on multi hops setups. + type: boolean + minimumTtl: + description: 'For multi hop sessions only: configure the minimum expected + TTL for an incoming BFD control packet.' + format: int32 + maximum: 254 + minimum: 1 + type: integer + passiveMode: + description: 'Mark session as passive: a passive session will not + attempt to start the connection and will wait for control packets + from peer before it begins replying.' + type: boolean + receiveInterval: + description: The minimum interval that this system is capable of receiving + control packets in milliseconds. Defaults to 300ms. + format: int32 + maximum: 60000 + minimum: 10 + type: integer + transmitInterval: + description: The minimum transmission interval (less jitter) that + this system wants to use to send BFD control packets in milliseconds. + Defaults to 300ms + format: int32 + maximum: 60000 + minimum: 10 + type: integer + type: object + status: + description: BFDProfileStatus defines the observed state of BFDProfile. + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: metallb/templates/controller/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: bgpadvertisements.metallb.io +spec: + group: metallb.io + names: + kind: BGPAdvertisement + listKind: BGPAdvertisementList + plural: bgpadvertisements + singular: bgpadvertisement + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: BGPAdvertisement allows to advertise the IPs coming from the + selected IPAddressPools via BGP, setting the parameters of the BGP Advertisement. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BGPAdvertisementSpec defines the desired state of BGPAdvertisement. + properties: + aggregationLength: + default: 32 + description: The aggregation-length advertisement option lets you + “roll up” the /32s into a larger prefix. Defaults to 32. Works for + IPv4 addresses. + format: int32 + minimum: 1 + type: integer + aggregationLengthV6: + default: 128 + description: The aggregation-length advertisement option lets you + “roll up” the /128s into a larger prefix. Defaults to 128. Works + for IPv6 addresses. + format: int32 + type: integer + communities: + description: The BGP communities to be associated with the announcement. + Each item can be a community of the form 1234:1234 or the name of + an alias defined in the Community CRD. + items: + type: string + type: array + ipAddressPoolSelectors: + description: A selector for the IPAddressPools which would get advertised + via this advertisement. If no IPAddressPool is selected by this + or by the list, the advertisement is applied to all the IPAddressPools. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + type: array + ipAddressPools: + description: The list of IPAddressPools to advertise via this advertisement, + selected by name. + items: + type: string + type: array + localPref: + description: The BGP LOCAL_PREF attribute which is used by BGP best + path algorithm, Path with higher localpref is preferred over one + with lower localpref. + format: int32 + type: integer + nodeSelectors: + description: NodeSelectors allows to limit the nodes to announce as + next hops for the LoadBalancer IP. When empty, all the nodes having are + announced as next hops. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + type: array + peers: + description: Peers limits the bgppeer to advertise the ips of the + selected pools to. When empty, the loadbalancer IP is announced + to all the BGPPeers configured. + items: + type: string + type: array + type: object + status: + description: BGPAdvertisementStatus defines the observed state of BGPAdvertisement. + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: metallb/templates/controller/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: bgppeers.metallb.io +spec: + group: metallb.io + names: + kind: BGPPeer + listKind: BGPPeerList + plural: bgppeers + singular: bgppeer + scope: Namespaced + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: ["v1beta1", "v1beta2"] + clientConfig: + # this is a valid pem format, otherwise the apiserver will reject the deletion of the crds + # with "unable to parse bytes as PEM block", The controller will patch it with the right content after it starts + caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + service: + namespace: "default" + name: metallb-webhook-service + path: /convert + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: BGPPeer is the Schema for the peers API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BGPPeerSpec defines the desired state of Peer. + properties: + bfdProfile: + type: string + ebgpMultiHop: + description: EBGP peer is multi-hops away + type: boolean + holdTime: + description: Requested BGP hold time, per RFC4271. + type: string + keepaliveTime: + description: Requested BGP keepalive time, per RFC4271. + type: string + myASN: + description: AS number to use for the local end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + nodeSelectors: + description: Only connect to this peer on nodes that match one of + these selectors. + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + minItems: 1 + type: array + required: + - key + - operator + - values + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + password: + description: Authentication password for routers enforcing TCP MD5 + authenticated sessions + type: string + peerASN: + description: AS number to expect from the remote end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + peerAddress: + description: Address to dial when establishing the session. + type: string + peerPort: + description: Port to dial when establishing the session. + maximum: 16384 + minimum: 0 + type: integer + routerID: + description: BGP router ID to advertise to the peer + type: string + sourceAddress: + description: Source address to use when establishing the session. + type: string + required: + - myASN + - peerASN + - peerAddress + type: object + status: + description: BGPPeerStatus defines the observed state of Peer. + type: object + type: object + served: true + storage: false + subresources: + status: {} + - name: v1beta2 + schema: + openAPIV3Schema: + description: BGPPeer is the Schema for the peers API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BGPPeerSpec defines the desired state of Peer. + properties: + bfdProfile: + description: The name of the BFD Profile to be used for the BFD session + associated to the BGP session. If not set, the BFD session won't + be set up. + type: string + ebgpMultiHop: + description: To set if the BGPPeer is multi-hops away. Needed for + FRR mode only. + type: boolean + holdTime: + description: Requested BGP hold time, per RFC4271. + type: string + keepaliveTime: + description: Requested BGP keepalive time, per RFC4271. + type: string + myASN: + description: AS number to use for the local end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + nodeSelectors: + description: Only connect to this peer on nodes that match one of + these selectors. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + type: array + password: + description: Authentication password for routers enforcing TCP MD5 + authenticated sessions + type: string + passwordSecret: + description: passwordSecret is name of the authentication secret for + BGP Peer. the secret must be of type "kubernetes.io/basic-auth", + and created in the same namespace as the MetalLB deployment. The + password is stored in the secret as the key "password". + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + peerASN: + description: AS number to expect from the remote end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + peerAddress: + description: Address to dial when establishing the session. + type: string + peerPort: + default: 179 + description: Port to dial when establishing the session. + maximum: 16384 + minimum: 0 + type: integer + routerID: + description: BGP router ID to advertise to the peer + type: string + sourceAddress: + description: Source address to use when establishing the session. + type: string + required: + - myASN + - peerASN + - peerAddress + type: object + status: + description: BGPPeerStatus defines the observed state of Peer. + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: metallb/templates/controller/crds.yaml +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: ipaddresspools.metallb.io +spec: + group: metallb.io + names: + kind: IPAddressPool + listKind: IPAddressPoolList + plural: ipaddresspools + singular: ipaddresspool + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: IPAddressPool represents a pool of IP addresses that can be allocated + to LoadBalancer services. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IPAddressPoolSpec defines the desired state of IPAddressPool. + properties: + addresses: + description: A list of IP address ranges over which MetalLB has authority. + You can list multiple ranges in a single pool, they will all share + the same settings. Each range can be either a CIDR prefix, or an + explicit start-end range of IPs. + items: + type: string + type: array + autoAssign: + default: true + description: AutoAssign flag used to prevent MetallB from automatic + allocation for a pool. + type: boolean + avoidBuggyIPs: + default: false + description: AvoidBuggyIPs prevents addresses ending with .0 and .255 + to be used by a pool. + type: boolean + required: + - addresses + type: object + status: + description: IPAddressPoolStatus defines the observed state of IPAddressPool. + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: metallb/templates/controller/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: l2advertisements.metallb.io +spec: + group: metallb.io + names: + kind: L2Advertisement + listKind: L2AdvertisementList + plural: l2advertisements + singular: l2advertisement + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: L2Advertisement allows to advertise the LoadBalancer IPs provided + by the selected pools via L2. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: L2AdvertisementSpec defines the desired state of L2Advertisement. + properties: + ipAddressPoolSelectors: + description: A selector for the IPAddressPools which would get advertised + via this advertisement. If no IPAddressPool is selected by this + or by the list, the advertisement is applied to all the IPAddressPools. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + type: array + ipAddressPools: + description: The list of IPAddressPools to advertise via this advertisement, + selected by name. + items: + type: string + type: array + nodeSelectors: + description: NodeSelectors allows to limit the nodes to announce as + next hops for the LoadBalancer IP. When empty, all the nodes having are + announced as next hops. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + type: array + type: object + status: + description: L2AdvertisementStatus defines the observed state of L2Advertisement. + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +# Source: metallb/templates/controller/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: communities.metallb.io +spec: + group: metallb.io + names: + kind: Community + listKind: CommunityList + plural: communities + singular: community + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Community is a collection of aliases for communities. Users can + define named aliases to be used in the BGPPeer CRD. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CommunitySpec defines the desired state of Community. + properties: + communities: + items: + properties: + name: + description: The name of the alias for the community. + type: string + value: + description: The BGP community value corresponding to the given + name. + type: string + type: object + type: array + type: object + status: + description: CommunityStatus defines the observed state of Community. + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/k8s/metallb-rbac.yaml b/k8s/metallb-rbac.yaml new file mode 100644 index 0000000..90ccbf1 --- /dev/null +++ b/k8s/metallb-rbac.yaml @@ -0,0 +1,352 @@ +# Based on bitnami/metallb helm chart 4.1.12 for metallb 0.13.7 + +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + namespace: metallb + name: metallb-controller +automountServiceAccountToken: true + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: metallb-controller +rules: + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - update + - apiGroups: + - "" + resources: + - services/status + verbs: + - update + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - policy + resourceNames: + - metallb-controller + resources: + - podsecuritypolicies + verbs: + - use + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + - mutatingwebhookconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metallb-controller +subjects: + - kind: ServiceAccount + namespace: metallb + name: metallb-controller +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: metallb-controller + +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: metallb + name: metallb-controller +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - get + - list + - watch + - apiGroups: + - "" + resources: + - secrets + resourceNames: + - metallb-memberlist + verbs: + - list + - apiGroups: + - apps + resources: + - deployments + resourceNames: + - metallb-controller + verbs: + - get + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - metallb.io + resources: + - addresspools + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - ipaddresspools + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - bgppeers + verbs: + - get + - list + - apiGroups: + - metallb.io + resources: + - bgpadvertisements + verbs: + - get + - list + - apiGroups: + - metallb.io + resources: + - l2advertisements + verbs: + - get + - list + - apiGroups: + - metallb.io + resources: + - communities + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - bfdprofiles + verbs: + - get + - list + - watch + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: metallb + name: metallb-controller +subjects: + - kind: ServiceAccount + namespace: metallb + name: metallb-controller +roleRef: + kind: Role + apiGroup: rbac.authorization.k8s.io + name: metallb-controller + +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + namespace: metallb + name: metallb-speaker +automountServiceAccountToken: true + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: metallb-speaker +rules: + - apiGroups: + - "" + resources: + - services + - endpoints + - nodes + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - policy + resourceNames: + - metallb-speaker + resources: + - podsecuritypolicies + verbs: + - use + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metallb-speaker +subjects: + - kind: ServiceAccount + namespace: metallb + name: metallb-speaker +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metallb-speaker + +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: metallb + name: metallb-pod-lister +rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - list + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - addresspools + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - bfdprofiles + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - bgppeers + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - l2advertisements + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - bgpadvertisements + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - ipaddresspools + verbs: + - get + - list + - watch + - apiGroups: + - metallb.io + resources: + - communities + verbs: + - get + - list + - watch + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: metallb + name: metallb-pod-lister +roleRef: + kind: Role + apiGroup: rbac.authorization.k8s.io + name: metallb-pod-lister +subjects: + - kind: ServiceAccount + name: metallb-speaker diff --git a/k8s/metallb.yaml b/k8s/metallb.yaml new file mode 100644 index 0000000..a6c56a3 --- /dev/null +++ b/k8s/metallb.yaml @@ -0,0 +1,326 @@ +# Based on bitnami/metallb helm chart 4.1.12 for metallb 0.13.7 + +--- +kind: DaemonSet +apiVersion: apps/v1 +metadata: + namespace: metallb + name: metallb-speaker +spec: + updateStrategy: + type: RollingUpdate + selector: + matchLabels: + app: metallb-speaker + template: + metadata: + labels: + app: metallb-speaker + spec: + serviceAccountName: metallb-speaker + hostNetwork: true + securityContext: + fsGroup: 0 + terminationGracePeriodSeconds: 2 + containers: + - name: metallb-speaker + image: "docker.io/bitnami/metallb-speaker:0.13.7-debian-11-r8" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_ADMIN + - NET_RAW + - SYS_ADMIN + drop: + - ALL + readOnlyRootFilesystem: true + runAsUser: 0 + args: + - "--port=7472" + env: + - name: METALLB_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: METALLB_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: METALLB_ML_BIND_ADDR + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: METALLB_ML_LABELS + value: app=metallb-speaker + - name: METALLB_ML_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: METALLB_ML_SECRET_KEY + valueFrom: + secretKeyRef: + name: metallb-memberlist + key: secretkey + ports: + - name: metrics + containerPort: 7472 + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + httpGet: + path: /metrics + port: metrics + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + httpGet: + path: /metrics + port: metrics + resources: {} + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: metallb + name: webhook-server-cert + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + namespace: metallb + name: metallb-controller + labels: + app.kubernetes.io/name: metallb +spec: + replicas: 1 + strategy: + type: RollingUpdate + revisionHistoryLimit: 3 + selector: + matchLabels: + app: metallb-controller + template: + metadata: + labels: + app: metallb-controller + spec: + serviceAccountName: metallb-controller + securityContext: + fsGroup: 1001 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert + containers: + - name: metallb-controller + image: "docker.io/bitnami/metallb-controller:0.13.7-debian-11-r9" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1001 + args: + - --port=7472 + - --cert-service-name=metallb-webhook-service + ports: + - name: webhook-server + containerPort: 9443 + - name: metrics + containerPort: 7472 + volumeMounts: + - name: cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + httpGet: + path: /metrics + port: metrics + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + httpGet: + path: /metrics + port: metrics + resources: {} + +--- +kind: Service +apiVersion: v1 +metadata: + namespace: metallb + name: metallb-webhook-service +spec: + ports: + - port: 443 + targetPort: 9443 + selector: + app: metallb-controller + +--- +kind: ValidatingWebhookConfiguration +apiVersion: admissionregistration.k8s.io/v1 +metadata: + name: metallb-webhook-configuration +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta1-addresspool + failurePolicy: Fail + name: addresspoolvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - addresspools + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta2-bgppeer + failurePolicy: Fail + name: bgppeervalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - bgppeers + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta1-ipaddresspool + failurePolicy: Fail + name: ipaddresspoolvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - ipaddresspools + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta1-bgpadvertisement + failurePolicy: Fail + name: bgpadvertisementvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - bgpadvertisements + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta1-community + failurePolicy: Fail + name: communityvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - communities + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta1-bfdprofile + failurePolicy: Fail + name: bfdprofileyvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - DELETE + resources: + - bfdprofiles + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + namespace: metallb + name: metallb-webhook-service + path: /validate-metallb-io-v1beta1-l2advertisement + failurePolicy: Fail + name: l2advertisementvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - l2advertisements + sideEffects: None diff --git a/k8s/namespaces.yaml b/k8s/namespaces.yaml new file mode 100644 index 0000000..86e4829 --- /dev/null +++ b/k8s/namespaces.yaml @@ -0,0 +1,23 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: traefik + +--- +kind: Namespace +apiVersion: v1 +metadata: + name: metallb + +--- +kind: Namespace +apiVersion: v1 +metadata: + name: riju + +--- +kind: Namespace +apiVersion: v1 +metadata: + name: riju-user diff --git a/k8s/provisioning.md b/k8s/provisioning.md new file mode 100644 index 0000000..9a9c4ee --- /dev/null +++ b/k8s/provisioning.md @@ -0,0 +1,25 @@ +```bash +curl -sSLf https://get.k0s.sh | sudo sh +sudo mkdir /etc/k0s +k0s config create > /etc/k0s/k0s.yaml +``` + +Edit to have this config: + +```yaml +spec: + extensions: + storage: + type: openebs_local_storage +``` + +```bash +sudo k0s install controller --single +sudo k0s start +``` + +Go to client machine: + +```bash +ssh riju-k8s sudo -S k0s kubeconfig admin > ~/.kube/config +``` diff --git a/k8s/riju-docker-registry.yaml b/k8s/riju-docker-registry.yaml new file mode 100644 index 0000000..4eb007d --- /dev/null +++ b/k8s/riju-docker-registry.yaml @@ -0,0 +1,102 @@ +--- +kind: StatefulSet +apiVersion: apps/v1 +metadata: + namespace: riju + name: docker-registry +spec: + replicas: 1 + serviceName: docker-registry + selector: + matchLabels: + app: docker-registry + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 128Gi + storageClassName: openebs-hostpath + template: + metadata: + labels: + app: docker-registry + spec: + volumes: + - name: auth + secret: + secretName: registry-auth + containers: + - name: registry + image: "registry:2" + resources: {} + readinessProbe: + httpGet: + path: / + port: 5000 + scheme: HTTP + failureThreshold: 1 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + httpGet: + path: / + port: 5000 + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + env: + - name: REGISTRY_AUTH + value: htpasswd + - name: REGISTRY_AUTH_HTPASSWD_REALM + value: "Registry Realm" + - name: REGISTRY_AUTH_HTPASSWD_PATH + value: /var/run/registry/auth/htpasswd + ports: + - name: api + containerPort: 5000 + volumeMounts: + - name: auth + mountPath: /var/run/registry/auth + - name: data + mountPath: /var/lib/registry + +--- +kind: Service +apiVersion: v1 +metadata: + namespace: riju + name: docker-registry +spec: + selector: + app: docker-registry + type: NodePort + ports: + - name: api + port: 80 + nodePort: 30999 + targetPort: 5000 + +--- +kind: IngressRoute +apiVersion: traefik.containo.us/v1alpha1 +metadata: + namespace: riju + name: docker-registry +spec: + entryPoints: + - docker + routes: + - kind: Rule + match: "PathPrefix(`/`)" + services: + - namespace: riju + name: docker-registry + port: 80 diff --git a/k8s/riju-minio.yaml b/k8s/riju-minio.yaml new file mode 100644 index 0000000..486a937 --- /dev/null +++ b/k8s/riju-minio.yaml @@ -0,0 +1,101 @@ +--- +kind: StatefulSet +apiVersion: apps/v1 +metadata: + namespace: riju + name: minio +spec: + replicas: 1 + serviceName: minio + selector: + matchLabels: + app: minio + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 16Gi + storageClassName: openebs-hostpath + template: + metadata: + labels: + app: minio + spec: + containers: + - name: minio + image: "minio/minio:RELEASE.2022-12-12T19-27-27Z" + resources: {} + readinessProbe: + httpGet: + path: /minio/health/live + port: 9000 + scheme: HTTP + failureThreshold: 1 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + httpGet: + path: /minio/health/live + port: 9000 + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + args: + - "server" + - "/data" + env: + - name: MINIO_ACCESS_KEY + valueFrom: + secretKeyRef: + name: minio-keys + key: access-key + - name: MINIO_SECRET_KEY + valueFrom: + secretKeyRef: + name: minio-keys + key: secret-key + ports: + - name: api + containerPort: 9000 + volumeMounts: + - name: data + mountPath: /data + +--- +kind: Service +apiVersion: v1 +metadata: + namespace: riju + name: minio +spec: + selector: + app: minio + ports: + - name: api + port: 80 + targetPort: 9000 + +--- +kind: IngressRoute +apiVersion: traefik.containo.us/v1alpha1 +metadata: + namespace: riju + name: minio +spec: + entryPoints: + - minio + routes: + - kind: Rule + match: "PathPrefix(`/`)" + services: + - namespace: riju + name: minio + port: 80 diff --git a/k8s/riju-proxy.yaml b/k8s/riju-proxy.yaml new file mode 100644 index 0000000..cbd0847 --- /dev/null +++ b/k8s/riju-proxy.yaml @@ -0,0 +1,117 @@ +--- +kind: ConfigMap +apiVersion: v1 +metadata: + namespace: riju + name: riju-proxy-config +data: + default.conf: | + underscores_in_headers on; + + server { + resolver kube-dns.kube-system.svc.cluster.local; + listen 1869 default_server; + + auth_basic "Riju administrative proxy"; + auth_basic_user_file /etc/nginx/passwd; + + location ~ /(10\.[0-9]+\.[0-9]+\.[0-9]+)/health { + proxy_pass http://$1:869/health; + } + + location ~ /(10\.[0-9]+\.[0-9]+\.[0-9]+)/exec { + proxy_pass http://$1:869/exec$is_args$args; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + } + + location / { + return 404; + } + } + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + namespace: riju + name: riju-proxy +spec: + replicas: 1 + selector: + matchLabels: + app: riju-proxy + template: + metadata: + labels: + app: riju-proxy + spec: + volumes: + - name: config + configMap: + name: riju-proxy-config + - name: auth + secret: + secretName: riju-proxy-auth + containers: + - name: nginx + image: "nginx:1.23" + resources: {} + readinessProbe: + tcpSocket: + port: 1869 + failureThreshold: 1 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + tcpSocket: + port: 1869 + failureThreshold: 3 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + ports: + - name: http + containerPort: 1869 + volumeMounts: + - name: config + mountPath: /etc/nginx/conf.d + - name: auth + mountPath: /etc/nginx/passwd + subPath: htpasswd + +--- +kind: Service +apiVersion: v1 +metadata: + namespace: riju + name: riju-proxy +spec: + selector: + app: riju-proxy + ports: + - name: http + port: 1869 + targetPort: 1869 + +--- +kind: IngressRoute +apiVersion: traefik.containo.us/v1alpha1 +metadata: + namespace: riju + name: riju-proxy +spec: + entryPoints: + - proxy + routes: + - kind: Rule + match: "PathPrefix(`/`)" + services: + - namespace: riju + name: riju-proxy + port: 1869 diff --git a/k8s/riju-server.yaml b/k8s/riju-server.yaml new file mode 100644 index 0000000..34c391d --- /dev/null +++ b/k8s/riju-server.yaml @@ -0,0 +1,89 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + namespace: riju + name: riju-server +spec: + replicas: 1 + selector: + matchLabels: + app: riju-server + template: + metadata: + labels: + app: riju-server + spec: + volumes: + - name: cache + hostPath: + path: /var/cache/riju + - name: docker + hostPath: + path: /var/run/docker.sock + imagePullSecrets: + - name: registry-login + containers: + - name: server + image: "localhost:30999/riju:app" + resources: {} + readinessProbe: + httpGet: + path: / + port: 6119 + scheme: HTTP + failureThreshold: 1 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + httpGet: + path: / + port: 6119 + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + ports: + - name: http + containerPort: 6119 + volumeMounts: + - name: cache + mountPath: /var/cache/riju + - name: docker + mountPath: /var/run/docker.sock + readOnly: true + +--- +kind: Service +apiVersion: v1 +metadata: + namespace: riju + name: riju-server +spec: + selector: + app: riju-server + ports: + - name: http + port: 80 + targetPort: 6119 + +--- +kind: IngressRoute +apiVersion: traefik.containo.us/v1alpha1 +metadata: + namespace: riju + name: riju-server +spec: + entryPoints: + - https + routes: + - kind: Rule + match: "PathPrefix(`/`)" + services: + - namespace: riju + name: riju-server + port: 80 diff --git a/k8s/secrets.in.yaml b/k8s/secrets.in.yaml new file mode 100644 index 0000000..7cffa97 --- /dev/null +++ b/k8s/secrets.in.yaml @@ -0,0 +1,95 @@ +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: metallb + name: metallb-memberlist +data: + secretkey: "{{ .metallb.secretkey | b64enc }}" + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: riju + name: registry-auth +data: + htpasswd: "{{ .registry.htpasswd | println | b64enc }}" + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: riju + name: registry-login +type: kubernetes.io/dockerconfigjson +stringData: + .dockerconfigjson: | + { + "auths": { + "localhost:30999": { + "username": "admin", + "password": "{{ .registry.password }}", + "auth": "{{ .registry.password | printf "admin:%s" | b64enc }}" + } + } + } + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: riju-user + name: registry-user-login +type: kubernetes.io/dockerconfigjson +stringData: + .dockerconfigjson: | + { + "auths": { + "localhost:30999": { + "username": "admin", + "password": "{{ .registry.password }}", + "auth": "{{ .registry.password | printf "admin:%s" | b64enc }}" + } + } + } + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: riju + name: minio-keys +stringData: + access-key: "{{ .minio.accessKey }}" + secret-key: "{{ .minio.secretKey }}" + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: riju-user + name: minio-user-login +stringData: + config.json: | + { + "version": "10", + "aliases": { + "riju": { + "url": "http://minio.riju.svc", + "accessKey": "{{ .minio.accessKey }}", + "secretKey": "{{ .minio.secretKey }}", + "api": "s3v4", + "path": "auto" + } + } + } + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: riju + name: riju-proxy-auth +data: + htpasswd: "{{ .proxy.htpasswd | println | b64enc }}" diff --git a/k8s/traefik-config.in.yaml b/k8s/traefik-config.in.yaml new file mode 100644 index 0000000..52ff2e6 --- /dev/null +++ b/k8s/traefik-config.in.yaml @@ -0,0 +1,60 @@ +--- +kind: ConfigMap +apiVersion: v1 +metadata: + namespace: traefik + name: traefik-config +data: + traefik.yaml: | + entryPoints: + proxy: + address: ":1869" + http: + tls: + certResolver: riju + domains: + - main: k8s.riju.codes + http: + address: ":8000" + https: + address: ":8443" + http: + tls: + certResolver: riju + domains: + - main: k8s.riju.codes + healthcheck: + address: ":9000" + metrics: + address: ":9100" + docker: + address: ":31000" + http: + tls: + certResolver: riju + domains: + - main: k8s.riju.codes + minio: + address: ":32000" + http: + tls: + certResolver: riju + domains: + - main: k8s.riju.codes + ping: + entryPoint: "healthcheck" + metrics: + prometheus: + entryPoint: "metrics" + providers: + kubernetesCRD: {} + certificatesResolvers: + riju: + acme: + {{- if not .contact.letsEncryptProductionEnabled }} + caServer: https://acme-staging-v02.api.letsencrypt.org/directory + {{- end }} + email: "{{ .contact.letsEncryptEmail }}" + storage: /data/acme.json + httpChallenge: + entryPoint: http diff --git a/k8s/traefik-crds.yaml b/k8s/traefik-crds.yaml new file mode 100644 index 0000000..a4b290e --- /dev/null +++ b/k8s/traefik-crds.yaml @@ -0,0 +1,2288 @@ +# https://github.com/traefik/traefik/blob/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: ingressroutes.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRoute + listKind: IngressRouteList + plural: ingressroutes + singular: ingressroute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRoute is the CRD implementation of a Traefik HTTP Router. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteSpec defines the desired state of IngressRoute. + properties: + entryPoints: + description: 'EntryPoints defines the list of entry point names to + bind to. Entry points have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + Default: all.' + items: + type: string + type: array + routes: + description: Routes defines the list of routes. + items: + description: Route holds the HTTP route configuration. + properties: + kind: + description: Kind defines the kind of the route. Rule is the + only supported kind. + enum: + - Rule + type: string + match: + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule' + type: string + middlewares: + description: 'Middlewares defines the list of references to + Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware' + items: + description: MiddlewareRef is a reference to a Middleware + resource. + properties: + name: + description: Name defines the name of the referenced Middleware + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Middleware resource. + type: string + required: + - name + type: object + type: array + priority: + description: 'Priority defines the router''s priority. More + info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority' + type: integer + services: + description: Services defines the list of Service. It can contain + any combination of TraefikService and/or reference to a Kubernetes + Service. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: Name defines the name of the referenced Kubernetes + Service or TraefikService. The differentiation between + the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + passHostHeader: + description: PassHostHeader defines whether the client + Host header is forwarded to the upstream Kubernetes + Service. By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to + the client. + properties: + flushInterval: + description: 'FlushInterval defines the interval, + in milliseconds, in between flushes to the client + while copying the response body. A negative value + means to flush immediately after each write to the + client. This configuration is ignored when ReverseProxy + recognizes a response as a streaming response; for + such responses, writes are flushed to the client + immediately. Default: 100ms' + type: string + type: object + scheme: + description: Scheme defines the scheme to use for the + request to the upstream Kubernetes Service. It defaults + to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: ServersTransport defines the name of ServersTransport + resource to use. It allows to configure the transport + between Traefik and your servers. Can only be used on + a Kubernetes Service. + type: string + sticky: + description: 'Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as + JavaScript. + type: boolean + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: 'SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + type: string + secure: + description: Secure defines whether the cookie + can only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: Strategy defines the load balancing strategy + between the servers. RoundRobin is the only supported + value at the moment. + type: string + weight: + description: Weight defines the weight and should only + be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round + Robin). + type: integer + required: + - name + type: object + type: array + required: + - kind + - match + type: object + type: array + tls: + description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls' + properties: + certResolver: + description: 'CertResolver defines the name of the certificate + resolver to use. Cert resolvers have to be configured in the + static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + type: string + domains: + description: 'Domains defines the list of domains that will be + used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + items: + description: Domain holds a domain name with SANs. + properties: + main: + description: Main defines the main domain name. + type: string + sans: + description: SANs defines the subject alternative domain + names. + items: + type: string + type: array + type: object + type: array + options: + description: 'Options defines the reference to a TLSOption, that + specifies the parameters of the TLS connection. If not defined, + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + properties: + name: + description: 'Name defines the name of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + type: string + namespace: + description: 'Namespace defines the namespace of the referenced + TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + type: string + required: + - name + type: object + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + store: + description: Store defines the reference to the TLSStore, that + will be used to store certificates. Please note that only `default` + TLSStore can be used. + properties: + name: + description: 'Name defines the name of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + type: string + namespace: + description: 'Namespace defines the namespace of the referenced + TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: ingressroutetcps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRouteTCP + listKind: IngressRouteTCPList + plural: ingressroutetcps + singular: ingressroutetcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteTCP is the CRD implementation of a Traefik TCP Router. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteTCPSpec defines the desired state of IngressRouteTCP. + properties: + entryPoints: + description: 'EntryPoints defines the list of entry point names to + bind to. Entry points have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + Default: all.' + items: + type: string + type: array + routes: + description: Routes defines the list of routes. + items: + description: RouteTCP holds the TCP route configuration. + properties: + match: + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1' + type: string + middlewares: + description: Middlewares defines the list of references to MiddlewareTCP + resources. + items: + description: ObjectReference is a generic reference to a Traefik + resource. + properties: + name: + description: Name defines the name of the referenced Traefik + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Traefik resource. + type: string + required: + - name + type: object + type: array + priority: + description: 'Priority defines the router''s priority. More + info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1' + type: integer + services: + description: Services defines the list of TCP services. + items: + description: ServiceTCP defines an upstream TCP service to + proxy traffic to. + properties: + name: + description: Name defines the name of the referenced Kubernetes + Service. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service. + type: string + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + proxyProtocol: + description: 'ProxyProtocol defines the PROXY protocol + configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol' + properties: + version: + description: Version defines the PROXY Protocol version + to use. + type: integer + type: object + terminationDelay: + description: TerminationDelay defines the deadline that + the proxy sets, after one of its connected peers indicates + it has closed the writing capability of its connection, + to close the reading capability as well, hence fully + terminating the connection. It is a duration in milliseconds, + defaulting to 100. A negative value means an infinite + deadline (i.e. the reading capability is never closed). + type: integer + weight: + description: Weight defines the weight used when balancing + requests between multiple Kubernetes Service. + type: integer + required: + - name + - port + type: object + type: array + required: + - match + type: object + type: array + tls: + description: 'TLS defines the TLS configuration on a layer 4 / TCP + Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1' + properties: + certResolver: + description: 'CertResolver defines the name of the certificate + resolver to use. Cert resolvers have to be configured in the + static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + type: string + domains: + description: 'Domains defines the list of domains that will be + used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + items: + description: Domain holds a domain name with SANs. + properties: + main: + description: Main defines the main domain name. + type: string + sans: + description: SANs defines the subject alternative domain + names. + items: + type: string + type: array + type: object + type: array + options: + description: 'Options defines the reference to a TLSOption, that + specifies the parameters of the TLS connection. If not defined, + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + properties: + name: + description: Name defines the name of the referenced Traefik + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Traefik resource. + type: string + required: + - name + type: object + passthrough: + description: Passthrough defines whether a TLS router will terminate + the TLS connection. + type: boolean + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + store: + description: Store defines the reference to the TLSStore, that + will be used to store certificates. Please note that only `default` + TLSStore can be used. + properties: + name: + description: Name defines the name of the referenced Traefik + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Traefik resource. + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: ingressrouteudps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: IngressRouteUDP + listKind: IngressRouteUDPList + plural: ingressrouteudps + singular: ingressrouteudp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteUDP is a CRD implementation of a Traefik UDP Router. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IngressRouteUDPSpec defines the desired state of a IngressRouteUDP. + properties: + entryPoints: + description: 'EntryPoints defines the list of entry point names to + bind to. Entry points have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + Default: all.' + items: + type: string + type: array + routes: + description: Routes defines the list of routes. + items: + description: RouteUDP holds the UDP route configuration. + properties: + services: + description: Services defines the list of UDP services. + items: + description: ServiceUDP defines an upstream UDP service to + proxy traffic to. + properties: + name: + description: Name defines the name of the referenced Kubernetes + Service. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service. + type: string + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + weight: + description: Weight defines the weight used when balancing + requests between multiple Kubernetes Service. + type: integer + required: + - name + - port + type: object + type: array + type: object + type: array + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: middlewares.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: Middleware + listKind: MiddlewareList + plural: middlewares + singular: middleware + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'Middleware is the CRD implementation of a Traefik Middleware. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MiddlewareSpec defines the desired state of a Middleware. + properties: + addPrefix: + description: 'AddPrefix holds the add prefix middleware configuration. + This middleware updates the path of a request before forwarding + it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/' + properties: + prefix: + description: Prefix is the string to add before the current path + in the requested URL. It should include a leading slash (/). + type: string + type: object + basicAuth: + description: 'BasicAuth holds the basic auth middleware configuration. + This middleware restricts access to your services to known users. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/' + properties: + headerField: + description: 'HeaderField defines a header field to store the + authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + type: string + realm: + description: 'Realm allows the protected resources on a server + to be partitioned into a set of protection spaces, each with + its own authentication scheme. Default: traefik.' + type: string + removeHeader: + description: 'RemoveHeader sets the removeHeader option to true + to remove the authorization header before forwarding the request + to your service. Default: false.' + type: boolean + secret: + description: Secret is the name of the referenced Kubernetes Secret + containing user credentials. + type: string + type: object + buffering: + description: 'Buffering holds the buffering middleware configuration. + This middleware retries or limits the size of requests that can + be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes' + properties: + maxRequestBodyBytes: + description: 'MaxRequestBodyBytes defines the maximum allowed + body size for the request (in bytes). If the request exceeds + the allowed size, it is not forwarded to the service, and the + client gets a 413 (Request Entity Too Large) response. Default: + 0 (no maximum).' + format: int64 + type: integer + maxResponseBodyBytes: + description: 'MaxResponseBodyBytes defines the maximum allowed + response size from the service (in bytes). If the response exceeds + the allowed size, it is not forwarded to the client. The client + gets a 500 (Internal Server Error) response instead. Default: + 0 (no maximum).' + format: int64 + type: integer + memRequestBodyBytes: + description: 'MemRequestBodyBytes defines the threshold (in bytes) + from which the request will be buffered on disk instead of in + memory. Default: 1048576 (1Mi).' + format: int64 + type: integer + memResponseBodyBytes: + description: 'MemResponseBodyBytes defines the threshold (in bytes) + from which the response will be buffered on disk instead of + in memory. Default: 1048576 (1Mi).' + format: int64 + type: integer + retryExpression: + description: 'RetryExpression defines the retry conditions. It + is a logical combination of functions with operators AND (&&) + and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression' + type: string + type: object + chain: + description: 'Chain holds the configuration of the chain middleware. + This middleware enables to define reusable combinations of other + pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/' + properties: + middlewares: + description: Middlewares is the list of MiddlewareRef which composes + the chain. + items: + description: MiddlewareRef is a reference to a Middleware resource. + properties: + name: + description: Name defines the name of the referenced Middleware + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Middleware resource. + type: string + required: + - name + type: object + type: array + type: object + circuitBreaker: + description: CircuitBreaker holds the circuit breaker configuration. + properties: + checkPeriod: + anyOf: + - type: integer + - type: string + description: CheckPeriod is the interval between successive checks + of the circuit breaker condition (when in standby state). + x-kubernetes-int-or-string: true + expression: + description: Expression is the condition that triggers the tripped + state. + type: string + fallbackDuration: + anyOf: + - type: integer + - type: string + description: FallbackDuration is the duration for which the circuit + breaker will wait before trying to recover (from a tripped state). + x-kubernetes-int-or-string: true + recoveryDuration: + anyOf: + - type: integer + - type: string + description: RecoveryDuration is the duration for which the circuit + breaker will try to recover (as soon as it is in recovering + state). + x-kubernetes-int-or-string: true + type: object + compress: + description: 'Compress holds the compress middleware configuration. + This middleware compresses responses before sending them to the + client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/' + properties: + excludedContentTypes: + description: ExcludedContentTypes defines the list of content + types to compare the Content-Type header of the incoming requests + and responses before compressing. + items: + type: string + type: array + minResponseBodyBytes: + description: 'MinResponseBodyBytes defines the minimum amount + of bytes a response body must have to be compressed. Default: + 1024.' + type: integer + type: object + contentType: + description: ContentType holds the content-type middleware configuration. + This middleware exists to enable the correct behavior until at least + the default one can be changed in a future version. + properties: + autoDetect: + description: AutoDetect specifies whether to let the `Content-Type` + header, if it has not been set by the backend, be automatically + set to a value derived from the contents of the response. As + a proxy, the default behavior should be to leave the header + alone, regardless of what the backend did with it. However, + the historic default was to always auto-detect and set the header + if it was nil, and it is going to be kept that way in order + to support users currently relying on it. + type: boolean + type: object + digestAuth: + description: 'DigestAuth holds the digest auth middleware configuration. + This middleware restricts access to your services to known users. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/' + properties: + headerField: + description: 'HeaderField defines a header field to store the + authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + type: string + realm: + description: 'Realm allows the protected resources on a server + to be partitioned into a set of protection spaces, each with + its own authentication scheme. Default: traefik.' + type: string + removeHeader: + description: RemoveHeader defines whether to remove the authorization + header before forwarding the request to the backend. + type: boolean + secret: + description: Secret is the name of the referenced Kubernetes Secret + containing user credentials. + type: string + type: object + errors: + description: 'ErrorPage holds the custom error middleware configuration. + This middleware returns a custom page in lieu of the default, according + to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/' + properties: + query: + description: Query defines the URL for the error page (hosted + by service). The {status} variable can be used in order to insert + the status code in the URL. + type: string + service: + description: 'Service defines the reference to a Kubernetes Service + that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service' + properties: + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: Name defines the name of the referenced Kubernetes + Service or TraefikService. The differentiation between the + two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + passHostHeader: + description: PassHostHeader defines whether the client Host + header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: 'FlushInterval defines the interval, in milliseconds, + in between flushes to the client while copying the response + body. A negative value means to flush immediately after + each write to the client. This configuration is ignored + when ReverseProxy recognizes a response as a streaming + response; for such responses, writes are flushed to + the client immediately. Default: 100ms' + type: string + type: object + scheme: + description: Scheme defines the scheme to use for the request + to the upstream Kubernetes Service. It defaults to https + when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: ServersTransport defines the name of ServersTransport + resource to use. It allows to configure the transport between + Traefik and your servers. Can only be used on a Kubernetes + Service. + type: string + sticky: + description: 'Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie can + be accessed by client-side APIs, such as JavaScript. + type: boolean + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: 'SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: Strategy defines the load balancing strategy + between the servers. RoundRobin is the only supported value + at the moment. + type: string + weight: + description: Weight defines the weight and should only be + specified when Name references a TraefikService object (and + to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + status: + description: Status defines which status or range of statuses + should result in an error page. It can be either a status code + as a number (500), as multiple comma-separated numbers (500,502), + as ranges by separating two codes with a dash (500-599), or + a combination of the two (404,418,500-599). + items: + type: string + type: array + type: object + forwardAuth: + description: 'ForwardAuth holds the forward auth middleware configuration. + This middleware delegates the request authentication to a Service. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/' + properties: + address: + description: Address defines the authentication server address. + type: string + authRequestHeaders: + description: AuthRequestHeaders defines the list of the headers + to copy from the request to the authentication server. If not + set or empty then all request headers are passed. + items: + type: string + type: array + authResponseHeaders: + description: AuthResponseHeaders defines the list of headers to + copy from the authentication server response and set on forwarded + request, replacing any existing conflicting headers. + items: + type: string + type: array + authResponseHeadersRegex: + description: 'AuthResponseHeadersRegex defines the regex to match + headers to copy from the authentication server response and + set on forwarded request, after stripping all headers that match + the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex' + type: string + tls: + description: TLS defines the configuration used to secure the + connection to the authentication server. + properties: + caOptional: + type: boolean + caSecret: + description: CASecret is the name of the referenced Kubernetes + Secret containing the CA to validate the server certificate. + The CA certificate is extracted from key `tls.ca` or `ca.crt`. + type: string + certSecret: + description: CertSecret is the name of the referenced Kubernetes + Secret containing the client certificate. The client certificate + is extracted from the keys `tls.crt` and `tls.key`. + type: string + insecureSkipVerify: + description: InsecureSkipVerify defines whether the server + certificates should be validated. + type: boolean + type: object + trustForwardHeader: + description: 'TrustForwardHeader defines whether to trust (ie: + forward) all X-Forwarded-* headers.' + type: boolean + type: object + headers: + description: 'Headers holds the headers middleware configuration. + This middleware manages the requests and responses headers. More + info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders' + properties: + accessControlAllowCredentials: + description: AccessControlAllowCredentials defines whether the + request can include user credentials. + type: boolean + accessControlAllowHeaders: + description: AccessControlAllowHeaders defines the Access-Control-Request-Headers + values sent in preflight response. + items: + type: string + type: array + accessControlAllowMethods: + description: AccessControlAllowMethods defines the Access-Control-Request-Method + values sent in preflight response. + items: + type: string + type: array + accessControlAllowOriginList: + description: AccessControlAllowOriginList is a list of allowable + origins. Can also be a wildcard origin "*". + items: + type: string + type: array + accessControlAllowOriginListRegex: + description: AccessControlAllowOriginListRegex is a list of allowable + origins written following the Regular Expression syntax (https://golang.org/pkg/regexp/). + items: + type: string + type: array + accessControlExposeHeaders: + description: AccessControlExposeHeaders defines the Access-Control-Expose-Headers + values sent in preflight response. + items: + type: string + type: array + accessControlMaxAge: + description: AccessControlMaxAge defines the time that a preflight + request may be cached. + format: int64 + type: integer + addVaryHeader: + description: AddVaryHeader defines whether the Vary header is + automatically added/updated when the AccessControlAllowOriginList + is set. + type: boolean + allowedHosts: + description: AllowedHosts defines the fully qualified list of + allowed domain names. + items: + type: string + type: array + browserXssFilter: + description: BrowserXSSFilter defines whether to add the X-XSS-Protection + header with the value 1; mode=block. + type: boolean + contentSecurityPolicy: + description: ContentSecurityPolicy defines the Content-Security-Policy + header value. + type: string + contentTypeNosniff: + description: ContentTypeNosniff defines whether to add the X-Content-Type-Options + header with the nosniff value. + type: boolean + customBrowserXSSValue: + description: CustomBrowserXSSValue defines the X-XSS-Protection + header value. This overrides the BrowserXssFilter option. + type: string + customFrameOptionsValue: + description: CustomFrameOptionsValue defines the X-Frame-Options + header value. This overrides the FrameDeny option. + type: string + customRequestHeaders: + additionalProperties: + type: string + description: CustomRequestHeaders defines the header names and + values to apply to the request. + type: object + customResponseHeaders: + additionalProperties: + type: string + description: CustomResponseHeaders defines the header names and + values to apply to the response. + type: object + featurePolicy: + description: 'Deprecated: use PermissionsPolicy instead.' + type: string + forceSTSHeader: + description: ForceSTSHeader defines whether to add the STS header + even when the connection is HTTP. + type: boolean + frameDeny: + description: FrameDeny defines whether to add the X-Frame-Options + header with the DENY value. + type: boolean + hostsProxyHeaders: + description: HostsProxyHeaders defines the header keys that may + hold a proxied hostname value for the request. + items: + type: string + type: array + isDevelopment: + description: IsDevelopment defines whether to mitigate the unwanted + effects of the AllowedHosts, SSL, and STS options when developing. + Usually testing takes place using HTTP, not HTTPS, and on localhost, + not your production domain. If you would like your development + environment to mimic production with complete Host blocking, + SSL redirects, and STS headers, leave this as false. + type: boolean + permissionsPolicy: + description: PermissionsPolicy defines the Permissions-Policy + header value. This allows sites to control browser features. + type: string + publicKey: + description: PublicKey is the public key that implements HPKP + to prevent MITM attacks with forged certificates. + type: string + referrerPolicy: + description: ReferrerPolicy defines the Referrer-Policy header + value. This allows sites to control whether browsers forward + the Referer header to other sites. + type: string + sslForceHost: + description: 'Deprecated: use RedirectRegex instead.' + type: boolean + sslHost: + description: 'Deprecated: use RedirectRegex instead.' + type: string + sslProxyHeaders: + additionalProperties: + type: string + description: 'SSLProxyHeaders defines the header keys with associated + values that would indicate a valid HTTPS request. It can be + useful when using other proxies (example: "X-Forwarded-Proto": + "https").' + type: object + sslRedirect: + description: 'Deprecated: use EntryPoint redirection or RedirectScheme + instead.' + type: boolean + sslTemporaryRedirect: + description: 'Deprecated: use EntryPoint redirection or RedirectScheme + instead.' + type: boolean + stsIncludeSubdomains: + description: STSIncludeSubdomains defines whether the includeSubDomains + directive is appended to the Strict-Transport-Security header. + type: boolean + stsPreload: + description: STSPreload defines whether the preload flag is appended + to the Strict-Transport-Security header. + type: boolean + stsSeconds: + description: STSSeconds defines the max-age of the Strict-Transport-Security + header. If set to 0, the header is not set. + format: int64 + type: integer + type: object + inFlightReq: + description: 'InFlightReq holds the in-flight request middleware configuration. + This middleware limits the number of requests being processed and + served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/' + properties: + amount: + description: Amount defines the maximum amount of allowed simultaneous + in-flight request. The middleware responds with HTTP 429 Too + Many Requests if there are already amount requests in progress + (based on the same sourceCriterion strategy). + format: int64 + type: integer + sourceCriterion: + description: 'SourceCriterion defines what criterion is used to + group requests as originating from a common source. If several + strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the requestHost. More + info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipwhitelist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position + (starting from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the + X-Forwarded-For header and select the first IP not in + the list. + items: + type: string + type: array + type: object + requestHeaderName: + description: RequestHeaderName defines the name of the header + used to group incoming requests. + type: string + requestHost: + description: RequestHost defines whether to consider the request + Host as the source. + type: boolean + type: object + type: object + ipWhiteList: + description: 'IPWhiteList holds the IP whitelist middleware configuration. + This middleware accepts / refuses requests based on the client IP. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipwhitelist/' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipwhitelist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + passTLSClientCert: + description: 'PassTLSClientCert holds the pass TLS client cert middleware + configuration. This middleware adds the selected data from the passed + client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/' + properties: + info: + description: Info selects the specific client certificate details + you want to add to the X-Forwarded-Tls-Client-Cert-Info header. + properties: + issuer: + description: Issuer defines the client certificate issuer + details to add to the X-Forwarded-Tls-Client-Cert-Info header. + properties: + commonName: + description: CommonName defines whether to add the organizationalUnit + information into the issuer. + type: boolean + country: + description: Country defines whether to add the country + information into the issuer. + type: boolean + domainComponent: + description: DomainComponent defines whether to add the + domainComponent information into the issuer. + type: boolean + locality: + description: Locality defines whether to add the locality + information into the issuer. + type: boolean + organization: + description: Organization defines whether to add the organization + information into the issuer. + type: boolean + province: + description: Province defines whether to add the province + information into the issuer. + type: boolean + serialNumber: + description: SerialNumber defines whether to add the serialNumber + information into the issuer. + type: boolean + type: object + notAfter: + description: NotAfter defines whether to add the Not After + information from the Validity part. + type: boolean + notBefore: + description: NotBefore defines whether to add the Not Before + information from the Validity part. + type: boolean + sans: + description: Sans defines whether to add the Subject Alternative + Name information from the Subject Alternative Name part. + type: boolean + serialNumber: + description: SerialNumber defines whether to add the client + serialNumber information. + type: boolean + subject: + description: Subject defines the client certificate subject + details to add to the X-Forwarded-Tls-Client-Cert-Info header. + properties: + commonName: + description: CommonName defines whether to add the organizationalUnit + information into the subject. + type: boolean + country: + description: Country defines whether to add the country + information into the subject. + type: boolean + domainComponent: + description: DomainComponent defines whether to add the + domainComponent information into the subject. + type: boolean + locality: + description: Locality defines whether to add the locality + information into the subject. + type: boolean + organization: + description: Organization defines whether to add the organization + information into the subject. + type: boolean + organizationalUnit: + description: OrganizationalUnit defines whether to add + the organizationalUnit information into the subject. + type: boolean + province: + description: Province defines whether to add the province + information into the subject. + type: boolean + serialNumber: + description: SerialNumber defines whether to add the serialNumber + information into the subject. + type: boolean + type: object + type: object + pem: + description: PEM sets the X-Forwarded-Tls-Client-Cert header with + the certificate. + type: boolean + type: object + plugin: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + description: 'Plugin defines the middleware plugin configuration. + More info: https://doc.traefik.io/traefik/plugins/' + type: object + rateLimit: + description: 'RateLimit holds the rate limit configuration. This middleware + ensures that services will receive a fair amount of requests, and + allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/' + properties: + average: + description: Average is the maximum rate, by default in requests/s, + allowed for the given source. It defaults to 0, which means + no rate limiting. The rate is actually defined by dividing Average + by Period. So for a rate below 1req/s, one needs to define a + Period larger than a second. + format: int64 + type: integer + burst: + description: Burst is the maximum number of requests allowed to + arrive in the same arbitrarily small period of time. It defaults + to 1. + format: int64 + type: integer + period: + anyOf: + - type: integer + - type: string + description: 'Period, in combination with Average, defines the + actual maximum rate, such as: r = Average / Period. It defaults + to a second.' + x-kubernetes-int-or-string: true + sourceCriterion: + description: SourceCriterion defines what criterion is used to + group requests as originating from a common source. If several + strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the request's remote + address field (as an ipStrategy). + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipwhitelist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position + (starting from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the + X-Forwarded-For header and select the first IP not in + the list. + items: + type: string + type: array + type: object + requestHeaderName: + description: RequestHeaderName defines the name of the header + used to group incoming requests. + type: string + requestHost: + description: RequestHost defines whether to consider the request + Host as the source. + type: boolean + type: object + type: object + redirectRegex: + description: 'RedirectRegex holds the redirect regex middleware configuration. + This middleware redirects a request using regex matching and replacement. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex' + properties: + permanent: + description: Permanent defines whether the redirection is permanent + (301). + type: boolean + regex: + description: Regex defines the regex used to match and capture + elements from the request URL. + type: string + replacement: + description: Replacement defines how to modify the URL to have + the new target URL. + type: string + type: object + redirectScheme: + description: 'RedirectScheme holds the redirect scheme middleware + configuration. This middleware redirects requests from a scheme/port + to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/' + properties: + permanent: + description: Permanent defines whether the redirection is permanent + (301). + type: boolean + port: + description: Port defines the port of the new URL. + type: string + scheme: + description: Scheme defines the scheme of the new URL. + type: string + type: object + replacePath: + description: 'ReplacePath holds the replace path middleware configuration. + This middleware replaces the path of the request URL and store the + original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/' + properties: + path: + description: Path defines the path to use as replacement in the + request URL. + type: string + type: object + replacePathRegex: + description: 'ReplacePathRegex holds the replace path regex middleware + configuration. This middleware replaces the path of a URL using + regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/' + properties: + regex: + description: Regex defines the regular expression used to match + and capture the path from the request URL. + type: string + replacement: + description: Replacement defines the replacement path format, + which can include captured variables. + type: string + type: object + retry: + description: 'Retry holds the retry middleware configuration. This + middleware reissues requests a given number of times to a backend + server if that server does not reply. As soon as the server answers, + the middleware stops retrying, regardless of the response status. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/' + properties: + attempts: + description: Attempts defines how many times the request should + be retried. + type: integer + initialInterval: + anyOf: + - type: integer + - type: string + description: InitialInterval defines the first wait time in the + exponential backoff series. The maximum interval is calculated + as twice the initialInterval. If unspecified, requests will + be retried immediately. The value of initialInterval should + be provided in seconds or as a valid duration format, see https://pkg.go.dev/time#ParseDuration. + x-kubernetes-int-or-string: true + type: object + stripPrefix: + description: 'StripPrefix holds the strip prefix middleware configuration. + This middleware removes the specified prefixes from the URL path. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/' + properties: + forceSlash: + description: 'ForceSlash ensures that the resulting stripped path + is not the empty string, by replacing it with / when necessary. + Default: true.' + type: boolean + prefixes: + description: Prefixes defines the prefixes to strip from the request + URL. + items: + type: string + type: array + type: object + stripPrefixRegex: + description: 'StripPrefixRegex holds the strip prefix regex middleware + configuration. This middleware removes the matching prefixes from + the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/' + properties: + regex: + description: Regex defines the regular expression to match the + path prefix from the request URL. + items: + type: string + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: middlewaretcps.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: MiddlewareTCP + listKind: MiddlewareTCPList + plural: middlewaretcps + singular: middlewaretcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. + More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MiddlewareTCPSpec defines the desired state of a MiddlewareTCP. + properties: + inFlightConn: + description: InFlightConn defines the InFlightConn middleware configuration. + properties: + amount: + description: Amount defines the maximum amount of allowed simultaneous + connections. The middleware closes the connection if there are + already amount connections opened. + format: int64 + type: integer + type: object + ipWhiteList: + description: IPWhiteList defines the IPWhiteList middleware configuration. + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: serverstransports.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: ServersTransport + listKind: ServersTransportList + plural: serverstransports + singular: serverstransport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'ServersTransport is the CRD implementation of a ServersTransport. + If no serversTransport is specified, the default@internal will be used. + The default@internal serversTransport is created from the static configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ServersTransportSpec defines the desired state of a ServersTransport. + properties: + certificatesSecrets: + description: CertificatesSecrets defines a list of secret storing + client certificates for mTLS. + items: + type: string + type: array + disableHTTP2: + description: DisableHTTP2 disables HTTP/2 for connections with backend + servers. + type: boolean + forwardingTimeouts: + description: ForwardingTimeouts defines the timeouts for requests + forwarded to the backend servers. + properties: + dialTimeout: + anyOf: + - type: integer + - type: string + description: DialTimeout is the amount of time to wait until a + connection to a backend server can be established. + x-kubernetes-int-or-string: true + idleConnTimeout: + anyOf: + - type: integer + - type: string + description: IdleConnTimeout is the maximum period for which an + idle HTTP keep-alive connection will remain open before closing + itself. + x-kubernetes-int-or-string: true + pingTimeout: + anyOf: + - type: integer + - type: string + description: PingTimeout is the timeout after which the HTTP/2 + connection will be closed if a response to ping is not received. + x-kubernetes-int-or-string: true + readIdleTimeout: + anyOf: + - type: integer + - type: string + description: ReadIdleTimeout is the timeout after which a health + check using ping frame will be carried out if no frame is received + on the HTTP/2 connection. + x-kubernetes-int-or-string: true + responseHeaderTimeout: + anyOf: + - type: integer + - type: string + description: ResponseHeaderTimeout is the amount of time to wait + for a server's response headers after fully writing the request + (including its body, if any). + x-kubernetes-int-or-string: true + type: object + insecureSkipVerify: + description: InsecureSkipVerify disables SSL certificate verification. + type: boolean + maxIdleConnsPerHost: + description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) + to keep per-host. + type: integer + peerCertURI: + description: PeerCertURI defines the peer cert URI used to match against + SAN URI during the peer certificate verification. + type: string + rootCAsSecrets: + description: RootCAsSecrets defines a list of CA secret used to validate + self-signed certificate. + items: + type: string + type: array + serverName: + description: ServerName defines the server name used to contact the + server. + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: tlsoptions.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSOption + listKind: TLSOptionList + plural: tlsoptions + singular: tlsoption + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'TLSOption is the CRD implementation of a Traefik TLS Option, + allowing to configure some parameters of the TLS connection. More info: + https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSOptionSpec defines the desired state of a TLSOption. + properties: + alpnProtocols: + description: 'ALPNProtocols defines the list of supported application + level protocols for the TLS handshake, in order of preference. More + info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols' + items: + type: string + type: array + cipherSuites: + description: 'CipherSuites defines the list of supported cipher suites + for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites' + items: + type: string + type: array + clientAuth: + description: ClientAuth defines the server's policy for TLS Client + Authentication. + properties: + clientAuthType: + description: ClientAuthType defines the client authentication + type to apply. + enum: + - NoClientCert + - RequestClientCert + - RequireAnyClientCert + - VerifyClientCertIfGiven + - RequireAndVerifyClientCert + type: string + secretNames: + description: SecretNames defines the names of the referenced Kubernetes + Secret storing certificate details. + items: + type: string + type: array + type: object + curvePreferences: + description: 'CurvePreferences defines the preferred elliptic curves + in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences' + items: + type: string + type: array + maxVersion: + description: 'MaxVersion defines the maximum TLS version that Traefik + will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, + VersionTLS13. Default: None.' + type: string + minVersion: + description: 'MinVersion defines the minimum TLS version that Traefik + will accept. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, + VersionTLS13. Default: VersionTLS10.' + type: string + preferServerCipherSuites: + description: 'PreferServerCipherSuites defines whether the server + chooses a cipher suite among his own instead of among the client''s. + It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430' + type: boolean + sniStrict: + description: SniStrict defines whether Traefik allows connections + from clients connections that do not specify a server_name extension. + type: boolean + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: tlsstores.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSStore + listKind: TLSStoreList + plural: tlsstores + singular: tlsstore + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For + the time being, only the TLSStore named default is supported. This means + that you cannot have two stores that are named default in different Kubernetes + namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TLSStoreSpec defines the desired state of a TLSStore. + properties: + certificates: + description: Certificates is a list of secret names, each secret holding + a key/certificate pair to add to the store. + items: + description: Certificate holds a secret name for the TLSStore resource. + properties: + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + required: + - secretName + type: object + type: array + defaultCertificate: + description: DefaultCertificate defines the default certificate configuration. + properties: + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + required: + - secretName + type: object + defaultGeneratedCert: + description: DefaultGeneratedCert defines the default generated certificate + configuration. + properties: + domain: + description: Domain is the domain definition for the DefaultCertificate. + properties: + main: + description: Main defines the main domain name. + type: string + sans: + description: SANs defines the subject alternative domain names. + items: + type: string + type: array + type: object + resolver: + description: Resolver is the name of the resolver that will be + used to issue the DefaultCertificate. + type: string + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.2 + creationTimestamp: null + name: traefikservices.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TraefikService + listKind: TraefikServiceList + plural: traefikservices + singular: traefikservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'TraefikService is the CRD implementation of a Traefik Service. + TraefikService object allows to: - Apply weight to Services on load-balancing + - Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TraefikServiceSpec defines the desired state of a TraefikService. + properties: + mirroring: + description: Mirroring defines the Mirroring service configuration. + properties: + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + maxBodySize: + description: MaxBodySize defines the maximum size allowed for + the body of the request. If the body is larger, the request + is not mirrored. Default value is -1, which means unlimited + size. + format: int64 + type: integer + mirrors: + description: Mirrors defines the list of mirrors where Traefik + will duplicate the traffic. + items: + description: MirrorService holds the mirror configuration. + properties: + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: Name defines the name of the referenced Kubernetes + Service or TraefikService. The differentiation between + the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + passHostHeader: + description: PassHostHeader defines whether the client Host + header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + percent: + description: 'Percent defines the part of the traffic to + mirror. Supported values: 0 to 100.' + type: integer + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: 'FlushInterval defines the interval, in + milliseconds, in between flushes to the client while + copying the response body. A negative value means + to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes + a response as a streaming response; for such responses, + writes are flushed to the client immediately. Default: + 100ms' + type: string + type: object + scheme: + description: Scheme defines the scheme to use for the request + to the upstream Kubernetes Service. It defaults to https + when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: ServersTransport defines the name of ServersTransport + resource to use. It allows to configure the transport + between Traefik and your servers. Can only be used on + a Kubernetes Service. + type: string + sticky: + description: 'Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: 'SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: Strategy defines the load balancing strategy + between the servers. RoundRobin is the only supported + value at the moment. + type: string + weight: + description: Weight defines the weight and should only be + specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + name: + description: Name defines the name of the referenced Kubernetes + Service or TraefikService. The differentiation between the two + is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + passHostHeader: + description: PassHostHeader defines whether the client Host header + is forwarded to the upstream Kubernetes Service. By default, + passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. This + can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards the + response from the upstream Kubernetes Service to the client. + properties: + flushInterval: + description: 'FlushInterval defines the interval, in milliseconds, + in between flushes to the client while copying the response + body. A negative value means to flush immediately after + each write to the client. This configuration is ignored + when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms' + type: string + type: object + scheme: + description: Scheme defines the scheme to use for the request + to the upstream Kubernetes Service. It defaults to https when + Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: ServersTransport defines the name of ServersTransport + resource to use. It allows to configure the transport between + Traefik and your servers. Can only be used on a Kubernetes Service. + type: string + sticky: + description: 'Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie can be + accessed by client-side APIs, such as JavaScript. + type: boolean + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: 'SameSite defines the same site policy. More + info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + type: string + secure: + description: Secure defines whether the cookie can only + be transmitted over an encrypted connection (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: Strategy defines the load balancing strategy between + the servers. RoundRobin is the only supported value at the moment. + type: string + weight: + description: Weight defines the weight and should only be specified + when Name references a TraefikService object (and to be precise, + one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + weighted: + description: Weighted defines the Weighted Round Robin configuration. + properties: + services: + description: Services defines the list of Kubernetes Service and/or + TraefikService to load-balance, with weight. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: Name defines the name of the referenced Kubernetes + Service or TraefikService. The differentiation between + the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + passHostHeader: + description: PassHostHeader defines whether the client Host + header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: 'FlushInterval defines the interval, in + milliseconds, in between flushes to the client while + copying the response body. A negative value means + to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes + a response as a streaming response; for such responses, + writes are flushed to the client immediately. Default: + 100ms' + type: string + type: object + scheme: + description: Scheme defines the scheme to use for the request + to the upstream Kubernetes Service. It defaults to https + when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: ServersTransport defines the name of ServersTransport + resource to use. It allows to configure the transport + between Traefik and your servers. Can only be used on + a Kubernetes Service. + type: string + sticky: + description: 'Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: 'SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: Strategy defines the load balancing strategy + between the servers. RoundRobin is the only supported + value at the moment. + type: string + weight: + description: Weight defines the weight and should only be + specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + sticky: + description: 'Sticky defines whether sticky sessions are enabled. + More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie can be + accessed by client-side APIs, such as JavaScript. + type: boolean + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: 'SameSite defines the same site policy. More + info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite' + type: string + secure: + description: Secure defines whether the cookie can only + be transmitted over an encrypted connection (i.e. HTTPS). + type: boolean + type: object + type: object + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/k8s/traefik-rbac.yaml b/k8s/traefik-rbac.yaml new file mode 100644 index 0000000..180f6ba --- /dev/null +++ b/k8s/traefik-rbac.yaml @@ -0,0 +1,72 @@ +# Based on traefik/traefik helm chart 20.8.0 for traefik v2.9.6 + +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + namespace: traefik + name: traefik + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: traefik +rules: + - apiGroups: + - extensions + - networking.k8s.io + resources: + - ingressclasses + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + - endpoints + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - extensions + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - traefik.containo.us + resources: + - ingressroutes + - ingressroutetcps + - ingressrouteudps + - middlewares + - middlewaretcps + - tlsoptions + - tlsstores + - traefikservices + - serverstransports + verbs: + - get + - list + - watch + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: traefik +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: traefik +subjects: + - namespace: traefik + kind: ServiceAccount + name: traefik diff --git a/k8s/traefik.yaml b/k8s/traefik.yaml new file mode 100644 index 0000000..0a4b749 --- /dev/null +++ b/k8s/traefik.yaml @@ -0,0 +1,150 @@ +# Based on traefik/traefik helm chart 20.8.0 for traefik v2.9.6 + +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + namespace: traefik + name: traefik-data +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 128Mi + storageClassName: openebs-hostpath + +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + namespace: traefik + name: traefik +spec: + replicas: 1 + selector: + matchLabels: + app: traefik + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + minReadySeconds: 0 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/path: "/metrics" + prometheus.io/port: "9100" + labels: + app: traefik + spec: + serviceAccountName: traefik + terminationGracePeriodSeconds: 60 + hostNetwork: false + initContainers: + - name: volume-permissions + image: busybox:1.35 + command: + - "sh" + - "-c" + - "touch /data/acme.json && chmod -Rv 600 /data/* && chown 65532:65532 /data/acme.json" + volumeMounts: + - name: data + mountPath: /data + containers: + - image: traefik:v2.9.6 + imagePullPolicy: IfNotPresent + name: traefik + resources: {} + readinessProbe: + httpGet: + path: /ping + port: 9000 + scheme: HTTP + failureThreshold: 1 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + httpGet: + path: /ping + port: 9000 + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + ports: + - name: http + containerPort: 8000 + - name: https + containerPort: 8443 + - name: ping + containerPort: 9000 + - name: docker + containerPort: 31000 + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + volumeMounts: + - name: config + mountPath: /etc/traefik + - name: data + mountPath: /data + - name: tmp + mountPath: /tmp + volumes: + - name: config + configMap: + name: traefik-config + - name: data + persistentVolumeClaim: + claimName: traefik-data + - name: tmp + emptyDir: {} + securityContext: + fsGroup: 65532 + +--- +kind: Service +apiVersion: v1 +metadata: + namespace: traefik + name: traefik + annotations: + metallb.universe.tf/allow-shared-ip: main +spec: + type: LoadBalancer + selector: + app: traefik + ports: + - port: 80 + name: http + targetPort: 8000 + - port: 443 + name: https + targetPort: 8443 + - port: 1869 + name: proxy + - port: 31000 + name: docker + - port: 32000 + name: minio + +--- +kind: IngressClass +apiVersion: networking.k8s.io/v1 +metadata: + name: traefik + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +spec: + controller: traefik.io/ingress-controller diff --git a/langs/ante.yaml b/langs/ante.yaml new file mode 100644 index 0000000..bd1be00 --- /dev/null +++ b/langs/ante.yaml @@ -0,0 +1,44 @@ +id: "ante" +aliases: + - "an" +name: "Ante" + +install: + prepare: + apt: + - cargo + - cmake + - libssl-dev + - pkg-config + - python3-distutils + manual: | + export PATH="$HOME/.cargo/bin:$PATH" + cargo install llvmenv + llvmenv init + # If compiler is not explicitly set to LLVM, then we get an + # error: unrecognized command-line option '-Wnewline-eof'. + CC=/usr/bin/clang CXX=/usr/bin/clang++ llvmenv build-entry -G Makefile -j$(nproc) 10.0.1 + llvmenv global 10.0.1 + manual: | + git clone https://github.com/jfecher/ante.git -n + pushd ante + git checkout ba940f3b492fb448a6a73b139403eefa7a0daedc + LLVM_SYS_100_PREFIX="$(llvmenv prefix)" cargo build --release + install -d "${pkg}/opt/ante" + install -d "${pkg}/usr/local/bin" + cp target/release/ante "${pkg}/usr/local/bin/" + cp -R stdlib "${pkg}/opt/ante/" + popd + +setup: | + mkdir -p "$HOME/.config/ante" + cp -R /opt/ante/stdlib "$HOME/.config/ante/" + +main: "main.an" +template: | + print "Hello, world!" + +compile: | + ante main.an +run: | + ./main diff --git a/langs/claro.yaml b/langs/claro.yaml new file mode 100644 index 0000000..2f6aae4 --- /dev/null +++ b/langs/claro.yaml @@ -0,0 +1,107 @@ +id: "claro" +name: "Claro" + +info: + impl: "Claro" + year: 2021 + desc: "High-level toy programming language providing standardized Software Engineering best practices out of the box" + ext: + - claro + web: + home: "https://clarolang.com/" + source: "https://github.com/JasonSteving99/claro-lang" + category: general + mode: + - compiled + - interpreted + platform: [] + syntax: + - c + typing: static + paradigm: + - functional + - imperative + usage: personal + +install: + apt: + - default-jdk + manual: | + install -d "${pkg}/opt/claro/programs" + + # Pull resources from the latest Claro repo release. + ver="$(latest_release JasonSteving99/claro-lang | sed 's/^v//')" + + # Pull the tarball of the built Bazel repo's bazel-bin instead of just the sources. + wget "https://github.com/JasonSteving99/claro-lang/releases/download/v${ver}/claro-lang-bazel-bin.tar.gz" + tar -xf claro-lang-bazel-bin.tar.gz + + ##################################################################################################### + # We don't want to depend on Bazel at all for rebuilding, it's just one file changing. Rebuild it using + # the packaged jar files and then update the jar holding that recompiled file and run the Bazel gen'd + # run script which points at all the correct jar runfiles. First though, we need to slightly modify + # the Bazel gen'd runscript to rebuild using the packaged jars for us (without explicitly rerunning Bazel + # itself since this is super slow and involves starting up a new Bazel server...). + pushd claro_programs + + read -r -d '' MY_SCRIPT <<"EOF" ||: + REBUILD_CLASSPATH="${CLASSPATH}" + # For the purpose of rebuilding, we need lombok and autovalue on the classpath. + REBUILD_CLASSPATH+=":lombok-1.18.20.jar" + REBUILD_CLASSPATH+=":auto-value-1.5.3.jar" + javac -classpath $REBUILD_CLASSPATH Conditions.java + # There's an assumption that the dir ./com/claro/ was made in the tarball before this. + mv Conditions*.class com/claro + jar -uf "${RUNPATH}src/java/com/claro/claro_programs/conditions_compiled_claro_image.jar" com/claro/Conditions*.class + java -classpath $CLASSPATH "${ARGS[@]}" + EOF + + # Insert MY_SCRIPT into the Bazel run script just before java gets executed. We're reusing Bazel's run + # script basically just to get a conveniently curated CLASSPATH variable generated to point to all the + # randomly scattered jar files that Bazel places throughout bazel-bin/. + sed -i "s|exec \$JAVABIN.*|${MY_SCRIPT//$'\n'/\\n}|" conditions_compiled_claro_image + + chmod -R u+rw * + + popd + ##################################################################################################### + + cp -R claro_programs/. "${pkg}/opt/claro/programs/" + + wget "https://github.com/JasonSteving99/claro-lang/releases/download/v${ver}/claro_compiler_binary_deploy.jar" + cp claro_compiler_binary_deploy.jar "${pkg}/opt/claro/" + +setup: | + cp -R /opt/claro/programs "./" + +main: "programs/Conditions.claro" +template: | + # Thanks for trying out Claro during its early development stages! + # To learn Claro by example, check out: + # https://clarolang.com/tree/main/src/java/com/claro/claro_programs + + print("Hello, world!"); + +repl: | + java -jar /opt/claro/claro_compiler_binary_deploy.jar --repl --silent + +# Skip rebuilding the entire compiler all over again and instead just +# use the pre-built Claro compiler jar. +compile: | + java -jar /opt/claro/claro_compiler_binary_deploy.jar \ + --java_source --silent \ + --classname=Conditions --package=com.claro \ + < programs/Conditions.claro \ + > programs/Conditions.java +run: | + set -e + + cd programs + ./conditions_compiled_claro_image ||: + + java -jar /opt/claro/claro_compiler_binary_deploy.jar --repl --silent + +input: | + print(123 * 234); + +timeoutFactor: 2 diff --git a/langs/groovy.yaml b/langs/groovy.yaml new file mode 100644 index 0000000..a3da221 --- /dev/null +++ b/langs/groovy.yaml @@ -0,0 +1,22 @@ +id: "groovy" +name: "Groovy" + +install: + apt: + - groovy + +repl: | + JAVA_OPTS="-Djava.util.prefs.systemRoot=$PWD/.java -Djava.util.prefs.userRoot=$PWD/.java/.userPrefs" groovysh + +main: "main.groovy" +template: | + print "Hello, world!"; + +run: | + JAVA_OPTS="-Djava.util.prefs.systemRoot=$PWD/.java -Djava.util.prefs.userRoot=$PWD/.java/.userPrefs" groovysh main.groovy + +scope: + code: | + x = 123 * 234; + +timeoutFactor: 4 diff --git a/langs/ioke.yaml b/langs/ioke.yaml new file mode 100644 index 0000000..bbde50a --- /dev/null +++ b/langs/ioke.yaml @@ -0,0 +1,30 @@ +id: "ioke" +aliases: + - "ik" +name: "Ioke" + +install: + prepare: + cert: + - "https://letsencrypt.org/certs/lets-encrypt-r3.pem" + apt: + - default-jdk + manual: | + install -d "${pkg}/opt/ioke" + install -d "${pkg}/usr/local/bin" + + wget https://ioke.org/dist/ioke-ikj-latest.tar.gz -O ioke.tar.gz + tar -xf ioke.tar.gz -C "${pkg}/opt/ioke" --strip-components=1 + ln -s /opt/ioke/bin/ioke "${pkg}/usr/local/bin/ioke" + +repl: | + JAVA_OPTS="-Duser.home=$PWD" ioke + +main: "main.ik" +template: | + "Hello, world!" println + +run: | + JAVA_OPTS="-Duser.home=$PWD" ioke main.ik; JAVA_OPTS="-Duser.home=$PWD" ioke + +timeoutFactor: 4 diff --git a/langs/kalyn.yaml b/langs/kalyn.yaml new file mode 100644 index 0000000..e22c425 --- /dev/null +++ b/langs/kalyn.yaml @@ -0,0 +1,29 @@ +id: "kalyn" +name: "Kalyn" + +install: + prepare: + apt: + - haskell-stack + manual: | + install -d "${pkg}/opt/kalyn" + install -d "${pkg}/usr/local/bin" + + git clone https://github.com/radian-software/kalyn.git + pushd kalyn + stack build kalyn + cp "$(stack exec which kalyn)" "${pkg}/usr/local/bin/" + cp -R src-kalyn/Stdlib src-kalyn/Stdlib.kalyn "${pkg}/opt/kalyn/" + popd + +main: "src-kalyn/Main.kalyn" +template: | + (import "/opt/kalyn/Stdlib.kalyn") + + (public def main (IO Empty) + (print "Hello, world!\n")) + +compile: | + kalyn +run: | + out-kalyn/Main diff --git a/langs/kotlin.yaml b/langs/kotlin.yaml new file mode 100644 index 0000000..67283a2 --- /dev/null +++ b/langs/kotlin.yaml @@ -0,0 +1,40 @@ +id: "kotlin" +aliases: + - "kts" + - "kotlinc" +name: "Kotlin" +monacoLang: kotlin + +install: + apt: + - default-jre + manual: | + install -d "${pkg}/opt" + install -d "${pkg}/usr/local/bin" + install -d "${pkg}/usr/local/lib" + + ver="$(latest_release JetBrains/kotlin)" + wget "https://github.com/JetBrains/kotlin/releases/download/${ver}/kotlin-compiler-$(sed 's/^v//' <<< "$ver").zip" -O kotlin.zip + unzip kotlin.zip + cp -RT kotlinc "${pkg}/opt/kotlin" + + ls "${pkg}/opt/kotlin/bin" | while read name; do + ln -s "/opt/kotlin/bin/${name}" "${pkg}/usr/local/bin/" + done + + ls "${pkg}/opt/kotlin/lib" | while read name; do + ln -s "/opt/kotlin/lib/${name}" "${pkg}/usr/local/lib/" + done + +repl: | + JAVA_OPTS="-Duser.home=$PWD" kotlinc + +main: "main.kts" +template: | + println("Hello, world!") + +run: | + JAVA_OPTS="-Duser.home=$PWD" kotlinc -script main.kts + kotlinc + +timeoutFactor: 4 diff --git a/langs/python.yaml b/langs/python.yaml index 7bf0624..a59d661 100644 --- a/langs/python.yaml +++ b/langs/python.yaml @@ -41,15 +41,8 @@ install: - python3 - python3-pip - black - manual: | - install -d "${pkg}/opt/mspyls" - install -d "${pkg}/usr/local/bin" - - url="$(curl -fsSL "https://pvsc.blob.core.windows.net/python-language-server-stable?restype=container&comp=list&prefix=Python-Language-Server-linux-x64" | grep -Eo 'https://[^<]+\.nupkg' | tail -n1)" - wget "${url}" - unzip -d "${pkg}/opt/mspyls" Python-Language-Server-linux-x64.*.nupkg - chmod +x "${pkg}/opt/mspyls/Microsoft.Python.LanguageServer" - ln -s "/opt/mspyls/Microsoft.Python.LanguageServer" "${pkg}/usr/local/bin/Microsoft.Python.LanguageServer" + npm: + - pyright repl: | python3 -u @@ -83,10 +76,13 @@ pkg: lsp: start: | - Microsoft.Python.LanguageServer - init: - interpreter: - properties: - InterpreterPath: /usr/bin/python3 + pyright-langserver --stdio code: "import func" - item: "functools" \ No newline at end of file +<<<<<<< HEAD + item: "functools" +======= + item: "functools" + +skip: + - lsp +>>>>>>> d370c5fbc85bf9f479adca7c532ec13c2b54199f diff --git a/langs/qsharp.yaml b/langs/qsharp.yaml new file mode 100644 index 0000000..19abf41 --- /dev/null +++ b/langs/qsharp.yaml @@ -0,0 +1,64 @@ +id: "qsharp" +aliases: + - "q" + - "qs" +name: "Q#" + +install: + # Apparently, the Q# project template is hardcoded to use version + # 3.x of the .NET SDK. Not sure why. + prepare: &install-dotnet + preface: | + wget "https://packages.microsoft.com/config/ubuntu/${ubuntu_ver}/packages-microsoft-prod.deb" + sudo --preserve-env=DEBIAN_FRONTEND apt-get install ./packages-microsoft-prod.deb + sudo --preserve-env=DEBIAN_FRONTEND apt-get update + apt: + - $(grep-aptavail -wF Package "dotnet-sdk-3\.[0-9.]+" -s Package -n | sort -Vr | head -n1) + <<: *install-dotnet + # We should cache the .dotnet directory to avoid a .NET banner being + # printed, and we should cache the main directory because there is a + # generated main.csproj file that is needed by .NET. Finally we + # should cache the .nuget directory as well as the build artifacts + # inside main to avoid a 30s initial compile time. + # + # We could optimize further but I don't feel like it right now. + manual: | + install -d "${pkg}/opt/qsharp/skel-home" + install -d "${pkg}/opt/qsharp/skel-src" + + dotnet new -i Microsoft.Quantum.ProjectTemplates + dotnet new console -lang Q# -o main + dotnet run --project main + + shopt -s dotglob + cp -R main "${pkg}/opt/qsharp/skel-src/" + cp -R "${HOME}/.dotnet" "${HOME}/.nuget" "${pkg}/opt/qsharp/skel-home/" + rm "${pkg}/opt/qsharp/skel-src/main/Program.qs" + chmod -R a=u,go-w "${pkg}/opt/qsharp" + manualInstall: | + wget "https://packages.microsoft.com/config/ubuntu/${ubuntu_ver}/packages-microsoft-prod.deb" + sudo --preserve-env=DEBIAN_FRONTEND apt-get update + sudo --preserve-env=DEBIAN_FRONTEND apt-get install ./packages-microsoft-prod.deb + +setup: | + shopt -s dotglob + cp -R /opt/qsharp/skel-src/* ./ + cp -R /opt/qsharp/skel-home/* "${HOME}/" + +main: "main/Main.qs" +template: | + namespace main { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + + @EntryPoint() + operation Main() : Unit { + Message("Hello, world!"); + } + } + +run: | + dotnet run --project main + +timeoutFactor: 4 diff --git a/langs/red.yaml b/langs/red.yaml new file mode 100644 index 0000000..8bcf716 --- /dev/null +++ b/langs/red.yaml @@ -0,0 +1,36 @@ +id: "red" +name: "Red" + +install: + apt: + - libcurl4:i386 + manual: | + install -d "${pkg}/opt/red/skel" + install -d "${pkg}/usr/local/bin" + + path="$(curl -fsSL https://static.red-lang.org/download.html | grep -Eo '/dl/linux/[^"]+' | head -n1)" + wget "https://static.red-lang.org${path}" -O red + chmod +x red + cp red "${pkg}/usr/local/bin/" + + ./red <<< quit + cp -R "$HOME/.red" "${pkg}/opt/red/skel/" + +setup: | + shopt -s dotglob; cp -R /opt/red/skel/* "${HOME}/" + +# https://github.com/red/red/issues/543#issuecomment-25404212 +repl: | + "$(which red)" +input: | + DELAY: 5 + 123 * 234 + +main: "main.red" +template: | + Red [Title: "Main"] + + print "Hello, world!" + +run: | + "$(which red)" main.red; "$(which red)" diff --git a/langs/scala.yaml b/langs/scala.yaml new file mode 100644 index 0000000..746f302 --- /dev/null +++ b/langs/scala.yaml @@ -0,0 +1,25 @@ +id: "scala" +name: "Scala" + +install: + apt: + - scala + +repl: | + scala +input: | + DELAY: 5 + 123 * 234 + +main: "main.scala" +template: | + println("Hello, world!") + +run: | + scala -i main.scala + +scope: + code: | + val x = 123 * 234 + +timeoutFactor: 8 diff --git a/langs/unison.yaml b/langs/unison.yaml new file mode 100644 index 0000000..9e9d9b3 --- /dev/null +++ b/langs/unison.yaml @@ -0,0 +1,73 @@ +id: "unison" +aliases: + - "ucm" +name: "Unison" + +install: + prepare: + apt: + - haskell-stack + manual: | + mkdir -p "${pkg}/opt/unison/skel" + install -d "${pkg}/usr/local/bin" + + git clone https://github.com/unisonweb/unison.git + pushd unison + stack build + cp "$(stack exec which unison)" "${pkg}/usr/local/bin/" + popd + + pushd "${pkg}/opt/unison/skel" + "${pkg}/usr/local/bin/unison" -codebase . init + LESS="+q" "${pkg}/usr/local/bin/unison" -codebase . <<< 'pull https://github.com/unisonweb/base:.trunk .base' + popd + +setup: | + shopt -s dotglob + cp -R /opt/unison/skel/* ./ + +repl: | + unison -codebase . +input: | + DELAY: 10 + find : [a] -> [a] +output: | + base.List.reverse + +# runProg implementation courtesy of Robert Offner from Unison Slack! +main: "main.u" +template: | + use io + + runProg: '{IO, Exception} a -> '{IO} () + runProg f = 'let + printErr err = match err with + Failure _ errMsg _ -> handle putBytes (stdHandle StdErr) (toUtf8 errMsg) with cases + {raise _ -> _} -> () + {_} -> () + match catch f with + Left err -> printErr err + Right _ -> () + + main: '{IO} () + main = runProg 'let + printLine "Hello, world!" +createEmpty: "" + +run: | + unison -codebase . run.file main.u main + echo "Type 'load main.u' at the repl prompt to bring variables into scope." + unison -codebase . + +scope: + code: | + x = 123 * 234 + input: | + DELAY: 15 + load main.u + DELAY: 5 + add x + DELAY: 5 + display x + +timeoutFactor: 4 diff --git a/langs/v.yaml b/langs/v.yaml new file mode 100644 index 0000000..a8c1b11 --- /dev/null +++ b/langs/v.yaml @@ -0,0 +1,60 @@ +id: "v" +aliases: + - "vlang" +name: "V" + +info: + year: 2019 + desc: "Simple, statically-typed compiled programming language designed for building maintainable software" + ext: + - v + web: + home: "https://vlang.io/" + source: "https://github.com/vlang/v" + category: general + mode: compiled + platform: clr + syntax: + - c + typing: + - static + paradigm: + - functional + - imperative + usage: personal + +install: + manual: | + install -d "${pkg}/opt" + install -d "${pkg}/usr/local/bin" + + git clone https://github.com/vlang/v.git "${pkg}/opt/v" + pushd "${pkg}/opt/v" + + make + ln -s /opt/v/v "${pkg}/usr/local/bin/" + + # Force vfmt to get compiled ahead of time, otherwise this will + # happen at first invocation and fail due to lack of write + # permissions on /opt/v. + ./v fmt < /dev/null + + popd + +main: "main.v" +template: | + fn main() { + println('Hello, world!') + } + +run: | + v run main.v + +format: + run: | + v fmt main.v + input: | + fn main() + { + println("Hello, world!") + } diff --git a/langs/verilog.yaml b/langs/verilog.yaml new file mode 100644 index 0000000..e5f283f --- /dev/null +++ b/langs/verilog.yaml @@ -0,0 +1,24 @@ +id: "verilog" +aliases: + - "systemverilog" + - "iverilog" +name: "Verilog" + +install: + apt: + - iverilog + +main: "main.v" +template: | + module main; + + initial begin + $display("Hello, world!"); + end + + endmodule + +compile: | + iverilog main.v -o main +run: | + ./main diff --git a/package.json b/package.json index fc6a208..778eba9 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@babel/parser": "^7.13.11", "@babel/preset-env": "^7.12.11", "@balena/dockerignore": "^1.0.2", + "@kubernetes/client-node": "^0.18.0", "@sentry/node": "^6.11.0", "async-lock": "^1.2.6", "babel-loader": "^8.2.2", @@ -19,7 +20,7 @@ "css-loader": "^5.0.1", "debounce": "^1.2.0", "docker-file-parser": "^1.0.5", - "ejs": "^3.1.5", + "ejs": "^3.1.7", "express": "^4.17.1", "express-ws": "^4.0.0", "file-loader": "^6.2.0", @@ -32,9 +33,12 @@ "p-queue": "^6.6.2", "parse-passwd": "^1.0.0", "prettier": "^2.3.1", + "prom-client": "^14.0.1", "regenerator-runtime": "^0.13.7", + "semaphore": "^1.1.0", "strip-ansi": "^6.0.0", "style-loader": "^2.0.0", + "unique-names-generator": "^4.7.1", "uuid": "^8.3.2", "vscode-languageserver-protocol": "3.15.3", "webpack": "^4.44.2", @@ -42,5 +46,9 @@ "xterm": "^4.9.0", "xterm-addon-fit": "^0.4.0", "yaml": "^1.10.0" - } + }, + "$comments": [ + "limiter version pinned due to https://github.com/jhurliman/node-rate-limiter/issues/80", + "monaco-languageclient, monaco-editor, vscode-languageserver-protocol pinned because their APIs changed a bunch and Riju hasn't been updated yet" + ] } diff --git a/packer/ci.pkr.hcl b/packer/ci.pkr.hcl deleted file mode 100644 index c172a8e..0000000 --- a/packer/ci.pkr.hcl +++ /dev/null @@ -1,48 +0,0 @@ -data "amazon-ami" "ubuntu" { - filters = { - name = "ubuntu/images/hvm-ssd/ubuntu-*-21.04-amd64-server-*" - root-device-type = "ebs" - virtualization-type = "hvm" - } - most_recent = true - owners = ["099720109477"] -} - -locals { - timestamp = regex_replace(timestamp(), "[- TZ:]", "") -} - -source "amazon-ebs" "ubuntu" { - ami_name = "riju-ci-${local.timestamp}" - instance_type = "t3.micro" - source_ami = "${data.amazon-ami.ubuntu.id}" - ssh_username = "ubuntu" - - tag { - key = "BillingCategory" - value = "Riju" - } - - tag { - key = "BillingSubcategory" - value = "Riju:AMI" - } - - tag { - key = "Name" - value = "riju-ci-${local.timestamp}" - } -} - -build { - sources = ["source.amazon-ebs.ubuntu"] - - provisioner "file" { - destination = "/tmp/riju-init-volume" - source = "riju-init-volume" - } - - provisioner "shell" { - script = "provision-ci.bash" - } -} diff --git a/packer/cloudwatch.json b/packer/cloudwatch.json deleted file mode 100644 index 36ec712..0000000 --- a/packer/cloudwatch.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "agent": { - "metrics_collection_interval": 60, - "run_as_user": "root" - }, - "metrics": { - "append_dimensions": { - "ImageId": "${aws:ImageId}", - "InstanceId": "${aws:InstanceId}", - "InstanceType": "${aws:InstanceType}" - }, - "aggregation_dimensions": [ - ["RijuInstanceGroup"], - ["RijuInstanceGroup", "path"] - ], - "metrics_collected": { - "cpu": { - "append_dimensions": { - "RijuInstanceGroup": "Webserver" - }, - "measurement": ["usage_active"], - "metrics_collection_interval": 60 - }, - "disk": { - "append_dimensions": { - "RijuInstanceGroup": "Webserver" - }, - "measurement": ["used_percent"], - "metrics_collection_interval": 60, - "resources": ["/", "/mnt/riju"] - }, - "mem": { - "append_dimensions": { - "RijuInstanceGroup": "Webserver" - }, - "measurement": ["mem_used_percent"], - "metrics_collection_interval": 60 - } - } - } -} diff --git a/packer/node-exporter.service b/packer/node-exporter.service new file mode 100644 index 0000000..5a42d9f --- /dev/null +++ b/packer/node-exporter.service @@ -0,0 +1,13 @@ +[Unit] +Description=Prometheus node exporter +StartLimitBurst=5 +StartLimitIntervalSec=300 + +[Service] +Type=exec +ExecStart=node_exporter +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/packer/prometheus.service b/packer/prometheus.service new file mode 100644 index 0000000..aa588ce --- /dev/null +++ b/packer/prometheus.service @@ -0,0 +1,13 @@ +[Unit] +Description=Prometheus +StartLimitBurst=5 +StartLimitIntervalSec=300 + +[Service] +Type=exec +ExecStart=bash -c 'EC2_INSTANCE_ID="$(curl -fsSL http://169.254.169.254/latest/meta-data/instance-id)" prometheus --config.file /etc/prometheus/config.yaml --enable-feature=expand-external-labels' +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/packer/prometheus.yaml b/packer/prometheus.yaml new file mode 100644 index 0000000..c29f345 --- /dev/null +++ b/packer/prometheus.yaml @@ -0,0 +1,21 @@ +global: + scrape_interval: 15s + external_labels: + node: "${EC2_INSTANCE_ID}" + +scrape_configs: + - job_name: server + static_configs: + - targets: ["localhost:6121"] + - job_name: node + static_configs: + - targets: ["localhost:9100"] + - job_name: prometheus + static_configs: + - targets: ["localhost:9090"] + +remote_write: + - url: "$GRAFANA_PROMETHEUS_HOSTNAME" + basic_auth: + username: "$GRAFANA_PROMETHEUS_USERNAME" + password: "$GRAFANA_API_KEY" diff --git a/packer/promtail.service b/packer/promtail.service index eabf709..36eba70 100644 --- a/packer/promtail.service +++ b/packer/promtail.service @@ -5,7 +5,7 @@ StartLimitIntervalSec=300 [Service] Type=exec -ExecStart=bash -c 'promtail -config.file /etc/promtail/config.yaml -client.external-labels instance="$(curl -fsSL http://169.254.169.254/latest/meta-data/instance-id)"' +ExecStart=bash -c 'promtail -config.file /etc/promtail/config.yaml -client.external-labels node="$(curl -fsSL http://169.254.169.254/latest/meta-data/instance-id)"' Restart=always RestartSec=5 diff --git a/packer/promtail.yaml b/packer/promtail.yaml index f2e1c5d..eaec8ba 100644 --- a/packer/promtail.yaml +++ b/packer/promtail.yaml @@ -7,7 +7,7 @@ positions: filename: /tmp/positions.yaml client: - url: https://72217:$GRAFANA_API_KEY@logs-prod-us-central1.grafana.net/api/prom/push + url: https://$GRAFANA_LOKI_USERNAME:$GRAFANA_API_KEY@$GRAFANA_LOKI_HOSTNAME/api/prom/push scrape_configs: - job_name: kernel @@ -39,6 +39,16 @@ scrape_configs: regex: "riju\\.service" target_label: source replacement: "supervisor" + - source_labels: + - __journal__systemd_unit + regex: "prometheus\\.service" + target_label: source + replacement: "prometheus" + - source_labels: + - __journal__systemd_unit + regex: "node-exporter\\.service" + target_label: source + replacement: "node-exporter" - source_labels: - source regex: "systemd" diff --git a/packer/provision-ci.bash b/packer/provision-ci.bash deleted file mode 100755 index d1258b1..0000000 --- a/packer/provision-ci.bash +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# I think there is a race condition related to Ubuntu wanting to do an -# automated system upgrade at boot, which causes 'apt-get update' to -# sometimes fail with an obscure error message. -sleep 5 - -mkdir /tmp/riju-work -pushd /tmp/riju-work - -export DEBIAN_FRONTEND=noninteractive - -sudo -E apt-get update -sudo -E apt-get dist-upgrade -y - -sudo -E apt-get install -y curl gnupg lsb-release - -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo -E apt-key add - - -ubuntu_name="$(lsb_release -cs)" - -sudo tee -a /etc/apt/sources.list.d/custom.list >/dev/null < 500) + die("illegal repo name"); + for (char *ptr = repo; *ptr; ++ptr) + if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') || + *ptr == '/' || *ptr == '.' || *ptr == '-' || *ptr == '_')) + die("illegal repo name"); + return repo; +} + +char *parseTag(char *tag) +{ + if (strnlen(tag, 501) > 500) + die("illegal tag name"); + for (char *ptr = tag; *ptr; ++ptr) + if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') || + *ptr == '.' || *ptr == '-' || *ptr == '_')) + die("illegal tag name"); + return tag; +} + char *timeout_msg; void sigalrm_die(int signum) @@ -132,7 +156,81 @@ void sigalrm_kill_parent(int signum) exit(EXIT_FAILURE); } -void session(char *uuid, char *lang, char *imageHash) +void cmd_list() +{ + // This command prints a bunch of empty lines because there is no + // way to filter to a desired set of images. Caller is expected to + // remove empty lines because it's easier in JS than C. + char *argv[] = { + "docker", + "image", + "ls", + "--format", + "{{ if eq .Repository \"riju\" }}{{ .Tag }}{{ end }}", + NULL, + }; + execvp(argv[0], argv); + die("execvp failed"); +} + +void cmd_pull(char *repo, char *tag) +{ + char *localImage, *remoteImage; + if (asprintf(&remoteImage, "%s:%s", repo, tag) < 0) + die("asprintf failed"); + if (asprintf(&localImage, "riju:%s", tag) < 0) + die("asprintf failed"); + pid_t orig_ppid = getpid(); + pid_t pid = fork(); + if (pid < 0) + die("fork failed"); + else if (pid == 0) { + if (freopen("/dev/null", "w", stdout) == NULL) + die("freopen failed"); + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + die("prctl failed"); + if (getppid() != orig_ppid) + exit(EXIT_FAILURE); + char *argv[] = { + "docker", "inspect", "--", localImage, NULL, + }; + execvp(argv[0], argv); + die("execvp failed"); + } + siginfo_t info; + if (waitid(P_PID, pid, &info, WEXITED) < 0) + die("waitid failed"); + if (info.si_status == 0) { + // Image exists already, no need to pull. It is only appropriate + // to use cmd_pull with immutable images. + return; + } + orig_ppid = getpid(); + pid = fork(); + if (pid < 0) + die("fork failed"); + else if (pid == 0) { + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + die("prctl failed"); + if (getppid() != orig_ppid) + exit(EXIT_FAILURE); + char *argv[] = { + "docker", "pull", "--", remoteImage, NULL, + }; + execvp(argv[0], argv); + } + if (waitid(P_PID, pid, &info, WEXITED) < 0) + die("waitid failed"); + if (info.si_status != 0) + die("child process failed"); + char *argv[] = { + "docker", "tag", "--", remoteImage, localImage, + }; + execvp(argv[0], argv); + die("execvp failed"); +} + +void cmd_session(char *uuid, char *lang, char *imageHash) { if (setvbuf(stdout, NULL, _IONBF, 0) != 0) die("setvbuf failed"); @@ -240,6 +338,13 @@ void session(char *uuid, char *lang, char *imageHash) "4000", "--cgroup-parent", "riju.slice", + // Deny access to outside networking for now in order to limit + // abuse, as we've received abuse reports from AWS. We should + // be able to remove this (and indeed we'll *want* to, in + // order to support package installation) by replacing it with + // a more fine-grained network control such as limiting + // outbound bandwidth. + "--network=none", "--label", "riju.category=user-session", "--label", @@ -250,6 +355,7 @@ void session(char *uuid, char *lang, char *imageHash) (char *)sentinel_bash, NULL, }; + execvp(argv[0], argv); die("execvp failed"); } @@ -287,7 +393,7 @@ void session(char *uuid, char *lang, char *imageHash) } } -void exec(char *uuid, int argc, char **cmdline, bool pty) +void cmd_exec(char *uuid, int argc, char **cmdline, bool pty) { if (setvbuf(stdout, NULL, _IONBF, 0) != 0) die("setvbuf failed"); @@ -455,7 +561,7 @@ void exec(char *uuid, int argc, char **cmdline, bool pty) } } -void teardown(char *uuid) +void cmd_teardown(char *uuid) { if (setuid(0) != 0) die("setuid failed"); @@ -483,31 +589,45 @@ int main(int argc, char **argv) die("seteuid failed"); if (argc < 2) die_with_usage(); + if (!strcmp(argv[1], "list")) { + if (argc != 2) + die_with_usage(); + cmd_list(); + return 0; + } + if (!strcmp(argv[1], "pull")) { + if (argc != 4) + die_with_usage(); + char *repo = parseRepo(argv[2]); + char *tag = parseTag(argv[3]); + cmd_pull(repo, tag); + return 0; + } if (!strcmp(argv[1], "session")) { if (argc < 4 || argc > 5) die_with_usage(); char *uuid = parseUUID(argv[2]); char *lang = parseLang(argv[3]); char *imageHash = argc == 5 ? parseImageHash(argv[4]) : NULL; - session(uuid, lang, imageHash); + cmd_session(uuid, lang, imageHash); return 0; } if (!strcmp(argv[1], "exec")) { if (argc < 4) die_with_usage(); - exec(parseUUID(argv[2]), argc - 3, &argv[3], false); + cmd_exec(parseUUID(argv[2]), argc - 3, &argv[3], false); return 0; } if (!strcmp(argv[1], "pty")) { if (argc < 4) die_with_usage(); - exec(parseUUID(argv[2]), argc - 3, &argv[3], true); + cmd_exec(parseUUID(argv[2]), argc - 3, &argv[3], true); return 0; } if (!strcmp(argv[1], "teardown")) { if (argc < 2) die_with_usage(); - teardown(argc >= 3 ? parseUUID(argv[2]) : NULL); + cmd_teardown(argc >= 3 ? parseUUID(argv[2]) : NULL); return 0; } die_with_usage(); diff --git a/tf/ami.tf b/tf/ami.tf index 786d29f..42894c5 100644 --- a/tf/ami.tf +++ b/tf/ami.tf @@ -6,12 +6,3 @@ data "aws_ami" "server" { values = [data.external.env.result.AMI_NAME] } } - -# data "aws_ami" "ci" { -# owners = ["self"] - -# filter { -# name = "name" -# values = [data.external.env.result.CI_AMI_NAME] -# } -# } diff --git a/tf/cloudwatch.tf b/tf/cloudwatch.tf deleted file mode 100644 index 130fd9b..0000000 --- a/tf/cloudwatch.tf +++ /dev/null @@ -1,174 +0,0 @@ -resource "aws_cloudwatch_metric_alarm" "server_cpu" { - alarm_name = "riju-server-cpu-high" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = "30" - datapoints_to_alarm = "15" - metric_name = "cpu_usage_active" - namespace = "CWAgent" - period = "60" - statistic = "Average" - threshold = "70" - alarm_description = "Average CPU usage on Riju server is above 70% for 30 minutes" - ok_actions = [aws_sns_topic.riju.arn] - alarm_actions = [aws_sns_topic.riju.arn] - insufficient_data_actions = [aws_sns_topic.riju.arn] - dimensions = { - RijuInstanceGroup = "Webserver" - } - - tags = { - BillingSubcategory = "Riju:CloudWatch:Alarm" - } -} - -resource "aws_cloudwatch_metric_alarm" "server_memory" { - alarm_name = "riju-server-memory-high" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = "30" - datapoints_to_alarm = "15" - metric_name = "mem_used_percent" - namespace = "CWAgent" - period = "60" - statistic = "Average" - threshold = "70" - alarm_description = "Average memory usage on Riju server is above 70% for 30 minutes" - ok_actions = [aws_sns_topic.riju.arn] - alarm_actions = [aws_sns_topic.riju.arn] - insufficient_data_actions = [aws_sns_topic.riju.arn] - dimensions = { - RijuInstanceGroup = "Webserver" - } - - tags = { - BillingSubcategory = "Riju:CloudWatch:Alarm" - } -} - -resource "aws_cloudwatch_metric_alarm" "server_data_volume_disk_space" { - alarm_name = "riju-server-data-volume-disk-usage-high" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = "5" - datapoints_to_alarm = "5" - metric_name = "disk_used_percent" - namespace = "CWAgent" - period = "60" - statistic = "Average" - threshold = "70" - alarm_description = "Disk space usage for data volume on Riju server is above 70%" - ok_actions = [aws_sns_topic.riju.arn] - alarm_actions = [aws_sns_topic.riju.arn] - insufficient_data_actions = [aws_sns_topic.riju.arn] - dimensions = { - RijuInstanceGroup = "Webserver" - path = "/mnt/riju" - } - - tags = { - BillingSubcategory = "Riju:CloudWatch:Alarm" - } -} - -resource "aws_cloudwatch_metric_alarm" "server_root_volume_disk_space" { - alarm_name = "riju-server-root-volume-disk-usage-high" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = "5" - datapoints_to_alarm = "5" - metric_name = "disk_used_percent" - namespace = "CWAgent" - period = "60" - statistic = "Average" - threshold = "70" - alarm_description = "Disk space usage for root volume on Riju server is above 70%" - ok_actions = [aws_sns_topic.riju.arn] - alarm_actions = [aws_sns_topic.riju.arn] - insufficient_data_actions = [aws_sns_topic.riju.arn] - dimensions = { - RijuInstanceGroup = "Webserver" - path = "/" - } - - tags = { - BillingSubcategory = "Riju:CloudWatch:Alarm" - } -} - -resource "aws_cloudwatch_dashboard" "riju" { - dashboard_name = "Riju" - dashboard_body = < - fs.readFile(`build/shared/${name}/install.bash`) - ) - ); - const allInstallContents = [].concat.apply( - [installContents], - sharedInstallContents - ); - const hash = await hashDockerfile( - "lang", - { - "riju:base": await getLocalImageLabel("riju:base", "riju.image-hash"), - }, - { - salt: { - langHash: await getDebHash(`build/lang/${lang}/riju-lang-${lang}.deb`), - sharedHashes: ( - await Promise.all( - sharedDeps.map( - async (name) => - await getDebHash(`build/shared/${name}/riju-shared-${name}.deb`) - ) - ) - ).sort(), - installHash: allInstallContents - .map((c) => crypto.createHash("sha1").update(c).digest("hex")) - .join(""), - }, - } - ); const server = getServer(); await new Promise((resolve) => server.listen(8487, "localhost", resolve)); try { @@ -76,11 +33,11 @@ async function main() { await runCommand( `docker build . -f docker/lang/Dockerfile ` + `--build-arg LANG=${lang} -t riju:lang-${lang} ` + - `--network host --no-cache --label riju.image-hash=${hash}` + `--network host --no-cache` ); } } finally { - await server.close(); + await new Promise((resolve) => server.close(resolve)); } process.exit(0); } diff --git a/tools/ci-bootstrap.bash b/tools/ci-bootstrap.bash deleted file mode 100755 index 65fbde2..0000000 --- a/tools/ci-bootstrap.bash +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -: ${AWS_ACCESS_KEY_ID} -: ${AWS_SECRET_ACCESS_KEY} -: ${DOCKER_REPO} -: ${S3_BUCKET} - -make image shell I=ci CMD="tools/ci-run.bash" NI=1 diff --git a/tools/ci-ec2.bash b/tools/ci-ec2.bash deleted file mode 100755 index fa54cbf..0000000 --- a/tools/ci-ec2.bash +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -remote_url="$(git remote get-url origin | sed -E 's|git@github\.com:|https://github.com/|')" -commit="$(git rev-parse HEAD)" - -echo >&2 "[ci-run.bash] Fetching build parameters from SSM." - -parameters="riju-ci-ami-id riju-docker-repo-host riju-public-docker-repo-host riju-s3-bucket-name" -resp="$(aws ssm get-parameters --names ${parameters})" - -read -r ami < <(jq '.Parameters[] | select(.Name == "riju-ci-ami-id").Value' -r <<< "${resp}") -read -r docker_repo < <(jq '.Parameters[] | select(.Name == "riju-docker-repo-host").Value' -r <<< "${resp}") -read -r public_docker_repo < <(jq '.Parameters[] | select(.Name == "riju-public-docker-repo-host").Value' -r <<< "${resp}") -read -r s3_bucket < <(jq '.Parameters[] | select(.Name == "riju-s3-bucket-name").Value' -r <<< "${resp}") - -echo >&2 "[ci-run.bash] Launching EC2 instance for CI job." - -ebs_config="DeviceName=/dev/sdh,Ebs={DeleteOnTermination=true,VolumeSize=128,VolumeType=gp3}" -instance_tags="ResourceType=instance,Tags=[{Key=Name,Value=Riju CI},{Key=BillingCategory,Value=Riju},{Key=BillingSubcategory,Value=Riju:EC2:CI}]" -ebs_tags="ResourceType=volume,Tags=[{Key=Name,Value=Riju CI},{Key=BillingCategory,Value=Riju},{Key=BillingSubcategory,Value=Riju:EBS:CI}]" - -resp="$(aws ec2 run-instances \ - --image-id "${ami}" \ - --instance-type t3.2xlarge \ - --security-groups riju-deploy \ - --iam-instance-profile Name=riju-deploy \ - --instance-initiated-shutdown-behavior terminate \ - --user-data file://tools/ci-user-data.bash \ - --tag-specifications "${instance_tags}" "${ebs_tags}" \ - --block-device-mappings "${ebs_config}")" - -instance_id="$(jq '.Instances[].InstanceId' -r <<< "${resp}")" - -echo >&2 "[ci-run.bash] Waiting for instance ${instance_id} to become ready." - -success= -for i in $(seq 1 15); do - sleep 2 - resp="$(aws ec2 describe-instance-status --instance-id "${instance_id}")" - status="$(jq '.InstanceStatuses[].InstanceState.Name' -r <<< "${resp}")" - status="${status:-unknown}" - case "${status}" in - pending|unknown) ;; - running) success=yes; break ;; - * ) exit 1 ;; - esac -done - -if [[ -z "${success}}" ]]; then - exit 124 -fi - -echo >&2 "[ci-run.bash] Waiting for SSH to come online." - -success= -for i in $(seq 1 15); do - if (yes || true) | timeout 5 mssh "ubuntu@${instance_id}" true 2>/dev/null; then - success=yes - break - elif (( $# == 124 )); then - exit 1 - fi - sleep 2 -done - -if [[ -z "${success}}" ]]; then - exit 124 -fi - -echo >&2 "[ci-run.bash] Running CI remotely using EC2 Instance Connect." - -mssh "ubuntu@${instance_id}" bash <&2 "[ci-run.bash] CI completed." diff --git a/tools/ci-run.bash b/tools/ci-run.bash deleted file mode 100755 index f1c3875..0000000 --- a/tools/ci-run.bash +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -make ecr -make env CMD="dep deploy:live --registry --publish --yes" Z=xz CI=1 TEST_PATIENCE=4 TEST_CONCURRENCY=1 diff --git a/tools/ci-user-data.bash b/tools/ci-user-data.bash deleted file mode 100755 index a0e8dbe..0000000 --- a/tools/ci-user-data.bash +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [[ -z "${NOHUP:-}" ]]; then - NOHUP=1 nohup "$0" "$@" & -fi - -while true; do - sleep 60 - # https://unix.stackexchange.com/a/92579 - if ! sudo netstat -tnpa | grep 'ESTABLISHED.*sshd'; then - sudo shutdown -h now - fi -done diff --git a/tools/depgraph.js b/tools/depgraph.js index 7a773cc..bf2eeef 100644 --- a/tools/depgraph.js +++ b/tools/depgraph.js @@ -23,6 +23,22 @@ import { import { getBaseImages, hashDockerfile } from "./hash-dockerfile.js"; import { runCommand } from "./util.js"; +const CONCURRENCY = 1; + +async function allPromises(callables, { concurrency }) { + const queue = new PQueue({ concurrency: concurrency }); + const results = []; + for (const callable of callables) { + queue.add(async () => { + console.log("START"); + results.push(await callable()); + console.log("END"); + }); + } + await queue.onIdle(); + return results; +} + function getS3Bucket() { if (!process.env.S3_BUCKET) { throw new Error(`unset environment variable: \$S3_BUCKET`); diff --git a/tools/docker-util.js b/tools/docker-util.js index 7121d46..930b12e 100644 --- a/tools/docker-util.js +++ b/tools/docker-util.js @@ -1,5 +1,7 @@ import process from "process"; +import semaphore from "semaphore"; + import { runCommand } from "./util.js"; // Return the digest of a local image. This is the actual image @@ -41,33 +43,40 @@ export async function getRemoteRepositoryTags(repo) { ).Tags; } +const remoteImageRateLimiter = semaphore(16); + // Return the value of a label on a Docker image that is on a remote // registry. If the image or label doesn't exist, return null. You // have to pass in a list of tags on the remote repository (see // getRemoteRepositoryTags) so that we can distinguish between missing // images and network errors. export async function getRemoteImageLabel(image, label, tags) { - const [repo, tag] = image.split(":"); - let output; + await new Promise((resolve) => remoteImageRateLimiter.take(resolve)); try { - output = ( - await runCommand(`skopeo inspect docker://${image}`, { - getStdout: true, - }) - ).stdout; - } catch (err) { - if (tags.includes(tag)) { - // Tag exists, something unexpected must have gone wrong when - // running skopeo inspect. - throw err; - } else { - // Tag does not exist, that must be why skopeo inspect didn't - // work. - return null; + const [_repo, tag] = image.split(":"); + let output; + try { + output = ( + await runCommand(`skopeo inspect docker://${image}`, { + getStdout: true, + }) + ).stdout; + } catch (err) { + if (tags.includes(tag)) { + // Tag exists, something unexpected must have gone wrong when + // running skopeo inspect. + throw err; + } else { + // Tag does not exist, that must be why skopeo inspect didn't + // work. + return null; + } } + const labels = JSON.parse(output).Labels; + return (labels && labels[label]) || null; + } finally { + remoteImageRateLimiter.leave(); } - const labels = JSON.parse(output).Labels; - return (labels && labels[label]) || null; } // Return the value of $DOCKER_REPO, throwing an error if it's not set diff --git a/tools/generate-build-script.js b/tools/generate-build-script.js index 3952b72..cd7a1d5 100644 --- a/tools/generate-build-script.js +++ b/tools/generate-build-script.js @@ -312,12 +312,11 @@ sudo --preserve-env=DEBIAN_FRONTEND apt-get update`); Package: riju-${isShared ? "shared" : "lang"}-${id} Version: \$(date +%s%3N) Architecture: amd64 -Maintainer: Radon Rosborough +Maintainer: Radian LLC Description: The ${name} ${ isShared ? "shared dependency" : "language" } packaged for Riju -Depends: \$(IFS=,; echo "\${depends[*]}" | sed -E 's/^[ ,]*|[ ,]*$| *(, *)+/},{/g' | sed -E 's/ *(\\| *)+/}\\|{/g'${stripDependsFilter} | tr -d '{}' | sed -E 's/^[,|]+|[,|]+$//g' | sed -E 's/[,|]*,[,|]*/,/g' | sed -E 's/\\|+/|/g') -Riju-Script-Hash: \$((cat "\$0"; echo "\${RIJU_IMAGE_HASH}") | sha1sum - | awk '{ print \$1 }')`; +Depends: \$(IFS=,; echo "\${depends[*]}" | sed -E 's/^[ ,]*|[ ,]*$| *(, *)+/},{/g' | sed -E 's/ *(\\| *)+/}\\|{/g'${stripDependsFilter} | tr -d '{}' | sed -E 's/^[,|]+|[,|]+$//g' | sed -E 's/[,|]*,[,|]*/,/g' | sed -E 's/\\|+/|/g')`; parts.push(`\ install -d "\${pkg}/DEBIAN" cat < "\${pkg}/DEBIAN/control" diff --git a/tools/packer-build-ci.bash b/tools/packer-build-ci.bash deleted file mode 100755 index 415a3e4..0000000 --- a/tools/packer-build-ci.bash +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -packer build ci.pkr.hcl diff --git a/tools/packer-build-web.bash b/tools/packer-build.bash similarity index 93% rename from tools/packer-build-web.bash rename to tools/packer-build.bash index c0c7891..b556f71 100755 --- a/tools/packer-build-web.bash +++ b/tools/packer-build.bash @@ -4,6 +4,7 @@ set -euo pipefail : ${ADMIN_PASSWORD} : ${S3_BUCKET} +: ${S3_CONFIG_PATH} : ${SUPERVISOR_ACCESS_TOKEN} export AWS_REGION="${AWS_REGION:-$(aws configure get region)}" diff --git a/tools/template/Makefile b/tools/template/Makefile new file mode 100644 index 0000000..6b3dfec --- /dev/null +++ b/tools/template/Makefile @@ -0,0 +1,5 @@ +bin: + mkdir bin + +bin/template: bin go.mod go.sum template.go + go build -o bin/template template.go diff --git a/tools/template/go.mod b/tools/template/go.mod new file mode 100644 index 0000000..bf84ca2 --- /dev/null +++ b/tools/template/go.mod @@ -0,0 +1,8 @@ +module github.com/raxod502/riju/tools/go + +go 1.16 + +require ( + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/tools/template/go.sum b/tools/template/go.sum new file mode 100644 index 0000000..5ad2ee8 --- /dev/null +++ b/tools/template/go.sum @@ -0,0 +1,39 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/tools/template/template b/tools/template/template new file mode 100755 index 0000000..d52b63d --- /dev/null +++ b/tools/template/template @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")" +make -s bin/template +exec bin/template diff --git a/tools/template/template.go b/tools/template/template.go new file mode 100644 index 0000000..b863108 --- /dev/null +++ b/tools/template/template.go @@ -0,0 +1,88 @@ +package main + +import ( + "bytes" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + "gopkg.in/yaml.v2" +) + +func findProjectRoot() (string, error) { + curDir, err := os.Getwd() + if err != nil { + return "", err + } + prevDir := "" + for curDir != prevDir { + if _, err := os.Stat(filepath.Join(curDir, ".git")); err == nil { + return curDir, nil + } else if !os.IsNotExist(err) { + return "", err + } + prevDir = curDir + curDir = filepath.Dir(curDir) + } + return "", fmt.Errorf("not a git repository (or any of the parent directories): .git") +} + +func mainInternal() error { + projectRoot, err := findProjectRoot() + if err != nil { + return err + } + envText, err := os.ReadFile(filepath.Join(projectRoot, "env.yaml")) + if err != nil { + return err + } + var env interface{} + if err := yaml.Unmarshal(envText, &env); err != nil { + return err + } + if err := filepath.WalkDir(projectRoot, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.Name() == ".git" { + return fs.SkipDir + } + dir := filepath.Dir(path) + if strings.Contains(d.Name(), ".in.") { + fmt.Fprintf(os.Stderr, "template_secrets.go: processing %s\n", path) + input, err := os.ReadFile(path) + if err != nil { + return err + } + tmpl, err := template.New("").Funcs(sprig.TxtFuncMap()).Option("missingkey=error").Parse(string(input)) + if err != nil { + return err + } + var output bytes.Buffer + if err := tmpl.Execute(&output, env); err != nil { + return err + } + if err := os.WriteFile( + filepath.Join(dir, strings.ReplaceAll(d.Name(), ".in.", ".out.")), + output.Bytes(), 0644, + ); err != nil { + return err + } + } + return nil + }); err != nil { + return err + } + return nil +} + +func main() { + if err := mainInternal(); err != nil { + fmt.Fprintf(os.Stderr, "template.go: %s\n", err.Error()) + os.Exit(1) + } +} diff --git a/yarn.lock b/yarn.lock index 4a7a9fc..251a14a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -878,6 +878,32 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" integrity sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g== +"@kubernetes/client-node@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.18.0.tgz#b1002f3c19fb7509ce521ea78f500589f8e3e047" + integrity sha512-Mp6q0OkZQBp+HslIgvHYpsPJk8z6mch231QWtIZQHvs+PaTE6mkUfusYE8fNw3jMjru5mVO/JDz6PTjB9YT2rQ== + dependencies: + "@types/js-yaml" "^4.0.1" + "@types/node" "^10.12.0" + "@types/request" "^2.47.1" + "@types/ws" "^6.0.1" + byline "^5.0.0" + execa "5.0.0" + isomorphic-ws "^4.0.1" + js-yaml "^4.1.0" + jsonpath-plus "^0.19.0" + request "^2.88.0" + rfc4648 "^1.3.0" + shelljs "^0.8.5" + stream-buffers "^3.0.2" + tar "^6.1.11" + tmp-promise "^3.0.2" + tslib "^2.4.1" + underscore "^1.13.6" + ws "^7.3.1" + optionalDependencies: + openid-client "^5.3.0" + "@sentry/core@6.11.0": version "6.11.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.11.0.tgz#40e94043afcf6407a109be26655c77832c64e740" @@ -946,11 +972,53 @@ "@sentry/types" "6.11.0" tslib "^1.9.3" +"@types/caseless@*": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" + integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== + +"@types/js-yaml@^4.0.1": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== + "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/node@*": + version "18.11.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5" + integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng== + +"@types/node@^10.12.0": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +"@types/request@^2.47.1": + version "2.48.8" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c" + integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + +"@types/tough-cookie@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + +"@types/ws@^6.0.1": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" + integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg== + dependencies: + "@types/node" "*" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -1153,7 +1221,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1175,6 +1243,13 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1196,6 +1271,11 @@ aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1231,6 +1311,18 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + assert@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" @@ -1259,16 +1351,31 @@ async-lock@^1.2.6: resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.0.tgz#0fba111bea8b9693020857eba4f9adca173df3e5" integrity sha512-8A7SkiisnEgME2zEedtDYPxUPzdv3x//E7n5IFktPAtMYSEAV7eNJF0rMwrVyUFj6d/8rgajLantbjcNRQYXIg== -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= +async@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + babel-loader@^8.2.2: version "8.2.2" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" @@ -1340,6 +1447,13 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1362,6 +1476,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bintrees@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" + integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= + bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -1502,9 +1621,9 @@ browserslist@^4.16.6: node-releases "^1.1.71" buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-xor@^1.0.3: version "1.0.3" @@ -1533,6 +1652,11 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -1587,7 +1711,12 @@ caniuse-lite@^1.0.30001219: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz#4b7783661515b8e7151fc6376cfd97f0e427b9e5" integrity sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw== -chalk@^2.0.0, chalk@^2.4.2: +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1596,6 +1725,14 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -1635,6 +1772,11 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -1682,16 +1824,35 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -1796,7 +1957,7 @@ core-js-compat@^3.14.0, core-js-compat@^3.9.1: browserslist "^4.16.6" semver "7.0.0" -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -1892,6 +2053,13 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + debounce@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" @@ -1952,6 +2120,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -1999,17 +2172,25 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -ejs@^3.1.5: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== +ejs@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006" + integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw== dependencies: - jake "^10.6.1" + jake "^10.8.5" electron-to-chromium@^1.3.723: version "1.3.752" @@ -2135,6 +2316,21 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +execa@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2221,6 +2417,11 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -2235,6 +2436,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2351,6 +2562,29 @@ for-in@^1.0.2: resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -2386,6 +2620,13 @@ fs-extra@^0.24.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -2443,6 +2684,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -2463,6 +2711,18 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob@^7.0.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.3, glob@^7.1.4: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -2485,11 +2745,29 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" @@ -2588,6 +2866,15 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -2669,6 +2956,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" @@ -2719,6 +3011,13 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -2806,6 +3105,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -2838,21 +3142,48 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jake@^10.6.1: - version "10.8.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" - integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: - async "0.9.x" - chalk "^2.4.2" + async "^3.2.3" + chalk "^4.0.2" filelist "^1.0.1" minimatch "^3.0.4" +jose@^4.10.0: + version "4.11.1" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.11.1.tgz#8f7443549befe5bddcf4bae664a9cbc1a62da4fa" + integrity sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -2873,6 +3204,16 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -2894,11 +3235,26 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz#b901e57607055933dc9a8bef0cc25160ee9dd64c" + integrity sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg== + jsonschema@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3094,6 +3450,18 @@ mime-db@1.48.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@~2.1.24: version "2.1.31" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" @@ -3128,10 +3496,39 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.0.tgz#7cebb0f9fa7d56f0c5b17853cbe28838a8dbbd3b" + integrity sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" mississippi@^3.0.0: version "3.0.0" @@ -3164,6 +3561,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3: dependencies: minimist "^1.2.5" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + monaco-editor-webpack-plugin@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.9.0.tgz#5b547281b9f404057dc5d8c5722390df9ac90be6" @@ -3219,9 +3621,9 @@ nan@^2.12.1: integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== nanoid@^3.1.23: - version "3.1.23" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== + version "3.2.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" + integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== nanomatch@^1.2.9: version "1.2.13" @@ -3303,6 +3705,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3317,6 +3724,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -3346,6 +3758,11 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" +oidc-token-hash@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" + integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -3367,6 +3784,16 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +openid-client@^5.3.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.3.1.tgz#69a5fa7d2b5ad479032f576852d40b4d4435488a" + integrity sha512-RLfehQiHch9N6tRWNx68cicf3b1WR0x74bJWHRc25uYIbSRwjxYcTFaRnzbbpls5jroLAaB/bFIodTgA5LJMvw== + dependencies: + jose "^4.10.0" + lru-cache "^6.0.0" + object-hash "^2.0.1" + oidc-token-hash "^5.0.1" + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -3488,7 +3915,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -3509,6 +3936,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -3603,6 +4035,13 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +prom-client@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-14.0.1.tgz#bdd9583e02ec95429677c0e013712d42ef1f86a8" + integrity sha512-HxTArb6fkOntQHoRGvv4qd/BkorjliiuO2uSWC2KC17MUTKYttWdDoXX/vxOhQdkoECEM9BBH0pj2l8G8kev6w== + dependencies: + tdigest "^0.1.1" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -3621,6 +4060,11 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= +psl@^1.1.28: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -3668,7 +4112,7 @@ punycode@^1.2.4: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -3678,6 +4122,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -3756,6 +4205,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + rechoir@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" @@ -3834,6 +4290,32 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -3851,6 +4333,15 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@^1.1.6: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.14.2, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -3864,6 +4355,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +rfc4648@^1.3.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.2.tgz#cf5dac417dd83e7f4debf52e3797a723c1373383" + integrity sha512-tLOizhR6YGovrEBLatX1sdcuhoSCXddw3mqNVAcKxGJ+J0hFeJ+SjeWCv5UPA/WU3YzWPPuCVYgXBKZUPGpKtg== + rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -3871,6 +4367,13 @@ rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -3903,7 +4406,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -3935,6 +4438,11 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +semaphore@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" + integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== + semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -4040,6 +4548,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -4097,9 +4614,9 @@ source-map-resolve@^0.5.0: urix "^0.1.0" source-map-support@~0.5.12: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -4126,6 +4643,21 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sshpk@^1.7.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + ssri@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" @@ -4154,6 +4686,11 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" +stream-buffers@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" + integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -4219,11 +4756,42 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tar@^6.1.11: + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^4.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tdigest@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" + integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE= + dependencies: + bintrees "1.0.1" + terser-webpack-plugin@^1.4.3: version "1.4.5" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" @@ -4240,9 +4808,9 @@ terser-webpack-plugin@^1.4.3: worker-farm "^1.7.0" terser@^4.1.2: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + version "4.8.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" + integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -4263,6 +4831,20 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tmp-promise@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" + integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== + dependencies: + tmp "^0.2.0" + +tmp@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -4310,16 +4892,41 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -4333,6 +4940,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +underscore@^1.13.6: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -4373,6 +4985,11 @@ unique-filename@^1.1.1: dependencies: unique-slug "^2.0.0" +unique-names-generator@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.7.1.tgz#966407b12ba97f618928f77322cfac8c80df5597" + integrity sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow== + unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" @@ -4447,6 +5064,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -4462,6 +5084,15 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -4611,6 +5242,11 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" +ws@^7.3.1: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"