├── .github ├── dependabot.yml └── workflows │ ├── go-test.yml │ ├── helm-test.yml │ └── release.yml ├── .gitignore ├── .ko.yaml ├── CONTRIBUTORS.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── clean.go ├── root.go └── serv.go ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── generated └── client │ └── clientset │ └── versioned │ ├── clientset.go │ ├── doc.go │ ├── fake │ ├── clientset_generated.go │ ├── doc.go │ └── register.go │ ├── scheme │ ├── doc.go │ └── register.go │ └── typed │ └── prescaling.bedrock.tech │ └── v1 │ ├── doc.go │ ├── fake │ ├── doc.go │ ├── fake_prescaling.bedrock.tech_client.go │ └── fake_prescalingevent.go │ ├── generated_expansion.go │ ├── prescaling.bedrock.tech_client.go │ └── prescalingevent.go ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── tools.go ├── update-codegen.sh └── verify-codegen.sh ├── helm ├── prescaling-exporter │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── clusterrole.yaml │ │ ├── clusterrolebinding.yaml │ │ ├── crds │ │ │ ├── prescalingevent.yaml │ │ │ └── testevents.yaml │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── monitor.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ └── values.yaml └── test-values.yml ├── main.go ├── pkg ├── apis │ └── prescaling.bedrock.tech │ │ └── v1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── types.go │ │ └── zz_generated.deepcopy.go ├── config │ ├── config.go │ └── config_test.go ├── exporter │ └── exporter.go ├── handlers │ ├── event_handlers.go │ └── status_handlers.go ├── k8s │ └── client.go ├── prescaling │ ├── desired_scaling.go │ ├── desired_scaling_test.go │ ├── get_hpa.go │ ├── get_hpa_test.go │ ├── hpa.go │ └── prescaling.go ├── server │ └── server.go ├── services │ ├── prescaling_event.go │ └── prescaling_event_test.go └── utils │ ├── utils.go │ ├── utils_test.go │ └── write_response.go ├── skaffold.yaml ├── swagger └── doc.yaml └── test-api.http /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | open-pull-requests-limit: 4 13 | - package-ecosystem: "github-actions" # See documentation for possible values 14 | directory: "/" # Location of package manifests 15 | schedule: 16 | interval: "weekly" 17 | open-pull-requests-limit: 2 18 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Go Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - .github/workflows/go-test.yml 7 | - pkg/** 8 | - cmd/** 9 | - docs/** 10 | - generated/** 11 | - go.mod 12 | - go.sum 13 | - main.go 14 | 15 | jobs: 16 | linting: 17 | name: Go lint 18 | runs-on: ubuntu-latest 19 | steps: 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up Go 1.x 25 | uses: actions/setup-go@v4 26 | with: 27 | go-version: stable 28 | 29 | - name: golangci-lint 30 | uses: golangci/golangci-lint-action@v3 31 | with: 32 | args: --timeout=30m 33 | 34 | test: 35 | name: Go test 36 | runs-on: ubuntu-latest 37 | steps: 38 | 39 | - name: Check out code into the Go module directory 40 | uses: actions/checkout@v4 41 | 42 | - name: Set up Go 1.x 43 | uses: actions/setup-go@v4 44 | with: 45 | go-version: stable 46 | 47 | - name: Install 48 | run: go mod download -x 49 | 50 | - name: Test 51 | run: go test -v ./... 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/helm-test.yml: -------------------------------------------------------------------------------- 1 | name: Helm Lint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - helm/** 7 | 8 | jobs: 9 | lint-test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set up Helm 18 | uses: azure/setup-helm@v3 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - name: Helm3 lint 23 | run : helm lint ./helm/prescaling-exporter 24 | 25 | - name: Helm3 template 26 | run : helm template prescaling-exporter helm/prescaling-exporter -f helm/test-values.yml 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*.*.*' 9 | pull_request: 10 | types: 11 | - labeled 12 | 13 | jobs: 14 | release: 15 | if: github.event.action != 'labeled' 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Bump version on merging Pull Requests with specific labels 21 | id: bumpr 22 | if: "!startsWith(github.ref, 'refs/tags/')" 23 | uses: haya14busa/action-bumpr@v1 24 | 25 | - name: Update corresponding major and minor tag 26 | uses: haya14busa/action-update-semver@v1 27 | if: "!steps.bumpr.outputs.skip" 28 | with: 29 | tag: ${{ steps.bumpr.outputs.next_version }} 30 | 31 | - name: Get tag name 32 | id: tag 33 | uses: haya14busa/action-cond@v1 34 | with: 35 | cond: "${{ startsWith(github.ref, 'refs/tags/') }}" 36 | if_true: ${{ github.ref }} 37 | if_false: ${{ steps.bumpr.outputs.next_version }} 38 | 39 | - id: version_tag 40 | run: echo "::set-output name=tag::$(echo ${{steps.tag.outputs.value}} | cut -c 2-)" 41 | 42 | # Update version in Chart.yaml 43 | - if: steps.tag.outputs.value != '' 44 | run: | 45 | sed -i '/^version:/c\version: ${{steps.version_tag.outputs.tag}}' ./helm/prescaling-exporter/Chart.yaml 46 | sed -i '/^appVersion:/c\appVersion: "${{steps.version_tag.outputs.tag}}"' ./helm/prescaling-exporter/Chart.yaml 47 | - if: steps.tag.outputs.value != '' 48 | uses: stefanzweifel/git-auto-commit-action@v4 49 | with: 50 | commit_message: "ci: bump charts to ${{steps.version_tag.outputs.tag}}" 51 | 52 | - name: Create release 53 | uses: shogo82148/actions-create-release@v1 54 | if: "steps.tag.outputs.value != ''" 55 | with: 56 | github_token: ${{ secrets.GITHUB_TOKEN }} 57 | tag_name: ${{ steps.tag.outputs.value }} 58 | release_name: Release ${{ steps.tag.outputs.value }} 59 | body: ${{ steps.bumpr.outputs.message }} 60 | draft: false 61 | prerelease: false 62 | 63 | - uses: actions/setup-go@v4 64 | if: "steps.tag.outputs.value != ''" 65 | with: 66 | go-version: '>=1.18.0' 67 | 68 | - name: Login to Docker Hub 69 | uses: docker/login-action@v2 70 | if: "steps.tag.outputs.value != ''" 71 | with: 72 | username: ${{secrets.DOCKER_USERNAME}} 73 | password: ${{ secrets.DOCKER_AUTH_TOKEN }} 74 | 75 | - uses: imjasonh/setup-ko@v0.6 76 | 77 | - name: Build and Push on Docker Hub 78 | if: "steps.tag.outputs.value != ''" 79 | env: 80 | KO_DOCKER_REPO: bedrockstreaming/prescaling-exporter 81 | run: ko publish . --bare --tags latest,${{steps.version_tag.outputs.tag}} 82 | 83 | release-helm: 84 | needs: [release] 85 | runs-on: ubuntu-latest 86 | steps: 87 | - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" 88 | uses: actions/checkout@v3 89 | with: 90 | fetch-depth: 0 91 | ref: 'main' 92 | 93 | - name: Configure Git 94 | run: | 95 | git config user.name "$GITHUB_ACTOR" 96 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 97 | - name: Install Helm 98 | uses: azure/setup-helm@v3 99 | with: 100 | token: ${{ secrets.GITHUB_TOKEN }} 101 | 102 | - name: Run chart-releaser 103 | uses: helm/chart-releaser-action@v1.5.0 104 | with: 105 | charts_dir: helm 106 | env: 107 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 108 | 109 | release-check: 110 | if: github.event.action == 'labeled' 111 | runs-on: ubuntu-latest 112 | steps: 113 | - uses: actions/checkout@v3 114 | - name: Post bumpr status comment 115 | uses: haya14busa/action-bumpr@v1 116 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - id: fo 3 | main: ./ 4 | env: 5 | - CGO_ENABLED=0 6 | - GOAMD64=v3 7 | ldflags: 8 | - -s 9 | - -w 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | [Valentin Chabrier](https://github.com/Eriwyr) 2 | [Mickaël Villers](https://github.com/villers) 3 | [Jérôme Foray](https://github.com/Meroje) 4 | [Timothée Aufort](https://github.com/taufort) 5 | [Jérémy Planckeel](https://github.com/jplanckeel) 6 | [Kilian Perrier](https://github.com/NightPlow) 7 | [Gabriel Forien](https://github.com/gforien) 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18 2 | 3 | ENV GO111MODULE=off 4 | 5 | RUN go get k8s.io/code-generator k8s.io/apimachinery 6 | 7 | ARG repo="${GOPATH}/src/github.com/bedrockstreaming/prescaling-exporter" 8 | 9 | RUN mkdir -p $repo 10 | WORKDIR $repo 11 | VOLUME $repo 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bedrock Streaming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Setting SHELL to bash allows bash commands to be executed by recipes. 2 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 3 | SHELL = /usr/bin/env bash -o pipefail 4 | .SHELLFLAGS = -ec 5 | 6 | .PHONY: all 7 | all: build 8 | 9 | ##@ General 10 | 11 | # The help target prints out all targets with their descriptions organized 12 | # beneath their categories. The categories are represented by '##@' and the 13 | # target descriptions by '##'. The awk commands is responsible for reading the 14 | # entire set of makefiles included in this invocation, looking for lines of the 15 | # file as xyz: ## something, and then pretty-format the target and help. Then, 16 | # if there's a line with ##@ something, that gets pretty-printed as a category. 17 | # More info on the usage of ANSI control characters for terminal formatting: 18 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 19 | # More info on the awk command: 20 | # http://linuxcommand.org/lc3_adv_awk.php 21 | 22 | .PHONY: help 23 | help: ## Display this help. 24 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 25 | 26 | ##@ Development 27 | 28 | .PHONY: generate 29 | generate: pkg/apis/prescaling.bedrock.tech/v1/types.go vendor/modules.txt ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 30 | hack/update-codegen.sh 31 | 32 | .PHONY: fmt 33 | fmt: ## Run go fmt against code. 34 | goimports -local $$(go list -m) -w $$(find . -type f -iname '*.go' ! -path './vendor/*' -print) 35 | 36 | .PHONY: vet 37 | vet: ## Run go vet against code. 38 | go vet $$(go list -e -compiled -test=false -export=false -deps=false -find=false -tags= -- ./...) 39 | 40 | .PHONY: test 41 | test: generate fmt vet ## Run tests. 42 | go test -coverprofile cover.out ./... 43 | 44 | ##@ Build 45 | 46 | .PHONY: build 47 | build: generate fmt vet ## Build manager binary. 48 | go build -o bin/manager main.go 49 | 50 | .PHONY: run 51 | run: manifests generate fmt vet ## Run a controller from your host. 52 | go run ./main.go 53 | 54 | ##@ Build Dependencies 55 | 56 | vendor/modules.txt: go.mod go.sum 57 | go mod vendor 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prescaling-exporter 2 | 3 | This project is a Prometheus exporter written in Golang, its goal is to provide a metric that scales applications to a requested capacity on a daily time slot or on an event declared in the embedded api. 4 | 5 | The exporter exposes a metric calculated according to the annotations defined on the HPA. The project also exposes an API that allows to register an event with a capacity multiplier. 6 | 7 | ## Requirements 8 | 9 | - Prometheus Stack or Victoria Metrics Stack 10 | - [Prometheus Adapater](https://github.com/kubernetes-sigs/prometheus-adapter) 11 | 12 | > Info: It is quite possible to use this solution with another observability stack than Prometheus. For example, Datadog or Newrelic, but we do not provide a configuration example. 13 | 14 | # Install 15 | ## Kubernetes Deployement 16 | 17 | - Clone repo 18 | - Run this command with Helm3 19 | 20 | ```bash 21 | helm install prescaling-exporter ./helm/prescaling-exporter -n prescaling-exporter --create-namespace 22 | ``` 23 | 24 | > You can use skaffold if you want. 25 | 26 | ## Configure an Horizontal Pod Autoscaler 27 | 28 | To be able to pre-scale an application every day before a traffic spike, you must add the 29 | following annotations on the HPA: 30 | 31 | Annotations | values 32 | --- | --- 33 | annotations.scaling.exporter.time.start | "hh:mm:ss" 34 | annotations.scaling.exporter.time.end | "hh:mm:ss" 35 | annotations.scaling.exporter.replica.min | "integer" 36 | 37 | 38 | ``` 39 | apiVersion: autoscaling/v2 40 | kind: HorizontalPodAutoscaler 41 | metadata: 42 | name: "{{ .Release.Name }}" 43 | annotations: 44 | annotations.scaling.exporter.replica.min: "{{ .Values.hpa.annotations.replica_min" 45 | annotations.scaling.exporter.time.end: "{{ .Values.hpa.annotations.time_end }}" 46 | annotations.scaling.exporter.time.start: "{{ .Values.hpa.annotations.time_start }}" 47 | spec: 48 | scaleTargetRef: 49 | apiVersion: apps/v1 50 | kind: Deployment 51 | name: "{{ .Release.Name }}" 52 | minReplicas: {{ .Values.hpa.minReplicas }} 53 | maxReplicas: {{ .Values.hpa.maxReplicas }} 54 | metrics: 55 | - type: External 56 | external: 57 | metric: 58 | name: "prescaling_metric" 59 | selector: 60 | matchLabels: 61 | deployment: "{{ .Release.Name }}" 62 | target 63 | type: Value 64 | value: 10 65 | ``` 66 | 67 | > It's important to set the `target.value` to 10. The metric's value provided by the exporter in order to scale is 11. The scale up of pods will be gradual. Currently, the increment is carried out 10% by 10%. 68 | 69 | ## Configure prometheus adapter 70 | 71 | Here is a configuration example using the prometheus adapter to supply the metric to the Kubernetes cluster: 72 | 73 | ``` 74 | - "metricsQuery": "avg(<<.Series>>{<<.LabelMatchers>>})" 75 | "name": 76 | "as": "prescaling_metric" 77 | "resources": 78 | "overrides": 79 | "namespace": 80 | "resource": "namespace" 81 | "seriesQuery": "prescaling_metric" 82 | ``` 83 | 84 | ## Configure parameters 85 | 86 | You can modify the application's settings to use different annotations or a different port. Configurable variable environments in the chart values are used to do this: 87 | 88 | Parameters | Default values | Comment 89 | --- | --- | --- 90 | Namespace | "prescaling-exporter" | Namespace for the prescaling stack 91 | Port | "9101" | Application port 92 | AnnotationEndTime | "annotations.scaling.exporter.time.end" | Prescaling end time 93 | AnnotationStartTime | "annotations.scaling.exporter.time.start" | Prescaling start time 94 | AnnotationMinReplicas | "annotations.scaling.exporter.replica.min"| Minimum of desired replicas during the prescaling 95 | LabelProject | "project" | k8s label used to add a label in the prescaling metric 96 | 97 | # Prescaling Web server 98 | ## OpenAPI docs 99 | 100 | This application provides a swagger UI which is accessible on `/swagger/index.html`. 101 | 102 | ## Event API 103 | 104 | To allow the platform to scale up and down on different schedules and with a multiplier, prescaling events can be registered by the DRBs or the API. 105 | 106 | The following API allows the creation, modification and deletion of Prescaling Event CRDs in the cluster: `/api/v1/events/`. 107 | 108 | ## Metrics 109 | 110 | The metrics are exposed on `/metrics` endpoint. 111 | 112 | # Build 113 | ## Golang utils 114 | 115 | 1. Golang build 116 | 117 | ```bash 118 | ./generate_type.sh 119 | go build 120 | ``` 121 | 122 | 2. Run test 123 | 124 | ```bash 125 | go test -v ./... 126 | ``` 127 | 3. Run test with coverate report 128 | 129 | ```bash 130 | go test -coverprofile=cover.out -v ./... 131 | ``` 132 | 133 | ``` 134 | openapi-generator generate -i api/openapi.yaml -g go-server -o . 135 | ``` 136 | 137 | ## Generate crd files 138 | 139 | ``` 140 | ./generate_type.sh 141 | ``` 142 | 143 | ## Generate swagger docs 144 | 145 | ``` 146 | go install github.com/swaggo/swag/cmd/swag@latest 147 | swag init -g pkg/server/server.go --parseInternal=true 148 | ``` 149 | -------------------------------------------------------------------------------- /cmd/clean.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "github.com/spf13/cobra" 6 | "k8s.io/utils/clock" 7 | 8 | "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned" 9 | "github.com/BedrockStreaming/prescaling-exporter/pkg/config" 10 | "github.com/BedrockStreaming/prescaling-exporter/pkg/k8s" 11 | "github.com/BedrockStreaming/prescaling-exporter/pkg/services" 12 | ) 13 | 14 | var retention int 15 | 16 | var clean = &cobra.Command{ 17 | Use: "clean", 18 | Short: "Run clean CRD prescaling events", 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | client, err := k8s.NewClient() 21 | if err != nil { 22 | log.Error("error - k8s client login error: ", err) 23 | return err 24 | } 25 | prescalingClient, err := versioned.NewForConfig(client.Config) 26 | if err != nil { 27 | log.Error("error - k8s prescaling client login error: ", err) 28 | return err 29 | } 30 | 31 | prescalingEvents := prescalingClient.PrescalingV1().PrescalingEvents(config.Config.Namespace) 32 | time := clock.RealClock{} 33 | eventService := services.NewEventService(prescalingEvents, time) 34 | 35 | return eventService.Clean(retention) 36 | }, 37 | } 38 | 39 | func init() { 40 | rootCmd.AddCommand(clean) 41 | clean.PersistentFlags().IntVarP(&retention, "retention", "r", 10, "retention days for CRD") 42 | } 43 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // rootCmd represents the base command when called without any subcommands 10 | var rootCmd = &cobra.Command{ 11 | Use: "prescaling", 12 | Short: "a prescaling k8s solution", 13 | Long: "K8S Prescaling based on CRD and prometheus exporter", 14 | } 15 | 16 | func Execute() { 17 | err := rootCmd.Execute() 18 | if err != nil { 19 | os.Exit(1) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cmd/serv.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "k8s.io/utils/clock" 6 | 7 | "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned" 8 | "github.com/BedrockStreaming/prescaling-exporter/pkg/config" 9 | "github.com/BedrockStreaming/prescaling-exporter/pkg/exporter" 10 | "github.com/BedrockStreaming/prescaling-exporter/pkg/handlers" 11 | "github.com/BedrockStreaming/prescaling-exporter/pkg/k8s" 12 | "github.com/BedrockStreaming/prescaling-exporter/pkg/prescaling" 13 | "github.com/BedrockStreaming/prescaling-exporter/pkg/server" 14 | "github.com/BedrockStreaming/prescaling-exporter/pkg/services" 15 | 16 | log "github.com/sirupsen/logrus" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var serv = &cobra.Command{ 21 | Use: "serv", 22 | Short: "Run prescaling server", 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | client, err := k8s.NewClient() 25 | if err != nil { 26 | log.Error("error - k8s client login error: ", err) 27 | return err 28 | } 29 | 30 | prescalingClient, err := versioned.NewForConfig(client.Config) 31 | if err != nil { 32 | log.Error("error - k8s prescaling client login error: ", err) 33 | return err 34 | } 35 | 36 | prescalingEvents := prescalingClient.PrescalingV1().PrescalingEvents(config.Config.Namespace) 37 | time := clock.RealClock{} 38 | 39 | eventService := services.NewEventService(prescalingEvents, time) 40 | eventHandler := handlers.NewEventHandlers(eventService) 41 | statusHandler := handlers.NewStatusHandlers() 42 | 43 | collector := exporter.NewPrescalingCollector( 44 | prescaling.NewPrescaling(client, eventService), 45 | ) 46 | 47 | prometheus.MustRegister(collector) 48 | 49 | return server.NewServer(statusHandler, eventHandler).Initialize() 50 | }, 51 | } 52 | 53 | func init() { 54 | rootCmd.AddCommand(serv) 55 | } 56 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT 2 | package docs 3 | 4 | import "github.com/swaggo/swag" 5 | 6 | const docTemplate = `{ 7 | "schemes": {{ marshal .Schemes }}, 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "{{escape .Description}}", 11 | "title": "{{.Title}}", 12 | "contact": {}, 13 | "version": "{{.Version}}" 14 | }, 15 | "host": "{{.Host}}", 16 | "basePath": "{{.BasePath}}", 17 | "paths": { 18 | "/api/v1/events/": { 19 | "get": { 20 | "description": "List all prescaling Events", 21 | "consumes": [ 22 | "application/json" 23 | ], 24 | "produces": [ 25 | "application/json" 26 | ], 27 | "tags": [ 28 | "prescalingevent" 29 | ], 30 | "summary": "List all prescaling Events", 31 | "responses": { 32 | "200": { 33 | "description": "OK", 34 | "schema": { 35 | "type": "array", 36 | "items": { 37 | "$ref": "#/definitions/services.PrescalingEventOutput" 38 | } 39 | } 40 | }, 41 | "400": { 42 | "description": "Bad Request", 43 | "schema": { 44 | "type": "string" 45 | } 46 | } 47 | } 48 | }, 49 | "post": { 50 | "description": "Create a prescaling Event", 51 | "consumes": [ 52 | "application/json" 53 | ], 54 | "produces": [ 55 | "application/json" 56 | ], 57 | "tags": [ 58 | "prescalingevent" 59 | ], 60 | "summary": "Create a prescaling Event", 61 | "parameters": [ 62 | { 63 | "description": "The Request body", 64 | "name": "data", 65 | "in": "body", 66 | "required": true, 67 | "schema": { 68 | "$ref": "#/definitions/handlers.CreateDTO" 69 | } 70 | } 71 | ], 72 | "responses": { 73 | "200": { 74 | "description": "OK", 75 | "schema": { 76 | "$ref": "#/definitions/services.PrescalingEventOutput" 77 | } 78 | }, 79 | "500": { 80 | "description": "Internal Server Error", 81 | "schema": { 82 | "type": "string" 83 | } 84 | } 85 | } 86 | } 87 | }, 88 | "/api/v1/events/current/": { 89 | "get": { 90 | "description": "Get current prescaling Event", 91 | "consumes": [ 92 | "application/json" 93 | ], 94 | "produces": [ 95 | "application/json" 96 | ], 97 | "tags": [ 98 | "prescalingevent" 99 | ], 100 | "summary": "Get current prescaling Event", 101 | "responses": { 102 | "200": { 103 | "description": "OK", 104 | "schema": { 105 | "$ref": "#/definitions/services.PrescalingEventOutput" 106 | } 107 | }, 108 | "204": { 109 | "description": "No Content", 110 | "schema": { 111 | "type": "string" 112 | } 113 | } 114 | } 115 | } 116 | }, 117 | "/api/v1/events/{name}": { 118 | "get": { 119 | "description": "Get a prescaling Events by name", 120 | "consumes": [ 121 | "application/json" 122 | ], 123 | "produces": [ 124 | "application/json" 125 | ], 126 | "tags": [ 127 | "prescalingevent" 128 | ], 129 | "summary": "Get a prescaling Events by name", 130 | "parameters": [ 131 | { 132 | "type": "string", 133 | "description": "event-name-1", 134 | "name": "name", 135 | "in": "path", 136 | "required": true 137 | } 138 | ], 139 | "responses": { 140 | "200": { 141 | "description": "OK", 142 | "schema": { 143 | "$ref": "#/definitions/services.PrescalingEventOutput" 144 | } 145 | }, 146 | "404": { 147 | "description": "Not Found", 148 | "schema": { 149 | "type": "string" 150 | } 151 | } 152 | } 153 | }, 154 | "put": { 155 | "description": "Update a prescaling Event by name", 156 | "consumes": [ 157 | "application/json" 158 | ], 159 | "produces": [ 160 | "application/json" 161 | ], 162 | "tags": [ 163 | "prescalingevent" 164 | ], 165 | "summary": "Update a prescaling Event by name", 166 | "parameters": [ 167 | { 168 | "description": "The Request body", 169 | "name": "data", 170 | "in": "body", 171 | "required": true, 172 | "schema": { 173 | "$ref": "#/definitions/handlers.UpdateDTO" 174 | } 175 | }, 176 | { 177 | "type": "string", 178 | "description": "event-name-1", 179 | "name": "name", 180 | "in": "path", 181 | "required": true 182 | } 183 | ], 184 | "responses": { 185 | "200": { 186 | "description": "OK", 187 | "schema": { 188 | "$ref": "#/definitions/services.PrescalingEventOutput" 189 | } 190 | }, 191 | "400": { 192 | "description": "Bad Request", 193 | "schema": { 194 | "type": "string" 195 | } 196 | }, 197 | "404": { 198 | "description": "Not Found", 199 | "schema": { 200 | "type": "string" 201 | } 202 | } 203 | } 204 | }, 205 | "delete": { 206 | "description": "Delete a prescaling Event by name", 207 | "consumes": [ 208 | "application/json" 209 | ], 210 | "produces": [ 211 | "application/json" 212 | ], 213 | "tags": [ 214 | "prescalingevent" 215 | ], 216 | "summary": "Delete a prescaling Event by name", 217 | "parameters": [ 218 | { 219 | "type": "string", 220 | "description": "event-name-1", 221 | "name": "name", 222 | "in": "path", 223 | "required": true 224 | } 225 | ], 226 | "responses": { 227 | "200": { 228 | "description": "OK" 229 | }, 230 | "400": { 231 | "description": "Bad Request", 232 | "schema": { 233 | "type": "string" 234 | } 235 | }, 236 | "404": { 237 | "description": "Not Found", 238 | "schema": { 239 | "type": "string" 240 | } 241 | } 242 | } 243 | } 244 | } 245 | }, 246 | "definitions": { 247 | "handlers.CreateDTO": { 248 | "type": "object", 249 | "properties": { 250 | "date": { 251 | "type": "string", 252 | "example": "2022-05-25" 253 | }, 254 | "description": { 255 | "type": "string", 256 | "example": "a good description" 257 | }, 258 | "end_time": { 259 | "type": "string", 260 | "example": "23:59:59" 261 | }, 262 | "multiplier": { 263 | "type": "integer", 264 | "example": 2 265 | }, 266 | "name": { 267 | "type": "string", 268 | "example": "prescaling-event-1" 269 | }, 270 | "start_time": { 271 | "type": "string", 272 | "example": "20:00:00" 273 | } 274 | } 275 | }, 276 | "handlers.UpdateDTO": { 277 | "type": "object", 278 | "properties": { 279 | "date": { 280 | "type": "string", 281 | "example": "2022-05-25" 282 | }, 283 | "description": { 284 | "type": "string", 285 | "example": "a good description" 286 | }, 287 | "end_time": { 288 | "type": "string", 289 | "example": "23:59:59" 290 | }, 291 | "multiplier": { 292 | "type": "integer", 293 | "example": 2 294 | }, 295 | "name": { 296 | "type": "string", 297 | "example": "prescaling-event-1" 298 | }, 299 | "start_time": { 300 | "type": "string", 301 | "example": "20:00:00" 302 | } 303 | } 304 | }, 305 | "services.PrescalingEventOutput": { 306 | "type": "object", 307 | "properties": { 308 | "date": { 309 | "type": "string", 310 | "example": "2022-05-25" 311 | }, 312 | "description": { 313 | "type": "string", 314 | "example": "a good description" 315 | }, 316 | "end_time": { 317 | "type": "string", 318 | "example": "23:59:59" 319 | }, 320 | "multiplier": { 321 | "type": "integer", 322 | "example": 2 323 | }, 324 | "name": { 325 | "type": "string" 326 | }, 327 | "start_time": { 328 | "type": "string", 329 | "example": "20:00:00" 330 | } 331 | } 332 | } 333 | } 334 | }` 335 | 336 | // SwaggerInfo holds exported Swagger Info so clients can modify it 337 | var SwaggerInfo = &swag.Spec{ 338 | Version: "1.0.0", 339 | Host: "", 340 | BasePath: "", 341 | Schemes: []string{}, 342 | Title: "Prescaling API", 343 | Description: "This API was built with FastAPI to deal with prescaling recordings in CRD", 344 | InfoInstanceName: "swagger", 345 | SwaggerTemplate: docTemplate, 346 | LeftDelim: "{{", 347 | RightDelim: "}}", 348 | } 349 | 350 | func init() { 351 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 352 | } 353 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This API was built with FastAPI to deal with prescaling recordings in CRD", 5 | "title": "Prescaling API", 6 | "contact": {}, 7 | "version": "1.0.0" 8 | }, 9 | "paths": { 10 | "/api/v1/events/": { 11 | "get": { 12 | "description": "List all prescaling Events", 13 | "consumes": [ 14 | "application/json" 15 | ], 16 | "produces": [ 17 | "application/json" 18 | ], 19 | "tags": [ 20 | "prescalingevent" 21 | ], 22 | "summary": "List all prescaling Events", 23 | "responses": { 24 | "200": { 25 | "description": "OK", 26 | "schema": { 27 | "type": "array", 28 | "items": { 29 | "$ref": "#/definitions/services.PrescalingEventOutput" 30 | } 31 | } 32 | }, 33 | "400": { 34 | "description": "Bad Request", 35 | "schema": { 36 | "type": "string" 37 | } 38 | } 39 | } 40 | }, 41 | "post": { 42 | "description": "Create a prescaling Event", 43 | "consumes": [ 44 | "application/json" 45 | ], 46 | "produces": [ 47 | "application/json" 48 | ], 49 | "tags": [ 50 | "prescalingevent" 51 | ], 52 | "summary": "Create a prescaling Event", 53 | "parameters": [ 54 | { 55 | "description": "The Request body", 56 | "name": "data", 57 | "in": "body", 58 | "required": true, 59 | "schema": { 60 | "$ref": "#/definitions/handlers.CreateDTO" 61 | } 62 | } 63 | ], 64 | "responses": { 65 | "200": { 66 | "description": "OK", 67 | "schema": { 68 | "$ref": "#/definitions/services.PrescalingEventOutput" 69 | } 70 | }, 71 | "500": { 72 | "description": "Internal Server Error", 73 | "schema": { 74 | "type": "string" 75 | } 76 | } 77 | } 78 | } 79 | }, 80 | "/api/v1/events/current/": { 81 | "get": { 82 | "description": "Get current prescaling Event", 83 | "consumes": [ 84 | "application/json" 85 | ], 86 | "produces": [ 87 | "application/json" 88 | ], 89 | "tags": [ 90 | "prescalingevent" 91 | ], 92 | "summary": "Get current prescaling Event", 93 | "responses": { 94 | "200": { 95 | "description": "OK", 96 | "schema": { 97 | "$ref": "#/definitions/services.PrescalingEventOutput" 98 | } 99 | }, 100 | "204": { 101 | "description": "No Content", 102 | "schema": { 103 | "type": "string" 104 | } 105 | } 106 | } 107 | } 108 | }, 109 | "/api/v1/events/{name}": { 110 | "get": { 111 | "description": "Get a prescaling Events by name", 112 | "consumes": [ 113 | "application/json" 114 | ], 115 | "produces": [ 116 | "application/json" 117 | ], 118 | "tags": [ 119 | "prescalingevent" 120 | ], 121 | "summary": "Get a prescaling Events by name", 122 | "parameters": [ 123 | { 124 | "type": "string", 125 | "description": "event-name-1", 126 | "name": "name", 127 | "in": "path", 128 | "required": true 129 | } 130 | ], 131 | "responses": { 132 | "200": { 133 | "description": "OK", 134 | "schema": { 135 | "$ref": "#/definitions/services.PrescalingEventOutput" 136 | } 137 | }, 138 | "404": { 139 | "description": "Not Found", 140 | "schema": { 141 | "type": "string" 142 | } 143 | } 144 | } 145 | }, 146 | "put": { 147 | "description": "Update a prescaling Event by name", 148 | "consumes": [ 149 | "application/json" 150 | ], 151 | "produces": [ 152 | "application/json" 153 | ], 154 | "tags": [ 155 | "prescalingevent" 156 | ], 157 | "summary": "Update a prescaling Event by name", 158 | "parameters": [ 159 | { 160 | "description": "The Request body", 161 | "name": "data", 162 | "in": "body", 163 | "required": true, 164 | "schema": { 165 | "$ref": "#/definitions/handlers.UpdateDTO" 166 | } 167 | }, 168 | { 169 | "type": "string", 170 | "description": "event-name-1", 171 | "name": "name", 172 | "in": "path", 173 | "required": true 174 | } 175 | ], 176 | "responses": { 177 | "200": { 178 | "description": "OK", 179 | "schema": { 180 | "$ref": "#/definitions/services.PrescalingEventOutput" 181 | } 182 | }, 183 | "400": { 184 | "description": "Bad Request", 185 | "schema": { 186 | "type": "string" 187 | } 188 | }, 189 | "404": { 190 | "description": "Not Found", 191 | "schema": { 192 | "type": "string" 193 | } 194 | } 195 | } 196 | }, 197 | "delete": { 198 | "description": "Delete a prescaling Event by name", 199 | "consumes": [ 200 | "application/json" 201 | ], 202 | "produces": [ 203 | "application/json" 204 | ], 205 | "tags": [ 206 | "prescalingevent" 207 | ], 208 | "summary": "Delete a prescaling Event by name", 209 | "parameters": [ 210 | { 211 | "type": "string", 212 | "description": "event-name-1", 213 | "name": "name", 214 | "in": "path", 215 | "required": true 216 | } 217 | ], 218 | "responses": { 219 | "200": { 220 | "description": "OK" 221 | }, 222 | "400": { 223 | "description": "Bad Request", 224 | "schema": { 225 | "type": "string" 226 | } 227 | }, 228 | "404": { 229 | "description": "Not Found", 230 | "schema": { 231 | "type": "string" 232 | } 233 | } 234 | } 235 | } 236 | } 237 | }, 238 | "definitions": { 239 | "handlers.CreateDTO": { 240 | "type": "object", 241 | "properties": { 242 | "date": { 243 | "type": "string", 244 | "example": "2022-05-25" 245 | }, 246 | "description": { 247 | "type": "string", 248 | "example": "a good description" 249 | }, 250 | "end_time": { 251 | "type": "string", 252 | "example": "23:59:59" 253 | }, 254 | "multiplier": { 255 | "type": "integer", 256 | "example": 2 257 | }, 258 | "name": { 259 | "type": "string", 260 | "example": "prescaling-event-1" 261 | }, 262 | "start_time": { 263 | "type": "string", 264 | "example": "20:00:00" 265 | } 266 | } 267 | }, 268 | "handlers.UpdateDTO": { 269 | "type": "object", 270 | "properties": { 271 | "date": { 272 | "type": "string", 273 | "example": "2022-05-25" 274 | }, 275 | "description": { 276 | "type": "string", 277 | "example": "a good description" 278 | }, 279 | "end_time": { 280 | "type": "string", 281 | "example": "23:59:59" 282 | }, 283 | "multiplier": { 284 | "type": "integer", 285 | "example": 2 286 | }, 287 | "name": { 288 | "type": "string", 289 | "example": "prescaling-event-1" 290 | }, 291 | "start_time": { 292 | "type": "string", 293 | "example": "20:00:00" 294 | } 295 | } 296 | }, 297 | "services.PrescalingEventOutput": { 298 | "type": "object", 299 | "properties": { 300 | "date": { 301 | "type": "string", 302 | "example": "2022-05-25" 303 | }, 304 | "description": { 305 | "type": "string", 306 | "example": "a good description" 307 | }, 308 | "end_time": { 309 | "type": "string", 310 | "example": "23:59:59" 311 | }, 312 | "multiplier": { 313 | "type": "integer", 314 | "example": 2 315 | }, 316 | "name": { 317 | "type": "string" 318 | }, 319 | "start_time": { 320 | "type": "string", 321 | "example": "20:00:00" 322 | } 323 | } 324 | } 325 | } 326 | } -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | definitions: 2 | handlers.CreateDTO: 3 | properties: 4 | date: 5 | example: "2022-05-25" 6 | type: string 7 | description: 8 | example: a good description 9 | type: string 10 | end_time: 11 | example: "23:59:59" 12 | type: string 13 | multiplier: 14 | example: 2 15 | type: integer 16 | name: 17 | example: prescaling-event-1 18 | type: string 19 | start_time: 20 | example: "20:00:00" 21 | type: string 22 | type: object 23 | handlers.UpdateDTO: 24 | properties: 25 | date: 26 | example: "2022-05-25" 27 | type: string 28 | description: 29 | example: a good description 30 | type: string 31 | end_time: 32 | example: "23:59:59" 33 | type: string 34 | multiplier: 35 | example: 2 36 | type: integer 37 | name: 38 | example: prescaling-event-1 39 | type: string 40 | start_time: 41 | example: "20:00:00" 42 | type: string 43 | type: object 44 | services.PrescalingEventOutput: 45 | properties: 46 | date: 47 | example: "2022-05-25" 48 | type: string 49 | description: 50 | example: a good description 51 | type: string 52 | end_time: 53 | example: "23:59:59" 54 | type: string 55 | multiplier: 56 | example: 2 57 | type: integer 58 | name: 59 | type: string 60 | start_time: 61 | example: "20:00:00" 62 | type: string 63 | type: object 64 | info: 65 | contact: {} 66 | description: This API was built with FastAPI to deal with prescaling recordings 67 | in CRD 68 | title: Prescaling API 69 | version: 1.0.0 70 | paths: 71 | /api/v1/events/: 72 | get: 73 | consumes: 74 | - application/json 75 | description: List all prescaling Events 76 | produces: 77 | - application/json 78 | responses: 79 | "200": 80 | description: OK 81 | schema: 82 | items: 83 | $ref: '#/definitions/services.PrescalingEventOutput' 84 | type: array 85 | "400": 86 | description: Bad Request 87 | schema: 88 | type: string 89 | summary: List all prescaling Events 90 | tags: 91 | - prescalingevent 92 | post: 93 | consumes: 94 | - application/json 95 | description: Create a prescaling Event 96 | parameters: 97 | - description: The Request body 98 | in: body 99 | name: data 100 | required: true 101 | schema: 102 | $ref: '#/definitions/handlers.CreateDTO' 103 | produces: 104 | - application/json 105 | responses: 106 | "200": 107 | description: OK 108 | schema: 109 | $ref: '#/definitions/services.PrescalingEventOutput' 110 | "500": 111 | description: Internal Server Error 112 | schema: 113 | type: string 114 | summary: Create a prescaling Event 115 | tags: 116 | - prescalingevent 117 | /api/v1/events/{name}: 118 | delete: 119 | consumes: 120 | - application/json 121 | description: Delete a prescaling Event by name 122 | parameters: 123 | - description: event-name-1 124 | in: path 125 | name: name 126 | required: true 127 | type: string 128 | produces: 129 | - application/json 130 | responses: 131 | "200": 132 | description: OK 133 | "400": 134 | description: Bad Request 135 | schema: 136 | type: string 137 | "404": 138 | description: Not Found 139 | schema: 140 | type: string 141 | summary: Delete a prescaling Event by name 142 | tags: 143 | - prescalingevent 144 | get: 145 | consumes: 146 | - application/json 147 | description: Get a prescaling Events by name 148 | parameters: 149 | - description: event-name-1 150 | in: path 151 | name: name 152 | required: true 153 | type: string 154 | produces: 155 | - application/json 156 | responses: 157 | "200": 158 | description: OK 159 | schema: 160 | $ref: '#/definitions/services.PrescalingEventOutput' 161 | "404": 162 | description: Not Found 163 | schema: 164 | type: string 165 | summary: Get a prescaling Events by name 166 | tags: 167 | - prescalingevent 168 | put: 169 | consumes: 170 | - application/json 171 | description: Update a prescaling Event by name 172 | parameters: 173 | - description: The Request body 174 | in: body 175 | name: data 176 | required: true 177 | schema: 178 | $ref: '#/definitions/handlers.UpdateDTO' 179 | - description: event-name-1 180 | in: path 181 | name: name 182 | required: true 183 | type: string 184 | produces: 185 | - application/json 186 | responses: 187 | "200": 188 | description: OK 189 | schema: 190 | $ref: '#/definitions/services.PrescalingEventOutput' 191 | "400": 192 | description: Bad Request 193 | schema: 194 | type: string 195 | "404": 196 | description: Not Found 197 | schema: 198 | type: string 199 | summary: Update a prescaling Event by name 200 | tags: 201 | - prescalingevent 202 | /api/v1/events/current/: 203 | get: 204 | consumes: 205 | - application/json 206 | description: Get current prescaling Event 207 | produces: 208 | - application/json 209 | responses: 210 | "200": 211 | description: OK 212 | schema: 213 | $ref: '#/definitions/services.PrescalingEventOutput' 214 | "204": 215 | description: No Content 216 | schema: 217 | type: string 218 | summary: Get current prescaling Event 219 | tags: 220 | - prescalingevent 221 | swagger: "2.0" 222 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package versioned 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | 9 | prescalingv1 "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1" 10 | discovery "k8s.io/client-go/discovery" 11 | rest "k8s.io/client-go/rest" 12 | flowcontrol "k8s.io/client-go/util/flowcontrol" 13 | ) 14 | 15 | type Interface interface { 16 | Discovery() discovery.DiscoveryInterface 17 | PrescalingV1() prescalingv1.PrescalingV1Interface 18 | } 19 | 20 | // Clientset contains the clients for groups. 21 | type Clientset struct { 22 | *discovery.DiscoveryClient 23 | prescalingV1 *prescalingv1.PrescalingV1Client 24 | } 25 | 26 | // PrescalingV1 retrieves the PrescalingV1Client 27 | func (c *Clientset) PrescalingV1() prescalingv1.PrescalingV1Interface { 28 | return c.prescalingV1 29 | } 30 | 31 | // Discovery retrieves the DiscoveryClient 32 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 33 | if c == nil { 34 | return nil 35 | } 36 | return c.DiscoveryClient 37 | } 38 | 39 | // NewForConfig creates a new Clientset for the given config. 40 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 41 | // NewForConfig will generate a rate-limiter in configShallowCopy. 42 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 43 | // where httpClient was generated with rest.HTTPClientFor(c). 44 | func NewForConfig(c *rest.Config) (*Clientset, error) { 45 | configShallowCopy := *c 46 | 47 | if configShallowCopy.UserAgent == "" { 48 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() 49 | } 50 | 51 | // share the transport between all clients 52 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return NewForConfigAndClient(&configShallowCopy, httpClient) 58 | } 59 | 60 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 61 | // Note the http client provided takes precedence over the configured transport values. 62 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 63 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 64 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 65 | configShallowCopy := *c 66 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 67 | if configShallowCopy.Burst <= 0 { 68 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 69 | } 70 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 71 | } 72 | 73 | var cs Clientset 74 | var err error 75 | cs.prescalingV1, err = prescalingv1.NewForConfigAndClient(&configShallowCopy, httpClient) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return &cs, nil 85 | } 86 | 87 | // NewForConfigOrDie creates a new Clientset for the given config and 88 | // panics if there is an error in the config. 89 | func NewForConfigOrDie(c *rest.Config) *Clientset { 90 | cs, err := NewForConfig(c) 91 | if err != nil { 92 | panic(err) 93 | } 94 | return cs 95 | } 96 | 97 | // New creates a new Clientset for the given RESTClient. 98 | func New(c rest.Interface) *Clientset { 99 | var cs Clientset 100 | cs.prescalingV1 = prescalingv1.New(c) 101 | 102 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 103 | return &cs 104 | } 105 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated clientset. 4 | package versioned 5 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | clientset "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned" 7 | prescalingv1 "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1" 8 | fakeprescalingv1 "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/fake" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "k8s.io/apimachinery/pkg/watch" 11 | "k8s.io/client-go/discovery" 12 | fakediscovery "k8s.io/client-go/discovery/fake" 13 | "k8s.io/client-go/testing" 14 | ) 15 | 16 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 17 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 18 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 19 | // for a real clientset and is mostly useful in simple unit tests. 20 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 21 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 22 | for _, obj := range objects { 23 | if err := o.Add(obj); err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | cs := &Clientset{tracker: o} 29 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 30 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 31 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 32 | gvr := action.GetResource() 33 | ns := action.GetNamespace() 34 | watch, err := o.Watch(gvr, ns) 35 | if err != nil { 36 | return false, nil, err 37 | } 38 | return true, watch, nil 39 | }) 40 | 41 | return cs 42 | } 43 | 44 | // Clientset implements clientset.Interface. Meant to be embedded into a 45 | // struct to get a default implementation. This makes faking out just the method 46 | // you want to test easier. 47 | type Clientset struct { 48 | testing.Fake 49 | discovery *fakediscovery.FakeDiscovery 50 | tracker testing.ObjectTracker 51 | } 52 | 53 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 54 | return c.discovery 55 | } 56 | 57 | func (c *Clientset) Tracker() testing.ObjectTracker { 58 | return c.tracker 59 | } 60 | 61 | var ( 62 | _ clientset.Interface = &Clientset{} 63 | _ testing.FakeClient = &Clientset{} 64 | ) 65 | 66 | // PrescalingV1 retrieves the PrescalingV1Client 67 | func (c *Clientset) PrescalingV1() prescalingv1.PrescalingV1Interface { 68 | return &fakeprescalingv1.FakePrescalingV1{Fake: &c.Fake} 69 | } 70 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated fake clientset. 4 | package fake 5 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | prescalingv1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | ) 13 | 14 | var scheme = runtime.NewScheme() 15 | var codecs = serializer.NewCodecFactory(scheme) 16 | 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | prescalingv1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package contains the scheme of the automatically generated clientset. 4 | package scheme 5 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package scheme 4 | 5 | import ( 6 | prescalingv1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | ) 13 | 14 | var Scheme = runtime.NewScheme() 15 | var Codecs = serializer.NewCodecFactory(Scheme) 16 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | prescalingv1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(Scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated typed clients. 4 | package v1 5 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // Package fake has the automatically generated clients. 4 | package fake 5 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/fake/fake_prescaling.bedrock.tech_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | v1 "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1" 7 | rest "k8s.io/client-go/rest" 8 | testing "k8s.io/client-go/testing" 9 | ) 10 | 11 | type FakePrescalingV1 struct { 12 | *testing.Fake 13 | } 14 | 15 | func (c *FakePrescalingV1) PrescalingEvents(namespace string) v1.PrescalingEventInterface { 16 | return &FakePrescalingEvents{c, namespace} 17 | } 18 | 19 | // RESTClient returns a RESTClient that is used to communicate 20 | // with API server by this client implementation. 21 | func (c *FakePrescalingV1) RESTClient() rest.Interface { 22 | var ret *rest.RESTClient 23 | return ret 24 | } 25 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/fake/fake_prescalingevent.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | "context" 7 | 8 | prescalingbedrocktechv1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | labels "k8s.io/apimachinery/pkg/labels" 11 | schema "k8s.io/apimachinery/pkg/runtime/schema" 12 | types "k8s.io/apimachinery/pkg/types" 13 | watch "k8s.io/apimachinery/pkg/watch" 14 | testing "k8s.io/client-go/testing" 15 | ) 16 | 17 | // FakePrescalingEvents implements PrescalingEventInterface 18 | type FakePrescalingEvents struct { 19 | Fake *FakePrescalingV1 20 | ns string 21 | } 22 | 23 | var prescalingeventsResource = schema.GroupVersionResource{Group: "prescaling.bedrock.tech", Version: "v1", Resource: "prescalingevents"} 24 | 25 | var prescalingeventsKind = schema.GroupVersionKind{Group: "prescaling.bedrock.tech", Version: "v1", Kind: "PrescalingEvent"} 26 | 27 | // Get takes name of the prescalingEvent, and returns the corresponding prescalingEvent object, and an error if there is any. 28 | func (c *FakePrescalingEvents) Get(ctx context.Context, name string, options v1.GetOptions) (result *prescalingbedrocktechv1.PrescalingEvent, err error) { 29 | obj, err := c.Fake. 30 | Invokes(testing.NewGetAction(prescalingeventsResource, c.ns, name), &prescalingbedrocktechv1.PrescalingEvent{}) 31 | 32 | if obj == nil { 33 | return nil, err 34 | } 35 | return obj.(*prescalingbedrocktechv1.PrescalingEvent), err 36 | } 37 | 38 | // List takes label and field selectors, and returns the list of PrescalingEvents that match those selectors. 39 | func (c *FakePrescalingEvents) List(ctx context.Context, opts v1.ListOptions) (result *prescalingbedrocktechv1.PrescalingEventList, err error) { 40 | obj, err := c.Fake. 41 | Invokes(testing.NewListAction(prescalingeventsResource, prescalingeventsKind, c.ns, opts), &prescalingbedrocktechv1.PrescalingEventList{}) 42 | 43 | if obj == nil { 44 | return nil, err 45 | } 46 | 47 | label, _, _ := testing.ExtractFromListOptions(opts) 48 | if label == nil { 49 | label = labels.Everything() 50 | } 51 | list := &prescalingbedrocktechv1.PrescalingEventList{ListMeta: obj.(*prescalingbedrocktechv1.PrescalingEventList).ListMeta} 52 | for _, item := range obj.(*prescalingbedrocktechv1.PrescalingEventList).Items { 53 | if label.Matches(labels.Set(item.Labels)) { 54 | list.Items = append(list.Items, item) 55 | } 56 | } 57 | return list, err 58 | } 59 | 60 | // Watch returns a watch.Interface that watches the requested prescalingEvents. 61 | func (c *FakePrescalingEvents) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 62 | return c.Fake. 63 | InvokesWatch(testing.NewWatchAction(prescalingeventsResource, c.ns, opts)) 64 | 65 | } 66 | 67 | // Create takes the representation of a prescalingEvent and creates it. Returns the server's representation of the prescalingEvent, and an error, if there is any. 68 | func (c *FakePrescalingEvents) Create(ctx context.Context, prescalingEvent *prescalingbedrocktechv1.PrescalingEvent, opts v1.CreateOptions) (result *prescalingbedrocktechv1.PrescalingEvent, err error) { 69 | obj, err := c.Fake. 70 | Invokes(testing.NewCreateAction(prescalingeventsResource, c.ns, prescalingEvent), &prescalingbedrocktechv1.PrescalingEvent{}) 71 | 72 | if obj == nil { 73 | return nil, err 74 | } 75 | return obj.(*prescalingbedrocktechv1.PrescalingEvent), err 76 | } 77 | 78 | // Update takes the representation of a prescalingEvent and updates it. Returns the server's representation of the prescalingEvent, and an error, if there is any. 79 | func (c *FakePrescalingEvents) Update(ctx context.Context, prescalingEvent *prescalingbedrocktechv1.PrescalingEvent, opts v1.UpdateOptions) (result *prescalingbedrocktechv1.PrescalingEvent, err error) { 80 | obj, err := c.Fake. 81 | Invokes(testing.NewUpdateAction(prescalingeventsResource, c.ns, prescalingEvent), &prescalingbedrocktechv1.PrescalingEvent{}) 82 | 83 | if obj == nil { 84 | return nil, err 85 | } 86 | return obj.(*prescalingbedrocktechv1.PrescalingEvent), err 87 | } 88 | 89 | // Delete takes name of the prescalingEvent and deletes it. Returns an error if one occurs. 90 | func (c *FakePrescalingEvents) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 91 | _, err := c.Fake. 92 | Invokes(testing.NewDeleteActionWithOptions(prescalingeventsResource, c.ns, name, opts), &prescalingbedrocktechv1.PrescalingEvent{}) 93 | 94 | return err 95 | } 96 | 97 | // DeleteCollection deletes a collection of objects. 98 | func (c *FakePrescalingEvents) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 99 | action := testing.NewDeleteCollectionAction(prescalingeventsResource, c.ns, listOpts) 100 | 101 | _, err := c.Fake.Invokes(action, &prescalingbedrocktechv1.PrescalingEventList{}) 102 | return err 103 | } 104 | 105 | // Patch applies the patch and returns the patched prescalingEvent. 106 | func (c *FakePrescalingEvents) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *prescalingbedrocktechv1.PrescalingEvent, err error) { 107 | obj, err := c.Fake. 108 | Invokes(testing.NewPatchSubresourceAction(prescalingeventsResource, c.ns, name, pt, data, subresources...), &prescalingbedrocktechv1.PrescalingEvent{}) 109 | 110 | if obj == nil { 111 | return nil, err 112 | } 113 | return obj.(*prescalingbedrocktechv1.PrescalingEvent), err 114 | } 115 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1 4 | 5 | type PrescalingEventExpansion interface{} 6 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/prescaling.bedrock.tech_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/scheme" 9 | v1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 10 | rest "k8s.io/client-go/rest" 11 | ) 12 | 13 | type PrescalingV1Interface interface { 14 | RESTClient() rest.Interface 15 | PrescalingEventsGetter 16 | } 17 | 18 | // PrescalingV1Client is used to interact with features provided by the prescaling.bedrock.tech group. 19 | type PrescalingV1Client struct { 20 | restClient rest.Interface 21 | } 22 | 23 | func (c *PrescalingV1Client) PrescalingEvents(namespace string) PrescalingEventInterface { 24 | return newPrescalingEvents(c, namespace) 25 | } 26 | 27 | // NewForConfig creates a new PrescalingV1Client for the given config. 28 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 29 | // where httpClient was generated with rest.HTTPClientFor(c). 30 | func NewForConfig(c *rest.Config) (*PrescalingV1Client, error) { 31 | config := *c 32 | if err := setConfigDefaults(&config); err != nil { 33 | return nil, err 34 | } 35 | httpClient, err := rest.HTTPClientFor(&config) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return NewForConfigAndClient(&config, httpClient) 40 | } 41 | 42 | // NewForConfigAndClient creates a new PrescalingV1Client for the given config and http client. 43 | // Note the http client provided takes precedence over the configured transport values. 44 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*PrescalingV1Client, error) { 45 | config := *c 46 | if err := setConfigDefaults(&config); err != nil { 47 | return nil, err 48 | } 49 | client, err := rest.RESTClientForConfigAndClient(&config, h) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &PrescalingV1Client{client}, nil 54 | } 55 | 56 | // NewForConfigOrDie creates a new PrescalingV1Client for the given config and 57 | // panics if there is an error in the config. 58 | func NewForConfigOrDie(c *rest.Config) *PrescalingV1Client { 59 | client, err := NewForConfig(c) 60 | if err != nil { 61 | panic(err) 62 | } 63 | return client 64 | } 65 | 66 | // New creates a new PrescalingV1Client for the given RESTClient. 67 | func New(c rest.Interface) *PrescalingV1Client { 68 | return &PrescalingV1Client{c} 69 | } 70 | 71 | func setConfigDefaults(config *rest.Config) error { 72 | gv := v1.SchemeGroupVersion 73 | config.GroupVersion = &gv 74 | config.APIPath = "/apis" 75 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 76 | 77 | if config.UserAgent == "" { 78 | config.UserAgent = rest.DefaultKubernetesUserAgent() 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // RESTClient returns a RESTClient that is used to communicate 85 | // with API server by this client implementation. 86 | func (c *PrescalingV1Client) RESTClient() rest.Interface { 87 | if c == nil { 88 | return nil 89 | } 90 | return c.restClient 91 | } 92 | -------------------------------------------------------------------------------- /generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/prescalingevent.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1 4 | 5 | import ( 6 | "context" 7 | "time" 8 | 9 | scheme "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/scheme" 10 | v1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | types "k8s.io/apimachinery/pkg/types" 13 | watch "k8s.io/apimachinery/pkg/watch" 14 | rest "k8s.io/client-go/rest" 15 | ) 16 | 17 | // PrescalingEventsGetter has a method to return a PrescalingEventInterface. 18 | // A group's client should implement this interface. 19 | type PrescalingEventsGetter interface { 20 | PrescalingEvents(namespace string) PrescalingEventInterface 21 | } 22 | 23 | // PrescalingEventInterface has methods to work with PrescalingEvent resources. 24 | type PrescalingEventInterface interface { 25 | Create(ctx context.Context, prescalingEvent *v1.PrescalingEvent, opts metav1.CreateOptions) (*v1.PrescalingEvent, error) 26 | Update(ctx context.Context, prescalingEvent *v1.PrescalingEvent, opts metav1.UpdateOptions) (*v1.PrescalingEvent, error) 27 | Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error 28 | DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error 29 | Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.PrescalingEvent, error) 30 | List(ctx context.Context, opts metav1.ListOptions) (*v1.PrescalingEventList, error) 31 | Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) 32 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.PrescalingEvent, err error) 33 | PrescalingEventExpansion 34 | } 35 | 36 | // prescalingEvents implements PrescalingEventInterface 37 | type prescalingEvents struct { 38 | client rest.Interface 39 | ns string 40 | } 41 | 42 | // newPrescalingEvents returns a PrescalingEvents 43 | func newPrescalingEvents(c *PrescalingV1Client, namespace string) *prescalingEvents { 44 | return &prescalingEvents{ 45 | client: c.RESTClient(), 46 | ns: namespace, 47 | } 48 | } 49 | 50 | // Get takes name of the prescalingEvent, and returns the corresponding prescalingEvent object, and an error if there is any. 51 | func (c *prescalingEvents) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.PrescalingEvent, err error) { 52 | result = &v1.PrescalingEvent{} 53 | err = c.client.Get(). 54 | Namespace(c.ns). 55 | Resource("prescalingevents"). 56 | Name(name). 57 | VersionedParams(&options, scheme.ParameterCodec). 58 | Do(ctx). 59 | Into(result) 60 | return 61 | } 62 | 63 | // List takes label and field selectors, and returns the list of PrescalingEvents that match those selectors. 64 | func (c *prescalingEvents) List(ctx context.Context, opts metav1.ListOptions) (result *v1.PrescalingEventList, err error) { 65 | var timeout time.Duration 66 | if opts.TimeoutSeconds != nil { 67 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 68 | } 69 | result = &v1.PrescalingEventList{} 70 | err = c.client.Get(). 71 | Namespace(c.ns). 72 | Resource("prescalingevents"). 73 | VersionedParams(&opts, scheme.ParameterCodec). 74 | Timeout(timeout). 75 | Do(ctx). 76 | Into(result) 77 | return 78 | } 79 | 80 | // Watch returns a watch.Interface that watches the requested prescalingEvents. 81 | func (c *prescalingEvents) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 82 | var timeout time.Duration 83 | if opts.TimeoutSeconds != nil { 84 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 85 | } 86 | opts.Watch = true 87 | return c.client.Get(). 88 | Namespace(c.ns). 89 | Resource("prescalingevents"). 90 | VersionedParams(&opts, scheme.ParameterCodec). 91 | Timeout(timeout). 92 | Watch(ctx) 93 | } 94 | 95 | // Create takes the representation of a prescalingEvent and creates it. Returns the server's representation of the prescalingEvent, and an error, if there is any. 96 | func (c *prescalingEvents) Create(ctx context.Context, prescalingEvent *v1.PrescalingEvent, opts metav1.CreateOptions) (result *v1.PrescalingEvent, err error) { 97 | result = &v1.PrescalingEvent{} 98 | err = c.client.Post(). 99 | Namespace(c.ns). 100 | Resource("prescalingevents"). 101 | VersionedParams(&opts, scheme.ParameterCodec). 102 | Body(prescalingEvent). 103 | Do(ctx). 104 | Into(result) 105 | return 106 | } 107 | 108 | // Update takes the representation of a prescalingEvent and updates it. Returns the server's representation of the prescalingEvent, and an error, if there is any. 109 | func (c *prescalingEvents) Update(ctx context.Context, prescalingEvent *v1.PrescalingEvent, opts metav1.UpdateOptions) (result *v1.PrescalingEvent, err error) { 110 | result = &v1.PrescalingEvent{} 111 | err = c.client.Put(). 112 | Namespace(c.ns). 113 | Resource("prescalingevents"). 114 | Name(prescalingEvent.Name). 115 | VersionedParams(&opts, scheme.ParameterCodec). 116 | Body(prescalingEvent). 117 | Do(ctx). 118 | Into(result) 119 | return 120 | } 121 | 122 | // Delete takes name of the prescalingEvent and deletes it. Returns an error if one occurs. 123 | func (c *prescalingEvents) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 124 | return c.client.Delete(). 125 | Namespace(c.ns). 126 | Resource("prescalingevents"). 127 | Name(name). 128 | Body(&opts). 129 | Do(ctx). 130 | Error() 131 | } 132 | 133 | // DeleteCollection deletes a collection of objects. 134 | func (c *prescalingEvents) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 135 | var timeout time.Duration 136 | if listOpts.TimeoutSeconds != nil { 137 | timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second 138 | } 139 | return c.client.Delete(). 140 | Namespace(c.ns). 141 | Resource("prescalingevents"). 142 | VersionedParams(&listOpts, scheme.ParameterCodec). 143 | Timeout(timeout). 144 | Body(&opts). 145 | Do(ctx). 146 | Error() 147 | } 148 | 149 | // Patch applies the patch and returns the patched prescalingEvent. 150 | func (c *prescalingEvents) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.PrescalingEvent, err error) { 151 | result = &v1.PrescalingEvent{} 152 | err = c.client.Patch(pt). 153 | Namespace(c.ns). 154 | Resource("prescalingevents"). 155 | Name(name). 156 | SubResource(subresources...). 157 | VersionedParams(&opts, scheme.ParameterCodec). 158 | Body(data). 159 | Do(ctx). 160 | Into(result) 161 | return 162 | } 163 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/BedrockStreaming/prescaling-exporter 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.0 7 | github.com/prometheus/client_golang v1.14.0 8 | github.com/sirupsen/logrus v1.9.0 9 | github.com/spf13/cobra v1.6.1 10 | github.com/stretchr/testify v1.8.2 11 | github.com/swaggo/http-swagger v1.3.4 12 | github.com/swaggo/swag v1.16.2 13 | k8s.io/api v0.27.3 14 | k8s.io/apimachinery v0.28.4 15 | k8s.io/client-go v0.27.3 16 | k8s.io/code-generator v0.26.3 17 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 18 | ) 19 | 20 | require ( 21 | github.com/KyleBanks/depth v1.2.1 // indirect 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 24 | github.com/davecgh/go-spew v1.1.1 // indirect 25 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 26 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 27 | github.com/go-logr/logr v1.2.4 // indirect 28 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 29 | github.com/go-openapi/jsonreference v0.20.2 // indirect 30 | github.com/go-openapi/spec v0.20.6 // indirect 31 | github.com/go-openapi/swag v0.22.3 // indirect 32 | github.com/gogo/protobuf v1.3.2 // indirect 33 | github.com/golang/protobuf v1.5.3 // indirect 34 | github.com/google/gnostic v0.7.0 // indirect 35 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect 36 | github.com/google/go-cmp v0.5.9 // indirect 37 | github.com/google/gofuzz v1.2.0 // indirect 38 | github.com/google/uuid v1.3.0 // indirect 39 | github.com/imdario/mergo v0.3.13 // indirect 40 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/mailru/easyjson v0.7.7 // indirect 44 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.2 // indirect 47 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 48 | github.com/pkg/errors v0.9.1 // indirect 49 | github.com/pmezard/go-difflib v1.0.0 // indirect 50 | github.com/prometheus/client_model v0.3.0 // indirect 51 | github.com/prometheus/common v0.37.0 // indirect 52 | github.com/prometheus/procfs v0.8.0 // indirect 53 | github.com/spf13/pflag v1.0.5 // indirect 54 | github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect 55 | golang.org/x/mod v0.10.0 // indirect 56 | golang.org/x/net v0.17.0 // indirect 57 | golang.org/x/oauth2 v0.6.0 // indirect 58 | golang.org/x/sys v0.13.0 // indirect 59 | golang.org/x/term v0.13.0 // indirect 60 | golang.org/x/text v0.13.0 // indirect 61 | golang.org/x/time v0.3.0 // indirect 62 | golang.org/x/tools v0.8.0 // indirect 63 | google.golang.org/appengine v1.6.7 // indirect 64 | google.golang.org/protobuf v1.31.0 // indirect 65 | gopkg.in/inf.v0 v0.9.1 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect 69 | k8s.io/klog/v2 v2.100.1 // indirect 70 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 71 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 72 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 73 | sigs.k8s.io/yaml v1.3.0 // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BedrockStreaming/prescaling-exporter/6486b557cb1ca37579cfabc476f5c9ad36a630b0/hack/boilerplate.go.txt -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "k8s.io/code-generator" 8 | ) 9 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | MODULE_ROOT=$(go list -m) 22 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 23 | CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo k8s.io/code-generator)} 24 | 25 | tempDir="$(mktemp -d)" 26 | cleanup() { 27 | rm -rf "${tempDir}" 28 | } 29 | trap "cleanup" EXIT SIGINT 30 | 31 | mkdir -p "${tempDir}/${MODULE_ROOT}}" 32 | 33 | #go mod vendor 34 | 35 | # generate the code with: 36 | # --output-base because this script should also be able to run inside the vendor dir of 37 | # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir 38 | # instead of the $GOPATH directly. For normal projects this can be dropped. 39 | bash "${CODEGEN_PKG}"/generate-groups.sh client,deepcopy \ 40 | ${MODULE_ROOT}/generated/client ${MODULE_ROOT}/pkg/apis \ 41 | prescaling.bedrock.tech:v1 \ 42 | --output-base "${tempDir}" \ 43 | --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt 44 | 45 | # To use your own boilerplate text append: 46 | # --go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt 47 | 48 | rsync -chavzP "${tempDir}/${MODULE_ROOT}/" "${SCRIPT_ROOT}/" 49 | -------------------------------------------------------------------------------- /hack/verify-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 22 | 23 | DIFFROOT="${SCRIPT_ROOT}/pkg" 24 | TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" 25 | _tmp="${SCRIPT_ROOT}/_tmp" 26 | 27 | cleanup() { 28 | rm -rf "${_tmp}" 29 | } 30 | trap "cleanup" EXIT SIGINT 31 | 32 | cleanup 33 | 34 | mkdir -p "${TMP_DIFFROOT}" 35 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" 36 | 37 | "${SCRIPT_ROOT}/hack/update-codegen.sh" 38 | echo "diffing ${DIFFROOT} against freshly generated codegen" 39 | ret=0 40 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? 41 | cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" 42 | if [[ $ret -eq 0 ]] 43 | then 44 | echo "${DIFFROOT} up to date." 45 | else 46 | echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: prescaling-exporter 3 | description: A Helm chart dedicated to deploy a prometheus exporter to prescale applications in Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.8.1 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "0.8.1" 25 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "prescaling-exporter.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "prescaling-exporter.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "prescaling-exporter.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "prescaling-exporter.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "prescaling-exporter.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "prescaling-exporter.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "prescaling-exporter.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "prescaling-exporter.labels" -}} 37 | helm.sh/chart: {{ include "prescaling-exporter.chart" . }} 38 | {{ include "prescaling-exporter.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "prescaling-exporter.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "prescaling-exporter.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "prescaling-exporter.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "prescaling-exporter.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ include "prescaling-exporter.fullname" . }} 6 | labels: 7 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 8 | rules: 9 | # read hpa 10 | - verbs: 11 | - get 12 | - list 13 | apiGroups: 14 | - autoscaling 15 | resources: 16 | - horizontalpodautoscalers 17 | # CRUD prescaling events 18 | - verbs: 19 | - get 20 | - list 21 | - create 22 | - delete 23 | - update 24 | apiGroups: 25 | - prescaling.bedrock.tech 26 | resources: 27 | - prescalingevents 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ include "prescaling-exporter.fullname" . }} 6 | labels: 7 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: {{ include "prescaling-exporter.fullname" . }} 12 | subjects: 13 | - kind: ServiceAccount 14 | name: {{ include "prescaling-exporter.serviceAccountName" . }} 15 | namespace: {{ .Release.Namespace | quote }} 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/crds/prescalingevent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: prescalingevents.prescaling.bedrock.tech 5 | labels: 6 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 7 | spec: 8 | group: prescaling.bedrock.tech 9 | versions: 10 | - name: v1 11 | served: true 12 | storage: true 13 | schema: 14 | openAPIV3Schema: 15 | type: object 16 | properties: 17 | spec: 18 | type: object 19 | required: ["date", "start_time", "end_time", "multiplier", "description"] 20 | properties: 21 | date: 22 | type: string 23 | start_time: 24 | type: string 25 | end_time: 26 | type: string 27 | multiplier: 28 | type: integer 29 | description: 30 | type: string 31 | scope: Namespaced 32 | names: 33 | plural: prescalingevents 34 | singular: prescalingevent 35 | kind: PrescalingEvent 36 | shortNames: 37 | - pe 38 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/crds/testevents.yaml: -------------------------------------------------------------------------------- 1 | {{/*apiVersion: prescaling.bedrock.tech/v1*/}} 2 | {{/*kind: PrescalingEvent*/}} 3 | {{/*metadata:*/}} 4 | {{/* name: test-event*/}} 5 | {{/*spec:*/}} 6 | {{/* date: "01:01:2022"*/}} 7 | {{/* start_time: "10:00:00"*/}} 8 | {{/* end_time: "23:59:59"*/}} 9 | {{/* multiplier: 2*/}} 10 | {{/* description: "test event"*/}} 11 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "prescaling-exporter.fullname" . }} 5 | labels: 6 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "prescaling-exporter.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "prescaling-exporter.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "prescaling-exporter.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - name: metrics 38 | containerPort: {{ .Values.service.port }} 39 | protocol: TCP 40 | livenessProbe: 41 | httpGet: 42 | path: /status 43 | port: metrics 44 | readinessProbe: 45 | httpGet: 46 | path: /status 47 | port: metrics 48 | env: 49 | - name: PORT 50 | value: {{ .Values.service.port | quote }} 51 | - name: TZ 52 | value: {{ .Values.env.timezone | quote }} 53 | - name: ANNOTATION_START_TIME 54 | value: {{ .Values.env.annotationStartTime | quote }} 55 | - name: ANNOTATION_END_TIME 56 | value: {{ .Values.env.annotationEndTime | quote }} 57 | - name: ANNOTATION_MIN_REPLICAS 58 | value: {{ .Values.env.annotationMinReplicas | quote }} 59 | - name: LABEL_PROJECT 60 | value: {{ .Values.env.labelProject | quote }} 61 | - name: NAMESPACE 62 | valueFrom: 63 | fieldRef: 64 | fieldPath: metadata.namespace 65 | command: 66 | - /ko-app/prescaling-exporter 67 | - serv 68 | resources: 69 | {{- toYaml .Values.resources | nindent 12 }} 70 | {{- with .Values.nodeSelector }} 71 | nodeSelector: 72 | {{- toYaml . | nindent 8 }} 73 | {{- end }} 74 | {{- with .Values.affinity }} 75 | affinity: 76 | {{- toYaml . | nindent 8 }} 77 | {{- end }} 78 | {{- with .Values.tolerations }} 79 | tolerations: 80 | {{- toYaml . | nindent 8 }} 81 | {{- end }} 82 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "prescaling-exporter.fullname" . }} 6 | labels: 7 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "prescaling-exporter.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "prescaling-exporter.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/monitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.prometheus.monitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "prescaling-exporter.fullname" . }} 6 | labels: 7 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 8 | {{- if .Values.prometheus.monitor.additionalLabels }} 9 | {{- toYaml .Values.prometheus.monitor.additionalLabels | nindent 4 }} 10 | {{- end }} 11 | spec: 12 | selector: 13 | matchLabels: 14 | app: {{ template "prescaling-exporter.name" . }} 15 | release: {{ .Release.Name }} 16 | {{- if .Values.service.additionalLabels }} 17 | {{- toYaml .Values.service.additionalLabels | nindent 6 }} 18 | {{- end }} 19 | endpoints: 20 | - port: metrics 21 | {{- if .Values.prometheus.monitor.scrapeTimeout }} 22 | scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} 23 | {{- end }} 24 | {{- if .Values.prometheus.monitor.relabelings }} 25 | relabelings: 26 | {{- toYaml .Values.prometheus.monitor.relabelings | nindent 8 }} 27 | {{- end }} 28 | {{- end }} 29 | 30 | --- 31 | 32 | {{- if .Values.victoriametrics.monitor.enabled }} 33 | apiVersion: operator.victoriametrics.com/v1beta1 34 | kind: VMServiceScrape 35 | metadata: 36 | name: {{ include "prescaling-exporter.fullname" . }} 37 | labels: 38 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 39 | {{- if .Values.victoriametrics.monitor.additionalLabels }} 40 | {{- toYaml .Values.victoriametrics.monitor.additionalLabels | nindent 4 }} 41 | {{- end }} 42 | spec: 43 | endpoints: 44 | - path: /metrics 45 | port: metrics 46 | scrapeTimeout: {{ .Values.victoriametrics.monitor.scrapeTimeout }} 47 | {{- if .Values.victoriametrics.monitor.relabelings }} 48 | relabelConfigs: 49 | {{- toYaml .Values.victoriametrics.monitor.relabelings | nindent 8 }} 50 | {{- end }} 51 | jobLabel: {{ template "prescaling-exporter.fullname" . }} 52 | selector: 53 | matchLabels: 54 | {{- include "prescaling-exporter.selectorLabels" . | nindent 8 }} 55 | {{- if .Values.service.additionalLabels }} 56 | {{- toYaml .Values.service.additionalLabels | nindent 8 }} 57 | {{- end }} 58 | {{- end }} 59 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "prescaling-exporter.fullname" . }} 5 | labels: 6 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 7 | {{- if .Values.service.additionalLabels }} 8 | {{- toYaml .Values.service.additionalLabels | nindent 4 }} 9 | {{- end }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: metrics 15 | protocol: TCP 16 | name: metrics 17 | selector: 18 | {{- include "prescaling-exporter.selectorLabels" . | nindent 4 }} 19 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "prescaling-exporter.serviceAccountName" . }} 6 | labels: 7 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "prescaling-exporter.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "prescaling-exporter.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "prescaling-exporter.fullname" . }}:{{ .Values.service.port }}/status'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /helm/prescaling-exporter/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for prescaling-exporter. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | env: 8 | timezone: Europe/Paris 9 | annotationStartTime: "annotations.scaling.exporter.time.start" 10 | annotationEndTime: "annotations.scaling.exporter.time.end" 11 | annotationMinReplicas: "annotations.scaling.exporter.replica.min" 12 | labelProject: "project" 13 | retentionDays: "10" 14 | 15 | image: 16 | repository: bedrockstreaming/prescaling-exporter 17 | pullPolicy: IfNotPresent 18 | # Overrides the image tag whose default is the chart appVersion. 19 | tag: "" 20 | 21 | imagePullSecrets: [] 22 | nameOverride: "" 23 | fullnameOverride: "" 24 | 25 | serviceAccount: 26 | # Specifies whether a service account should be created 27 | create: true 28 | # Annotations to add to the service account 29 | annotations: {} 30 | # The name of the service account to use. 31 | # If not set and create is true, a name is generated using the fullname template 32 | name: "" 33 | 34 | rbac: 35 | create: true 36 | scope: false 37 | 38 | podAnnotations: {} 39 | 40 | podSecurityContext: {} 41 | # fsGroup: 2000 42 | 43 | securityContext: {} 44 | # capabilities: 45 | # drop: 46 | # - ALL 47 | # readOnlyRootFilesystem: true 48 | # runAsNonRoot: true 49 | # runAsUser: 1000 50 | 51 | service: 52 | type: ClusterIP 53 | port: 9101 54 | additionalLabels: {} 55 | 56 | ingress: 57 | enabled: false 58 | className: "" 59 | annotations: {} 60 | # kubernetes.io/ingress.class: nginx 61 | # kubernetes.io/tls-acme: "true" 62 | hosts: 63 | - host: prescaling-exporter.local 64 | paths: 65 | - path: / 66 | pathType: ImplementationSpecific 67 | tls: [] 68 | # - secretName: chart-example-tls 69 | # hosts: 70 | # - chart-example.local 71 | 72 | resources: 73 | limits: 74 | cpu: 250m 75 | memory: 128Mi 76 | requests: 77 | cpu: 250m 78 | memory: 128Mi 79 | 80 | 81 | # Configure Prometheus service scrapping with 82 | prometheus: 83 | monitor: 84 | enabled: false 85 | additionalLabels: {} 86 | relabelings: [] 87 | scrapeTimeout: 10s 88 | 89 | # Configure VictoiraMetrics service scrapping with 90 | victoriametrics: 91 | monitor: 92 | enabled: false 93 | additionalLabels: {} 94 | relabelings: [] 95 | scrapeTimeout: 10s 96 | 97 | autoscaling: 98 | enabled: false 99 | minReplicas: 1 100 | maxReplicas: 2 101 | targetCPUUtilizationPercentage: 80 102 | targetMemoryUtilizationPercentage: 80 103 | 104 | nodeSelector: {} 105 | 106 | tolerations: [] 107 | 108 | affinity: {} 109 | -------------------------------------------------------------------------------- /helm/test-values.yml: -------------------------------------------------------------------------------- 1 | # Configure Prometheus service scrapping with 2 | prometheus: 3 | monitor: 4 | enabled: false 5 | additionalLabels: {} 6 | relabelings: 7 | - action: replace 8 | regex: (.*) 9 | replacement: $1 10 | sourceLabels: 11 | - __meta_kubernetes_pod_name 12 | targetLabel: kubernetes_pod_name 13 | - action: replace 14 | regex: (.*) 15 | replacement: $1 16 | sourceLabels: 17 | - __meta_kubernetes_namespace 18 | targetLabel: kubernetes_namespace 19 | - action: labelmap 20 | regex: __meta_kubernetes_pod_label_(.+) 21 | - action: labeldrop 22 | regex: (endpoint|pod_template_hash|pod_template_generation|controller_revision_hash) 23 | scrapeTimeout: 10s 24 | 25 | # Configure VictoiraMetrics service scrapping with 26 | victoriametrics: 27 | monitor: 28 | enabled: true 29 | additionalLabels: {} 30 | relabelings: 31 | - action: replace 32 | regex: (.*) 33 | replacement: $1 34 | sourceLabels: 35 | - __meta_kubernetes_pod_name 36 | targetLabel: kubernetes_pod_name 37 | - action: replace 38 | regex: (.*) 39 | replacement: $1 40 | sourceLabels: 41 | - __meta_kubernetes_namespace 42 | targetLabel: kubernetes_namespace 43 | - action: labelmap 44 | regex: __meta_kubernetes_pod_label_(.+) 45 | - action: labeldrop 46 | regex: (endpoint|pod_template_hash|pod_template_generation|controller_revision_hash) 47 | scrapeTimeout: 10s 48 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/BedrockStreaming/prescaling-exporter/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/apis/prescaling.bedrock.tech/v1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package 2 | // +k8s:defaulter-gen=TypeMeta 3 | // +groupName=prescaling.bedrock.tech 4 | 5 | package v1 6 | -------------------------------------------------------------------------------- /pkg/apis/prescaling.bedrock.tech/v1/register.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | const GroupName = "prescaling.bedrock.tech" 10 | const GroupVersion = "v1" 11 | 12 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} 13 | 14 | var ( 15 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 16 | AddToScheme = SchemeBuilder.AddToScheme 17 | ) 18 | 19 | func addKnownTypes(scheme *runtime.Scheme) error { 20 | scheme.AddKnownTypes(SchemeGroupVersion, 21 | &PrescalingEvent{}, 22 | &PrescalingEventList{}, 23 | ) 24 | 25 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/apis/prescaling.bedrock.tech/v1/types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | // +genclient 6 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 7 | 8 | type PrescalingEvent struct { 9 | metav1.TypeMeta `json:",inline"` 10 | metav1.ObjectMeta `json:"metadata,omitempty"` 11 | 12 | Spec PrescalingEventSpec `json:"spec"` 13 | } 14 | 15 | type PrescalingEventSpec struct { 16 | Date string `json:"date" example:"2022-05-25"` 17 | StartTime string `json:"start_time" example:"20:00:00"` 18 | EndTime string `json:"end_time" example:"23:59:59"` 19 | Multiplier int `json:"multiplier" example:"2"` 20 | Description string `json:"description" example:"a good description"` 21 | } 22 | 23 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 24 | 25 | type PrescalingEventList struct { 26 | metav1.TypeMeta `json:",inline"` 27 | metav1.ListMeta `json:"metadata,omitempty"` 28 | 29 | Items []PrescalingEvent `json:"items"` 30 | } 31 | -------------------------------------------------------------------------------- /pkg/apis/prescaling.bedrock.tech/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Code generated by deepcopy-gen. DO NOT EDIT. 5 | 6 | package v1 7 | 8 | import ( 9 | runtime "k8s.io/apimachinery/pkg/runtime" 10 | ) 11 | 12 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 13 | func (in *PrescalingEvent) DeepCopyInto(out *PrescalingEvent) { 14 | *out = *in 15 | out.TypeMeta = in.TypeMeta 16 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 17 | out.Spec = in.Spec 18 | return 19 | } 20 | 21 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrescalingEvent. 22 | func (in *PrescalingEvent) DeepCopy() *PrescalingEvent { 23 | if in == nil { 24 | return nil 25 | } 26 | out := new(PrescalingEvent) 27 | in.DeepCopyInto(out) 28 | return out 29 | } 30 | 31 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 32 | func (in *PrescalingEvent) DeepCopyObject() runtime.Object { 33 | if c := in.DeepCopy(); c != nil { 34 | return c 35 | } 36 | return nil 37 | } 38 | 39 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 40 | func (in *PrescalingEventList) DeepCopyInto(out *PrescalingEventList) { 41 | *out = *in 42 | out.TypeMeta = in.TypeMeta 43 | in.ListMeta.DeepCopyInto(&out.ListMeta) 44 | if in.Items != nil { 45 | in, out := &in.Items, &out.Items 46 | *out = make([]PrescalingEvent, len(*in)) 47 | for i := range *in { 48 | (*in)[i].DeepCopyInto(&(*out)[i]) 49 | } 50 | } 51 | return 52 | } 53 | 54 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrescalingEventList. 55 | func (in *PrescalingEventList) DeepCopy() *PrescalingEventList { 56 | if in == nil { 57 | return nil 58 | } 59 | out := new(PrescalingEventList) 60 | in.DeepCopyInto(out) 61 | return out 62 | } 63 | 64 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 65 | func (in *PrescalingEventList) DeepCopyObject() runtime.Object { 66 | if c := in.DeepCopy(); c != nil { 67 | return c 68 | } 69 | return nil 70 | } 71 | 72 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 73 | func (in *PrescalingEventSpec) DeepCopyInto(out *PrescalingEventSpec) { 74 | *out = *in 75 | return 76 | } 77 | 78 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrescalingEventSpec. 79 | func (in *PrescalingEventSpec) DeepCopy() *PrescalingEventSpec { 80 | if in == nil { 81 | return nil 82 | } 83 | out := new(PrescalingEventSpec) 84 | in.DeepCopyInto(out) 85 | return out 86 | } 87 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type Struct struct { 8 | Namespace string 9 | Port string 10 | AnnotationEndTime string 11 | AnnotationStartTime string 12 | AnnotationMinReplicas string 13 | LabelProject string 14 | } 15 | 16 | var Config = Struct{ 17 | Namespace: "prescaling-exporter", 18 | Port: "9101", 19 | AnnotationEndTime: "annotations.scaling.exporter.time.end", 20 | AnnotationStartTime: "annotations.scaling.exporter.time.start", 21 | AnnotationMinReplicas: "annotations.scaling.exporter.replica.min", 22 | LabelProject: "project", 23 | } 24 | 25 | func init() { 26 | if os.Getenv("NAMESPACE") != "" { 27 | Config.Namespace = os.Getenv("NAMESPACE") 28 | } 29 | if os.Getenv("PORT") != "" { 30 | Config.Port = os.Getenv("PORT") 31 | } 32 | if os.Getenv("ANNOTATION_END_TIME") != "" { 33 | Config.AnnotationEndTime = os.Getenv("ANNOTATION_END_TIME") 34 | } 35 | if os.Getenv("ANNOTATION_START_TIME") != "" { 36 | Config.AnnotationStartTime = os.Getenv("ANNOTATION_START_TIME") 37 | } 38 | if os.Getenv("ANNOTATION_MIN_REPLICAS") != "" { 39 | Config.AnnotationMinReplicas = os.Getenv("ANNOTATION_MIN_REPLICAS") 40 | } 41 | if os.Getenv("LABEL_PROJECT") != "" { 42 | Config.LabelProject = os.Getenv("LABEL_PROJECT") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDefaultConfigValues(t *testing.T) { 10 | 11 | assert.Equal(t, Config.Port, "9101") 12 | assert.Equal(t, Config.AnnotationEndTime, "annotations.scaling.exporter.time.end") 13 | assert.Equal(t, Config.AnnotationStartTime, "annotations.scaling.exporter.time.start") 14 | assert.Equal(t, Config.LabelProject, "project") 15 | } 16 | -------------------------------------------------------------------------------- /pkg/exporter/exporter.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | log "github.com/sirupsen/logrus" 6 | 7 | "github.com/BedrockStreaming/prescaling-exporter/pkg/prescaling" 8 | "github.com/BedrockStreaming/prescaling-exporter/pkg/utils" 9 | ) 10 | 11 | type prescalingCollector struct { 12 | prescaleMetrics *prometheus.Desc 13 | minMetrics *prometheus.Desc 14 | multiplierMetrics *prometheus.Desc 15 | prescaling prescaling.IPrescaling 16 | } 17 | 18 | func NewPrescalingCollector(p prescaling.IPrescaling) prometheus.Collector { 19 | return &prescalingCollector{ 20 | prescaleMetrics: prometheus.NewDesc( 21 | "prescaling_metric", 22 | "Number used for prescale application", 23 | []string{"project", "deployment", "namespace"}, 24 | nil, 25 | ), minMetrics: prometheus.NewDesc( 26 | "prescaling_min_replica", 27 | "Number of pod desired for prescale", 28 | []string{"project", "deployment", "namespace"}, 29 | nil, 30 | ), multiplierMetrics: prometheus.NewDesc( 31 | "prescaling_multiplier", 32 | "Multiplying factor of min replica", 33 | []string{}, 34 | nil, 35 | ), 36 | prescaling: p, 37 | } 38 | } 39 | 40 | func (collector *prescalingCollector) Describe(ch chan<- *prometheus.Desc) { 41 | ch <- collector.prescaleMetrics 42 | ch <- collector.minMetrics 43 | ch <- collector.multiplierMetrics 44 | } 45 | 46 | func (collector *prescalingCollector) Collect(ch chan<- prometheus.Metric) { 47 | var multiplier int = 1 48 | log.Info("Collect") 49 | hpaList := collector.prescaling.GetHpa() 50 | if len(hpaList) == 0 { 51 | log.Error("error - no prescaling hpa configuration found") 52 | return 53 | } 54 | 55 | currentPrescalingEvent, err := collector.prescaling.GetEventService().Current() 56 | 57 | now := collector.prescaling.GetEventService().GetClock().Now() 58 | for _, hpa := range hpaList { 59 | if err == nil && currentPrescalingEvent.StartTime != "" && currentPrescalingEvent.EndTime != "" { 60 | hpa.Start, _ = utils.SetTime(currentPrescalingEvent.StartTime, now) 61 | hpa.End, _ = utils.SetTime(currentPrescalingEvent.EndTime, now) 62 | multiplier = currentPrescalingEvent.Multiplier 63 | } 64 | 65 | collector.addHpaDataToMetrics(ch, multiplier, hpa) 66 | } 67 | 68 | collector.addDataToMetrics(ch, multiplier) 69 | 70 | } 71 | 72 | func (collector *prescalingCollector) addHpaDataToMetrics(ch chan<- prometheus.Metric, multiplier int, hpa prescaling.Hpa) { 73 | eventInRangeTime := utils.InRangeTime(hpa.Start, hpa.End, collector.prescaling.GetEventService().GetClock().Now()) 74 | desiredScalingType := prescaling.DesiredScaling(eventInRangeTime, multiplier, hpa.Replica, hpa.CurrentReplicas) 75 | prescaleMetric := prometheus.MustNewConstMetric(collector.prescaleMetrics, prometheus.GaugeValue, float64(desiredScalingType), hpa.Project, hpa.Deployment, hpa.Namespace) 76 | minMetric := prometheus.MustNewConstMetric(collector.minMetrics, prometheus.GaugeValue, float64(hpa.Replica), hpa.Project, hpa.Deployment, hpa.Namespace) 77 | ch <- prescaleMetric 78 | ch <- minMetric 79 | } 80 | 81 | func (collector *prescalingCollector) addDataToMetrics(ch chan<- prometheus.Metric, multiplier int) { 82 | multiplierMetric := prometheus.MustNewConstMetric(collector.multiplierMetrics, prometheus.GaugeValue, float64(multiplier)) 83 | ch <- multiplierMetric 84 | } 85 | -------------------------------------------------------------------------------- /pkg/handlers/event_handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | log "github.com/sirupsen/logrus" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | 12 | v1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 13 | "github.com/BedrockStreaming/prescaling-exporter/pkg/services" 14 | "github.com/BedrockStreaming/prescaling-exporter/pkg/utils" 15 | ) 16 | 17 | type CreateDTO struct { 18 | Name string `json:"name" example:"prescaling-event-1"` 19 | v1.PrescalingEventSpec 20 | } 21 | 22 | type UpdateDTO struct { 23 | Name string `json:"name" example:"prescaling-event-1"` 24 | v1.PrescalingEventSpec 25 | } 26 | 27 | type IEventHandlers interface { 28 | Create(w http.ResponseWriter, r *http.Request) 29 | Delete(w http.ResponseWriter, r *http.Request) 30 | List(w http.ResponseWriter, r *http.Request) 31 | Get(w http.ResponseWriter, r *http.Request) 32 | Update(w http.ResponseWriter, r *http.Request) 33 | Current(w http.ResponseWriter, r *http.Request) 34 | } 35 | 36 | type EventHandlers struct { 37 | eventService services.IPrescalingEventService 38 | } 39 | 40 | func NewEventHandlers(userService services.IPrescalingEventService) IEventHandlers { 41 | return &EventHandlers{ 42 | eventService: userService, 43 | } 44 | } 45 | 46 | // Create 47 | // @Summary Create a prescaling Event 48 | // @Description Create a prescaling Event 49 | // @Tags prescalingevent 50 | // @Accept json 51 | // @Produce json 52 | // @Param data body CreateDTO true "The Request body" 53 | // @Success 200 {object} services.PrescalingEventOutput 54 | // @Failure 500 {object} string 55 | // @Router /api/v1/events/ [post] 56 | func (e *EventHandlers) Create(w http.ResponseWriter, r *http.Request) { 57 | var query CreateDTO 58 | err := json.NewDecoder(r.Body).Decode(&query) 59 | if err != nil { 60 | http.Error(w, err.Error(), http.StatusBadRequest) 61 | return 62 | } 63 | 64 | prescalingevent := &v1.PrescalingEvent{ 65 | TypeMeta: metav1.TypeMeta{ 66 | Kind: "PrescalingEventOutput", 67 | APIVersion: "v1", 68 | }, 69 | ObjectMeta: metav1.ObjectMeta{ 70 | Name: query.Name, 71 | }, 72 | Spec: v1.PrescalingEventSpec{ 73 | Date: query.Date, 74 | StartTime: query.StartTime, 75 | EndTime: query.EndTime, 76 | Multiplier: query.Multiplier, 77 | Description: query.Description, 78 | }, 79 | } 80 | prescalingEventCreate, err := e.eventService.Create(prescalingevent) 81 | if err != nil { 82 | http.Error(w, err.Error(), http.StatusBadRequest) 83 | return 84 | } 85 | log.Info("New event created: ", prescalingEventCreate) 86 | utils.WriteResponse(w, http.StatusCreated, prescalingEventCreate) 87 | } 88 | 89 | // Delete 90 | // @Summary Delete a prescaling Event by name 91 | // @Description Delete a prescaling Event by name 92 | // @Tags prescalingevent 93 | // @Accept json 94 | // @Produce json 95 | // @Param name path string true "event-name-1" 96 | // @Success 200 {object} nil 97 | // @Failure 404 {object} string 98 | // @Failure 400 {object} string 99 | // @Router /api/v1/events/{name} [delete] 100 | func (e *EventHandlers) Delete(w http.ResponseWriter, r *http.Request) { 101 | var prescalingEventName = mux.Vars(r)["name"] 102 | 103 | err := e.eventService.Delete(prescalingEventName) 104 | if err != nil { 105 | if err.(*errors.StatusError).ErrStatus.Reason == metav1.StatusReasonNotFound { 106 | log.Info(err) 107 | http.Error(w, err.Error(), http.StatusNoContent) 108 | return 109 | } 110 | log.Error(err) 111 | http.Error(w, err.Error(), http.StatusBadRequest) 112 | return 113 | } 114 | log.Infof("event %s has been deleted", prescalingEventName) 115 | utils.WriteResponse(w, http.StatusOK, "OK") 116 | } 117 | 118 | // List 119 | // @Summary List all prescaling Events 120 | // @Description List all prescaling Events 121 | // @Tags prescalingevent 122 | // @Accept json 123 | // @Produce json 124 | // @Success 200 {object} services.PrescalingEventListOutput 125 | // @Failure 400 {object} string 126 | // @Router /api/v1/events/ [get] 127 | func (e *EventHandlers) List(w http.ResponseWriter, r *http.Request) { 128 | prescalingEvents, err := e.eventService.List() 129 | if err != nil { 130 | log.Error(err) 131 | http.Error(w, err.Error(), http.StatusBadRequest) 132 | } 133 | 134 | utils.WriteResponse(w, http.StatusOK, prescalingEvents) 135 | } 136 | 137 | // Get 138 | // @Summary Get a prescaling Events by name 139 | // @Description Get a prescaling Events by name 140 | // @Tags prescalingevent 141 | // @Accept json 142 | // @Produce json 143 | // @Param name path string true "event-name-1" 144 | // @Success 200 {object} services.PrescalingEventOutput 145 | // @Failure 404 {object} string 146 | // @Router /api/v1/events/{name} [get] 147 | func (e *EventHandlers) Get(w http.ResponseWriter, r *http.Request) { 148 | var prescalingEventName = mux.Vars(r)["name"] 149 | 150 | event, err := e.eventService.Get(prescalingEventName) 151 | if err != nil { 152 | log.Error(err) 153 | http.Error(w, err.Error(), http.StatusNotFound) 154 | return 155 | } 156 | 157 | utils.WriteResponse(w, http.StatusOK, event) 158 | } 159 | 160 | // Update 161 | // @Summary Update a prescaling Event by name 162 | // @Description Update a prescaling Event by name 163 | // @Tags prescalingevent 164 | // @Accept json 165 | // @Produce json 166 | // @Param data body UpdateDTO true "The Request body" 167 | // @Param name path string true "event-name-1" 168 | // @Success 200 {object} services.PrescalingEventOutput 169 | // @Failure 400 {object} string 170 | // @Failure 404 {object} string 171 | // @Router /api/v1/events/{name} [put] 172 | func (e *EventHandlers) Update(w http.ResponseWriter, r *http.Request) { 173 | var query UpdateDTO 174 | 175 | err := json.NewDecoder(r.Body).Decode(&query) 176 | if err != nil { 177 | http.Error(w, err.Error(), http.StatusNotFound) 178 | return 179 | } 180 | 181 | prescalingevent := &v1.PrescalingEvent{ 182 | TypeMeta: metav1.TypeMeta{ 183 | Kind: "PrescalingEventOutput", 184 | APIVersion: "v1", 185 | }, 186 | ObjectMeta: metav1.ObjectMeta{ 187 | Name: query.Name, 188 | }, 189 | Spec: v1.PrescalingEventSpec{ 190 | Date: query.Date, 191 | StartTime: query.StartTime, 192 | EndTime: query.EndTime, 193 | Multiplier: query.Multiplier, 194 | Description: query.Description, 195 | }, 196 | } 197 | prescalingEventUpdated, err := e.eventService.Update(prescalingevent) 198 | if err != nil { 199 | http.Error(w, err.Error(), http.StatusBadRequest) 200 | return 201 | } 202 | log.Infof("Event %s updated", prescalingEventUpdated.Name) 203 | 204 | utils.WriteResponse(w, http.StatusOK, prescalingEventUpdated) 205 | } 206 | 207 | // Current 208 | // @Summary Get current prescaling Event 209 | // @Description Get current prescaling Event 210 | // @Tags prescalingevent 211 | // @Accept json 212 | // @Produce json 213 | // @Success 200 {object} services.PrescalingEventOutput 214 | // @Failure 204 {object} string 215 | // @Router /api/v1/events/current/ [get] 216 | func (e *EventHandlers) Current(w http.ResponseWriter, r *http.Request) { 217 | currentPrescalingEvent, err := e.eventService.Current() 218 | if err != nil { 219 | http.Error(w, err.Error(), http.StatusNoContent) 220 | return 221 | } 222 | 223 | utils.WriteResponse(w, http.StatusOK, currentPrescalingEvent) 224 | } 225 | -------------------------------------------------------------------------------- /pkg/handlers/status_handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type IStatusHandlers interface { 9 | Index(w http.ResponseWriter, r *http.Request) 10 | } 11 | 12 | type StatusHandlers struct{} 13 | 14 | func NewStatusHandlers() IStatusHandlers { 15 | return &StatusHandlers{} 16 | } 17 | 18 | func (s StatusHandlers) Index(w http.ResponseWriter, r *http.Request) { 19 | fmt.Fprint(w, "OK") 20 | } 21 | -------------------------------------------------------------------------------- /pkg/k8s/client.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "os" 5 | 6 | "k8s.io/client-go/kubernetes" 7 | "k8s.io/client-go/rest" 8 | "k8s.io/client-go/tools/clientcmd" 9 | ) 10 | 11 | type Client struct { 12 | Clientset kubernetes.Interface 13 | Config *rest.Config 14 | } 15 | 16 | func NewClient() (*Client, error) { 17 | var config *rest.Config 18 | var err error 19 | 20 | if os.Getenv("KUBECONFIG") != "" { 21 | pathOptions := clientcmd.NewDefaultPathOptions() 22 | pathOptions.LoadingRules.DoNotResolvePaths = false 23 | c, err := pathOptions.GetStartingConfig() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | configOverrides := clientcmd.ConfigOverrides{} 29 | clientConfig := clientcmd.NewDefaultClientConfig(*c, &configOverrides) 30 | config, err = clientConfig.ClientConfig() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | } else { 36 | config, err = rest.InClusterConfig() 37 | } 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | clientset, err := kubernetes.NewForConfig(config) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return &Client{clientset, config}, nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/prescaling/desired_scaling.go: -------------------------------------------------------------------------------- 1 | package prescaling 2 | 3 | const ( 4 | UPSCALE int = 11 5 | NODOWNSCALE int = 10 6 | DOWNSCALE int = 9 7 | NOSCALE int = 0 8 | ) 9 | 10 | func DesiredScaling(eventInRangeTime bool, multiplier int, minReplica int, currentReplica int32) int { 11 | if !eventInRangeTime { 12 | return NOSCALE 13 | } 14 | 15 | if multiplier == 0 { 16 | multiplier = 1 17 | } 18 | 19 | desiredReplica := multiplier * minReplica 20 | 21 | if desiredReplica > int(currentReplica) { 22 | return UPSCALE 23 | } 24 | 25 | if desiredReplica == int(currentReplica) { 26 | return NODOWNSCALE 27 | } 28 | 29 | threshold := ((float64(currentReplica) - float64(desiredReplica)) / float64(desiredReplica)) * float64(100) 30 | if threshold > float64(10) { 31 | return DOWNSCALE 32 | } 33 | 34 | return NODOWNSCALE 35 | } 36 | -------------------------------------------------------------------------------- /pkg/prescaling/desired_scaling_test.go: -------------------------------------------------------------------------------- 1 | package prescaling 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDesiredScaling(t *testing.T) { 10 | 11 | testCases := []struct { 12 | b bool 13 | multiplier int 14 | desiredReplica int 15 | expected int 16 | currentReplica int32 17 | name string 18 | }{ 19 | { 20 | name: "Test - In Time Desired > Current", 21 | b: true, 22 | desiredReplica: 20, 23 | currentReplica: 10, 24 | multiplier: 1, 25 | expected: 11, 26 | }, 27 | { 28 | name: "Test - In Time and Multplier 0", 29 | b: true, 30 | desiredReplica: 20, 31 | currentReplica: 10, 32 | multiplier: 0, 33 | expected: 11, 34 | }, 35 | { 36 | name: "Test - Out Time and Multplier 0", 37 | b: false, 38 | desiredReplica: 20, 39 | currentReplica: 10, 40 | multiplier: 0, 41 | expected: 0, 42 | }, 43 | { 44 | name: "Test - Desired equal Current but multiplier 2", 45 | b: true, 46 | desiredReplica: 10, 47 | currentReplica: 10, 48 | multiplier: 2, 49 | expected: 11, 50 | }, 51 | { 52 | name: "Test - Desired equal Current", 53 | b: true, 54 | desiredReplica: 10, 55 | currentReplica: 10, 56 | multiplier: 0, 57 | expected: 10, 58 | }, 59 | { 60 | name: "Test - Current > Desired", 61 | b: true, 62 | desiredReplica: 10, 63 | currentReplica: 20, 64 | multiplier: 0, 65 | expected: 9, 66 | }, 67 | { 68 | name: "Test - Current > Desired with multiplier", 69 | b: true, 70 | desiredReplica: 10, 71 | currentReplica: 30, 72 | multiplier: 2, 73 | expected: 9, 74 | }, 75 | { 76 | name: "Test - Current > Desired but less 10% ", 77 | b: true, 78 | desiredReplica: 50, 79 | currentReplica: 51, 80 | multiplier: 0, 81 | expected: 10, 82 | }, 83 | } 84 | 85 | for _, testCase := range testCases { 86 | e := DesiredScaling(testCase.b, testCase.multiplier, testCase.desiredReplica, testCase.currentReplica) 87 | assert.Equal(t, e, testCase.expected, testCase.name) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /pkg/prescaling/get_hpa.go: -------------------------------------------------------------------------------- 1 | package prescaling 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | "github.com/BedrockStreaming/prescaling-exporter/pkg/config" 10 | "github.com/BedrockStreaming/prescaling-exporter/pkg/utils" 11 | 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | func (p Prescaling) GetHpa() []Hpa { 16 | hpaList, err := p.client.Clientset.AutoscalingV2(). 17 | HorizontalPodAutoscalers(""). 18 | List(context.Background(), metav1.ListOptions{}) 19 | 20 | if err != nil { // todo: retry ? 21 | panic(err.Error()) 22 | } 23 | 24 | var preScalingList []Hpa 25 | 26 | now := p.prescalingEventService.GetClock().Now() 27 | 28 | for _, hpa := range hpaList.Items { 29 | if hpa.ObjectMeta.Annotations[config.Config.AnnotationMinReplicas] != "" { 30 | replica, err := strconv.Atoi(hpa.ObjectMeta.Annotations[config.Config.AnnotationMinReplicas]) 31 | if err != nil { 32 | log.Infof("error - cannot convert string to int %s", err) 33 | } 34 | 35 | preScaling := Hpa{ 36 | Replica: replica, 37 | CurrentReplicas: hpa.Status.CurrentReplicas, 38 | Project: hpa.ObjectMeta.Labels[config.Config.LabelProject], 39 | Namespace: hpa.ObjectMeta.Namespace, 40 | Deployment: hpa.Spec.ScaleTargetRef.Name, 41 | } 42 | 43 | preScaling.Start, err = utils.SetTime(hpa.ObjectMeta.Annotations[config.Config.AnnotationStartTime], now) 44 | if err != nil { 45 | log.Infof("error - %s", err) 46 | } 47 | 48 | preScaling.End, err = utils.SetTime(hpa.ObjectMeta.Annotations[config.Config.AnnotationEndTime], now) 49 | if err != nil { 50 | log.Infof("error - %s", err) 51 | } 52 | 53 | err = preScaling.Validate() 54 | if err != nil { 55 | log.Infof("error - %s", err) 56 | } else { 57 | preScalingList = append(preScalingList, preScaling) 58 | } 59 | } 60 | } 61 | 62 | return preScalingList 63 | } 64 | -------------------------------------------------------------------------------- /pkg/prescaling/get_hpa_test.go: -------------------------------------------------------------------------------- 1 | package prescaling 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | clock "k8s.io/utils/clock/testing" 8 | 9 | "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/fake" 10 | "github.com/BedrockStreaming/prescaling-exporter/pkg/k8s" 11 | "github.com/BedrockStreaming/prescaling-exporter/pkg/services" 12 | 13 | "github.com/stretchr/testify/assert" 14 | "k8s.io/api/autoscaling/v2" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | testclientk8s "k8s.io/client-go/kubernetes/fake" 17 | ) 18 | 19 | var loc = time.Now().Local().Location() 20 | 21 | func TestGetHpa(t *testing.T) { 22 | fakeClock := clock.NewFakeClock(time.Date(2022, time.March, 2, 21, 0, 0, 0, loc)) 23 | 24 | var client k8s.Client 25 | client.Clientset = testclientk8s.NewSimpleClientset( 26 | &v2.HorizontalPodAutoscaler{ 27 | ObjectMeta: metav1.ObjectMeta{ 28 | Name: "project-a", 29 | Namespace: "default", 30 | Annotations: map[string]string{ 31 | "annotations.scaling.exporter.replica.min": "10", 32 | "annotations.scaling.exporter.time.start": "20:00:00", 33 | "annotations.scaling.exporter.time.end": "23:00:00", 34 | }, 35 | Labels: map[string]string{"project": "project-a"}, 36 | }, 37 | Spec: v2.HorizontalPodAutoscalerSpec{ 38 | ScaleTargetRef: v2.CrossVersionObjectReference{ 39 | Name: "project-a", 40 | }, 41 | }, 42 | Status: v2.HorizontalPodAutoscalerStatus{ 43 | CurrentReplicas: 2, 44 | }, 45 | }, 46 | &v2.HorizontalPodAutoscaler{ 47 | ObjectMeta: metav1.ObjectMeta{ 48 | Name: "project-b", 49 | Namespace: "default", 50 | Annotations: map[string]string{ 51 | "annotations.scaling.exporter.replica.min": "20", 52 | "annotations.scaling.exporter.time.start": "18:00:00", 53 | "annotations.scaling.exporter.time.end": "23:30:00", 54 | }, 55 | Labels: map[string]string{"project": "project-b"}, 56 | }, 57 | Spec: v2.HorizontalPodAutoscalerSpec{ 58 | ScaleTargetRef: v2.CrossVersionObjectReference{ 59 | Name: "project-b", 60 | }, 61 | }, 62 | Status: v2.HorizontalPodAutoscalerStatus{ 63 | CurrentReplicas: 2, 64 | }, 65 | }, 66 | ) 67 | 68 | prescalingClient := fake.NewSimpleClientset() 69 | prescalingEvents := prescalingClient.PrescalingV1().PrescalingEvents("default") 70 | prescalingEventService := services.NewEventService(prescalingEvents, fakeClock) 71 | prescaling := NewPrescaling(&client, prescalingEventService) 72 | 73 | expected := []Hpa{ 74 | { 75 | Replica: 10, 76 | CurrentReplicas: 2, 77 | Project: "project-a", 78 | Namespace: "default", 79 | Deployment: "project-a", 80 | Start: time.Date(2022, time.March, 2, 20, 00, 0, 0, loc), 81 | End: time.Date(2022, time.March, 2, 23, 00, 0, 0, loc), 82 | }, 83 | { 84 | Replica: 20, 85 | CurrentReplicas: 2, 86 | Project: "project-b", 87 | Namespace: "default", 88 | Deployment: "project-b", 89 | Start: time.Date(2022, time.March, 2, 18, 00, 0, 0, loc), 90 | End: time.Date(2022, time.March, 2, 23, 30, 0, 0, loc), 91 | }, 92 | } 93 | 94 | hpaList := prescaling.GetHpa() 95 | assert.Equal(t, hpaList, expected, "KO - result is not equal to input") 96 | } 97 | 98 | func TestGetHpaError(t *testing.T) { 99 | fakeClock := clock.NewFakeClock(time.Date(2022, time.March, 2, 21, 0, 0, 0, loc)) 100 | 101 | var client k8s.Client 102 | client.Clientset = testclientk8s.NewSimpleClientset( 103 | &v2.HorizontalPodAutoscaler{ 104 | ObjectMeta: metav1.ObjectMeta{ 105 | Name: "project-replica-nil", 106 | Namespace: "default", 107 | Annotations: map[string]string{ 108 | "annotations.scaling.exporter.replica.min": "0", 109 | "annotations.scaling.exporter.time.start": "20:00:00", 110 | "annotations.scaling.exporter.time.end": "23:00:00", 111 | }, 112 | Labels: map[string]string{"project": "project-replica-nil"}, 113 | }, 114 | Spec: v2.HorizontalPodAutoscalerSpec{ 115 | ScaleTargetRef: v2.CrossVersionObjectReference{ 116 | Name: "project-replica-nil", 117 | }, 118 | }, 119 | Status: v2.HorizontalPodAutoscalerStatus{ 120 | CurrentReplicas: 2, 121 | }, 122 | }, 123 | &v2.HorizontalPodAutoscaler{ 124 | ObjectMeta: metav1.ObjectMeta{ 125 | Name: "project-no-start", 126 | Namespace: "default", 127 | Annotations: map[string]string{ 128 | "annotations.scaling.exporter.replica.min": "20", 129 | "annotations.scaling.exporter.time.end": "23:30:00", 130 | }, 131 | Labels: map[string]string{"project": "project-no-start"}, 132 | }, 133 | Spec: v2.HorizontalPodAutoscalerSpec{ 134 | ScaleTargetRef: v2.CrossVersionObjectReference{ 135 | Name: "project-no-start", 136 | }, 137 | }, 138 | Status: v2.HorizontalPodAutoscalerStatus{ 139 | CurrentReplicas: 2, 140 | }, 141 | }, 142 | &v2.HorizontalPodAutoscaler{ 143 | ObjectMeta: metav1.ObjectMeta{ 144 | Name: "project-no-end", 145 | Namespace: "default", 146 | Annotations: map[string]string{ 147 | "annotations.scaling.exporter.replica.min": "20", 148 | "annotations.scaling.exporter.time.start": "23:30:00", 149 | }, 150 | Labels: map[string]string{"project": "project-no-end"}, 151 | }, 152 | Spec: v2.HorizontalPodAutoscalerSpec{ 153 | ScaleTargetRef: v2.CrossVersionObjectReference{ 154 | Name: "project-no-end", 155 | }, 156 | }, 157 | Status: v2.HorizontalPodAutoscalerStatus{ 158 | CurrentReplicas: 2, 159 | }, 160 | }, 161 | &v2.HorizontalPodAutoscaler{ 162 | ObjectMeta: metav1.ObjectMeta{ 163 | Name: "project-replica-misconfigured", 164 | Namespace: "default", 165 | Annotations: map[string]string{ 166 | "annotations.scaling.exporter.replica.min": "err", 167 | "annotations.scaling.exporter.time.start": "20:00:00", 168 | "annotations.scaling.exporter.time.end": "23:00:00", 169 | }, 170 | Labels: map[string]string{"project": "project-replica-misconfigured"}, 171 | }, 172 | Spec: v2.HorizontalPodAutoscalerSpec{ 173 | ScaleTargetRef: v2.CrossVersionObjectReference{ 174 | Name: "project-replica-misconfigured", 175 | }, 176 | }, 177 | Status: v2.HorizontalPodAutoscalerStatus{ 178 | CurrentReplicas: 2, 179 | }, 180 | }, 181 | ) 182 | 183 | prescalingClient := fake.NewSimpleClientset() 184 | prescalingEvents := prescalingClient.PrescalingV1().PrescalingEvents("default") 185 | prescalingEventService := services.NewEventService(prescalingEvents, fakeClock) 186 | precaling := NewPrescaling(&client, prescalingEventService) 187 | 188 | hpaList := precaling.GetHpa() 189 | assert.Nil(t, hpaList, "KO - result is not nul") 190 | } 191 | 192 | func TestCheckAnnotationsKO(t *testing.T) { 193 | testCases := []struct { 194 | name string 195 | expected string 196 | prescaling Hpa 197 | }{ 198 | { 199 | name: "OK", 200 | prescaling: Hpa{ 201 | Replica: 1, 202 | Start: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 203 | End: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 204 | }, 205 | }, 206 | { 207 | name: "KO - Replica is null", 208 | expected: "annotation replica min is misconfigured", 209 | prescaling: Hpa{ 210 | Replica: 0, 211 | Start: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 212 | End: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 213 | }, 214 | }, 215 | { 216 | name: "KO - Start is null", 217 | expected: "annotation time start is misconfigured", 218 | prescaling: Hpa{ 219 | Replica: 1, 220 | Start: time.Time{}, 221 | End: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 222 | }, 223 | }, 224 | { 225 | name: "KO - End is null", 226 | expected: "annotation time start is misconfigured", 227 | prescaling: Hpa{ 228 | Replica: 1, 229 | Start: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 230 | End: time.Time{}, 231 | }, 232 | }, 233 | } 234 | 235 | for _, testCase := range testCases { 236 | err := testCase.prescaling.Validate() 237 | if err == nil { 238 | assert.Equal(t, nil, err, testCase.name) 239 | } else { 240 | assert.EqualError(t, err, testCase.expected, testCase.name) 241 | } 242 | } 243 | } 244 | 245 | func TestCheckAnnotationsOK(t *testing.T) { 246 | testCases := []struct { 247 | name string 248 | expected string 249 | prescaling Hpa 250 | }{ 251 | { 252 | name: "OK - checkAnnotation return nil", 253 | prescaling: Hpa{ 254 | Replica: 1, 255 | Start: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 256 | End: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 257 | }, 258 | }, 259 | } 260 | 261 | for _, testCase := range testCases { 262 | err := testCase.prescaling.Validate() 263 | assert.Nil(t, err, testCase.name) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /pkg/prescaling/hpa.go: -------------------------------------------------------------------------------- 1 | package prescaling 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | type Hpa struct { 9 | Replica int 10 | CurrentReplicas int32 11 | Start time.Time 12 | End time.Time 13 | Project string 14 | Namespace string 15 | Deployment string 16 | } 17 | 18 | func (p Hpa) Validate() error { 19 | if p.Replica == 0 { 20 | err := errors.New("annotation replica min is misconfigured") 21 | return err 22 | } 23 | if p.Start.IsZero() { 24 | err := errors.New("annotation time start is misconfigured") 25 | return err 26 | } 27 | if p.End.IsZero() { 28 | err := errors.New("annotation time start is misconfigured") 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/prescaling/prescaling.go: -------------------------------------------------------------------------------- 1 | package prescaling 2 | 3 | import ( 4 | "github.com/BedrockStreaming/prescaling-exporter/pkg/k8s" 5 | "github.com/BedrockStreaming/prescaling-exporter/pkg/services" 6 | ) 7 | 8 | type IPrescaling interface { 9 | GetHpa() []Hpa 10 | GetEventService() services.IPrescalingEventService 11 | } 12 | 13 | type Prescaling struct { 14 | client *k8s.Client 15 | prescalingEventService services.IPrescalingEventService 16 | } 17 | 18 | func (p Prescaling) GetEventService() services.IPrescalingEventService { 19 | return p.prescalingEventService 20 | } 21 | 22 | func NewPrescaling(client *k8s.Client, eventService services.IPrescalingEventService) IPrescaling { 23 | return &Prescaling{ 24 | client: client, 25 | prescalingEventService: eventService, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | "github.com/prometheus/client_golang/prometheus/promhttp" 8 | log "github.com/sirupsen/logrus" 9 | httpSwagger "github.com/swaggo/http-swagger" 10 | 11 | _ "github.com/BedrockStreaming/prescaling-exporter/docs" 12 | "github.com/BedrockStreaming/prescaling-exporter/pkg/config" 13 | "github.com/BedrockStreaming/prescaling-exporter/pkg/handlers" 14 | ) 15 | 16 | // @title Prescaling API 17 | // @version 1.0.0 18 | // @description This API was built with FastAPI to deal with prescaling recordings in CRD 19 | 20 | type IServer interface { 21 | Initialize() error 22 | } 23 | 24 | type Server struct { 25 | statusHandler handlers.IStatusHandlers 26 | eventHandlers handlers.IEventHandlers 27 | } 28 | 29 | func NewServer(statusHandler handlers.IStatusHandlers, eventHandlers handlers.IEventHandlers) IServer { 30 | return &Server{ 31 | statusHandler: statusHandler, 32 | eventHandlers: eventHandlers, 33 | } 34 | } 35 | 36 | func (s *Server) Initialize() error { 37 | router := mux.NewRouter() 38 | 39 | router.PathPrefix("/swagger/").Handler(httpSwagger.Handler( 40 | httpSwagger.URL("doc.json"), //The url pointing to API definition 41 | httpSwagger.DeepLinking(true), 42 | httpSwagger.DocExpansion("none"), 43 | httpSwagger.DomID("swagger-ui"), 44 | )).Methods(http.MethodGet) 45 | 46 | router.Handle("/metrics", promhttp.Handler()) 47 | 48 | router.HandleFunc("/status", s.statusHandler.Index) 49 | 50 | apiv1 := router.PathPrefix("/api/v1/events").Subrouter() 51 | apiv1.HandleFunc("/", s.eventHandlers.List).Methods(http.MethodGet) 52 | apiv1.HandleFunc("/", s.eventHandlers.Create).Methods(http.MethodPost) 53 | apiv1.HandleFunc("/current", s.eventHandlers.Current).Methods(http.MethodGet) 54 | apiv1.HandleFunc("/{name}", s.eventHandlers.Get).Methods(http.MethodGet) 55 | apiv1.HandleFunc("/{name}", s.eventHandlers.Update).Methods(http.MethodPut) 56 | apiv1.HandleFunc("/{name}", s.eventHandlers.Delete).Methods(http.MethodDelete) 57 | 58 | log.Info("Listen on port: ", config.Config.Port) 59 | 60 | return http.ListenAndServe(":"+config.Config.Port, router) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/services/prescaling_event.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | log "github.com/sirupsen/logrus" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/utils/clock" 11 | 12 | prescalingv1 "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1" 13 | v1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 14 | "github.com/BedrockStreaming/prescaling-exporter/pkg/utils" 15 | ) 16 | 17 | type PrescalingEventOutput struct { 18 | Name string `json:"name"` 19 | v1.PrescalingEventSpec 20 | } 21 | 22 | type PrescalingEventListOutput []PrescalingEventOutput 23 | 24 | type IPrescalingEventService interface { 25 | Create(prescalingevent *v1.PrescalingEvent) (*PrescalingEventOutput, error) 26 | Delete(name string) error 27 | List() (*PrescalingEventListOutput, error) 28 | Get(name string) (*PrescalingEventOutput, error) 29 | Update(prescalingevent *v1.PrescalingEvent) (*PrescalingEventOutput, error) 30 | Current() (*PrescalingEventOutput, error) 31 | Clean(retentionDays int) error 32 | GetClock() clock.PassiveClock 33 | } 34 | 35 | type PrescalingEventService struct { 36 | prescalingEventRepository prescalingv1.PrescalingEventInterface 37 | clock clock.PassiveClock 38 | } 39 | 40 | func NewEventService(prescalingEventRepository prescalingv1.PrescalingEventInterface, clock clock.PassiveClock) IPrescalingEventService { 41 | return &PrescalingEventService{ 42 | prescalingEventRepository: prescalingEventRepository, 43 | clock: clock, 44 | } 45 | } 46 | 47 | func (e *PrescalingEventService) Create(prescalingevent *v1.PrescalingEvent) (*PrescalingEventOutput, error) { 48 | create, err := e.prescalingEventRepository.Create(context.Background(), prescalingevent, metav1.CreateOptions{}) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | r := &PrescalingEventOutput{ 54 | Name: create.Name, 55 | PrescalingEventSpec: v1.PrescalingEventSpec{ 56 | Date: create.Spec.Date, 57 | StartTime: create.Spec.StartTime, 58 | EndTime: create.Spec.EndTime, 59 | Multiplier: create.Spec.Multiplier, 60 | Description: create.Spec.Description, 61 | }, 62 | } 63 | 64 | return r, nil 65 | } 66 | 67 | func (e *PrescalingEventService) Delete(name string) error { 68 | err := e.prescalingEventRepository.Delete(context.Background(), name, metav1.DeleteOptions{}) 69 | if err != nil { 70 | return err 71 | } 72 | return nil 73 | } 74 | 75 | func (e *PrescalingEventService) List() (*PrescalingEventListOutput, error) { 76 | events, err := e.prescalingEventRepository.List(context.Background(), metav1.ListOptions{}) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | result := make(PrescalingEventListOutput, 0) 82 | for _, item := range events.Items { 83 | result = append(result, PrescalingEventOutput{ 84 | Name: item.Name, 85 | PrescalingEventSpec: v1.PrescalingEventSpec{ 86 | Date: item.Spec.Date, 87 | StartTime: item.Spec.StartTime, 88 | EndTime: item.Spec.EndTime, 89 | Multiplier: item.Spec.Multiplier, 90 | Description: item.Spec.Description, 91 | }, 92 | }) 93 | } 94 | 95 | return &result, nil 96 | } 97 | 98 | func (e *PrescalingEventService) Get(name string) (*PrescalingEventOutput, error) { 99 | event, err := e.prescalingEventRepository.Get(context.Background(), name, metav1.GetOptions{}) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | result := &PrescalingEventOutput{ 105 | Name: event.Name, 106 | PrescalingEventSpec: v1.PrescalingEventSpec{ 107 | Date: event.Spec.Date, 108 | StartTime: event.Spec.StartTime, 109 | EndTime: event.Spec.EndTime, 110 | Multiplier: event.Spec.Multiplier, 111 | Description: event.Spec.Description, 112 | }, 113 | } 114 | 115 | return result, nil 116 | } 117 | 118 | func (e *PrescalingEventService) Update(prescalingevent *v1.PrescalingEvent) (*PrescalingEventOutput, error) { 119 | find, err := e.prescalingEventRepository.Get(context.Background(), prescalingevent.Name, metav1.GetOptions{}) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | prescalingevent.ResourceVersion = find.ResourceVersion 125 | 126 | event, err := e.prescalingEventRepository.Update(context.Background(), prescalingevent, metav1.UpdateOptions{}) 127 | 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | response := &PrescalingEventOutput{ 133 | Name: event.Name, 134 | PrescalingEventSpec: v1.PrescalingEventSpec{ 135 | Date: event.Spec.Date, 136 | StartTime: event.Spec.StartTime, 137 | EndTime: event.Spec.EndTime, 138 | Multiplier: event.Spec.Multiplier, 139 | Description: event.Spec.Description, 140 | }, 141 | } 142 | 143 | return response, nil 144 | } 145 | 146 | func (e *PrescalingEventService) Current() (*PrescalingEventOutput, error) { 147 | events, err := e.prescalingEventRepository.List(context.Background(), metav1.ListOptions{}) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | filtered := filter(events.Items, func(event v1.PrescalingEvent) bool { 153 | date := e.clock.Now().Format("2006-01-02") 154 | 155 | start, _ := utils.SetTime(event.Spec.StartTime, e.clock.Now()) 156 | end, _ := utils.SetTime(event.Spec.EndTime, e.clock.Now()) 157 | 158 | return event.Spec.Date == date && utils.InRangeTime(start, end, e.clock.Now()) 159 | }) 160 | 161 | if len(filtered) == 0 { 162 | return nil, errors.New("no events found") 163 | } 164 | 165 | event := filtered[0] 166 | 167 | response := &PrescalingEventOutput{ 168 | Name: event.Name, 169 | PrescalingEventSpec: v1.PrescalingEventSpec{ 170 | Date: event.Spec.Date, 171 | StartTime: event.Spec.StartTime, 172 | EndTime: event.Spec.EndTime, 173 | Multiplier: event.Spec.Multiplier, 174 | Description: event.Spec.Description, 175 | }, 176 | } 177 | return response, nil 178 | } 179 | 180 | func (e *PrescalingEventService) Clean(retentionDays int) error { 181 | 182 | if retentionDays < 2 { 183 | return errors.New("retention days must be > 1") 184 | } 185 | 186 | events, err := e.prescalingEventRepository.List(context.Background(), metav1.ListOptions{}) 187 | 188 | if err != nil { 189 | return err 190 | } 191 | 192 | for _, item := range events.Items { 193 | itemDate, err := time.Parse("2006-01-02", item.Spec.Date) 194 | 195 | if err != nil { 196 | log.Error(err) 197 | continue 198 | } 199 | 200 | if utils.DaysBetweenDates(e.clock.Now(), itemDate) < retentionDays { 201 | continue 202 | } 203 | 204 | err = e.prescalingEventRepository.Delete(context.Background(), item.Name, metav1.DeleteOptions{}) 205 | if err != nil { 206 | log.Error(err) 207 | continue 208 | } 209 | log.Infof("prescaling events %s has been deleted", item.Name) 210 | } 211 | return nil 212 | } 213 | 214 | func (e *PrescalingEventService) GetClock() clock.PassiveClock { 215 | return e.clock 216 | } 217 | 218 | func filter(data []v1.PrescalingEvent, f func(v1.PrescalingEvent) bool) []v1.PrescalingEvent { 219 | fltd := make([]v1.PrescalingEvent, 0) 220 | for _, e := range data { 221 | if f(e) { 222 | fltd = append(fltd, e) 223 | } 224 | } 225 | 226 | return fltd 227 | } 228 | -------------------------------------------------------------------------------- /pkg/services/prescaling_event_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/utils/clock" 10 | testclock "k8s.io/utils/clock/testing" 11 | 12 | fakeclient "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/fake" 13 | prescalingv1client "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1" 14 | "github.com/BedrockStreaming/prescaling-exporter/generated/client/clientset/versioned/typed/prescaling.bedrock.tech/v1/fake" 15 | v1 "github.com/BedrockStreaming/prescaling-exporter/pkg/apis/prescaling.bedrock.tech/v1" 16 | ) 17 | 18 | func TestNewEventService(t *testing.T) { 19 | 20 | fakeClock := testclock.NewFakeClock(time.Date(2022, time.March, 2, 21, 0, 0, 0, time.Now().Local().Location())) 21 | fakePrescalingEvents := &fake.FakePrescalingEvents{} 22 | 23 | type args struct { 24 | prescalingEventRepository prescalingv1client.PrescalingEventInterface 25 | clock clock.PassiveClock 26 | } 27 | tests := []struct { 28 | name string 29 | args args 30 | want IPrescalingEventService 31 | }{ 32 | { 33 | name: "test-1", 34 | args: args{ 35 | prescalingEventRepository: fakePrescalingEvents, 36 | clock: fakeClock, 37 | }, 38 | want: &PrescalingEventService{ 39 | fakePrescalingEvents, 40 | fakeClock, 41 | }, 42 | }, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | if got := NewEventService(tt.args.prescalingEventRepository, tt.args.clock); !reflect.DeepEqual(got, tt.want) { 47 | t.Errorf("NewEventService() = %v, want %v", got, tt.want) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func TestPrescalingEventService_Current(t *testing.T) { 54 | loc, _ := time.LoadLocation("Europe/Paris") 55 | fakeClock := testclock.NewFakeClock(time.Date(2022, time.July, 2, 23, 0, 1, 0, loc)) 56 | 57 | cs := fakeclient.NewSimpleClientset( 58 | &v1.PrescalingEvent{ 59 | TypeMeta: metav1.TypeMeta{ 60 | Kind: "PrescalingEvent", 61 | APIVersion: "v1", 62 | }, 63 | ObjectMeta: metav1.ObjectMeta{ 64 | Name: "project-event-1", 65 | Namespace: "default", 66 | }, 67 | Spec: v1.PrescalingEventSpec{ 68 | Date: "2022-07-02", 69 | StartTime: "20:00:00", 70 | EndTime: "21:59:59", 71 | Multiplier: 0, 72 | Description: "", 73 | }, 74 | }, 75 | &v1.PrescalingEvent{ 76 | TypeMeta: metav1.TypeMeta{ 77 | Kind: "PrescalingEvent", 78 | APIVersion: "v1", 79 | }, 80 | ObjectMeta: metav1.ObjectMeta{ 81 | Name: "project-event-2", 82 | Namespace: "default", 83 | }, 84 | Spec: v1.PrescalingEventSpec{ 85 | Date: "2022-07-02", 86 | StartTime: "22:00:00", 87 | EndTime: "23:59:59", 88 | Multiplier: 0, 89 | Description: "", 90 | }, 91 | }, 92 | ) 93 | prescaling := cs.PrescalingV1().PrescalingEvents("default") 94 | 95 | type fields struct { 96 | prescalingEventRepository prescalingv1client.PrescalingEventInterface 97 | clock clock.PassiveClock 98 | } 99 | tests := []struct { 100 | name string 101 | fields fields 102 | want *PrescalingEventOutput 103 | wantErr bool 104 | }{ 105 | { 106 | name: "", 107 | fields: fields{ 108 | prescalingEventRepository: prescaling, 109 | clock: fakeClock, 110 | }, 111 | want: &PrescalingEventOutput{ 112 | Name: "project-event-2", 113 | PrescalingEventSpec: v1.PrescalingEventSpec{ 114 | Date: "2022-07-02", 115 | StartTime: "22:00:00", 116 | EndTime: "23:59:59", 117 | Multiplier: 0, 118 | Description: "", 119 | }, 120 | }, 121 | wantErr: false, 122 | }, 123 | } 124 | for _, tt := range tests { 125 | tt := tt 126 | t.Run(tt.name, func(t *testing.T) { 127 | e := &PrescalingEventService{ 128 | prescalingEventRepository: tt.fields.prescalingEventRepository, 129 | clock: tt.fields.clock, 130 | } 131 | got, err := e.Current() 132 | if (err != nil) != tt.wantErr { 133 | t.Errorf("Current() error = %v, wantErr %v", err, tt.wantErr) 134 | return 135 | } 136 | if !reflect.DeepEqual(got, tt.want) { 137 | t.Errorf("Current() got = %v, want %v", got, tt.want) 138 | } 139 | }) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | func SetTime(str string, now time.Time) (time.Time, error) { 9 | layOut := "15:04:05" 10 | timeStamp, err := time.Parse(layOut, str) 11 | if err != nil { 12 | return time.Time{}, errors.New("the annotation time.start or time.end is malformed") 13 | } 14 | 15 | hour, min, sec := timeStamp.Clock() 16 | dateTime := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, now.Location()) 17 | 18 | return dateTime, nil 19 | } 20 | 21 | func InRangeTime(dateStart time.Time, dateEnd time.Time, now time.Time) bool { 22 | if dateEnd.Before(dateStart) { 23 | dateEnd = dateEnd.AddDate(0, 0, 1) 24 | } 25 | 26 | if now.Equal(dateStart) || now.Equal(dateEnd) { 27 | return true 28 | } 29 | 30 | return now.After(dateStart) && now.Before(dateEnd) 31 | } 32 | 33 | func DaysBetweenDates(todayDate time.Time, eventDate time.Time) int { 34 | if todayDate.After(eventDate) { 35 | days := todayDate.Sub(eventDate).Hours() / 24 36 | return int(days) 37 | } 38 | return 0 39 | } 40 | -------------------------------------------------------------------------------- /pkg/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var loc = time.Now().Local().Location() 11 | 12 | func TestSetTime(t *testing.T) { 13 | 14 | faketime := time.Date(2022, time.March, 2, 21, 0, 0, 0, loc) 15 | 16 | testCases := []struct { 17 | name string 18 | dateStr string 19 | dateExpected time.Time 20 | }{ 21 | { 22 | name: "OK - Test 1", 23 | dateStr: "10:00:00", 24 | dateExpected: time.Date(2022, time.March, 2, 10, 0, 0, 0, loc), 25 | }, 26 | { 27 | name: "OK - Test 2", 28 | dateStr: "00:00:00", 29 | dateExpected: time.Date(2022, time.March, 2, 0, 0, 0, 0, loc), 30 | }, 31 | } 32 | 33 | for _, testCase := range testCases { 34 | e, _ := SetTime(testCase.dateStr, faketime) 35 | assert.Equal(t, e, testCase.dateExpected, testCase.name) 36 | } 37 | } 38 | 39 | func TestSetTimeError(t *testing.T) { 40 | 41 | faketime := time.Date(2022, time.March, 2, 21, 0, 0, 0, loc) 42 | 43 | testCases := []struct { 44 | name string 45 | dateStr string 46 | }{ 47 | { 48 | name: "KO - minute and second not set", 49 | dateStr: "10", 50 | }, 51 | { 52 | name: "KO - second not set", 53 | dateStr: "10:00", 54 | }, 55 | { 56 | name: "KO - hour not set", 57 | dateStr: ":10:00", 58 | }, 59 | { 60 | name: "KO - values is nil", 61 | dateStr: "", 62 | }, 63 | { 64 | name: "KO - min, sec is not integer", 65 | dateStr: "10:aa:aa", 66 | }, 67 | { 68 | name: "KO - sec is not integer", 69 | dateStr: "10:10:aa", 70 | }, 71 | } 72 | 73 | for _, testCase := range testCases { 74 | _, err := SetTime(testCase.dateStr, faketime) 75 | assert.Error(t, err, testCase.name) 76 | } 77 | } 78 | 79 | func TestInRangeTime(t *testing.T) { 80 | 81 | faketime := time.Date(2022, time.March, 2, 21, 0, 0, 0, loc) 82 | 83 | testCases := []struct { 84 | expected bool 85 | dateStart time.Time 86 | dateEnd time.Time 87 | name string 88 | }{ 89 | { 90 | name: "OK - is inside the period ", 91 | dateStart: time.Date(2022, time.March, 2, 20, 0, 0, 0, loc), 92 | dateEnd: time.Date(2022, time.March, 2, 22, 0, 0, 0, loc), 93 | expected: true, 94 | }, 95 | { 96 | name: "OK - dateStart and time.Now is Equal", 97 | dateStart: time.Date(2022, time.March, 2, 21, 0, 0, 0, loc), 98 | dateEnd: time.Date(2022, time.March, 2, 22, 0, 0, 0, loc), 99 | expected: true, 100 | }, 101 | { 102 | name: "OK - dateEnd and time.Now is Equal", 103 | dateStart: time.Date(2022, time.March, 2, 20, 0, 0, 0, loc), 104 | dateEnd: time.Date(2022, time.March, 2, 21, 0, 0, 0, loc), 105 | expected: true, 106 | }, 107 | { 108 | name: "OK - dateEnd is after midnight", 109 | dateStart: time.Date(2022, time.March, 2, 20, 0, 0, 0, loc), 110 | dateEnd: time.Date(2022, time.March, 2, 0, 30, 0, 0, loc), 111 | expected: true, 112 | }, 113 | { 114 | name: "KO - dateStart and dateEnd is inverted", 115 | dateStart: time.Date(2022, time.March, 2, 22, 0, 0, 0, loc), 116 | dateEnd: time.Date(2022, time.March, 2, 0, 30, 0, 0, loc), 117 | expected: false, 118 | }, 119 | { 120 | name: "KO - is outside the period", 121 | dateStart: time.Date(2022, time.March, 2, 19, 0, 0, 0, loc), 122 | dateEnd: time.Date(2022, time.March, 2, 20, 30, 0, 0, loc), 123 | expected: false, 124 | }, 125 | } 126 | 127 | for _, testCase := range testCases { 128 | err := InRangeTime(testCase.dateStart, testCase.dateEnd, faketime) 129 | assert.Equal(t, err, testCase.expected, testCase.name) 130 | } 131 | } 132 | 133 | func TestDaysBetweenDates(t *testing.T) { 134 | 135 | testCases := []struct { 136 | expected int 137 | today time.Time 138 | eventDate time.Time 139 | name string 140 | }{ 141 | { 142 | name: "OK - 10d8h", 143 | today: time.Date(2022, time.March, 13, 20, 0, 0, 0, loc), 144 | eventDate: time.Date(2022, time.March, 2, 22, 0, 0, 0, loc), 145 | expected: 10, 146 | }, 147 | { 148 | name: "OK - 9d,8h", 149 | today: time.Date(2022, time.March, 12, 20, 0, 0, 0, loc), 150 | eventDate: time.Date(2022, time.March, 2, 22, 0, 0, 0, loc), 151 | expected: 9, 152 | }, 153 | { 154 | name: "OK - 10d,1h", 155 | today: time.Date(2022, time.March, 12, 20, 0, 0, 0, loc), 156 | eventDate: time.Date(2022, time.March, 2, 19, 0, 0, 0, loc), 157 | expected: 10, 158 | }, 159 | { 160 | name: "OK - 10d,1h", 161 | today: time.Date(2022, time.March, 2, 20, 0, 0, 0, loc), 162 | eventDate: time.Date(2022, time.March, 12, 19, 0, 0, 0, loc), 163 | expected: 0, 164 | }, 165 | { 166 | name: "OK - 10d,1h", 167 | today: time.Date(2022, time.March, 2, 20, 0, 0, 0, loc), 168 | eventDate: time.Date(2022, time.March, 2, 19, 0, 0, 0, loc), 169 | expected: 0, 170 | }, 171 | } 172 | 173 | for _, testCase := range testCases { 174 | days := DaysBetweenDates(testCase.today, testCase.eventDate) 175 | assert.Equal(t, testCase.expected, days, testCase.name) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /pkg/utils/write_response.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | func WriteResponse(w http.ResponseWriter, code int, data interface{}) { 9 | w.Header().Add("Content-Type", "application/json") 10 | w.WriteHeader(code) 11 | 12 | err := json.NewEncoder(w).Encode(data) 13 | if err != nil { 14 | http.Error(w, err.Error(), http.StatusBadRequest) 15 | return 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta28 2 | kind: Config 3 | build: 4 | artifacts: 5 | - image: bedrockstreaming/prescaling-exporter 6 | ko: {} 7 | deploy: 8 | helm: 9 | releases: 10 | - name: prescaling-exporter 11 | chartPath: helm/prescaling-exporter 12 | namespace: prescaling-exporter 13 | createNamespace: true 14 | artifactOverrides: 15 | image: bedrockstreaming/prescaling-exporter 16 | imageStrategy: 17 | helm: {} 18 | -------------------------------------------------------------------------------- /swagger/doc.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: My API 4 | version: '1.0' 5 | x-logo: 6 | url: '' 7 | servers: 8 | - url: http://localhost:9101/ 9 | paths: 10 | /api/v1/events/{name}: 11 | get: 12 | operationId: getEventByName 13 | summary: Get a event by name 14 | responses: 15 | '200': 16 | content: 17 | application/json: 18 | schema: 19 | $ref: '#/components/schemas/GetEventResponse' 20 | description: '' 21 | '404': 22 | content: 23 | application/json: 24 | schema: 25 | $ref: '#/components/schemas/Error' 26 | description: 'No event found' 27 | put: 28 | operationId: updateEventByName 29 | summary: Update a event by name 30 | requestBody: 31 | content: 32 | application/json: 33 | schema: 34 | $ref: '#/components/schemas/UpdateEventRequest' 35 | responses: 36 | '200': 37 | description: 'Event has been updated' 38 | content: 39 | application/json: 40 | schema: 41 | $ref: '#/components/schemas/UpdateEventResponse' 42 | '404': 43 | description: 'No event found' 44 | content: 45 | application/json: 46 | schema: 47 | $ref: '#/components/schemas/Error' 48 | '400': 49 | description: "Event can't be updated" 50 | content: 51 | application/json: 52 | schema: 53 | $ref: '#/components/schemas/Error' 54 | delete: 55 | operationId: deleteEventByName 56 | summary: Delete a event by name 57 | responses: 58 | '200': 59 | description: 'Event has been deleted' 60 | '204': 61 | description: 'No content' 62 | '404': 63 | description: 'No event found' 64 | content: 65 | application/json: 66 | schema: 67 | $ref: '#/components/schemas/Error' 68 | '400': 69 | description: "Event can't be updated" 70 | content: 71 | application/json: 72 | schema: 73 | $ref: '#/components/schemas/Error' 74 | parameters: 75 | - $ref: '#/components/parameters/name' 76 | /api/v1/events/: 77 | get: 78 | operationId: getAllEventsByName 79 | summary: Get all events by name 80 | responses: 81 | '200': 82 | description: List all events 83 | content: 84 | application/json: 85 | schema: 86 | type: array 87 | items: 88 | $ref: '#/components/schemas/GetEventResponse' 89 | '400': 90 | description: "Event can't listed" 91 | content: 92 | application/json: 93 | schema: 94 | $ref: '#/components/schemas/Error' 95 | post: 96 | operationId: createEvent 97 | summary: Create a event 98 | requestBody: 99 | content: 100 | application/json: 101 | schema: 102 | $ref: '#/components/schemas/CreateEventRequest' 103 | responses: 104 | '201': 105 | description: Create a event 106 | content: 107 | application/json: 108 | schema: 109 | $ref: '#/components/schemas/CreateEventResponse' 110 | '400': 111 | description: "The request is invalid" 112 | content: 113 | application/json: 114 | schema: 115 | $ref: '#/components/schemas/Error' 116 | # /api/v1/event: 117 | # summary: Get current event 118 | # get: 119 | # responses: 120 | # '200': 121 | # description: Get current event 122 | components: 123 | schemas: 124 | CreateEventRequest: 125 | type: object 126 | properties: 127 | name: 128 | type: string 129 | example: 130 | - test-event-7 131 | date: 132 | type: string 133 | example: 134 | - '01:01:2023' 135 | start_time: 136 | type: string 137 | example: 138 | - '10:00:00' 139 | end_time: 140 | type: string 141 | example: 142 | - '23:59:59' 143 | multiplier: 144 | type: number 145 | example: 146 | - 2 147 | description: 148 | type: string 149 | example: 150 | - test event from postman 151 | example: 152 | name: test-event-7 153 | date: '01:01:2023' 154 | start_time: '10:00:00' 155 | end_time: '23:59:59' 156 | multiplier: 2 157 | description: test event from postman 158 | CreateEventResponse: 159 | type: object 160 | properties: 161 | name: 162 | type: string 163 | example: 164 | - test-event-7 165 | date: 166 | type: string 167 | example: 168 | - '01:01:2023' 169 | start_time: 170 | type: string 171 | example: 172 | - '10:00:00' 173 | end_time: 174 | type: string 175 | example: 176 | - '23:59:59' 177 | multiplier: 178 | type: number 179 | example: 180 | - 2 181 | description: 182 | type: string 183 | example: 184 | - test event from postman 185 | example: 186 | name: test-event-7 187 | date: '01:01:2023' 188 | start_time: '10:00:00' 189 | end_time: '23:59:59' 190 | multiplier: 2 191 | description: test event from postman 192 | UpdateEventRequest: 193 | type: object 194 | properties: 195 | date: 196 | type: string 197 | example: 198 | - '01:01:2023' 199 | start_time: 200 | type: string 201 | example: 202 | - '10:00:00' 203 | end_time: 204 | type: string 205 | example: 206 | - '23:59:59' 207 | multiplier: 208 | type: number 209 | example: 210 | - 2 211 | description: 212 | type: string 213 | example: 214 | - test event from postman 215 | example: 216 | date: '01:01:2023' 217 | start_time: '10:00:00' 218 | end_time: '23:59:59' 219 | multiplier: 2 220 | description: test event from postman 221 | UpdateEventResponse: 222 | type: object 223 | properties: 224 | name: 225 | type: string 226 | example: 227 | - test-event-7 228 | date: 229 | type: string 230 | example: 231 | - '01:01:2023' 232 | start_time: 233 | type: string 234 | example: 235 | - '10:00:00' 236 | end_time: 237 | type: string 238 | example: 239 | - '23:59:59' 240 | multiplier: 241 | type: number 242 | example: 243 | - 2 244 | description: 245 | type: string 246 | example: 247 | - test event from postman 248 | example: 249 | name: test-event-7 250 | date: '01:01:2023' 251 | start_time: '10:00:00' 252 | end_time: '23:59:59' 253 | multiplier: 2 254 | description: test event from postman 255 | GetEventResponse: 256 | type: object 257 | properties: 258 | name: 259 | type: string 260 | example: 261 | - test-event-7 262 | date: 263 | type: string 264 | example: 265 | - '01:01:2023' 266 | start_time: 267 | type: string 268 | example: 269 | - '10:00:00' 270 | end_time: 271 | type: string 272 | example: 273 | - '23:59:59' 274 | multiplier: 275 | type: number 276 | example: 277 | - 2 278 | description: 279 | type: string 280 | example: 281 | - test event from postman 282 | example: 283 | name: test-event-7 284 | date: '01:01:2023' 285 | start_time: '10:00:00' 286 | end_time: '23:59:59' 287 | multiplier: 2 288 | description: test event from postman 289 | Error: 290 | description: Used when an API throws an Error, typically with a HTTP error response-code (3xx, 4xx, 5xx) 291 | type: object 292 | required: 293 | - code 294 | - reason 295 | properties: 296 | code: 297 | type: string 298 | description: Application relevant detail, defined in the API or a common list. 299 | maxLength: 50 300 | reason: 301 | type: string 302 | description: Explanation of the reason for the error which can be shown to a 303 | client user. 304 | maxLength: 255 305 | message: 306 | type: string 307 | description: More details and corrective actions related to the error which can 308 | be shown to a client user. 309 | maxLength: 65535 310 | status: 311 | type: string 312 | description: HTTP Error code extension 313 | maxLength: 50 314 | referenceError: 315 | type: string 316 | format: uri 317 | description: URI of documentation describing the error. 318 | maxLength: 2048 319 | parameters: 320 | name: 321 | deprecated: false 322 | name: name 323 | schema: 324 | pattern: '[a-zA-Z0-9\-]*' 325 | type: string 326 | in: path 327 | required: true 328 | securitySchemes: {} 329 | headers: {} 330 | tags: [] 331 | security: [] 332 | -------------------------------------------------------------------------------- /test-api.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:9101/api/v1/events/ 2 | Content-Type: application/json 3 | 4 | { 5 | "name":"test-event-2", 6 | "date":"2022-05-25", 7 | "start_time":"10:00:00", 8 | "end_time":"23:59:59", 9 | "multiplier":2, 10 | "description":"test event from postman" 11 | } 12 | 13 | ### 14 | 15 | GET http://localhost:9101/api/v1/events/ 16 | 17 | ### 18 | 19 | GET http://localhost:9101/api/v1/events/test-event-1 20 | 21 | ### 22 | 23 | PUT http://localhost:9101/api/v1/events/test-event-1 24 | Content-Type: application/json 25 | 26 | { 27 | "name":"test-event-1", 28 | "date":"2022-05-25", 29 | "start_time":"10:00:00", 30 | "end_time":"23:59:59", 31 | "multiplier":2, 32 | "description":"test event from postman" 33 | } 34 | 35 | ### 36 | 37 | DELETE http://localhost:9101/api/v1/events/test-event-1 38 | 39 | ### 40 | 41 | GET http://localhost:9101/api/v1/events/current 42 | 43 | ### 44 | 45 | GET http://localhost:9101/status 46 | 47 | ### 48 | 49 | GET http://localhost:9101/metrics 50 | 51 | 52 | ### 53 | --------------------------------------------------------------------------------