├── .dockerignore ├── .github └── workflows │ ├── go.yml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── beekeeper │ ├── cmd │ ├── check.go │ ├── cluster.go │ ├── cmd.go │ ├── create.go │ ├── create_bee_cluster.go │ ├── create_k8s_namespace.go │ ├── delete.go │ ├── delete_bee_cluster.go │ ├── delete_k8s_namespace.go │ ├── fund.go │ ├── metrics.go │ ├── node_funder.go │ ├── operator.go │ ├── print.go │ ├── restart.go │ ├── simulate.go │ ├── stamper.go │ └── version.go │ └── main.go ├── config ├── beekeeper-local.yaml ├── config.yaml ├── light-node.yaml ├── local.yaml ├── localhost.yaml ├── public-testnet-static.yaml ├── public-testnet.yaml └── staging.yaml ├── go.mod ├── go.sum ├── mocks └── k8s │ ├── app.go │ ├── clientset.go │ ├── configmap.go │ ├── core.go │ ├── ingress.go │ ├── k8s.go │ ├── namespace.go │ ├── networking.go │ ├── persistentvolumeclaim.go │ ├── pod.go │ ├── roundtripper.go │ ├── secret.go │ ├── service.go │ ├── serviceaccount.go │ └── statefulset.go ├── pkg ├── bee │ ├── api │ │ ├── act.go │ │ ├── api.go │ │ ├── bytes.go │ │ ├── chunks.go │ │ ├── debugstore.go │ │ ├── dirs.go │ │ ├── errors.go │ │ ├── errors_test.go │ │ ├── feed.go │ │ ├── files.go │ │ ├── node.go │ │ ├── options.go │ │ ├── pingpong.go │ │ ├── pinning.go │ │ ├── postage.go │ │ ├── pss.go │ │ ├── soc.go │ │ ├── stake.go │ │ ├── status.go │ │ ├── stewardship.go │ │ └── tags.go │ ├── chunk.go │ ├── client.go │ ├── file.go │ └── postage.go ├── beekeeper │ ├── beekeeper.go │ └── tracing.go ├── bigint │ ├── bigint.go │ └── bigint_test.go ├── check │ ├── act │ │ └── act.go │ ├── balances │ │ └── balances.go │ ├── cashout │ │ └── cashout.go │ ├── datadurability │ │ ├── datadurability.go │ │ └── metrics.go │ ├── feed │ │ └── feed.go │ ├── fileretrieval │ │ ├── fileretrieval.go │ │ └── metrics.go │ ├── fullconnectivity │ │ └── fullconnectivity.go │ ├── gc │ │ └── reserve.go │ ├── gsoc │ │ └── gsoc.go │ ├── kademlia │ │ └── kademlia.go │ ├── longavailability │ │ ├── longavailability.go │ │ └── metrics.go │ ├── manifest │ │ └── manifest.go │ ├── networkavailability │ │ ├── check.go │ │ └── metrics.go │ ├── peercount │ │ └── peercount.go │ ├── pingpong │ │ ├── metrics.go │ │ └── pingpong.go │ ├── postage │ │ └── postage.go │ ├── pss │ │ ├── metrics.go │ │ └── pss.go │ ├── pullsync │ │ └── pullsync.go │ ├── pushsync │ │ ├── check_chunks.go │ │ ├── check_lightnode.go │ │ ├── metrics.go │ │ └── pushsync.go │ ├── redundancy │ │ └── redundancy.go │ ├── retrieval │ │ ├── metrics.go │ │ └── retrieval.go │ ├── runner.go │ ├── settlements │ │ └── settlements.go │ ├── smoke │ │ ├── load.go │ │ ├── metrics.go │ │ └── smoke.go │ ├── soc │ │ └── soc.go │ ├── stake │ │ ├── contract.go │ │ ├── contractutil.go │ │ └── stake.go │ └── withdraw │ │ └── withdraw.go ├── config │ ├── bee.go │ ├── check.go │ ├── cluster.go │ ├── config.go │ ├── funding.go │ ├── nodegroup.go │ └── simulation.go ├── funder │ ├── node │ │ └── node.go │ └── operator │ │ └── operator.go ├── httpx │ └── transport.go ├── k8s │ ├── config.go │ ├── configmap │ │ ├── configmap.go │ │ └── configmap_test.go │ ├── containers │ │ ├── containers.go │ │ ├── containers_test.go │ │ ├── env.go │ │ ├── ephemeral.go │ │ ├── ephermal_test.go │ │ ├── handler.go │ │ ├── lifecycle.go │ │ ├── port.go │ │ ├── probe.go │ │ ├── resources.go │ │ ├── security.go │ │ └── volume.go │ ├── customresource │ │ └── ingressroute │ │ │ ├── client.go │ │ │ ├── config.go │ │ │ ├── ingressroute.go │ │ │ ├── register.go │ │ │ └── types.go │ ├── ingress │ │ ├── backend.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── ingress.go │ │ ├── rule.go │ │ └── tls.go │ ├── k8s.go │ ├── k8s_test.go │ ├── namespace │ │ ├── namespace.go │ │ └── namespace_test.go │ ├── persistentvolumeclaim │ │ ├── accessmode.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── datasource.go │ │ ├── persistentvolumeclaim.go │ │ ├── persistentvolumeclaim_test.go │ │ └── selector.go │ ├── pod │ │ ├── affinity.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── dns.go │ │ ├── host.go │ │ ├── pod.go │ │ ├── pod_test.go │ │ ├── readiness.go │ │ ├── security.go │ │ ├── toleration.go │ │ ├── topology.go │ │ └── volume.go │ ├── secret │ │ ├── secret.go │ │ └── secret_test.go │ ├── service │ │ ├── client.go │ │ ├── client_test.go │ │ └── service.go │ ├── serviceaccount │ │ ├── serviceaccount.go │ │ └── serviceaccount_test.go │ ├── statefulset │ │ ├── client.go │ │ ├── client_test.go │ │ └── statefulset.go │ └── transport.go ├── logging │ ├── logging.go │ ├── logrusloki.go │ ├── loki │ │ ├── batch.go │ │ └── stream.go │ └── metrics.go ├── metrics │ └── metrics.go ├── orchestration │ ├── cluster.go │ ├── k8s │ │ ├── cluster.go │ │ ├── helpers.go │ │ ├── node.go │ │ ├── nodegroup.go │ │ └── orchestrator.go │ ├── key.go │ ├── node.go │ ├── nodegroup.go │ └── notset │ │ └── bee.go ├── random │ ├── random.go │ └── random_test.go ├── restart │ └── restart.go ├── scheduler │ ├── duration.go │ └── scheduler.go ├── simulate │ ├── pushsync │ │ ├── metrics.go │ │ ├── pushsync.go │ │ └── pushsync_test.go │ ├── retrieval │ │ ├── metrics.go │ │ └── retrieval.go │ └── upload │ │ └── upload.go ├── stamper │ ├── node.go │ └── stamper.go ├── swap │ ├── block.go │ ├── cache.go │ ├── geth.go │ ├── http-errors.go │ ├── http.go │ ├── notset.go │ └── swap.go ├── test │ ├── case.go │ ├── chunk.go │ ├── funcs.go │ ├── node.go │ └── uploader.go ├── tracing │ └── tracing.go └── wslistener │ └── wslistener.go ├── scripts ├── install.sh └── suite.sh └── version.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .DS_Store 3 | .git 4 | .gitignore 5 | .github 6 | .idea 7 | .vscode 8 | dist 9 | Dockerfile 10 | *.yaml 11 | *.yml 12 | *.md -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 1 17 | - name: Setup Go 18 | if: matrix.os == 'ubuntu-latest' 19 | uses: actions/setup-go@v5 20 | with: 21 | cache: false 22 | go-version-file: go.mod 23 | - name: Setup Go 24 | if: matrix.os == 'macos-latest' || matrix.os == 'windows-latest' 25 | uses: actions/setup-go@v5 26 | with: 27 | cache: true 28 | go-version-file: go.mod 29 | - name: Set git to use LF 30 | # make sure that line endings are not converted on windows 31 | # as gofmt linter will report that they need to be changed 32 | run: git config --global core.autocrlf false 33 | - name: Lint 34 | if: matrix.os == 'ubuntu-latest' 35 | uses: golangci/golangci-lint-action@v6 36 | with: 37 | version: v1.64.5 38 | args: --timeout 10m 39 | skip-cache: false 40 | - name: Vet 41 | if: matrix.os == 'ubuntu-latest' 42 | run: make vet 43 | - name: Build 44 | run: make build 45 | - name: Test 46 | run: make test 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - '**' 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | goreleaser: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Setup Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version-file: go.mod 22 | - name: Run GoReleaser 23 | uses: goreleaser/goreleaser-action@v4 24 | with: 25 | version: latest 26 | args: release --clean --timeout 1h 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.prof 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Test data directories, built with `go test -fuzz` 13 | testdata/ 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | vendor/ 20 | 21 | # Folders 22 | _obj 23 | _test 24 | dist/ 25 | .idea/ 26 | .vscode/ 27 | .vs/ 28 | .DS_Store/ 29 | tmp/ 30 | 31 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 32 | *.o 33 | *.a 34 | *.so 35 | 36 | # Architecture specific extensions/prefixes 37 | *.[568vq] 38 | [568vq].out 39 | 40 | *.cgo1.go 41 | *.cgo2.c 42 | _cgo_defun.c 43 | _cgo_gotypes.go 44 | _cgo_export.* 45 | 46 | _testmain.go 47 | 48 | *.log 49 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 10m 3 | linters: 4 | enable: 5 | - copyloopvar 6 | - errcheck 7 | - errname 8 | - errorlint 9 | - goconst 10 | - gofmt 11 | - gofumpt 12 | - govet 13 | - misspell 14 | - nilerr 15 | - staticcheck 16 | - unconvert 17 | - unused 18 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: beekeeper 2 | 3 | builds: 4 | - main: ./cmd/beekeeper/main.go 5 | 6 | binary: beekeeper 7 | 8 | flags: 9 | - -v 10 | - -trimpath 11 | 12 | ldflags: 13 | - -s -w -X github.com/ethersphere/beekeeper.version={{.Version}} -X github.com/ethersphere/beekeeper.commit={{.ShortCommit}} 14 | 15 | env: 16 | - CGO_ENABLED=0 17 | 18 | goos: 19 | - darwin 20 | - linux 21 | - windows 22 | 23 | goarch: 24 | - amd64 25 | - 386 26 | - arm64 27 | - arm 28 | 29 | ignore: 30 | - goos: darwin 31 | goarch: 386 32 | - goos: darwin 33 | goarch: arm 34 | - goos: windows 35 | goarch: arm64 36 | - goos: windows 37 | goarch: arm 38 | 39 | snapshot: 40 | name_template: "{{.Tag}}-snapshot" 41 | 42 | archives: 43 | - name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 44 | 45 | format: binary 46 | 47 | nfpms: 48 | - file_name_template: "{{ tolower .ProjectName }}-{{ tolower .Os }}-{{ tolower .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 49 | 50 | vendor: Ethereum Swarm 51 | homepage: https://swarm.ethereum.org/ 52 | 53 | maintainer: Svetomir Smiljkovic 54 | 55 | description: Ethereum Swarm Beekeeper 56 | 57 | license: BSD-3-Clause 58 | 59 | formats: 60 | - deb 61 | - rpm 62 | 63 | bindir: /usr/bin 64 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 AS build 2 | 3 | WORKDIR /src 4 | # enable modules caching in separate layer 5 | COPY go.mod go.sum ./ 6 | RUN go mod download 7 | COPY . ./ 8 | 9 | RUN make binary 10 | 11 | FROM debian:12.10-slim 12 | 13 | ENV DEBIAN_FRONTEND=noninteractive 14 | 15 | RUN apt-get update && apt-get install -y \ 16 | ca-certificates; \ 17 | apt-get clean; \ 18 | rm -rf /var/lib/apt/lists/*; \ 19 | groupadd -r beekeeper --gid 999; \ 20 | useradd -r -g beekeeper --uid 999 --no-log-init -m beekeeper; 21 | 22 | COPY --from=build /src/dist/beekeeper /usr/local/bin/beekeeper 23 | 24 | USER beekeeper 25 | WORKDIR /home/beekeeper 26 | 27 | ENTRYPOINT ["beekeeper"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Ethersphere 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= go 2 | GOLANGCI_LINT ?= golangci-lint 3 | GOLANGCI_LINT_VERSION ?= v1.64.5 4 | COMMIT ?= "$(shell git describe --long --dirty --always --match "" || true)" 5 | VERSION ?= "$(shell git describe --tags --abbrev=0 | cut -c2-)" 6 | LDFLAGS ?= -s -w \ 7 | -X github.com/ethersphere/beekeeper.commit="$(COMMIT)" \ 8 | -X github.com/ethersphere/beekeeper.version="$(VERSION)" 9 | 10 | .PHONY: all 11 | all: build lint vet test-race binary 12 | 13 | .PHONY: binary 14 | binary: export CGO_ENABLED=0 15 | binary: dist FORCE 16 | $(GO) version 17 | ifeq ($(OS),Windows_NT) 18 | $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o dist/beekeeper.exe ./cmd/beekeeper 19 | else 20 | $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o dist/beekeeper ./cmd/beekeeper 21 | endif 22 | 23 | dist: 24 | mkdir $@ 25 | 26 | .PHONY: lint 27 | lint: linter 28 | $(GOLANGCI_LINT) run 29 | 30 | .PHONY: linter 31 | linter: 32 | which $(GOLANGCI_LINT) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$($(GO) env GOPATH)/bin $(GOLANGCI_LINT_VERSION) 33 | 34 | .PHONY: vet 35 | vet: 36 | $(GO) vet ./... 37 | 38 | .PHONY: test-race 39 | test-race: 40 | $(GO) test -race -v ./... 41 | 42 | .PHONY: test 43 | test: 44 | $(GO) test -v ./pkg/... 45 | 46 | .PHONY: build 47 | build: export CGO_ENABLED=0 48 | build: 49 | $(GO) build -trimpath -ldflags "$(LDFLAGS)" ./... 50 | 51 | .PHONY: clean 52 | clean: 53 | $(GO) clean 54 | rm -rf dist/ 55 | 56 | FORCE: 57 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func (c *command) initCreateCmd() (err error) { 8 | cmd := &cobra.Command{ 9 | Use: "create", 10 | Short: "creates Bee infrastructure", 11 | Long: `Creates Bee infrastructure.`, 12 | RunE: func(cmd *cobra.Command, args []string) (err error) { 13 | return cmd.Help() 14 | }, 15 | } 16 | 17 | cmd.AddCommand(c.initCreateK8sNamespace()) 18 | cmd.AddCommand(c.initCreateBeeCluster()) 19 | 20 | c.root.AddCommand(cmd) 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/create_bee_cluster.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | const ( 11 | optionNameClusterName string = "cluster-name" 12 | optionNameWalletKey string = "wallet-key" 13 | optionNameTimeout string = "timeout" 14 | ) 15 | 16 | func (c *command) initCreateBeeCluster() *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: "bee-cluster", 19 | Short: "creates Bee cluster", 20 | Long: `creates Bee cluster.`, 21 | RunE: func(cmd *cobra.Command, args []string) (err error) { 22 | ctx, cancel := context.WithTimeout(cmd.Context(), c.globalConfig.GetDuration(optionNameTimeout)) 23 | defer cancel() 24 | start := time.Now() 25 | _, err = c.setupCluster(ctx, c.globalConfig.GetString(optionNameClusterName), true) 26 | c.log.Infof("cluster setup took %s", time.Since(start)) 27 | return err 28 | }, 29 | PreRunE: c.preRunE, 30 | } 31 | 32 | cmd.Flags().String(optionNameClusterName, "", "cluster name") 33 | cmd.Flags().String(optionNameWalletKey, "", "Hex-encoded private key for the Bee node wallet. Required.") 34 | cmd.Flags().Duration(optionNameTimeout, 30*time.Minute, "timeout") 35 | 36 | return cmd 37 | } 38 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/create_k8s_namespace.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const namespaceCmd string = "k8s-namespace" 10 | 11 | func (c *command) initCreateK8sNamespace() *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: namespaceCmd, 14 | Short: "creates Kubernetes namespace", 15 | Long: `creates Kubernetes namespace.`, 16 | Args: func(cmd *cobra.Command, args []string) error { 17 | if len(args) < 1 { 18 | return fmt.Errorf("requires exactly one argument representing name of the Kubernetes namespace") 19 | } 20 | 21 | return nil 22 | }, 23 | RunE: func(cmd *cobra.Command, args []string) (err error) { 24 | name := args[0] 25 | 26 | if _, err = c.k8sClient.Namespace.Create(cmd.Context(), name); err != nil { 27 | return fmt.Errorf("create namespace %s: %w", name, err) 28 | } 29 | 30 | c.log.Infof("namespace %s created", name) 31 | return 32 | }, 33 | PreRunE: func(cmd *cobra.Command, args []string) error { 34 | if err := c.setK8sClient(); err != nil { 35 | return err 36 | } 37 | 38 | if c.k8sClient == nil { 39 | return fmt.Errorf("k8s client not set") 40 | } 41 | 42 | return nil 43 | }, 44 | } 45 | 46 | return cmd 47 | } 48 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/delete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func (c *command) initDeleteCmd() (err error) { 8 | cmd := &cobra.Command{ 9 | Use: "delete", 10 | Short: "deletes Bee infrastructure", 11 | Long: `Deletes Bee infrastructure.`, 12 | RunE: func(cmd *cobra.Command, args []string) (err error) { 13 | return cmd.Help() 14 | }, 15 | } 16 | 17 | cmd.AddCommand(c.initDeleteK8SNamespace()) 18 | cmd.AddCommand(c.initDeleteBeeCluster()) 19 | 20 | c.root.AddCommand(cmd) 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/delete_bee_cluster.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func (c *command) initDeleteBeeCluster() *cobra.Command { 11 | const ( 12 | optionNameWithStorage = "with-storage" 13 | optionNameTimeout = "timeout" 14 | ) 15 | 16 | cmd := &cobra.Command{ 17 | Use: "bee-cluster", 18 | Short: "deletes Bee cluster", 19 | Long: `Deletes Bee cluster.`, 20 | RunE: func(cmd *cobra.Command, args []string) (err error) { 21 | ctx, cancel := context.WithTimeout(cmd.Context(), c.globalConfig.GetDuration(optionNameTimeout)) 22 | defer cancel() 23 | 24 | return c.deleteCluster(ctx, c.globalConfig.GetString(optionNameClusterName), c.config, c.globalConfig.GetBool(optionNameWithStorage)) 25 | }, 26 | PreRunE: c.preRunE, 27 | } 28 | 29 | cmd.Flags().String(optionNameClusterName, "", "cluster name") 30 | cmd.Flags().Bool(optionNameWithStorage, false, "delete storage") 31 | cmd.Flags().Duration(optionNameTimeout, 15*time.Minute, "timeout") 32 | 33 | return cmd 34 | } 35 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/delete_k8s_namespace.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func (c *command) initDeleteK8SNamespace() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: namespaceCmd, 12 | Short: "deletes Kubernetes namespace", 13 | Long: `Deletes Kubernetes namespace.`, 14 | Args: func(cmd *cobra.Command, args []string) error { 15 | if len(args) < 1 { 16 | return fmt.Errorf("requires exactly one argument representing name of the Kubernetes namespace") 17 | } 18 | 19 | return nil 20 | }, 21 | RunE: func(cmd *cobra.Command, args []string) (err error) { 22 | name := args[0] 23 | 24 | if err = c.k8sClient.Namespace.Delete(cmd.Context(), name); err != nil { 25 | return fmt.Errorf("delete namespace %s: %w", name, err) 26 | } 27 | 28 | c.log.Infof("namespace %s deleted", name) 29 | return 30 | }, 31 | PreRunE: func(cmd *cobra.Command, args []string) error { 32 | if err := c.setK8sClient(); err != nil { 33 | return err 34 | } 35 | 36 | if c.k8sClient == nil { 37 | return fmt.Errorf("k8s client not set") 38 | } 39 | 40 | return nil 41 | }, 42 | } 43 | 44 | return cmd 45 | } 46 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/metrics.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/logging" 8 | "github.com/prometheus/client_golang/prometheus/push" 9 | "github.com/prometheus/common/expfmt" 10 | ) 11 | 12 | // newMetricsPusher returns a new metrics pusher and a cleanup function. 13 | func newMetricsPusher(pusherAddress, job string, logger logging.Logger) (*push.Pusher, func()) { 14 | metricsPusher := push.New(pusherAddress, job) 15 | metricsPusher.Format(expfmt.NewFormat(expfmt.TypeTextPlain)) 16 | 17 | killC := make(chan struct{}) 18 | var wg sync.WaitGroup 19 | 20 | wg.Add(1) 21 | 22 | // start period flusher 23 | go func() { 24 | defer wg.Done() 25 | for { 26 | select { 27 | case <-killC: 28 | return 29 | case <-time.After(time.Second): 30 | if err := metricsPusher.Push(); err != nil { 31 | logger.Debugf("metrics pusher periodic push: %v", err) 32 | } 33 | } 34 | } 35 | }() 36 | cleanupFn := func() { 37 | close(killC) 38 | wg.Wait() 39 | // push metrics before returning 40 | if err := metricsPusher.Push(); err != nil { 41 | logger.Infof("metrics pusher push: %v", err) 42 | } 43 | } 44 | return metricsPusher, cleanupFn 45 | } 46 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/operator.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/ethersphere/beekeeper/pkg/funder/operator" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func (c *command) initOperatorCmd() error { 13 | const ( 14 | optionNameNamespace = "namespace" 15 | optionNameWalletKey = "wallet-key" 16 | optionNameMinNative = "min-native" 17 | optionNameMinSwarm = "min-swarm" 18 | optionNameTimeout = "timeout" 19 | optionNameLabelSelector = "label-selector" 20 | ) 21 | 22 | cmd := &cobra.Command{ 23 | Use: "node-operator", 24 | Short: "scans for scheduled Kubernetes pods and funds them", 25 | Long: `Node operator scans for scheduled Kubernetes pods and funds them using node-funder. beekeeper node-operator`, 26 | RunE: func(cmd *cobra.Command, args []string) (err error) { 27 | return c.withTimeoutHandler(cmd, func(ctx context.Context) error { 28 | namespace := c.globalConfig.GetString(optionNameNamespace) 29 | if namespace == "" { 30 | return errors.New("namespace not provided") 31 | } 32 | 33 | if !c.globalConfig.IsSet(optionNameGethURL) { 34 | return errBlockchainEndpointNotProvided 35 | } 36 | 37 | walletKey := c.globalConfig.GetString(optionNameWalletKey) 38 | if walletKey == "" { 39 | return errors.New("wallet key not provided") 40 | } 41 | 42 | return operator.NewClient(&operator.ClientConfig{ 43 | Log: c.log, 44 | Namespace: namespace, 45 | WalletKey: walletKey, 46 | ChainNodeEndpoint: c.globalConfig.GetString(optionNameGethURL), 47 | NativeToken: c.globalConfig.GetFloat64(optionNameMinNative), 48 | SwarmToken: c.globalConfig.GetFloat64(optionNameMinSwarm), 49 | K8sClient: c.k8sClient, 50 | LabelSelector: c.globalConfig.GetString(optionNameLabelSelector), 51 | }).Run(ctx) 52 | }) 53 | }, 54 | PreRunE: c.preRunE, 55 | } 56 | 57 | cmd.Flags().StringP(optionNameNamespace, "n", "", "Kubernetes namespace to scan for scheduled pods.") 58 | cmd.Flags().String(optionNameWalletKey, "", "Hex-encoded private key for the Bee node wallet. Required.") 59 | cmd.Flags().Float64(optionNameMinNative, 0, "Minimum amount of chain native coins (xDAI) nodes should have.") 60 | cmd.Flags().Float64(optionNameMinSwarm, 0, "Minimum amount of swarm tokens (xBZZ) nodes should have.") 61 | cmd.Flags().String(optionNameLabelSelector, nodeFunderLabelSelector, "Kubernetes label selector for filtering resources within the specified namespace. Use an empty string to select all resources.") 62 | cmd.Flags().Duration(optionNameTimeout, 0*time.Minute, "Operation timeout (e.g., 5s, 10m, 1.5h). Default is 0, which means no timeout.") 63 | 64 | c.root.AddCommand(cmd) 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/restart.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/ethersphere/beekeeper/pkg/restart" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func (c *command) initRestartCmd() (err error) { 14 | const ( 15 | optionNameLabelSelector = "label-selector" 16 | optionNameNamespace = "namespace" 17 | optionNameImage = "image" 18 | optionNameNodeGroups = "node-groups" 19 | optionNameTimeout = "timeout" 20 | ) 21 | 22 | cmd := &cobra.Command{ 23 | Use: "restart", 24 | Short: "Restart pods in a cluster or namespace", 25 | Long: `Restarts pods by deleting them. Uses cluster name as the primary scope or falls back to namespace, with optional label filtering.`, 26 | RunE: func(cmd *cobra.Command, args []string) (err error) { 27 | ctx, cancel := context.WithTimeout(cmd.Context(), c.globalConfig.GetDuration(optionNameTimeout)) 28 | defer cancel() 29 | 30 | clusterName := c.globalConfig.GetString(optionNameClusterName) 31 | namespace := c.globalConfig.GetString(optionNameNamespace) 32 | 33 | if clusterName == "" && namespace == "" { 34 | return errors.New("either cluster name or namespace must be provided") 35 | } 36 | 37 | restartClient := restart.NewClient(c.k8sClient, c.log) 38 | 39 | if clusterName != "" { 40 | clusterConfig, ok := c.config.Clusters[clusterName] 41 | if !ok { 42 | return fmt.Errorf("cluster config %s not defined", clusterName) 43 | } 44 | 45 | cluster, err := c.setupCluster(ctx, clusterName, false) 46 | if err != nil { 47 | return fmt.Errorf("setting up cluster %s: %w", clusterName, err) 48 | } 49 | 50 | c.log.Infof("restarting cluster %s", clusterName) 51 | 52 | if err := restartClient.RestartCluster(ctx, 53 | cluster, 54 | clusterConfig.GetNamespace(), 55 | c.globalConfig.GetString(optionNameImage), 56 | c.globalConfig.GetStringSlice(optionNameNodeGroups), 57 | ); err != nil { 58 | return fmt.Errorf("restarting cluster %s: %w", clusterName, err) 59 | } 60 | 61 | return nil 62 | } 63 | 64 | if err := restartClient.RestartPods(ctx, namespace, c.globalConfig.GetString(optionNameLabelSelector)); err != nil { 65 | return fmt.Errorf("restarting pods in namespace %s: %w", namespace, err) 66 | } 67 | 68 | return nil 69 | }, 70 | PreRunE: c.preRunE, 71 | } 72 | 73 | cmd.Flags().String(optionNameClusterName, "", "Kubernetes cluster to operate on (overrides namespace and label selector).") 74 | cmd.Flags().StringP(optionNameNamespace, "n", "", "Namespace to delete pods from (only used if cluster name is not set).") 75 | cmd.Flags().String(optionNameLabelSelector, "", "Label selector for resources in the namespace (only used with namespace).") 76 | cmd.Flags().String(optionNameImage, "", "Container image to use when restarting pods (defaults to current image if not set).") 77 | cmd.Flags().StringSlice(optionNameNodeGroups, nil, "List of node groups to target for restarts (applies to all groups if not set).") 78 | cmd.Flags().Duration(optionNameTimeout, 5*time.Minute, "Operation timeout (e.g., 5s, 10m, 1.5h).") 79 | 80 | c.root.AddCommand(cmd) 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /cmd/beekeeper/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/ethersphere/beekeeper" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func (c *command) initVersionCmd() { 10 | c.root.AddCommand(&cobra.Command{ 11 | Use: "version", 12 | Short: "prints version number", 13 | Long: `Prints version number.`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | cmd.Println(beekeeper.Version) 16 | }, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/beekeeper/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ethersphere/beekeeper/cmd/beekeeper/cmd" 8 | _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" 9 | ) 10 | 11 | func main() { 12 | if err := cmd.Execute(); err != nil { 13 | fmt.Fprintln(os.Stderr, "Error:", err) 14 | os.Exit(1) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/beekeeper-local.yaml: -------------------------------------------------------------------------------- 1 | # config-dir: $HOME/.beekeeper 2 | # config-git-repo: "https://github.com/ethersphere/beekeeper" 3 | # config-git-dir: "" 4 | # config-git-branch: main 5 | # config-git-username: 6 | # config-git-password: 7 | enable-k8s: true 8 | in-cluster: false 9 | kubeconfig: "~/.kube/config" 10 | geth-url: http://geth-swap.localhost #http://geth-swap.bee-playground.testnet.internal 11 | bzz-token-address: 0x6aab14fe9cccd64a502d23842d916eb5321c26e7 #0x543dDb01Ba47acB11de34891cD86B675F04840db Sepolia 12 | eth-account: 0x62cab2b3b55f341f10348720ca18063cdb779ad5 13 | wallet-key: 4663c222787e30c1994b59044aa5045377a6e79193a8ead88293926b535c722d 14 | log-verbosity: "info" 15 | tracing-enable: false 16 | tracing-endpoint: 10.10.11.199:6831 17 | # tracing-host: 127.0.0.1 18 | # tracing-port: 6831 19 | tracing-service-name: beekeeper 20 | loki-endpoint: "" #http://loki.testnet.internal/loki/api/v1/push 21 | -------------------------------------------------------------------------------- /config/light-node.yaml: -------------------------------------------------------------------------------- 1 | # bee config for light nodes 2 | bee-configs: 3 | light-node: 4 | _inherit: default 5 | full-node: false 6 | 7 | # node groups for light nodes 8 | node-groups: 9 | light-node: 10 | _inherit: default 11 | image: ethersphere/bee:2.3.0 12 | image-pull-policy: Always 13 | persistence-enabled: false 14 | -------------------------------------------------------------------------------- /config/localhost.yaml: -------------------------------------------------------------------------------- 1 | clusters: 2 | localhost: 3 | _inherit: "default" 4 | use-static-endpoints: true 5 | node-groups: 6 | bee-1: 7 | mode: node 8 | bee-config: default 9 | config: default 10 | count: 1 11 | endpoints: 12 | - name: bee-0 13 | api-url: http://localhost:1633 14 | -------------------------------------------------------------------------------- /config/public-testnet-static.yaml: -------------------------------------------------------------------------------- 1 | clusters: 2 | bee-testnet-static: 3 | _inherit: "default" 4 | namespace: bee-testnet 5 | use-static-endpoints: true 6 | node-groups: 7 | bee-1: 8 | mode: node 9 | bee-config: default 10 | config: default 11 | count: 5 12 | endpoints: 13 | - name: bee-1-0 14 | api-url: http://bee-1-0.bee-testnet.testnet.internal 15 | - name: bee-1-1 16 | api-url: http://bee-1-1.bee-testnet.testnet.internal 17 | - name: bee-1-2 18 | api-url: http://bee-1-2.bee-testnet.testnet.internal 19 | - name: bee-1-3 20 | api-url: http://bee-1-3.bee-testnet.testnet.internal 21 | - name: bee-1-4 22 | api-url: http://bee-1-4.bee-testnet.testnet.internal 23 | bee-2: 24 | mode: node 25 | bee-config: default 26 | config: default 27 | count: 5 28 | endpoints: 29 | - name: bee-2-0 30 | api-url: http://bee-2-0.bee-testnet.testnet.internal 31 | - name: bee-2-1 32 | api-url: http://bee-2-1.bee-testnet.testnet.internal 33 | - name: bee-2-2 34 | api-url: http://bee-2-2.bee-testnet.testnet.internal 35 | - name: bee-2-3 36 | api-url: http://bee-2-3.bee-testnet.testnet.internal 37 | - name: bee-2-4 38 | api-url: http://bee-2-4.bee-testnet.testnet.internal 39 | bee-3: 40 | mode: node 41 | bee-config: default 42 | config: default 43 | count: 5 44 | endpoints: 45 | - name: bee-3-0 46 | api-url: http://bee-3-0.bee-testnet.testnet.internal 47 | - name: bee-3-1 48 | api-url: http://bee-3-1.bee-testnet.testnet.internal 49 | - name: bee-3-2 50 | api-url: http://bee-3-2.bee-testnet.testnet.internal 51 | - name: bee-3-3 52 | api-url: http://bee-3-3.bee-testnet.testnet.internal 53 | - name: bee-3-4 54 | api-url: http://bee-3-4.bee-testnet.testnet.internal 55 | bee-4: 56 | mode: node 57 | bee-config: default 58 | config: default 59 | count: 5 60 | endpoints: 61 | - name: bee-4-0 62 | api-url: http://bee-4-0.bee-testnet.testnet.internal 63 | - name: bee-4-1 64 | api-url: http://bee-4-1.bee-testnet.testnet.internal 65 | - name: bee-4-2 66 | api-url: http://bee-4-2.bee-testnet.testnet.internal 67 | - name: bee-4-3 68 | api-url: http://bee-4-3.bee-testnet.testnet.internal 69 | - name: bee-4-4 70 | api-url: http://bee-4-4.bee-testnet.testnet.internal 71 | -------------------------------------------------------------------------------- /config/staging.yaml: -------------------------------------------------------------------------------- 1 | # clusters defines clusters Beekeeper works with 2 | # clusters may inherit it's configuration from already defined cluster and override specific fields from it 3 | clusters: 4 | staging: 5 | _inherit: "" 6 | namespace: bee-playground 7 | disable-namespace: false 8 | use-static-endpoints: false 9 | api-domain: testnet.internal # testnet.ethswarm.org 10 | api-domain-internal: svc.swarm1.local:1633 # Internal API domain with port when in-cluster is set to true 11 | api-insecure-tls: true 12 | api-scheme: http 13 | funding: 14 | eth: 0.01 15 | bzz: 1.0 16 | node-groups: 17 | bee: 18 | mode: node 19 | bee-config: staging 20 | config: staging 21 | count: 5 22 | 23 | # node-groups defines node groups that can be registered in the cluster 24 | # node-groups may inherit it's configuration from already defined node-group and override specific fields from it 25 | node-groups: 26 | staging: 27 | _inherit: "default" 28 | persistence-enabled: true 29 | 30 | # bee-configs defines Bee configuration that can be assigned to node-groups 31 | # bee-configs may inherit it's configuration from already defined bee-config and override specific fields from it 32 | bee-configs: 33 | staging: 34 | _inherit: "" 35 | api-addr: ":1633" 36 | blockchain-rpc-endpoint: http://rpc-sepolia-haproxy.default.svc.swarm1.local 37 | bootnodes: /dnsaddr/testnet.ethswarm.org 38 | full-node: true 39 | mainnet: false 40 | network-id: 10 41 | p2p-addr: ":1634" 42 | password: "beekeeper" 43 | swap-enable: true 44 | tracing-enabled: true 45 | tracing-endpoint: "10.10.11.199:6831" 46 | tracing-service-name: "bee" 47 | verbosity: 4 48 | welcome-message: Welcome to the bee staging environment created by Beekeeper! 49 | 50 | checks: 51 | st-testnet-load: 52 | options: 53 | content-size: 50000000 54 | postage-ttl: 336h 55 | postage-depth: 22 56 | postage-label: test-label 57 | duration: 12h 58 | uploader-count: 2 59 | downloader-count: 0 60 | max-use-batch: 1h 61 | max-committed-depth: 3 62 | committed-depth-check-wait: 5m 63 | upload-groups: 64 | - bee-1 65 | - bee-2 66 | download-groups: 67 | - bee-3 68 | - bee-4 69 | timeout: 12h 70 | type: load 71 | -------------------------------------------------------------------------------- /mocks/k8s/app.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | v1 "k8s.io/client-go/kubernetes/typed/apps/v1" 5 | rest "k8s.io/client-go/rest" 6 | ) 7 | 8 | const ( 9 | CreateBad string = "create_bad" 10 | UpdateBad string = "update_bad" 11 | DeleteBad string = "delete_bad" 12 | ) 13 | 14 | // compile simulation whether ClientsetMock implements interface 15 | var _ v1.AppsV1Interface = (*AppV1)(nil) 16 | 17 | type AppV1 struct{} 18 | 19 | func NewAppV1() *AppV1 { 20 | return &AppV1{} 21 | } 22 | 23 | // ControllerRevisions implements v1.AppsV1Interface 24 | func (*AppV1) ControllerRevisions(namespace string) v1.ControllerRevisionInterface { 25 | panic("unimplemented") 26 | } 27 | 28 | // DaemonSets implements v1.AppsV1Interface 29 | func (*AppV1) DaemonSets(namespace string) v1.DaemonSetInterface { 30 | panic("unimplemented") 31 | } 32 | 33 | // Deployments implements v1.AppsV1Interface 34 | func (*AppV1) Deployments(namespace string) v1.DeploymentInterface { 35 | panic("unimplemented") 36 | } 37 | 38 | // ReplicaSets implements v1.AppsV1Interface 39 | func (*AppV1) ReplicaSets(namespace string) v1.ReplicaSetInterface { 40 | panic("unimplemented") 41 | } 42 | 43 | // StatefulSets implements v1.AppsV1Interface 44 | func (*AppV1) StatefulSets(namespace string) v1.StatefulSetInterface { 45 | return NewStatefulSet(namespace) 46 | } 47 | 48 | // RESTClient implements v1.AppsV1Interface 49 | func (*AppV1) RESTClient() rest.Interface { 50 | panic("unimplemented") 51 | } 52 | -------------------------------------------------------------------------------- /mocks/k8s/configmap.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | types "k8s.io/apimachinery/pkg/types" 12 | watch "k8s.io/apimachinery/pkg/watch" 13 | cofnigcorev1 "k8s.io/client-go/applyconfigurations/core/v1" 14 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 15 | ) 16 | 17 | // compile simulation whether ClientsetMock implements interface 18 | var _ corev1.ConfigMapInterface = (*ConfigMap)(nil) 19 | 20 | type ConfigMap struct{} 21 | 22 | func NewConfigMap() *ConfigMap { 23 | return &ConfigMap{} 24 | } 25 | 26 | // Apply implements v1.ConfigMapInterface 27 | func (*ConfigMap) Apply(ctx context.Context, configMap *cofnigcorev1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions) (result *v1.ConfigMap, err error) { 28 | panic("unimplemented") 29 | } 30 | 31 | // Create implements v1.ConfigMapInterface 32 | func (c *ConfigMap) Create(ctx context.Context, configMap *v1.ConfigMap, opts metav1.CreateOptions) (*v1.ConfigMap, error) { 33 | if configMap.ObjectMeta.Name == CreateBad { 34 | return nil, fmt.Errorf("mock error: cannot create config map") 35 | } else { 36 | return nil, fmt.Errorf("mock error: unknown") 37 | } 38 | } 39 | 40 | // Delete implements v1.ConfigMapInterface 41 | func (c *ConfigMap) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 42 | if name == DeleteBad { 43 | return fmt.Errorf("mock error: cannot delete config map") 44 | } else { 45 | return errors.NewNotFound(schema.GroupResource{}, name) 46 | } 47 | } 48 | 49 | // DeleteCollection implements v1.ConfigMapInterface 50 | func (*ConfigMap) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 51 | panic("unimplemented") 52 | } 53 | 54 | // Get implements v1.ConfigMapInterface 55 | func (*ConfigMap) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ConfigMap, error) { 56 | panic("unimplemented") 57 | } 58 | 59 | // List implements v1.ConfigMapInterface 60 | func (*ConfigMap) List(ctx context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { 61 | panic("unimplemented") 62 | } 63 | 64 | // Patch implements v1.ConfigMapInterface 65 | func (*ConfigMap) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ConfigMap, err error) { 66 | panic("unimplemented") 67 | } 68 | 69 | // Update implements v1.ConfigMapInterface 70 | func (c *ConfigMap) Update(ctx context.Context, configMap *v1.ConfigMap, opts metav1.UpdateOptions) (*v1.ConfigMap, error) { 71 | if configMap.ObjectMeta.Name == UpdateBad { 72 | return nil, errors.NewBadRequest("mock error: cannot update config map") 73 | } else { 74 | return nil, errors.NewNotFound(schema.GroupResource{}, configMap.ObjectMeta.Name) 75 | } 76 | } 77 | 78 | // Watch implements v1.ConfigMapInterface 79 | func (*ConfigMap) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 80 | panic("unimplemented") 81 | } 82 | -------------------------------------------------------------------------------- /mocks/k8s/core.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 5 | rest "k8s.io/client-go/rest" 6 | ) 7 | 8 | // compile simulation whether ClientsetMock implements interface 9 | var _ corev1.CoreV1Interface = (*CoreV1)(nil) 10 | 11 | type CoreV1 struct{} 12 | 13 | func NewCoreV1() *CoreV1 { 14 | return &CoreV1{} 15 | } 16 | 17 | // ComponentStatuses implements v1.CoreV1Interface 18 | func (*CoreV1) ComponentStatuses() corev1.ComponentStatusInterface { 19 | panic("unimplemented") 20 | } 21 | 22 | // ConfigMaps implements v1.CoreV1Interface 23 | func (*CoreV1) ConfigMaps(namespace string) corev1.ConfigMapInterface { 24 | return NewConfigMap() 25 | } 26 | 27 | // Endpoints implements v1.CoreV1Interface 28 | func (*CoreV1) Endpoints(namespace string) corev1.EndpointsInterface { 29 | panic("unimplemented") 30 | } 31 | 32 | // Events implements v1.CoreV1Interface 33 | func (*CoreV1) Events(namespace string) corev1.EventInterface { 34 | panic("unimplemented") 35 | } 36 | 37 | // LimitRanges implements v1.CoreV1Interface 38 | func (*CoreV1) LimitRanges(namespace string) corev1.LimitRangeInterface { 39 | panic("unimplemented") 40 | } 41 | 42 | // Namespaces implements v1.CoreV1Interface 43 | func (*CoreV1) Namespaces() corev1.NamespaceInterface { 44 | return NewNamespace() 45 | } 46 | 47 | // Nodes implements v1.CoreV1Interface 48 | func (*CoreV1) Nodes() corev1.NodeInterface { 49 | panic("unimplemented") 50 | } 51 | 52 | // PersistentVolumes implements v1.CoreV1Interface 53 | func (*CoreV1) PersistentVolumes() corev1.PersistentVolumeInterface { 54 | panic("unimplemented") 55 | } 56 | 57 | // PersistentVolumeClaims implements v1.CoreV1Interface 58 | func (*CoreV1) PersistentVolumeClaims(namespace string) corev1.PersistentVolumeClaimInterface { 59 | return NewPvc() 60 | } 61 | 62 | // Pods implements v1.CoreV1Interface 63 | func (*CoreV1) Pods(namespace string) corev1.PodInterface { 64 | return NewPod() 65 | } 66 | 67 | // PodTemplates implements v1.CoreV1Interface 68 | func (*CoreV1) PodTemplates(namespace string) corev1.PodTemplateInterface { 69 | panic("unimplemented") 70 | } 71 | 72 | // ReplicationControllers implements v1.CoreV1Interface 73 | func (*CoreV1) ReplicationControllers(namespace string) corev1.ReplicationControllerInterface { 74 | panic("unimplemented") 75 | } 76 | 77 | // ResourceQuotas implements v1.CoreV1Interface 78 | func (*CoreV1) ResourceQuotas(namespace string) corev1.ResourceQuotaInterface { 79 | panic("unimplemented") 80 | } 81 | 82 | // Secrets implements v1.CoreV1Interface 83 | func (*CoreV1) Secrets(namespace string) corev1.SecretInterface { 84 | return NewSecret() 85 | } 86 | 87 | // Services implements v1.CoreV1Interface 88 | func (*CoreV1) Services(namespace string) corev1.ServiceInterface { 89 | return NewService() 90 | } 91 | 92 | // ServiceAccounts implements v1.CoreV1Interface 93 | func (*CoreV1) ServiceAccounts(namespace string) corev1.ServiceAccountInterface { 94 | return NewServiceAccount() 95 | } 96 | 97 | // RESTClient implements v1.CoreV1Interface 98 | func (*CoreV1) RESTClient() rest.Interface { 99 | panic("unimplemented") 100 | } 101 | -------------------------------------------------------------------------------- /mocks/k8s/ingress.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | netv1 "k8s.io/api/networking/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | types "k8s.io/apimachinery/pkg/types" 12 | watch "k8s.io/apimachinery/pkg/watch" 13 | networkingv1 "k8s.io/client-go/applyconfigurations/networking/v1" 14 | v1 "k8s.io/client-go/kubernetes/typed/networking/v1" 15 | ) 16 | 17 | // compile simulation whether ClientsetMock implements interface 18 | var _ v1.IngressInterface = (*Ingress)(nil) 19 | 20 | type Ingress struct{} 21 | 22 | func NewIngress() *Ingress { 23 | return &Ingress{} 24 | } 25 | 26 | // Apply implements v1.IngressInterface 27 | func (*Ingress) Apply(ctx context.Context, ingress *networkingv1.IngressApplyConfiguration, opts metav1.ApplyOptions) (result *netv1.Ingress, err error) { 28 | panic("unimplemented") 29 | } 30 | 31 | // ApplyStatus implements v1.IngressInterface 32 | func (*Ingress) ApplyStatus(ctx context.Context, ingress *networkingv1.IngressApplyConfiguration, opts metav1.ApplyOptions) (result *netv1.Ingress, err error) { 33 | panic("unimplemented") 34 | } 35 | 36 | // Create implements v1.IngressInterface 37 | func (*Ingress) Create(ctx context.Context, ingress *netv1.Ingress, opts metav1.CreateOptions) (*netv1.Ingress, error) { 38 | if ingress.ObjectMeta.Name == CreateBad { 39 | return nil, fmt.Errorf("mock error: cannot create ingress") 40 | } else { 41 | return nil, fmt.Errorf("mock error: unknown") 42 | } 43 | } 44 | 45 | // Delete implements v1.IngressInterface 46 | func (*Ingress) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 47 | if name == DeleteBad { 48 | return fmt.Errorf("mock error: cannot delete ingress") 49 | } else { 50 | return errors.NewNotFound(schema.GroupResource{}, name) 51 | } 52 | } 53 | 54 | // DeleteCollection implements v1.IngressInterface 55 | func (*Ingress) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 56 | panic("unimplemented") 57 | } 58 | 59 | // Get implements v1.IngressInterface 60 | func (*Ingress) Get(ctx context.Context, name string, opts metav1.GetOptions) (*netv1.Ingress, error) { 61 | panic("unimplemented") 62 | } 63 | 64 | // List implements v1.IngressInterface 65 | func (*Ingress) List(ctx context.Context, opts metav1.ListOptions) (*netv1.IngressList, error) { 66 | panic("unimplemented") 67 | } 68 | 69 | // Patch implements v1.IngressInterface 70 | func (*Ingress) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *netv1.Ingress, err error) { 71 | panic("unimplemented") 72 | } 73 | 74 | // Update implements v1.IngressInterface 75 | func (*Ingress) Update(ctx context.Context, ingress *netv1.Ingress, opts metav1.UpdateOptions) (*netv1.Ingress, error) { 76 | if ingress.ObjectMeta.Name == UpdateBad { 77 | return nil, errors.NewBadRequest("mock error: cannot update ingress") 78 | } else { 79 | return nil, errors.NewNotFound(schema.GroupResource{}, ingress.ObjectMeta.Name) 80 | } 81 | } 82 | 83 | // UpdateStatus implements v1.IngressInterface 84 | func (*Ingress) UpdateStatus(ctx context.Context, ingress *netv1.Ingress, opts metav1.UpdateOptions) (*netv1.Ingress, error) { 85 | panic("unimplemented") 86 | } 87 | 88 | // Watch implements v1.IngressInterface 89 | func (*Ingress) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 90 | panic("unimplemented") 91 | } 92 | -------------------------------------------------------------------------------- /mocks/k8s/k8s.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/k8s/customresource/ingressroute" 7 | "k8s.io/client-go/kubernetes" 8 | "k8s.io/client-go/rest" 9 | ) 10 | 11 | // compile simulation whether ClientsetMock implements interface 12 | var _ kubernetes.Interface = (*Clientset)(nil) 13 | 14 | type Client struct { 15 | expectError bool 16 | } 17 | 18 | func NewClient(expectError bool) *Client { 19 | return &Client{expectError: expectError} 20 | } 21 | 22 | // NewForConfig returns a new Kubernetes clientset 23 | func (c *Client) NewForConfig(*rest.Config) (*kubernetes.Clientset, error) { 24 | if c.expectError { 25 | return nil, fmt.Errorf("mock error") 26 | } 27 | return &kubernetes.Clientset{}, nil 28 | } 29 | 30 | // NewIngressRouteClientForConfig returns a new ingressroute client 31 | func (c *Client) NewIngressRouteClientForConfig(*rest.Config) (*ingressroute.CustomResourceClient, error) { 32 | if c.expectError { 33 | return nil, fmt.Errorf("mock error") 34 | } 35 | return &ingressroute.CustomResourceClient{}, nil 36 | } 37 | 38 | func (c *Client) InClusterConfig() (*rest.Config, error) { 39 | if c.expectError { 40 | return nil, fmt.Errorf("mock error") 41 | } 42 | return &rest.Config{}, nil 43 | } 44 | 45 | func (c *Client) BuildConfigFromFlags(masterUrl string, kubeconfigPath string) (*rest.Config, error) { 46 | if c.expectError { 47 | return nil, fmt.Errorf("mock error") 48 | } 49 | return &rest.Config{}, nil 50 | } 51 | 52 | func (c *Client) OsUserHomeDir() (string, error) { 53 | if c.expectError { 54 | return "", fmt.Errorf("mock error") 55 | } 56 | return "home", nil 57 | } 58 | 59 | func FlagString(name string, value string, usage string) *string { 60 | return new(string) 61 | } 62 | 63 | func FlagParse() {} 64 | -------------------------------------------------------------------------------- /mocks/k8s/namespace.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/ethersphere/beekeeper" 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | types "k8s.io/apimachinery/pkg/types" 11 | watch "k8s.io/apimachinery/pkg/watch" 12 | corev1 "k8s.io/client-go/applyconfigurations/core/v1" 13 | typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" 14 | ) 15 | 16 | // compile simulation whether ClientsetMock implements interface 17 | var _ typedv1.NamespaceInterface = (*Namespace)(nil) 18 | 19 | type Namespace struct{} 20 | 21 | func NewNamespace() *Namespace { 22 | return &Namespace{} 23 | } 24 | 25 | // Finalize implements v1.NamespaceInterface 26 | func (*Namespace) Finalize(ctx context.Context, item *v1.Namespace, opts metav1.UpdateOptions) (*v1.Namespace, error) { 27 | panic("unimplemented") 28 | } 29 | 30 | // Apply implements v1.NamespaceInterface 31 | func (*Namespace) Apply(ctx context.Context, namespace *corev1.NamespaceApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Namespace, err error) { 32 | panic("unimplemented") 33 | } 34 | 35 | // ApplyStatus implements v1.NamespaceInterface 36 | func (*Namespace) ApplyStatus(ctx context.Context, namespace *corev1.NamespaceApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Namespace, err error) { 37 | panic("unimplemented") 38 | } 39 | 40 | // Create implements v1.NamespaceInterface 41 | func (nm *Namespace) Create(ctx context.Context, namespace *v1.Namespace, opts metav1.CreateOptions) (*v1.Namespace, error) { 42 | return namespace, nil 43 | } 44 | 45 | // Delete implements v1.NamespaceInterface 46 | func (*Namespace) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 47 | return fmt.Errorf("mock error: namespace \"%s\" can not be deleted", name) 48 | } 49 | 50 | // Get implements v1.NamespaceInterface 51 | func (*Namespace) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Namespace, error) { 52 | return &v1.Namespace{ 53 | ObjectMeta: metav1.ObjectMeta{ 54 | Name: "test", 55 | Annotations: map[string]string{ 56 | "created-by": fmt.Sprintf("beekeeper:%s", beekeeper.Version), 57 | }, 58 | Labels: map[string]string{ 59 | "app.kubernetes.io/managed-by": "beekeeper", 60 | }, 61 | }, 62 | }, nil 63 | } 64 | 65 | // List implements v1.NamespaceInterface 66 | func (*Namespace) List(ctx context.Context, opts metav1.ListOptions) (*v1.NamespaceList, error) { 67 | panic("unimplemented") 68 | } 69 | 70 | // Patch implements v1.NamespaceInterface 71 | func (*Namespace) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Namespace, err error) { 72 | panic("unimplemented") 73 | } 74 | 75 | // Update implements v1.NamespaceInterface 76 | func (*Namespace) Update(ctx context.Context, namespace *v1.Namespace, opts metav1.UpdateOptions) (*v1.Namespace, error) { 77 | panic("unimplemented") 78 | } 79 | 80 | // UpdateStatus implements v1.NamespaceInterface 81 | func (*Namespace) UpdateStatus(ctx context.Context, namespace *v1.Namespace, opts metav1.UpdateOptions) (*v1.Namespace, error) { 82 | panic("unimplemented") 83 | } 84 | 85 | // Watch implements v1.NamespaceInterface 86 | func (*Namespace) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 87 | panic("unimplemented") 88 | } 89 | -------------------------------------------------------------------------------- /mocks/k8s/networking.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | v1 "k8s.io/client-go/kubernetes/typed/networking/v1" 5 | rest "k8s.io/client-go/rest" 6 | ) 7 | 8 | // compile simulation whether ClientsetMock implements interface 9 | var _ v1.NetworkingV1Interface = (*NetworkingV1)(nil) 10 | 11 | type NetworkingV1 struct{} 12 | 13 | func NewNetworkingV1() *NetworkingV1 { 14 | return &NetworkingV1{} 15 | } 16 | 17 | // Ingresses implements v1.NetworkingV1Interface 18 | func (*NetworkingV1) Ingresses(namespace string) v1.IngressInterface { 19 | return NewIngress() 20 | } 21 | 22 | // IngressClasses implements v1.NetworkingV1Interface 23 | func (*NetworkingV1) IngressClasses() v1.IngressClassInterface { 24 | panic("unimplemented") 25 | } 26 | 27 | // NetworkPolicies implements v1.NetworkingV1Interface 28 | func (*NetworkingV1) NetworkPolicies(namespace string) v1.NetworkPolicyInterface { 29 | panic("unimplemented") 30 | } 31 | 32 | // RESTClient implements v1.NetworkingV1Interface 33 | func (*NetworkingV1) RESTClient() rest.Interface { 34 | panic("unimplemented") 35 | } 36 | -------------------------------------------------------------------------------- /mocks/k8s/roundtripper.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "net/http" 4 | 5 | // MockRoundTripper is a mock implementation of the RoundTripper interface. 6 | type MockRoundTripper struct { 7 | RoundTripFunc func(req *http.Request) (*http.Response, error) 8 | } 9 | 10 | // RoundTrip calls the RoundTripFunc method of the mock. 11 | func (m *MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 12 | return m.RoundTripFunc(req) 13 | } 14 | -------------------------------------------------------------------------------- /mocks/k8s/secret.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | types "k8s.io/apimachinery/pkg/types" 12 | watch "k8s.io/apimachinery/pkg/watch" 13 | configcorev1 "k8s.io/client-go/applyconfigurations/core/v1" 14 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 15 | ) 16 | 17 | // compile simulation whether ClientsetMock implements interface 18 | var _ corev1.SecretInterface = (*Secret)(nil) 19 | 20 | type Secret struct{} 21 | 22 | func NewSecret() *Secret { 23 | return &Secret{} 24 | } 25 | 26 | // Apply implements v1.SecretInterface 27 | func (*Secret) Apply(ctx context.Context, secret *configcorev1.SecretApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Secret, err error) { 28 | panic("unimplemented") 29 | } 30 | 31 | // Create implements v1.SecretInterface 32 | func (*Secret) Create(ctx context.Context, secret *v1.Secret, opts metav1.CreateOptions) (*v1.Secret, error) { 33 | if secret.ObjectMeta.Name == CreateBad { 34 | return nil, fmt.Errorf("mock error: cannot create secret") 35 | } else { 36 | return nil, fmt.Errorf("mock error: unknown") 37 | } 38 | } 39 | 40 | // Delete implements v1.SecretInterface 41 | func (*Secret) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 42 | if name == DeleteBad { 43 | return fmt.Errorf("mock error: cannot delete secret") 44 | } else { 45 | return errors.NewNotFound(schema.GroupResource{}, name) 46 | } 47 | } 48 | 49 | // DeleteCollection implements v1.SecretInterface 50 | func (*Secret) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 51 | panic("unimplemented") 52 | } 53 | 54 | // Get implements v1.SecretInterface 55 | func (*Secret) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Secret, error) { 56 | panic("unimplemented") 57 | } 58 | 59 | // List implements v1.SecretInterface 60 | func (*Secret) List(ctx context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { 61 | panic("unimplemented") 62 | } 63 | 64 | // Patch implements v1.SecretInterface 65 | func (*Secret) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Secret, err error) { 66 | panic("unimplemented") 67 | } 68 | 69 | // Update implements v1.SecretInterface 70 | func (*Secret) Update(ctx context.Context, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error) { 71 | if secret.ObjectMeta.Name == UpdateBad { 72 | return nil, errors.NewBadRequest("mock error: cannot update secret") 73 | } else { 74 | return nil, errors.NewNotFound(schema.GroupResource{}, secret.ObjectMeta.Name) 75 | } 76 | } 77 | 78 | // Watch implements v1.SecretInterface 79 | func (*Secret) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 80 | panic("unimplemented") 81 | } 82 | -------------------------------------------------------------------------------- /pkg/bee/api/act.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | type ActService service 13 | 14 | type ActUploadResponse struct { 15 | Reference swarm.Address `json:"reference"` 16 | HistoryAddress swarm.Address 17 | } 18 | 19 | type ActGranteesResponse struct { 20 | Reference swarm.Address `json:"ref"` 21 | HistoryAddress swarm.Address `json:"historyref"` 22 | } 23 | 24 | func (a *ActService) Download(ctx context.Context, addr swarm.Address, opts *DownloadOptions) (resp io.ReadCloser, err error) { 25 | return a.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/bzz/"+addr.String()+"/", nil, opts) 26 | } 27 | 28 | func (a *ActService) Upload(ctx context.Context, name string, data io.Reader, o UploadOptions) (ActUploadResponse, error) { 29 | var resp ActUploadResponse 30 | h := http.Header{} 31 | h.Add(postageStampBatchHeader, o.BatchID) 32 | h.Add("swarm-deferred-upload", "true") 33 | h.Add("content-type", "application/octet-stream") 34 | h.Add("Swarm-Act", "true") 35 | h.Add(swarmPinHeader, "true") 36 | historyParser := func(h http.Header) { 37 | resp.HistoryAddress, _ = swarm.ParseHexAddress(h.Get("Swarm-Act-History-Address")) 38 | } 39 | err := a.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/bzz?"+url.QueryEscape("name="+name), h, data, &resp, historyParser) 40 | return resp, err 41 | } 42 | 43 | func (a *ActService) AddGrantees(ctx context.Context, data io.Reader, o UploadOptions) (ActGranteesResponse, error) { 44 | var resp ActGranteesResponse 45 | h := http.Header{} 46 | h.Add(postageStampBatchHeader, o.BatchID) 47 | h.Add(swarmActHistoryAddress, o.ActHistoryAddress.String()) 48 | err := a.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/grantee", h, data, &resp) 49 | return resp, err 50 | } 51 | 52 | func (a *ActService) GetGrantees(ctx context.Context, addr swarm.Address) (resp io.ReadCloser, err error) { 53 | return a.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/grantee/"+addr.String(), nil, nil) 54 | } 55 | 56 | func (a *ActService) PatchGrantees(ctx context.Context, data io.Reader, addr swarm.Address, haddr swarm.Address, batchID string) (ActGranteesResponse, error) { 57 | var resp ActGranteesResponse 58 | h := http.Header{} 59 | h.Add("swarm-postage-batch-id", batchID) 60 | h.Add("swarm-act-history-address", haddr.String()) 61 | err := a.client.requestWithHeader(ctx, http.MethodPatch, "/"+apiVersion+"/grantee/"+addr.String(), h, data, &resp) 62 | return resp, err 63 | } 64 | -------------------------------------------------------------------------------- /pkg/bee/api/bytes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | // BytesService represents Bee's Bytes service 13 | type BytesService service 14 | 15 | // Download downloads data from the node 16 | func (b *BytesService) Download(ctx context.Context, a swarm.Address, opts *DownloadOptions) (resp io.ReadCloser, err error) { 17 | return b.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/bytes/"+a.String(), nil, opts) 18 | } 19 | 20 | // BytesUploadResponse represents Upload's response 21 | type BytesUploadResponse struct { 22 | Reference swarm.Address `json:"reference"` 23 | } 24 | 25 | // Upload uploads bytes to the node 26 | func (b *BytesService) Upload(ctx context.Context, data io.Reader, o UploadOptions) (BytesUploadResponse, error) { 27 | var resp BytesUploadResponse 28 | h := http.Header{} 29 | if o.Pin { 30 | h.Add(swarmPinHeader, "true") 31 | } 32 | if o.Tag != 0 { 33 | h.Add(swarmTagHeader, strconv.FormatUint(o.Tag, 10)) 34 | } 35 | h.Add(deferredUploadHeader, strconv.FormatBool(!o.Direct)) 36 | h.Add(postageStampBatchHeader, o.BatchID) 37 | err := b.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/bytes", h, data, &resp) 38 | return resp, err 39 | } 40 | -------------------------------------------------------------------------------- /pkg/bee/api/chunks.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | // ChunksService represents Bee's Chunks service 13 | type ChunksService service 14 | 15 | // Download downloads data from the node 16 | func (c *ChunksService) Download(ctx context.Context, a swarm.Address, targets string, opts *DownloadOptions) (resp io.ReadCloser, err error) { 17 | if targets == "" { 18 | return c.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/chunks/"+a.String(), nil, opts) 19 | } 20 | 21 | return c.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/chunks/"+a.String()+"?targets="+targets, nil, opts) 22 | } 23 | 24 | // ChunksUploadResponse represents Upload's response 25 | type ChunksUploadResponse struct { 26 | Reference swarm.Address `json:"reference"` 27 | } 28 | 29 | // Upload uploads chunks to the node 30 | func (c *ChunksService) Upload(ctx context.Context, data []byte, o UploadOptions) (ChunksUploadResponse, error) { 31 | var resp ChunksUploadResponse 32 | h := http.Header{} 33 | if o.Pin { 34 | h.Add(swarmPinHeader, "true") 35 | } 36 | if o.Direct { 37 | h.Add(deferredUploadHeader, "false") 38 | } 39 | h.Add(postageStampBatchHeader, o.BatchID) 40 | err := c.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/chunks", h, bytes.NewReader(data), &resp) 41 | return resp, err 42 | } 43 | -------------------------------------------------------------------------------- /pkg/bee/api/debugstore.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // DebugStoreService represents Bee's debug store service 9 | type DebugStoreService service 10 | 11 | // DebugStore represents DebugStore's response 12 | type DebugStore map[string]int 13 | 14 | // GetDebugStore gets db indices 15 | func (d *DebugStoreService) GetDebugStore(ctx context.Context) (DebugStore, error) { 16 | resp := make(DebugStore) 17 | err := d.client.requestJSON(ctx, http.MethodGet, "/debugstore", nil, &resp) 18 | return resp, err 19 | } 20 | -------------------------------------------------------------------------------- /pkg/bee/api/dirs.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | // DirsService represents Bee's Dirs service 13 | type DirsService service 14 | 15 | // Download downloads data from the node 16 | func (s *DirsService) Download(ctx context.Context, a swarm.Address, path string) (resp io.ReadCloser, err error) { 17 | return s.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/bzz/"+a.String()+"/"+path, nil, nil) 18 | } 19 | 20 | // DirsUploadResponse represents Upload's response 21 | type DirsUploadResponse struct { 22 | Reference swarm.Address `json:"reference"` 23 | } 24 | 25 | // Upload uploads TAR collection to the node 26 | func (s *DirsService) Upload(ctx context.Context, data io.Reader, size int64, o UploadOptions) (resp DirsUploadResponse, err error) { 27 | header := make(http.Header) 28 | header.Set("Content-Type", "application/x-tar") 29 | header.Set("Content-Length", strconv.FormatInt(size, 10)) 30 | header.Set("swarm-collection", "True") 31 | header.Set(postageStampBatchHeader, o.BatchID) 32 | 33 | if o.IndexDocument != "" { 34 | header.Set(swarmIndexDocumentHeader, o.IndexDocument) 35 | } 36 | if o.ErrorDocument != "" { 37 | header.Set(swarmErrorDocumentHeader, o.ErrorDocument) 38 | } 39 | 40 | err = s.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/bzz", header, data, &resp) 41 | 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /pkg/bee/api/errors.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | // HTTPStatusError represents the error derived from the HTTP response status 10 | // code. 11 | type HTTPStatusError struct { 12 | Code int 13 | } 14 | 15 | // NewHTTPStatusError creates a new instance of HTTPStatusError based on the 16 | // provided code. 17 | func NewHTTPStatusError(code int) *HTTPStatusError { 18 | return &HTTPStatusError{ 19 | Code: code, 20 | } 21 | } 22 | 23 | func (e *HTTPStatusError) Error() string { 24 | return fmt.Sprintf("%d %s", e.Code, http.StatusText(e.Code)) 25 | } 26 | 27 | // IsHTTPStatusErrorCode return whether the error is HTTPStatusError with a 28 | // specific HTTP status code. 29 | func IsHTTPStatusErrorCode(err error, code int) bool { 30 | var e *HTTPStatusError 31 | if errors.As(err, &e) { 32 | return e.Code == code 33 | } 34 | return false 35 | } 36 | -------------------------------------------------------------------------------- /pkg/bee/api/files.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "strconv" 9 | 10 | "github.com/ethersphere/bee/v2/pkg/swarm" 11 | ) 12 | 13 | // FilesService represents Bee's Files service 14 | type FilesService service 15 | 16 | // Download downloads data from the node 17 | func (f *FilesService) Download(ctx context.Context, a swarm.Address, opts *DownloadOptions) (resp io.ReadCloser, err error) { 18 | return f.client.requestData(ctx, http.MethodGet, "/"+apiVersion+"/bzz/"+a.String(), nil, opts) 19 | } 20 | 21 | // FilesUploadResponse represents Upload's response 22 | type FilesUploadResponse struct { 23 | Reference swarm.Address `json:"reference"` 24 | } 25 | 26 | // Upload uploads files to the node 27 | func (f *FilesService) Upload(ctx context.Context, name string, data io.Reader, size int64, o UploadOptions) (resp FilesUploadResponse, err error) { 28 | header := make(http.Header) 29 | header.Set("Content-Type", "application/octet-stream") 30 | header.Set("Content-Length", strconv.FormatInt(size, 10)) 31 | if o.Pin { 32 | header.Set(swarmPinHeader, "true") 33 | } 34 | if o.Tag != 0 { 35 | header.Set(swarmTagHeader, strconv.FormatUint(o.Tag, 10)) 36 | } 37 | if o.Direct { 38 | header.Set(deferredUploadHeader, strconv.FormatBool(false)) 39 | } 40 | header.Set(postageStampBatchHeader, o.BatchID) 41 | 42 | err = f.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/bzz?"+url.QueryEscape("name="+name), header, data, &resp) 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /pkg/bee/api/options.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/ethersphere/bee/v2/pkg/swarm" 4 | 5 | type UploadOptions struct { 6 | Act bool 7 | Pin bool 8 | Tag uint64 9 | BatchID string 10 | Direct bool 11 | ActHistoryAddress swarm.Address 12 | 13 | // Dirs 14 | IndexDocument string 15 | ErrorDocument string 16 | } 17 | 18 | type DownloadOptions struct { 19 | Act *bool 20 | ActHistoryAddress *swarm.Address 21 | ActPublicKey *swarm.Address 22 | ActTimestamp *uint64 23 | Cache *bool 24 | RedundancyFallbackMode *bool 25 | OnlyRootChunk *bool 26 | } 27 | -------------------------------------------------------------------------------- /pkg/bee/api/pingpong.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/ethersphere/bee/v2/pkg/swarm" 8 | ) 9 | 10 | // PingPongService represents Bee's PingPong service 11 | type PingPongService service 12 | 13 | // Pong represents Ping's response 14 | type Pong struct { 15 | RTT string `json:"rtt"` 16 | } 17 | 18 | // Ping pings given node 19 | func (p *PingPongService) Ping(ctx context.Context, a swarm.Address) (resp Pong, err error) { 20 | err = p.client.requestJSON(ctx, http.MethodPost, "/pingpong/"+a.String(), nil, &resp) 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /pkg/bee/api/pinning.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/ethersphere/bee/v2/pkg/swarm" 9 | ) 10 | 11 | // PinningService represents Bee's Pin service 12 | type PinningService service 13 | 14 | // pinsBasePath is the pins API base path for http requests. 15 | const pinsBasePath = "/pins" 16 | 17 | func pinsPath(path string) string { return pinsBasePath + "/" + path } 18 | 19 | // PinRootHash pins root hash of given reference. 20 | func (ps *PinningService) PinRootHash(ctx context.Context, ref swarm.Address) error { 21 | res := struct { 22 | Message string `json:"message,omitempty"` 23 | Code int `json:"code,omitempty"` 24 | }{} 25 | return ps.client.requestJSON(ctx, http.MethodPost, pinsPath(ref.String()), nil, &res) 26 | } 27 | 28 | // UnpinRootHash unpins root hash of given reference. 29 | func (ps *PinningService) UnpinRootHash(ctx context.Context, ref swarm.Address) error { 30 | res := struct { 31 | Message string `json:"message,omitempty"` 32 | Code int `json:"code,omitempty"` 33 | }{} 34 | return ps.client.requestJSON(ctx, http.MethodDelete, pinsPath(ref.String()), nil, &res) 35 | } 36 | 37 | // GetPinnedRootHash determines if the root hash of 38 | // given reference is pinned by returning its reference. 39 | func (ps *PinningService) GetPinnedRootHash(ctx context.Context, ref swarm.Address) (swarm.Address, error) { 40 | res := struct { 41 | Reference swarm.Address `json:"reference"` 42 | }{} 43 | err := ps.client.requestJSON(ctx, http.MethodGet, pinsPath(ref.String()), nil, &res) 44 | if err != nil { 45 | return swarm.ZeroAddress, fmt.Errorf("get pinned root hash: %w", err) 46 | } 47 | return res.Reference, nil 48 | } 49 | 50 | // GetPins returns all references of pinned root hashes. 51 | func (ps *PinningService) GetPins(ctx context.Context) ([]swarm.Address, error) { 52 | res := struct { 53 | References []swarm.Address `json:"references"` 54 | }{} 55 | err := ps.client.requestJSON(ctx, http.MethodGet, pinsBasePath, nil, &res) 56 | if err != nil { 57 | return nil, fmt.Errorf("get pins: %w", err) 58 | } 59 | return res.References, nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/bee/api/pss.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | // PSSService represents Bee's PSS service 13 | type PSSService service 14 | 15 | // Sends a PSS message to a recipienct with a specific topic 16 | func (p *PSSService) SendMessage(ctx context.Context, nodeAddress swarm.Address, nodePublicKey string, topic string, prefix int, data io.Reader, batchID string) error { 17 | h := http.Header{} 18 | h.Add(postageStampBatchHeader, batchID) 19 | 20 | url := fmt.Sprintf("/%s/pss/send/%s/%s?recipient=%s", apiVersion, topic, nodeAddress.String()[:prefix], nodePublicKey) 21 | 22 | return p.client.requestWithHeader(ctx, http.MethodPost, url, h, data, nil) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/bee/api/soc.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | // PSSService represents Bee's PSS service 13 | type SOCService service 14 | 15 | type SocResponse struct { 16 | Reference swarm.Address 17 | } 18 | 19 | // Sends a PSS message to a recipienct with a specific topic 20 | func (p *SOCService) UploadSOC(ctx context.Context, owner, ID, signature string, data io.Reader, batchID string) (*SocResponse, error) { 21 | h := http.Header{} 22 | h.Add(postageStampBatchHeader, batchID) 23 | url := fmt.Sprintf("/%s/soc/%s/%s?sig=%s", apiVersion, owner, ID, signature) 24 | 25 | resp := SocResponse{} 26 | return &resp, p.client.requestWithHeader(ctx, http.MethodPost, url, h, data, &resp) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/bee/api/stake.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "net/http" 8 | 9 | "github.com/ethersphere/beekeeper/pkg/bigint" 10 | ) 11 | 12 | // StakingService represents Bee's staking service 13 | type StakingService service 14 | 15 | type getStakeResponse struct { 16 | StakedAmount *bigint.BigInt `json:"stakedAmount"` 17 | } 18 | 19 | type getWithdrawableResponse struct { 20 | WithdrawableAmount *bigint.BigInt `json:"withdrawableAmount"` 21 | } 22 | type stakeDepositResponse struct { 23 | TxHash string `json:"txhash"` 24 | } 25 | type stakeWithdrawResponse struct { 26 | TxHash string `json:"txhash"` 27 | } 28 | 29 | // DepositStake deposits stake 30 | func (s *StakingService) DepositStake(ctx context.Context, amount *big.Int) (txHash string, err error) { 31 | r := new(stakeDepositResponse) 32 | err = s.client.requestJSON(ctx, http.MethodPost, fmt.Sprintf("/stake/%d", amount), nil, r) 33 | if err != nil { 34 | return "", err 35 | } 36 | return r.TxHash, nil 37 | } 38 | 39 | // GetStakedAmount gets stake 40 | func (s *StakingService) GetStakedAmount(ctx context.Context) (stakedAmount *big.Int, err error) { 41 | r := new(getStakeResponse) 42 | err = s.client.requestJSON(ctx, http.MethodGet, "/stake", nil, r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return r.StakedAmount.Int, nil 47 | } 48 | 49 | // GetWithdrawableStake gets stake 50 | func (s *StakingService) GetWithdrawableStake(ctx context.Context) (withdrawableStake *big.Int, err error) { 51 | r := new(getWithdrawableResponse) 52 | err = s.client.requestJSON(ctx, http.MethodGet, "/stake/withdrawable", nil, r) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return r.WithdrawableAmount.Int, nil 57 | } 58 | 59 | // MigrateStake withdraws stake 60 | func (s *StakingService) MigrateStake(ctx context.Context) (txHash string, err error) { 61 | r := new(stakeWithdrawResponse) 62 | err = s.client.requestJSON(ctx, http.MethodDelete, "/stake", nil, r) 63 | if err != nil { 64 | return "", err 65 | } 66 | return r.TxHash, nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/bee/api/status.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | type StatusService service 9 | 10 | type StatusResponse struct { 11 | Overlay string `json:"overlay"` 12 | Proximity uint `json:"proximity"` 13 | BeeMode string `json:"beeMode"` 14 | ReserveSize uint64 `json:"reserveSize"` 15 | ReserveSizeWithinRadius uint64 `json:"reserveSizeWithinRadius"` 16 | PullsyncRate float64 `json:"pullsyncRate"` 17 | StorageRadius uint8 `json:"storageRadius"` 18 | ConnectedPeers uint64 `json:"connectedPeers"` 19 | NeighborhoodSize uint64 `json:"neighborhoodSize"` 20 | RequestFailed bool `json:"requestFailed,omitempty"` 21 | BatchCommitment uint64 `json:"batchCommitment"` 22 | IsReachable bool `json:"isReachable"` 23 | LastSyncedBlock uint64 `json:"lastSyncedBlock"` 24 | CommittedDepth uint8 `json:"committedDepth"` 25 | } 26 | 27 | // Ping pings given node 28 | func (s *StatusService) Status(ctx context.Context) (resp *StatusResponse, err error) { 29 | err = s.client.requestJSON(ctx, http.MethodGet, "/status", nil, &resp) 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /pkg/bee/api/stewardship.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/ethersphere/bee/v2/pkg/swarm" 8 | ) 9 | 10 | // StewardshipService represents Bee's Stewardship service. 11 | type StewardshipService service 12 | 13 | // stewardshipBasePath is the stewardship API base path for http requests. 14 | const stewardshipBasePath = "/stewardship" 15 | 16 | func stewardshipPath(path string) string { return stewardshipBasePath + "/" + path } 17 | 18 | // IsRetrievable checks whether the content on the given address is retrievable. 19 | func (ss *StewardshipService) IsRetrievable(ctx context.Context, ref swarm.Address) (bool, error) { 20 | res := struct { 21 | IsRetrievable bool `json:"isRetrievable"` 22 | }{} 23 | err := ss.client.requestJSON(ctx, http.MethodGet, stewardshipPath(ref.String()), nil, &res) 24 | if err != nil { 25 | return false, err 26 | } 27 | return res.IsRetrievable, nil 28 | } 29 | 30 | // Reupload re-uploads root hash and all of its underlying associated chunks to 31 | // the network. 32 | func (ss *StewardshipService) Reupload(ctx context.Context, ref swarm.Address) error { 33 | res := struct { 34 | Message string `json:"message,omitempty"` 35 | Code int `json:"code,omitempty"` 36 | }{} 37 | return ss.client.requestJSON(ctx, http.MethodPut, stewardshipPath(ref.String()), nil, &res) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/bee/api/tags.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/ethersphere/bee/v2/pkg/swarm" 10 | ) 11 | 12 | // TagsService represents Bee's Tag service 13 | type TagsService service 14 | 15 | type TagResponse struct { 16 | Split uint64 `json:"split"` 17 | Seen uint64 `json:"seen"` 18 | Stored uint64 `json:"stored"` 19 | Sent uint64 `json:"sent"` 20 | Synced uint64 `json:"synced"` 21 | Uid uint64 `json:"uid"` 22 | Address swarm.Address `json:"address"` 23 | StartedAt time.Time `json:"startedAt"` 24 | } 25 | 26 | // CreateTag creates new tag 27 | func (p *TagsService) CreateTag(ctx context.Context) (resp TagResponse, err error) { 28 | err = p.client.requestJSON(ctx, http.MethodPost, "/tags", nil, &resp) 29 | return 30 | } 31 | 32 | // GetTag gets a new tag 33 | func (p *TagsService) GetTag(ctx context.Context, tagUID uint64) (resp TagResponse, err error) { 34 | tag := strconv.FormatUint(tagUID, 10) 35 | 36 | err = p.client.requestJSON(ctx, http.MethodGet, "/tags/"+tag, nil, &resp) 37 | 38 | return resp, err 39 | } 40 | 41 | func (p *TagsService) WaitSync(ctx context.Context, tagUID uint64) (err error) { 42 | c := make(chan bool) 43 | defer close(c) 44 | 45 | e := make(chan error) 46 | defer close(e) 47 | 48 | go func(ctx context.Context, c chan bool, e chan error) { 49 | for { 50 | select { 51 | case <-ctx.Done(): 52 | return 53 | default: 54 | tr, err := p.GetTag(ctx, tagUID) 55 | if err != nil { 56 | e <- err 57 | return 58 | } 59 | 60 | if tr.Split-tr.Seen == tr.Synced { 61 | c <- true 62 | return 63 | } 64 | 65 | time.Sleep(1000 * time.Millisecond) 66 | } 67 | } 68 | }(ctx, c, e) 69 | 70 | select { 71 | case <-c: 72 | return 73 | case err := <-e: 74 | return err 75 | case <-ctx.Done(): 76 | return ctx.Err() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pkg/bee/file.go: -------------------------------------------------------------------------------- 1 | package bee 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "hash" 7 | "io" 8 | "math/rand" 9 | 10 | "github.com/ethersphere/bee/v2/pkg/swarm" 11 | "golang.org/x/crypto/sha3" 12 | ) 13 | 14 | // File represents Bee file 15 | type File struct { 16 | address swarm.Address 17 | name string 18 | hash []byte 19 | dataReader io.Reader 20 | size int64 21 | historyAddress swarm.Address 22 | } 23 | 24 | // NewRandomFile returns new pseudorandom file 25 | func NewRandomFile(r *rand.Rand, name string, size int64) File { 26 | return File{ 27 | name: name, 28 | dataReader: io.LimitReader(r, size), 29 | size: size, 30 | } 31 | } 32 | 33 | // NewBufferFile returns new file with specified buffer 34 | func NewBufferFile(name string, buffer *bytes.Buffer) File { 35 | return File{ 36 | name: name, 37 | dataReader: buffer, 38 | size: int64(buffer.Len()), 39 | } 40 | } 41 | 42 | // CalculateHash calculates hash from dataReader. 43 | // It replaces dataReader with another that will contain the data. 44 | func (f *File) CalculateHash() error { 45 | h := fileHasher() 46 | 47 | var buf bytes.Buffer 48 | tee := io.TeeReader(f.DataReader(), &buf) 49 | 50 | _, err := io.Copy(h, tee) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | f.hash = h.Sum(nil) 56 | f.dataReader = &buf 57 | 58 | return nil 59 | } 60 | 61 | // Address returns file's address 62 | func (f *File) Address() swarm.Address { 63 | return f.address 64 | } 65 | 66 | func (f *File) HistroryAddress() swarm.Address { 67 | return f.historyAddress 68 | } 69 | 70 | // Name returns file's name 71 | func (f *File) Name() string { 72 | return f.name 73 | } 74 | 75 | // Hash returns file's hash 76 | func (f *File) Hash() []byte { 77 | return f.hash 78 | } 79 | 80 | // DataReader returns file's data reader 81 | func (f *File) DataReader() io.Reader { 82 | return f.dataReader 83 | } 84 | 85 | // Size returns file size 86 | func (f *File) Size() int64 { 87 | return f.size 88 | } 89 | 90 | // ClosestNode returns file's closest node of a given set of nodes 91 | func (f *File) ClosestNode(nodes []swarm.Address) (closest swarm.Address, err error) { 92 | closest = nodes[0] 93 | for _, a := range nodes[1:] { 94 | dcmp, err := swarm.DistanceCmp(f.Address(), closest, a) 95 | if err != nil { 96 | return swarm.Address{}, fmt.Errorf("find closest node: %w", err) 97 | } 98 | switch dcmp { 99 | case 0: 100 | // do nothing 101 | case -1: 102 | // current node is closer 103 | closest = a 104 | case 1: 105 | // closest is already closer to chunk 106 | // do nothing 107 | } 108 | } 109 | 110 | return 111 | } 112 | 113 | func (f *File) SetAddress(a swarm.Address) { 114 | f.address = a 115 | } 116 | 117 | func (f *File) SetHistroryAddress(a swarm.Address) { 118 | f.historyAddress = a 119 | } 120 | 121 | func (f *File) SetHash(h []byte) { 122 | f.hash = h 123 | } 124 | 125 | func fileHasher() hash.Hash { 126 | return sha3.New256() 127 | } 128 | -------------------------------------------------------------------------------- /pkg/bee/postage.go: -------------------------------------------------------------------------------- 1 | package bee 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/ethersphere/bee/v2/pkg/swarm" 7 | ) 8 | 9 | const MinimumBatchDepth = 2 10 | 11 | func EstimatePostageBatchDepth(contentLength int64) uint64 { 12 | depth := uint64(math.Log2(float64(calculateNumberOfChunks(contentLength, false)))) 13 | if depth < MinimumBatchDepth { 14 | depth = MinimumBatchDepth 15 | } 16 | return depth 17 | } 18 | 19 | // calculateNumberOfChunks calculates the number of chunks in an arbitrary 20 | // content length. 21 | func calculateNumberOfChunks(contentLength int64, isEncrypted bool) int64 { 22 | if contentLength <= swarm.ChunkSize { 23 | return 1 24 | } 25 | branchingFactor := swarm.Branches 26 | if isEncrypted { 27 | branchingFactor = swarm.EncryptedBranches 28 | } 29 | 30 | dataChunks := math.Ceil(float64(contentLength) / float64(swarm.ChunkSize)) 31 | totalChunks := dataChunks 32 | intermediate := dataChunks / float64(branchingFactor) 33 | 34 | for intermediate > 1 { 35 | totalChunks += math.Ceil(intermediate) 36 | intermediate = intermediate / float64(branchingFactor) 37 | } 38 | 39 | return int64(totalChunks) + 1 40 | } 41 | -------------------------------------------------------------------------------- /pkg/beekeeper/beekeeper.go: -------------------------------------------------------------------------------- 1 | package beekeeper 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/orchestration" 7 | ) 8 | 9 | // Action defines Beekeeper Action's interface. An action that 10 | // needs to expose metrics should implement the metrics.Reporter 11 | // interface. 12 | type Action interface { 13 | Run(ctx context.Context, cluster orchestration.Cluster, o interface{}) (err error) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/beekeeper/tracing.go: -------------------------------------------------------------------------------- 1 | package beekeeper 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/orchestration" 7 | "github.com/opentracing/opentracing-go" 8 | ) 9 | 10 | var _ Action = (*actionMiddleware)(nil) 11 | 12 | type actionMiddleware struct { 13 | tracer opentracing.Tracer 14 | action Action 15 | actionName string 16 | } 17 | 18 | // ActionMiddleware tracks request, and adds spans 19 | // to context. 20 | func NewActionMiddleware(tracer opentracing.Tracer, action Action, actionName string) Action { 21 | return &actionMiddleware{ 22 | tracer: tracer, 23 | action: action, 24 | actionName: actionName, 25 | } 26 | } 27 | 28 | // Run implements beekeeper.Action 29 | func (am *actionMiddleware) Run(ctx context.Context, cluster orchestration.Cluster, o interface{}) (err error) { 30 | span := createSpan(ctx, am.tracer, am.actionName) 31 | defer span.Finish() 32 | ctx = opentracing.ContextWithSpan(ctx, span) 33 | return am.action.Run(ctx, cluster, o) 34 | } 35 | 36 | func createSpan(ctx context.Context, tracer opentracing.Tracer, opName string) opentracing.Span { 37 | if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { 38 | return tracer.StartSpan( 39 | opName, 40 | opentracing.ChildOf(parentSpan.Context()), 41 | ) 42 | } 43 | return tracer.StartSpan(opName) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/bigint/bigint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package bigint 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "math/big" 11 | ) 12 | 13 | type BigInt struct { 14 | *big.Int 15 | } 16 | 17 | func (i *BigInt) MarshalJSON() ([]byte, error) { 18 | return []byte(fmt.Sprintf(`"%s"`, i.String())), nil 19 | } 20 | 21 | func (i *BigInt) UnmarshalJSON(b []byte) error { 22 | var val string 23 | err := json.Unmarshal(b, &val) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if i.Int == nil { 29 | i.Int = new(big.Int) 30 | } 31 | 32 | i.SetString(val, 10) 33 | 34 | return nil 35 | } 36 | 37 | // Wrap wraps big.Int pointer into BigInt struct. 38 | func Wrap(i *big.Int) *BigInt { 39 | return &BigInt{i} 40 | } 41 | -------------------------------------------------------------------------------- /pkg/bigint/bigint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package bigint_test 6 | 7 | import ( 8 | "encoding/json" 9 | "math" 10 | "math/big" 11 | "reflect" 12 | "testing" 13 | 14 | "github.com/ethersphere/beekeeper/pkg/bigint" 15 | ) 16 | 17 | func TestMarshaling(t *testing.T) { 18 | mar, err := json.Marshal(struct { 19 | Bg *bigint.BigInt 20 | }{ 21 | Bg: bigint.Wrap(new(big.Int).Mul(big.NewInt(math.MaxInt64), big.NewInt(math.MaxInt64))), 22 | }) 23 | if err != nil { 24 | t.Errorf("Marshaling failed") 25 | } 26 | if !reflect.DeepEqual(mar, []byte("{\"Bg\":\"85070591730234615847396907784232501249\"}")) { 27 | t.Errorf("Wrongly marshaled data") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/check/datadurability/metrics.go: -------------------------------------------------------------------------------- 1 | package datadurability 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | ChunkDownloadAttempts prometheus.Counter 10 | ChunkDownloadErrors prometheus.Counter 11 | ChunkDownloadDuration prometheus.Histogram 12 | ChunksCount prometheus.Gauge 13 | FileDownloadAttempts prometheus.Counter 14 | FileDownloadErrors prometheus.Counter 15 | FileSize prometheus.Counter 16 | FileDownloadDuration prometheus.Histogram 17 | } 18 | 19 | func newMetrics(subsystem string) metrics { 20 | return metrics{ 21 | ChunkDownloadAttempts: prometheus.NewCounter( 22 | prometheus.CounterOpts{ 23 | Namespace: m.Namespace, 24 | Subsystem: subsystem, 25 | Name: "chunk_download_attempts", 26 | Help: "Number of download attempts for the chunks.", 27 | }, 28 | ), 29 | ChunkDownloadErrors: prometheus.NewCounter( 30 | prometheus.CounterOpts{ 31 | Namespace: m.Namespace, 32 | Subsystem: subsystem, 33 | Name: "chunk_download_errors", 34 | Help: "Number of download errors for the chunks.", 35 | }, 36 | ), 37 | ChunkDownloadDuration: prometheus.NewHistogram( 38 | prometheus.HistogramOpts{ 39 | Namespace: m.Namespace, 40 | Subsystem: subsystem, 41 | Name: "chunk_download_duration_seconds", 42 | Help: "Chunk download duration through the /chunks endpoint.", 43 | }, 44 | ), 45 | ChunksCount: prometheus.NewGauge( 46 | prometheus.GaugeOpts{ 47 | Namespace: m.Namespace, 48 | Subsystem: subsystem, 49 | Name: "chunks_count", 50 | Help: "The number of chunks in the check", 51 | }, 52 | ), 53 | FileDownloadAttempts: prometheus.NewCounter( 54 | prometheus.CounterOpts{ 55 | Namespace: m.Namespace, 56 | Subsystem: subsystem, 57 | Name: "file_download_attempts", 58 | Help: "Number of download attempts for the file.", 59 | }, 60 | ), 61 | FileDownloadErrors: prometheus.NewCounter( 62 | prometheus.CounterOpts{ 63 | Namespace: m.Namespace, 64 | Subsystem: subsystem, 65 | Name: "file_download_errors", 66 | Help: "Number of download errors for the file.", 67 | }, 68 | ), 69 | FileSize: prometheus.NewCounter( 70 | prometheus.CounterOpts{ 71 | Namespace: m.Namespace, 72 | Subsystem: subsystem, 73 | Name: "file_size_bytes", 74 | Help: "The size of the file downloaded (sum of chunk sizes)", 75 | }, 76 | ), 77 | FileDownloadDuration: prometheus.NewHistogram( 78 | prometheus.HistogramOpts{ 79 | Namespace: m.Namespace, 80 | Subsystem: subsystem, 81 | Name: "file_download_duration_seconds", 82 | Help: "File download duration", 83 | }, 84 | ), 85 | } 86 | } 87 | 88 | func (c *Check) Report() []prometheus.Collector { 89 | return m.PrometheusCollectorsFromFields(c.metrics) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/check/fileretrieval/metrics.go: -------------------------------------------------------------------------------- 1 | package fileretrieval 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | UploadedCounter *prometheus.CounterVec 10 | UploadTimeGauge *prometheus.GaugeVec 11 | UploadTimeHistogram prometheus.Histogram 12 | DownloadedCounter *prometheus.CounterVec 13 | DownloadTimeGauge *prometheus.GaugeVec 14 | DownloadTimeHistogram prometheus.Histogram 15 | RetrievedCounter *prometheus.CounterVec 16 | NotRetrievedCounter *prometheus.CounterVec 17 | } 18 | 19 | func newMetrics() metrics { 20 | subsystem := "check_fileretrieval" 21 | return metrics{ 22 | UploadedCounter: prometheus.NewCounterVec( 23 | prometheus.CounterOpts{ 24 | Namespace: m.Namespace, 25 | Subsystem: subsystem, 26 | Name: "files_uploaded_count", 27 | Help: "Number of uploaded files.", 28 | }, 29 | []string{"node"}, 30 | ), 31 | UploadTimeGauge: prometheus.NewGaugeVec( 32 | prometheus.GaugeOpts{ 33 | Namespace: m.Namespace, 34 | Subsystem: subsystem, 35 | Name: "file_upload_duration_seconds", 36 | Help: "File upload duration Gauge.", 37 | }, 38 | []string{"node", "file"}, 39 | ), 40 | UploadTimeHistogram: prometheus.NewHistogram( 41 | prometheus.HistogramOpts{ 42 | Namespace: m.Namespace, 43 | Subsystem: subsystem, 44 | Name: "file_upload_seconds", 45 | Help: "File upload duration Histogram.", 46 | Buckets: prometheus.LinearBuckets(0, 0.1, 10), 47 | }, 48 | ), 49 | DownloadedCounter: prometheus.NewCounterVec( 50 | prometheus.CounterOpts{ 51 | Namespace: m.Namespace, 52 | Subsystem: subsystem, 53 | Name: "files_downloaded_count", 54 | Help: "Number of downloaded files.", 55 | }, 56 | []string{"node"}, 57 | ), 58 | DownloadTimeGauge: prometheus.NewGaugeVec( 59 | prometheus.GaugeOpts{ 60 | Namespace: m.Namespace, 61 | Subsystem: subsystem, 62 | Name: "file_download_duration_seconds", 63 | Help: "File download duration Gauge.", 64 | }, 65 | []string{"node", "file"}, 66 | ), 67 | DownloadTimeHistogram: prometheus.NewHistogram( 68 | prometheus.HistogramOpts{ 69 | Namespace: m.Namespace, 70 | Subsystem: subsystem, 71 | Name: "file_download_seconds", 72 | Help: "File download duration Histogram.", 73 | Buckets: prometheus.LinearBuckets(0, 0.1, 10), 74 | }, 75 | ), 76 | RetrievedCounter: prometheus.NewCounterVec( 77 | prometheus.CounterOpts{ 78 | Namespace: m.Namespace, 79 | Subsystem: subsystem, 80 | Name: "files_retrieved_count", 81 | Help: "Number of files that has been retrieved.", 82 | }, 83 | []string{"node"}, 84 | ), 85 | NotRetrievedCounter: prometheus.NewCounterVec( 86 | prometheus.CounterOpts{ 87 | Namespace: m.Namespace, 88 | Subsystem: subsystem, 89 | Name: "files_not_retrieved_count", 90 | Help: "Number of files that has not been retrieved.", 91 | }, 92 | []string{"node"}, 93 | ), 94 | } 95 | } 96 | 97 | func (c *Check) Report() []prometheus.Collector { 98 | return m.PrometheusCollectorsFromFields(c.metrics) 99 | } 100 | -------------------------------------------------------------------------------- /pkg/check/longavailability/metrics.go: -------------------------------------------------------------------------------- 1 | package longavailability 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | DownloadErrors *prometheus.CounterVec 10 | DownloadAttempts *prometheus.CounterVec 11 | Retrieved *prometheus.CounterVec 12 | FailedDownloadAttempts *prometheus.CounterVec 13 | DownloadDuration *prometheus.HistogramVec 14 | DownloadSize *prometheus.GaugeVec 15 | DownloadStatus *prometheus.GaugeVec 16 | } 17 | 18 | func newMetrics(subsystem string, labels []string) metrics { 19 | return metrics{ 20 | DownloadAttempts: prometheus.NewCounterVec( 21 | prometheus.CounterOpts{ 22 | Namespace: m.Namespace, 23 | Subsystem: subsystem, 24 | Name: "download_attempts", 25 | Help: "Number of download attempts.", 26 | }, 27 | labels, 28 | ), 29 | Retrieved: prometheus.NewCounterVec( 30 | prometheus.CounterOpts{ 31 | Namespace: m.Namespace, 32 | Subsystem: subsystem, 33 | Name: "retrieved", 34 | Help: "Number of successful downloads.", 35 | }, 36 | labels, 37 | ), 38 | FailedDownloadAttempts: prometheus.NewCounterVec( 39 | prometheus.CounterOpts{ 40 | Namespace: m.Namespace, 41 | Subsystem: subsystem, 42 | Name: "failed_download_attempts", 43 | Help: "Number of failed download attempts.", 44 | }, 45 | labels, 46 | ), 47 | DownloadErrors: prometheus.NewCounterVec( 48 | prometheus.CounterOpts{ 49 | Namespace: m.Namespace, 50 | Subsystem: subsystem, 51 | Name: "download_errors_count", 52 | Help: "The total number of errors encountered before successful download.", 53 | }, 54 | labels, 55 | ), 56 | DownloadDuration: prometheus.NewHistogramVec( 57 | prometheus.HistogramOpts{ 58 | Namespace: m.Namespace, 59 | Subsystem: subsystem, 60 | Name: "d_download_duration_seconds", 61 | Help: "Data download duration through the /bytes endpoint.", 62 | }, 63 | labels, 64 | ), 65 | DownloadSize: prometheus.NewGaugeVec( 66 | prometheus.GaugeOpts{ 67 | Namespace: m.Namespace, 68 | Subsystem: subsystem, 69 | Name: "d_download_size_bytes", 70 | Help: "Amount of data downloaded per download.", 71 | }, 72 | labels, 73 | ), 74 | DownloadStatus: prometheus.NewGaugeVec( 75 | prometheus.GaugeOpts{ 76 | Namespace: m.Namespace, 77 | Subsystem: subsystem, 78 | Name: "d_download_status", 79 | Help: "Download status.", 80 | }, 81 | labels, 82 | ), 83 | } 84 | } 85 | 86 | func (c *Check) Report() []prometheus.Collector { 87 | return m.PrometheusCollectorsFromFields(c.metrics) 88 | } 89 | -------------------------------------------------------------------------------- /pkg/check/networkavailability/metrics.go: -------------------------------------------------------------------------------- 1 | package networkavailability 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | BatchCreateErrors prometheus.Counter 10 | BatchCreateAttempts prometheus.Counter 11 | UploadErrors prometheus.Counter 12 | UploadAttempts prometheus.Counter 13 | DownloadErrors prometheus.Counter 14 | DownloadMismatch prometheus.Counter 15 | DownloadAttempts prometheus.Counter 16 | UploadDuration *prometheus.HistogramVec 17 | DownloadDuration *prometheus.HistogramVec 18 | UploadSize prometheus.Gauge 19 | } 20 | 21 | const subsystem = "net_avail" 22 | 23 | func newMetrics() metrics { 24 | return metrics{ 25 | UploadAttempts: prometheus.NewCounter( 26 | prometheus.CounterOpts{ 27 | Namespace: m.Namespace, 28 | Subsystem: subsystem, 29 | Name: "upload_attempts", 30 | Help: "Number of upload attempts.", 31 | }, 32 | ), 33 | DownloadAttempts: prometheus.NewCounter( 34 | prometheus.CounterOpts{ 35 | Namespace: m.Namespace, 36 | Subsystem: subsystem, 37 | Name: "download_attempts", 38 | Help: "Number of download attempts.", 39 | }, 40 | ), 41 | UploadErrors: prometheus.NewCounter( 42 | prometheus.CounterOpts{ 43 | Namespace: m.Namespace, 44 | Subsystem: subsystem, 45 | Name: "upload_errors_count", 46 | Help: "The total number of errors encountered before successful upload.", 47 | }, 48 | ), 49 | DownloadErrors: prometheus.NewCounter( 50 | prometheus.CounterOpts{ 51 | Namespace: m.Namespace, 52 | Subsystem: subsystem, 53 | Name: "download_errors_count", 54 | Help: "The total number of errors encountered before successful download.", 55 | }, 56 | ), 57 | UploadDuration: prometheus.NewHistogramVec( 58 | prometheus.HistogramOpts{ 59 | Namespace: m.Namespace, 60 | Subsystem: subsystem, 61 | Name: "data_upload_duration", 62 | Help: "Data upload duration through the /bytes endpoint.", 63 | }, []string{"success"}, 64 | ), 65 | DownloadDuration: prometheus.NewHistogramVec( 66 | prometheus.HistogramOpts{ 67 | Namespace: m.Namespace, 68 | Subsystem: subsystem, 69 | Name: "data_download_duration", 70 | Help: "Data download duration through the /bytes endpoint.", 71 | }, []string{"success"}, 72 | ), 73 | } 74 | } 75 | 76 | func (c *Check) Report() []prometheus.Collector { 77 | return m.PrometheusCollectorsFromFields(c.metrics) 78 | } 79 | -------------------------------------------------------------------------------- /pkg/check/peercount/peercount.go: -------------------------------------------------------------------------------- 1 | package peercount 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/beekeeper" 7 | "github.com/ethersphere/beekeeper/pkg/logging" 8 | "github.com/ethersphere/beekeeper/pkg/orchestration" 9 | ) 10 | 11 | // compile check whether Check implements interface 12 | var _ beekeeper.Action = (*Check)(nil) 13 | 14 | // Check instance. 15 | type Check struct { 16 | logger logging.Logger 17 | } 18 | 19 | // NewCheck returns a new check instance. 20 | func NewCheck(logger logging.Logger) beekeeper.Action { 21 | return &Check{ 22 | logger: logger, 23 | } 24 | } 25 | 26 | func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts interface{}) (err error) { 27 | overlays, err := cluster.Overlays(ctx) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | peers, err := cluster.Peers(ctx) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | clusterSize := cluster.Size() 38 | for g, v := range peers { 39 | for n, p := range v { 40 | c.logger.Infof("Node %s. Peers %d/%d. Address: %s", n, len(p), clusterSize-1, overlays[g][n]) 41 | } 42 | } 43 | 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /pkg/check/pingpong/metrics.go: -------------------------------------------------------------------------------- 1 | package pingpong 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | RttGauge *prometheus.GaugeVec 10 | RttHistogram prometheus.Histogram 11 | } 12 | 13 | func newMetrics() metrics { 14 | subsystem := "check_pingpong" 15 | return metrics{ 16 | RttGauge: prometheus.NewGaugeVec( 17 | prometheus.GaugeOpts{ 18 | Namespace: m.Namespace, 19 | Subsystem: subsystem, 20 | Name: "rtt_duration_seconds", 21 | Help: "Ping round-trip time duration Gauge.", 22 | }, 23 | []string{"node", "peer"}, 24 | ), 25 | RttHistogram: prometheus.NewHistogram( 26 | prometheus.HistogramOpts{ 27 | Namespace: m.Namespace, 28 | Subsystem: subsystem, 29 | Name: "rtt_seconds", 30 | Help: "Ping round-trip time duration Histogram.", 31 | Buckets: prometheus.LinearBuckets(0, 0.003, 10), 32 | }, 33 | ), 34 | } 35 | } 36 | 37 | func (c *Check) Report() []prometheus.Collector { 38 | return m.PrometheusCollectorsFromFields(c.metrics) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/check/pss/metrics.go: -------------------------------------------------------------------------------- 1 | package pss 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | SendAndReceiveGauge *prometheus.GaugeVec 10 | } 11 | 12 | func newMetrics() metrics { 13 | subsystem := "check_pss" 14 | return metrics{ 15 | SendAndReceiveGauge: prometheus.NewGaugeVec( 16 | prometheus.GaugeOpts{ 17 | Namespace: m.Namespace, 18 | Subsystem: subsystem, 19 | Name: "pss_total_duration", 20 | Help: "total duration between sending a message and receiving it on the other end.", 21 | }, 22 | []string{"nodeA", "nodeB"}, 23 | ), 24 | } 25 | } 26 | 27 | func (c *Check) Report() []prometheus.Collector { 28 | return m.PrometheusCollectorsFromFields(c.metrics) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/check/pushsync/metrics.go: -------------------------------------------------------------------------------- 1 | package pushsync 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | UploadedCounter *prometheus.CounterVec 10 | UploadTimeGauge *prometheus.GaugeVec 11 | UploadTimeHistogram prometheus.Histogram 12 | SyncedCounter *prometheus.CounterVec 13 | NotSyncedCounter *prometheus.CounterVec 14 | } 15 | 16 | func newMetrics() metrics { 17 | subsystem := "check_pushsync" 18 | return metrics{ 19 | UploadedCounter: prometheus.NewCounterVec( 20 | prometheus.CounterOpts{ 21 | Namespace: m.Namespace, 22 | Subsystem: subsystem, 23 | Name: "chunks_uploaded_count", 24 | Help: "Number of uploaded chunks.", 25 | }, 26 | []string{"node"}, 27 | ), 28 | UploadTimeGauge: prometheus.NewGaugeVec( 29 | prometheus.GaugeOpts{ 30 | Namespace: m.Namespace, 31 | Subsystem: subsystem, 32 | Name: "chunk_upload_duration_seconds", 33 | Help: "Chunk upload duration Gauge.", 34 | }, 35 | []string{"node", "chunk"}, 36 | ), 37 | UploadTimeHistogram: prometheus.NewHistogram( 38 | prometheus.HistogramOpts{ 39 | Namespace: m.Namespace, 40 | Subsystem: subsystem, 41 | Name: "chunk_upload_seconds", 42 | Help: "Chunk upload duration Histogram.", 43 | }, 44 | ), 45 | SyncedCounter: prometheus.NewCounterVec( 46 | prometheus.CounterOpts{ 47 | Namespace: m.Namespace, 48 | Subsystem: subsystem, 49 | Name: "chunks_synced_count", 50 | Help: "Number of chunks that has been synced with the closest node.", 51 | }, 52 | []string{"node"}, 53 | ), 54 | NotSyncedCounter: prometheus.NewCounterVec( 55 | prometheus.CounterOpts{ 56 | Namespace: m.Namespace, 57 | Subsystem: subsystem, 58 | Name: "chunks_not_synced_count", 59 | Help: "Number of chunks that has not been synced with the closest node.", 60 | }, 61 | []string{"node"}, 62 | ), 63 | } 64 | } 65 | 66 | func (c *Check) Report() []prometheus.Collector { 67 | return m.PrometheusCollectorsFromFields(c.metrics) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/check/retrieval/metrics.go: -------------------------------------------------------------------------------- 1 | package retrieval 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | UploadedCounter *prometheus.CounterVec 10 | UploadTimeGauge *prometheus.GaugeVec 11 | UploadTimeHistogram prometheus.Histogram 12 | DownloadedCounter *prometheus.CounterVec 13 | DownloadTimeGauge *prometheus.GaugeVec 14 | DownloadTimeHistogram prometheus.Histogram 15 | RetrievedCounter *prometheus.CounterVec 16 | NotRetrievedCounter *prometheus.CounterVec 17 | } 18 | 19 | func newMetrics() metrics { 20 | subsystem := "check_retrieval" 21 | return metrics{ 22 | UploadedCounter: prometheus.NewCounterVec( 23 | prometheus.CounterOpts{ 24 | Namespace: m.Namespace, 25 | Subsystem: subsystem, 26 | Name: "chunks_uploaded_count", 27 | Help: "Number of uploaded chunks.", 28 | }, 29 | []string{"node"}, 30 | ), 31 | UploadTimeGauge: prometheus.NewGaugeVec( 32 | prometheus.GaugeOpts{ 33 | Namespace: m.Namespace, 34 | Subsystem: subsystem, 35 | Name: "chunk_upload_duration_seconds", 36 | Help: "Chunk upload duration Gauge.", 37 | }, 38 | []string{"node", "chunk"}, 39 | ), 40 | UploadTimeHistogram: prometheus.NewHistogram( 41 | prometheus.HistogramOpts{ 42 | Namespace: m.Namespace, 43 | Subsystem: subsystem, 44 | Name: "chunk_upload_seconds", 45 | Help: "Chunk upload duration Histogram.", 46 | Buckets: prometheus.LinearBuckets(0, 0.1, 10), 47 | }, 48 | ), 49 | DownloadedCounter: prometheus.NewCounterVec( 50 | prometheus.CounterOpts{ 51 | Namespace: m.Namespace, 52 | Subsystem: subsystem, 53 | Name: "chunks_downloaded_count", 54 | Help: "Number of downloaded chunks.", 55 | }, 56 | []string{"node"}, 57 | ), 58 | DownloadTimeGauge: prometheus.NewGaugeVec( 59 | prometheus.GaugeOpts{ 60 | Namespace: m.Namespace, 61 | Subsystem: subsystem, 62 | Name: "chunk_download_duration_seconds", 63 | Help: "Chunk download duration Gauge.", 64 | }, 65 | []string{"node", "chunk"}, 66 | ), 67 | DownloadTimeHistogram: prometheus.NewHistogram( 68 | prometheus.HistogramOpts{ 69 | Namespace: m.Namespace, 70 | Subsystem: subsystem, 71 | Name: "chunk_download_seconds", 72 | Help: "Chunk download duration Histogram.", 73 | Buckets: prometheus.LinearBuckets(0, 0.1, 10), 74 | }, 75 | ), 76 | RetrievedCounter: prometheus.NewCounterVec( 77 | prometheus.CounterOpts{ 78 | Namespace: m.Namespace, 79 | Subsystem: subsystem, 80 | Name: "chunks_retrieved_count", 81 | Help: "Number of chunks that has been retrieved.", 82 | }, 83 | []string{"node"}, 84 | ), 85 | NotRetrievedCounter: prometheus.NewCounterVec( 86 | prometheus.CounterOpts{ 87 | Namespace: m.Namespace, 88 | Subsystem: subsystem, 89 | Name: "chunks_not_retrieved_count", 90 | Help: "Number of chunks that has not been retrieved.", 91 | }, 92 | []string{"node"}, 93 | ), 94 | } 95 | } 96 | 97 | func (c *Check) Report() []prometheus.Collector { 98 | return m.PrometheusCollectorsFromFields(c.metrics) 99 | } 100 | -------------------------------------------------------------------------------- /pkg/check/stake/contractutil.go: -------------------------------------------------------------------------------- 1 | package stake 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/crypto" 13 | "github.com/ethereum/go-ethereum/ethclient" 14 | ) 15 | 16 | func newStake(opts Options) (*Stake, *ethclient.Client, error) { 17 | if opts.GethURL == "" { 18 | panic(errors.New("geth URL not provided")) 19 | } 20 | 21 | geth, err := ethclient.Dial(opts.GethURL) 22 | if err != nil { 23 | return nil, nil, fmt.Errorf("dial: %w", err) 24 | } 25 | 26 | addr := common.HexToAddress(opts.ContractAddr) 27 | contract, err := NewStake(addr, geth) 28 | if err != nil { 29 | return nil, nil, fmt.Errorf("new contract instance: %w", err) 30 | } 31 | 32 | return contract, geth, nil 33 | } 34 | 35 | func newSession(contract *Stake, geth *ethclient.Client, opts Options) (*StakeSession, error) { 36 | gasPrice, err := geth.SuggestGasPrice(context.Background()) 37 | if err != nil { 38 | return nil, fmt.Errorf("get suggested price: %w", err) 39 | } 40 | 41 | privateKey, err := crypto.HexToECDSA(opts.CallerPrivateKey) 42 | if err != nil { 43 | return nil, fmt.Errorf("parse private key: %w", err) 44 | } 45 | 46 | publicKey := privateKey.Public() 47 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 48 | if !ok { 49 | return nil, errors.New("casti public key to ECDSA") 50 | } 51 | 52 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 53 | nonce, err := geth.PendingNonceAt(context.Background(), fromAddress) 54 | if err != nil { 55 | return nil, fmt.Errorf("get nonce: %w", err) 56 | } 57 | 58 | chainID, err := geth.ChainID(context.Background()) 59 | if err != nil { 60 | return nil, fmt.Errorf("get chain ID: %w", err) 61 | } 62 | 63 | auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) 64 | if err != nil { 65 | return nil, fmt.Errorf("new transactor: %w", err) 66 | } 67 | 68 | session := &StakeSession{ 69 | Contract: contract, 70 | CallOpts: bind.CallOpts{}, 71 | TransactOpts: bind.TransactOpts{ 72 | Signer: auth.Signer, 73 | Nonce: big.NewInt(int64(nonce)), 74 | From: fromAddress, 75 | GasLimit: uint64(300_000), // in units, 76 | GasPrice: gasPrice, 77 | }, 78 | } 79 | 80 | return session, nil 81 | } 82 | -------------------------------------------------------------------------------- /pkg/check/withdraw/withdraw.go: -------------------------------------------------------------------------------- 1 | package withdraw 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethersphere/beekeeper/pkg/beekeeper" 10 | "github.com/ethersphere/beekeeper/pkg/logging" 11 | "github.com/ethersphere/beekeeper/pkg/orchestration" 12 | test "github.com/ethersphere/beekeeper/pkg/test" 13 | ) 14 | 15 | // Options represents check options 16 | type Options struct { 17 | TargetAddr string 18 | } 19 | 20 | // NewDefaultOptions returns new default options 21 | func NewDefaultOptions() Options { 22 | return Options{} 23 | } 24 | 25 | // compile check whether Check implements interface 26 | var _ beekeeper.Action = (*Check)(nil) 27 | 28 | // Check instance 29 | type Check struct { 30 | logger logging.Logger 31 | } 32 | 33 | // NewCheck returns new check 34 | func NewCheck(logger logging.Logger) beekeeper.Action { 35 | return &Check{ 36 | logger: logger, 37 | } 38 | } 39 | 40 | func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts interface{}) (err error) { 41 | o, ok := opts.(Options) 42 | if !ok { 43 | return fmt.Errorf("invalid options type") 44 | } 45 | 46 | var checkCase *test.CheckCase 47 | 48 | if checkCase, err = test.NewCheckCase(ctx, cluster, test.CaseOptions{}, c.logger); err != nil { 49 | return err 50 | } 51 | 52 | target := checkCase.Bee(1) 53 | 54 | c.logger.Infof("target is %s", target.Name()) 55 | 56 | c.logger.Info("withdrawing native...") 57 | 58 | if err := target.Withdraw(ctx, "NativeToken", o.TargetAddr); err != nil { 59 | return fmt.Errorf("withdraw native: %w", err) 60 | } 61 | 62 | c.logger.Info("success") 63 | c.logger.Info("withdrawing to a non whitelisted address") 64 | 65 | var zeroAddr common.Address 66 | 67 | if err := target.Withdraw(ctx, "NativeToken", zeroAddr.String()); err == nil { 68 | return errors.New("withdraw to non-whitelisted address expected to fail") 69 | } 70 | 71 | c.logger.Info("success") 72 | c.logger.Info("withdrawing bzz...") 73 | 74 | if err := target.Withdraw(ctx, "BZZ", o.TargetAddr); err != nil { 75 | return fmt.Errorf("withdraw bzz: %w", err) 76 | } 77 | 78 | c.logger.Info("success") 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /pkg/config/funding.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/orchestration" 7 | ) 8 | 9 | // Funding represents funding deposits for every node in the cluster 10 | type Funding struct { 11 | Bzz *float64 `yaml:"bzz"` 12 | Eth *float64 `yaml:"eth"` 13 | GBzz *float64 `yaml:"gbzz"` 14 | } 15 | 16 | // Export exports Funding to orchestration.FundingOptions 17 | func (f *Funding) Export() (o orchestration.FundingOptions) { 18 | localVal := reflect.ValueOf(f).Elem() 19 | localType := reflect.TypeOf(f).Elem() 20 | remoteVal := reflect.ValueOf(&o).Elem() 21 | 22 | for i := 0; i < localVal.NumField(); i++ { 23 | localField := localVal.Field(i) 24 | if localField.IsValid() && !localField.IsNil() { 25 | localFieldVal := localVal.Field(i).Elem() 26 | localFieldName := localType.Field(i).Name 27 | 28 | remoteFieldVal := remoteVal.FieldByName(localFieldName) 29 | if remoteFieldVal.IsValid() && remoteFieldVal.Type() == localFieldVal.Type() { 30 | remoteFieldVal.Set(localFieldVal) 31 | } 32 | } 33 | } 34 | 35 | return remoteVal.Interface().(orchestration.FundingOptions) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/config/nodegroup.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/orchestration" 7 | ) 8 | 9 | // NodeGroup represents node group configuration 10 | type NodeGroup struct { 11 | // parent to inherit settings from 12 | *Inherit `yaml:",inline"` 13 | // node group configuration 14 | Annotations *map[string]string `yaml:"annotations"` 15 | Image *string `yaml:"image"` 16 | ImagePullPolicy *string `yaml:"image-pull-policy"` 17 | ImagePullSecrets *[]string `yaml:"image-pull-secrets"` 18 | IngressAnnotations *map[string]string `yaml:"ingress-annotations"` 19 | IngressClass *string `yaml:"ingress-class"` 20 | Labels *map[string]string `yaml:"labels"` 21 | NodeSelector *map[string]string `yaml:"node-selector"` 22 | PersistenceEnabled *bool `yaml:"persistence-enabled"` 23 | PersistenceStorageClass *string `yaml:"persistence-storage-class"` 24 | PersistenceStorageRequest *string `yaml:"persistence-storage-request"` 25 | PodManagementPolicy *string `yaml:"pod-management-policy"` 26 | ResourcesLimitCPU *string `yaml:"resources-limit-cpu"` 27 | ResourcesLimitMemory *string `yaml:"resources-limit-memory"` 28 | ResourcesRequestCPU *string `yaml:"resources-request-cpu"` 29 | ResourcesRequestMemory *string `yaml:"resources-request-memory"` 30 | RestartPolicy *string `yaml:"restart-policy"` 31 | UpdateStrategy *string `yaml:"update-strategy"` 32 | } 33 | 34 | func (b NodeGroup) GetParentName() string { 35 | if b.Inherit != nil { 36 | return b.Inherit.ParentName 37 | } 38 | return "" 39 | } 40 | 41 | // Export exports NodeGroup to orchestration.NodeGroupOptions 42 | func (n *NodeGroup) Export() (o orchestration.NodeGroupOptions) { 43 | localVal := reflect.ValueOf(n).Elem() 44 | localType := reflect.TypeOf(n).Elem() 45 | remoteVal := reflect.ValueOf(&o).Elem() 46 | 47 | for i := 0; i < localVal.NumField(); i++ { 48 | localField := localVal.Field(i) 49 | if localField.IsValid() && !localField.IsNil() { 50 | localFieldVal := localVal.Field(i).Elem() 51 | localFieldName := localType.Field(i).Name 52 | 53 | remoteFieldVal := remoteVal.FieldByName(localFieldName) 54 | if remoteFieldVal.IsValid() && remoteFieldVal.Type() == localFieldVal.Type() { 55 | remoteFieldVal.Set(localFieldVal) 56 | } 57 | } 58 | } 59 | 60 | return remoteVal.Interface().(orchestration.NodeGroupOptions) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/funder/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/k8s" 8 | "github.com/ethersphere/beekeeper/pkg/logging" 9 | "github.com/ethersphere/node-funder/pkg/funder" 10 | ) 11 | 12 | type Client struct { 13 | k8sClient *k8s.Client 14 | inCluster bool 15 | label string 16 | log logging.Logger 17 | } 18 | 19 | func NewClient(k8sClient *k8s.Client, inCluster bool, label string, l logging.Logger) *Client { 20 | return &Client{ 21 | k8sClient: k8sClient, 22 | label: label, 23 | inCluster: inCluster, 24 | log: l, 25 | } 26 | } 27 | 28 | func (c *Client) List(ctx context.Context, namespace string) ([]funder.NodeInfo, error) { 29 | if c.k8sClient == nil { 30 | return nil, fmt.Errorf("k8s client not initialized") 31 | } 32 | 33 | if namespace == "" { 34 | return nil, fmt.Errorf("namespace not provided") 35 | } 36 | 37 | c.log.Debugf("Listing nodes with parameters: namespace=%s, label=%s, inCluster=%v", namespace, c.label, c.inCluster) 38 | 39 | if c.inCluster { 40 | return c.getServiceNodes(ctx, namespace) 41 | } 42 | 43 | return c.getIngressNodes(ctx, namespace) 44 | } 45 | 46 | func (c *Client) getServiceNodes(ctx context.Context, namespace string) ([]funder.NodeInfo, error) { 47 | svcNodes, err := c.k8sClient.Service.GetNodes(ctx, namespace, c.label) 48 | if err != nil { 49 | return nil, fmt.Errorf("list api services: %w", err) 50 | } 51 | 52 | nodes := make([]funder.NodeInfo, len(svcNodes)) 53 | for i, node := range svcNodes { 54 | nodes[i] = funder.NodeInfo{ 55 | Name: node.Name, 56 | Address: node.Endpoint, 57 | } 58 | } 59 | 60 | return nodes, nil 61 | } 62 | 63 | func (c *Client) getIngressNodes(ctx context.Context, namespace string) ([]funder.NodeInfo, error) { 64 | ingressNodes, err := c.k8sClient.Ingress.GetNodes(ctx, namespace, c.label) 65 | if err != nil { 66 | return nil, fmt.Errorf("list ingress api nodes hosts: %w", err) 67 | } 68 | 69 | ingressRouteNodes, err := c.k8sClient.IngressRoute.GetNodes(ctx, namespace, c.label) 70 | if err != nil { 71 | return nil, fmt.Errorf("list ingress route api nodes hosts: %w", err) 72 | } 73 | 74 | allNodes := append(ingressNodes, ingressRouteNodes...) 75 | nodes := make([]funder.NodeInfo, len(allNodes)) 76 | for i, node := range allNodes { 77 | nodes[i] = funder.NodeInfo{ 78 | Name: node.Name, 79 | Address: fmt.Sprintf("http://%s", node.Host), 80 | } 81 | } 82 | 83 | return nodes, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/httpx/transport.go: -------------------------------------------------------------------------------- 1 | package httpx 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/ethersphere/beekeeper" 7 | ) 8 | 9 | var userAgent = "beekeeper/" + beekeeper.Version 10 | 11 | type HeaderRoundTripper struct { 12 | Next http.RoundTripper 13 | } 14 | 15 | func (hrt *HeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 16 | req.Header.Set("User-Agent", userAgent) 17 | return hrt.Next.RoundTrip(req) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/k8s/config.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/k8s/customresource/ingressroute" 8 | "k8s.io/client-go/kubernetes" 9 | "k8s.io/client-go/rest" 10 | "k8s.io/client-go/tools/clientcmd" 11 | ) 12 | 13 | // newClientConfig returns a new default ClienConfig. 14 | func newClientConfig() *ClientConfig { 15 | return &ClientConfig{ 16 | NewForConfig: kubernetes.NewForConfig, // TODO: use NewForConfigAndClient 17 | NewIngressRouteClientForConfig: ingressroute.NewForConfig, 18 | InClusterConfig: rest.InClusterConfig, 19 | BuildConfigFromFlags: clientcmd.BuildConfigFromFlags, 20 | FlagString: flag.String, 21 | FlagParse: flag.Parse, 22 | OsUserHomeDir: os.UserHomeDir, 23 | } 24 | } 25 | 26 | // ClientConfig holds functions for configration of the Kubernetes client. 27 | // Functions are extracted to be able to mock them in tests. 28 | type ClientConfig struct { 29 | NewForConfig func(c *rest.Config) (*kubernetes.Clientset, error) // kubernetes.NewForConfig 30 | NewIngressRouteClientForConfig func(c *rest.Config) (*ingressroute.CustomResourceClient, error) // ingressroute.NewForConfig 31 | InClusterConfig func() (*rest.Config, error) // rest.InClusterConfig 32 | BuildConfigFromFlags func(masterUrl string, kubeconfigPath string) (*rest.Config, error) // clientcmd.BuildConfigFromFlags 33 | FlagString func(name string, value string, usage string) *string // flag.String 34 | FlagParse func() // flag.Parse 35 | OsUserHomeDir func() (string, error) // os.UserHomeDir 36 | } 37 | -------------------------------------------------------------------------------- /pkg/k8s/configmap/configmap.go: -------------------------------------------------------------------------------- 1 | package configmap 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | // Client manages communication with the Kubernetes ConfigMap. 14 | type Client struct { 15 | clientset kubernetes.Interface 16 | } 17 | 18 | // NewClient constructs a new Client. 19 | func NewClient(clientset kubernetes.Interface) *Client { 20 | return &Client{ 21 | clientset: clientset, 22 | } 23 | } 24 | 25 | // Options holds optional parameters for the Client. 26 | type Options struct { 27 | Annotations map[string]string 28 | Labels map[string]string 29 | Immutable bool 30 | Data map[string]string 31 | BinaryData map[string][]byte 32 | } 33 | 34 | // Set updates ConfigMap or creates it if it does not exist 35 | func (c *Client) Set(ctx context.Context, name, namespace string, o Options) (cm *v1.ConfigMap, err error) { 36 | spec := &v1.ConfigMap{ 37 | ObjectMeta: metav1.ObjectMeta{ 38 | Name: name, 39 | Namespace: namespace, 40 | Annotations: o.Annotations, 41 | Labels: o.Labels, 42 | }, 43 | Immutable: &o.Immutable, 44 | BinaryData: o.BinaryData, 45 | Data: o.Data, 46 | } 47 | 48 | cm, err = c.clientset.CoreV1().ConfigMaps(namespace).Update(ctx, spec, metav1.UpdateOptions{}) 49 | if err != nil { 50 | if errors.IsNotFound(err) { 51 | cm, err = c.clientset.CoreV1().ConfigMaps(namespace).Create(ctx, spec, metav1.CreateOptions{}) 52 | if err != nil { 53 | return nil, fmt.Errorf("creating configmap %s in namespace %s: %w", name, namespace, err) 54 | } 55 | } else { 56 | return nil, fmt.Errorf("updating configmap %s in namespace %s: %w", name, namespace, err) 57 | } 58 | } 59 | 60 | return 61 | } 62 | 63 | // Delete deletes ConfigMap 64 | func (c *Client) Delete(ctx context.Context, name, namespace string) (err error) { 65 | err = c.clientset.CoreV1().ConfigMaps(namespace).Delete(ctx, name, metav1.DeleteOptions{}) 66 | if err != nil { 67 | if errors.IsNotFound(err) { 68 | return nil 69 | } 70 | return fmt.Errorf("deleting configmap %s in namespace %s: %w", name, namespace, err) 71 | } 72 | 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /pkg/k8s/containers/containers.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // Containers represents Kubernetes Containers 8 | type Containers []Container 9 | 10 | // ToK8S converts Containers to Kubernetes client objects 11 | func (cs Containers) ToK8S() (l []v1.Container) { 12 | if len(cs) > 0 { 13 | l = make([]v1.Container, 0, len(cs)) 14 | for _, c := range cs { 15 | l = append(l, c.ToK8S()) 16 | } 17 | } 18 | return 19 | } 20 | 21 | // Container represents Kubernetes Container 22 | type Container struct { 23 | Name string 24 | Args []string 25 | Command []string 26 | Env EnvVars 27 | EnvFrom EnvFroms 28 | Image string 29 | ImagePullPolicy string 30 | Lifecycle Lifecycle 31 | LivenessProbe Probe 32 | Ports Ports 33 | ReadinessProbe Probe 34 | Resources Resources 35 | SecurityContext SecurityContext 36 | StartupProbe Probe 37 | Stdin bool 38 | StdinOnce bool 39 | TerminationMessagePath string 40 | TerminationMessagePolicy string 41 | TTY bool 42 | VolumeDevices VolumeDevices 43 | VolumeMounts VolumeMounts 44 | WorkingDir string 45 | } 46 | 47 | // ToK8S converts Container to Kubernetes client object 48 | func (c *Container) ToK8S() v1.Container { 49 | return v1.Container{ 50 | Name: c.Name, 51 | Args: c.Args, 52 | Command: c.Command, 53 | Env: c.Env.toK8S(), 54 | EnvFrom: c.EnvFrom.toK8S(), 55 | Image: c.Image, 56 | ImagePullPolicy: v1.PullPolicy(c.ImagePullPolicy), 57 | Lifecycle: c.Lifecycle.toK8S(), 58 | LivenessProbe: c.LivenessProbe.toK8S(), 59 | Ports: c.Ports.toK8S(), 60 | ReadinessProbe: c.ReadinessProbe.toK8S(), 61 | Resources: c.Resources.toK8S(), 62 | SecurityContext: c.SecurityContext.toK8S(), 63 | StartupProbe: c.StartupProbe.toK8S(), 64 | Stdin: c.Stdin, 65 | StdinOnce: c.StdinOnce, 66 | TerminationMessagePath: c.TerminationMessagePath, 67 | TerminationMessagePolicy: v1.TerminationMessagePolicy(c.TerminationMessagePolicy), 68 | TTY: c.TTY, 69 | VolumeDevices: c.VolumeDevices.toK8S(), 70 | VolumeMounts: c.VolumeMounts.toK8S(), 71 | WorkingDir: c.WorkingDir, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/k8s/containers/ephemeral.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // EphemeralContainers represents Kubernetes EphemeralContainers 6 | type EphemeralContainers []EphemeralContainer 7 | 8 | // ToK8S converts EphemeralContainers to Kubernetes client objects 9 | func (ecs EphemeralContainers) ToK8S() (l []v1.EphemeralContainer) { 10 | if len(ecs) > 0 { 11 | l = make([]v1.EphemeralContainer, 0, len(ecs)) 12 | for _, e := range ecs { 13 | l = append(l, e.ToK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // EphemeralContainer represents Kubernetes EphemeralContainer 20 | type EphemeralContainer struct { 21 | EphemeralContainerCommon 22 | TargetContainerName string 23 | } 24 | 25 | // ToK8S converts EphemeralContainer to Kubernetes client object 26 | func (ec *EphemeralContainer) ToK8S() v1.EphemeralContainer { 27 | return v1.EphemeralContainer{ 28 | EphemeralContainerCommon: ec.EphemeralContainerCommon.toK8S(), 29 | TargetContainerName: ec.TargetContainerName, 30 | } 31 | } 32 | 33 | // EphemeralContainerCommon represents Kubernetes EphemeralContainerCommon 34 | type EphemeralContainerCommon struct { 35 | Name string 36 | Args []string 37 | Command []string 38 | Env EnvVars 39 | EnvFrom EnvFroms 40 | Image string 41 | ImagePullPolicy string 42 | Lifecycle Lifecycle 43 | LivenessProbe Probe 44 | Ports Ports 45 | ReadinessProbe Probe 46 | Resources Resources 47 | SecurityContext SecurityContext 48 | StartupProbe Probe 49 | Stdin bool 50 | StdinOnce bool 51 | TerminationMessagePath string 52 | TerminationMessagePolicy string 53 | TTY bool 54 | VolumeDevices VolumeDevices 55 | VolumeMounts VolumeMounts 56 | WorkingDir string 57 | } 58 | 59 | // ToK8S converts EphemeralContainerCommon to Kubernetes client object 60 | func (ecc *EphemeralContainerCommon) toK8S() v1.EphemeralContainerCommon { 61 | return v1.EphemeralContainerCommon{ 62 | Name: ecc.Name, 63 | Args: ecc.Args, 64 | Command: ecc.Command, 65 | Env: ecc.Env.toK8S(), 66 | EnvFrom: ecc.EnvFrom.toK8S(), 67 | Image: ecc.Image, 68 | ImagePullPolicy: v1.PullPolicy(ecc.ImagePullPolicy), 69 | Lifecycle: ecc.Lifecycle.toK8S(), 70 | LivenessProbe: ecc.LivenessProbe.toK8S(), 71 | Ports: ecc.Ports.toK8S(), 72 | ReadinessProbe: ecc.ReadinessProbe.toK8S(), 73 | Resources: ecc.Resources.toK8S(), 74 | SecurityContext: ecc.SecurityContext.toK8S(), 75 | StartupProbe: ecc.StartupProbe.toK8S(), 76 | Stdin: ecc.Stdin, 77 | StdinOnce: ecc.StdinOnce, 78 | TerminationMessagePath: ecc.TerminationMessagePath, 79 | TerminationMessagePolicy: v1.TerminationMessagePolicy(ecc.TerminationMessagePolicy), 80 | TTY: ecc.TTY, 81 | VolumeDevices: ecc.VolumeDevices.toK8S(), 82 | VolumeMounts: ecc.VolumeMounts.toK8S(), 83 | WorkingDir: ecc.WorkingDir, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkg/k8s/containers/ephermal_test.go: -------------------------------------------------------------------------------- 1 | package containers_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/k8s/containers" 8 | v1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func Test_EphemeralContainers_ToK8S(t *testing.T) { 12 | testTable := []struct { 13 | name string 14 | containers containers.EphemeralContainers 15 | expectedContainers []v1.EphemeralContainer 16 | }{ 17 | { 18 | name: "default", 19 | containers: containers.EphemeralContainers{ 20 | { 21 | TargetContainerName: "test", 22 | }, 23 | }, 24 | expectedContainers: []v1.EphemeralContainer{ 25 | { 26 | TargetContainerName: "test", 27 | EphemeralContainerCommon: v1.EphemeralContainerCommon{ 28 | Resources: v1.ResourceRequirements{ 29 | Requests: v1.ResourceList{}, 30 | Limits: v1.ResourceList{}, 31 | }, 32 | SecurityContext: &v1.SecurityContext{ 33 | Privileged: new(bool), 34 | SELinuxOptions: &v1.SELinuxOptions{}, 35 | RunAsUser: new(int64), 36 | RunAsNonRoot: new(bool), 37 | ReadOnlyRootFilesystem: new(bool), 38 | AllowPrivilegeEscalation: new(bool), 39 | RunAsGroup: new(int64), 40 | ProcMount: func() *v1.ProcMountType { 41 | procMountType := v1.ProcMountType("") 42 | return &procMountType 43 | }(), 44 | }, 45 | }, 46 | }, 47 | }, 48 | }, 49 | } 50 | 51 | for _, test := range testTable { 52 | t.Run(test.name, func(t *testing.T) { 53 | containers := test.containers.ToK8S() 54 | 55 | if !reflect.DeepEqual(test.expectedContainers, containers) { 56 | t.Errorf("response expected: %#v, got: %#v", test.expectedContainers, containers) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/k8s/containers/handler.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/util/intstr" 6 | ) 7 | 8 | // LifecycleHandler represents Kubernetes LifecycleHandler 9 | type LifecycleHandler struct { 10 | Exec *ExecHandler 11 | HTTPGet *HTTPGetHandler 12 | TCPSocket *TCPSocketHandler 13 | } 14 | 15 | // toK8S converts Handler to Kubernetes client object 16 | func (h *LifecycleHandler) toK8S() v1.LifecycleHandler { 17 | if h.Exec != nil { 18 | return v1.LifecycleHandler{ 19 | Exec: h.Exec.toK8S(), 20 | } 21 | } else if h.HTTPGet != nil { 22 | return v1.LifecycleHandler{ 23 | HTTPGet: h.HTTPGet.toK8S(), 24 | } 25 | } else if h.TCPSocket != nil { 26 | return v1.LifecycleHandler{ 27 | TCPSocket: h.TCPSocket.toK8S(), 28 | } 29 | } 30 | return v1.LifecycleHandler{} 31 | } 32 | 33 | // ExecHandler represents Kubernetes ExecAction Handler 34 | type ExecHandler struct { 35 | Command []string 36 | } 37 | 38 | // toK8S converts ExecHandler to Kubernetes client object 39 | func (eh *ExecHandler) toK8S() *v1.ExecAction { 40 | return &v1.ExecAction{ 41 | Command: eh.Command, 42 | } 43 | } 44 | 45 | // HTTPGetHandler represents Kubernetes HTTPGetAction Handler 46 | type HTTPGetHandler struct { 47 | Host string 48 | Path string 49 | Port string 50 | Scheme string 51 | HTTPHeaders HTTPHeaders 52 | } 53 | 54 | // toK8S converts HTTPGetHandler to Kubernetes client object 55 | func (hg *HTTPGetHandler) toK8S() *v1.HTTPGetAction { 56 | return &v1.HTTPGetAction{ 57 | Host: hg.Host, 58 | Path: hg.Path, 59 | Port: intstr.FromString(hg.Port), 60 | Scheme: v1.URIScheme(hg.Scheme), 61 | HTTPHeaders: hg.HTTPHeaders.toK8S(), 62 | } 63 | } 64 | 65 | // HTTPHeaders represents Kubernetes HTTPHeader 66 | type HTTPHeaders []HTTPHeader 67 | 68 | // toK8S converts HTTPHeaders to Kubernetes client objects 69 | func (hhs HTTPHeaders) toK8S() (l []v1.HTTPHeader) { 70 | if len(hhs) > 0 { 71 | l = make([]v1.HTTPHeader, 0, len(hhs)) 72 | for _, h := range hhs { 73 | l = append(l, h.toK8S()) 74 | } 75 | } 76 | return 77 | } 78 | 79 | // HTTPHeader represents Kubernetes HTTPHeader 80 | type HTTPHeader struct { 81 | Name string 82 | Value string 83 | } 84 | 85 | // toK8S converts HTTPHeader to Kubernetes client object 86 | func (hh *HTTPHeader) toK8S() v1.HTTPHeader { 87 | return v1.HTTPHeader{ 88 | Name: hh.Name, 89 | Value: hh.Value, 90 | } 91 | } 92 | 93 | // TCPSocketHandler represents Kubernetes TCPSocket Handler 94 | type TCPSocketHandler struct { 95 | Host string 96 | Port string 97 | } 98 | 99 | // toK8S converts TCPSocketHandler to Kubernetes client object 100 | func (tcps *TCPSocketHandler) toK8S() *v1.TCPSocketAction { 101 | return &v1.TCPSocketAction{ 102 | Host: tcps.Host, 103 | Port: intstr.FromString(tcps.Port), 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pkg/k8s/containers/lifecycle.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // Lifecycle represents Kubernetes Lifecycle 6 | type Lifecycle struct { 7 | PostStart *LifecycleHandler 8 | PreStop *LifecycleHandler 9 | } 10 | 11 | // toK8S converts Lifecycle to Kubernetes client object 12 | func (l *Lifecycle) toK8S() *v1.Lifecycle { 13 | if l.PostStart == nil && l.PreStop == nil { 14 | return nil 15 | } 16 | 17 | var lifecycle v1.Lifecycle 18 | 19 | if l.PostStart != nil { 20 | postStart := l.PostStart.toK8S() 21 | lifecycle.PostStart = &postStart 22 | } 23 | 24 | if l.PreStop != nil { 25 | preStop := l.PreStop.toK8S() 26 | lifecycle.PreStop = &preStop 27 | } 28 | 29 | return &lifecycle 30 | } 31 | -------------------------------------------------------------------------------- /pkg/k8s/containers/port.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // Ports represents Kubernetes ContainerPorts 6 | type Ports []Port 7 | 8 | // toK8S converts Ports to Kubernetes client object 9 | func (ps Ports) toK8S() (l []v1.ContainerPort) { 10 | if len(ps) > 0 { 11 | l = make([]v1.ContainerPort, 0, len(ps)) 12 | for _, p := range ps { 13 | l = append(l, p.toK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // Port represents Kubernetes ContainerPort 20 | type Port struct { 21 | Name string 22 | ContainerPort int32 23 | HostIP string 24 | HostPort int32 25 | Protocol string 26 | } 27 | 28 | // toK8S converts Port to Kubernetes client object 29 | func (p *Port) toK8S() v1.ContainerPort { 30 | return v1.ContainerPort{ 31 | Name: p.Name, 32 | ContainerPort: p.ContainerPort, 33 | HostIP: p.HostIP, 34 | HostPort: p.HostPort, 35 | Protocol: v1.Protocol(p.Protocol), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/k8s/containers/probe.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // Probe represents Kubernetes Probe 8 | type Probe struct { 9 | Exec *ExecProbe 10 | HTTPGet *HTTPGetProbe 11 | TCPSocket *TCPSocketProbe 12 | } 13 | 14 | // toK8S converts Containers to Kubernetes client object 15 | func (p *Probe) toK8S() *v1.Probe { 16 | if p.Exec != nil { 17 | return p.Exec.toK8S() 18 | } else if p.HTTPGet != nil { 19 | return p.HTTPGet.toK8S() 20 | } else if p.TCPSocket != nil { 21 | return p.TCPSocket.toK8S() 22 | } 23 | return nil 24 | } 25 | 26 | // ExecProbe represents Kubernetes ExecHandler Probe 27 | type ExecProbe struct { 28 | FailureThreshold int32 29 | Handler ExecHandler 30 | InitialDelaySeconds int32 31 | PeriodSeconds int32 32 | SuccessThreshold int32 33 | TimeoutSeconds int32 34 | } 35 | 36 | // toK8S converts ExecProbe to Kubernetes client object 37 | func (ep *ExecProbe) toK8S() *v1.Probe { 38 | return &v1.Probe{ 39 | FailureThreshold: ep.FailureThreshold, 40 | ProbeHandler: v1.ProbeHandler{ 41 | Exec: ep.Handler.toK8S(), 42 | }, 43 | InitialDelaySeconds: ep.InitialDelaySeconds, 44 | PeriodSeconds: ep.PeriodSeconds, 45 | SuccessThreshold: ep.SuccessThreshold, 46 | TimeoutSeconds: ep.TimeoutSeconds, 47 | } 48 | } 49 | 50 | // HTTPGetProbe represents Kubernetes HTTPGetHandler Probe 51 | type HTTPGetProbe struct { 52 | FailureThreshold int32 53 | Handler HTTPGetHandler 54 | InitialDelaySeconds int32 55 | PeriodSeconds int32 56 | SuccessThreshold int32 57 | TimeoutSeconds int32 58 | } 59 | 60 | // toK8S converts HTTPGetProbe to Kubernetes client object 61 | func (hgp *HTTPGetProbe) toK8S() *v1.Probe { 62 | return &v1.Probe{ 63 | FailureThreshold: hgp.FailureThreshold, 64 | ProbeHandler: v1.ProbeHandler{ 65 | HTTPGet: hgp.Handler.toK8S(), 66 | }, 67 | InitialDelaySeconds: hgp.InitialDelaySeconds, 68 | PeriodSeconds: hgp.PeriodSeconds, 69 | SuccessThreshold: hgp.SuccessThreshold, 70 | TimeoutSeconds: hgp.TimeoutSeconds, 71 | } 72 | } 73 | 74 | // TCPSocketProbe represents Kubernetes TCPSocketHandler Probe 75 | type TCPSocketProbe struct { 76 | FailureThreshold int32 77 | Handler TCPSocketHandler 78 | InitialDelaySeconds int32 79 | PeriodSeconds int32 80 | SuccessThreshold int32 81 | TimeoutSeconds int32 82 | } 83 | 84 | // toK8S converts TCPSocketProbe to Kubernetes client object 85 | func (tsp *TCPSocketProbe) toK8S() *v1.Probe { 86 | return &v1.Probe{ 87 | FailureThreshold: tsp.FailureThreshold, 88 | ProbeHandler: v1.ProbeHandler{ 89 | TCPSocket: tsp.Handler.toK8S(), 90 | }, 91 | InitialDelaySeconds: tsp.InitialDelaySeconds, 92 | PeriodSeconds: tsp.PeriodSeconds, 93 | SuccessThreshold: tsp.SuccessThreshold, 94 | TimeoutSeconds: tsp.TimeoutSeconds, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkg/k8s/containers/resources.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/api/resource" 6 | ) 7 | 8 | // Resources represents Kubernetes ResourceRequirements 9 | type Resources struct { 10 | Limit Limit 11 | Request Request 12 | } 13 | 14 | // toK8S converts Resources to Kubernetes client object 15 | func (r *Resources) toK8S() v1.ResourceRequirements { 16 | return v1.ResourceRequirements{ 17 | Limits: r.Limit.toK8S(), 18 | Requests: r.Request.toK8S(), 19 | } 20 | } 21 | 22 | // Limit represents Kubernetes ResourceList with Limits 23 | type Limit struct { 24 | CPU string 25 | Memory string 26 | Storage string 27 | EphemeralStorage string 28 | } 29 | 30 | // toK8S converts Limit to Kubernetes client object 31 | func (l *Limit) toK8S() v1.ResourceList { 32 | m := map[v1.ResourceName]resource.Quantity{} 33 | if len(l.CPU) > 0 { 34 | m[v1.ResourceCPU] = resource.MustParse(l.CPU) 35 | } 36 | if len(l.Memory) > 0 { 37 | m[v1.ResourceMemory] = resource.MustParse(l.Memory) 38 | } 39 | if len(l.Storage) > 0 { 40 | m[v1.ResourceStorage] = resource.MustParse(l.Storage) 41 | } 42 | if len(l.EphemeralStorage) > 0 { 43 | m[v1.ResourceEphemeralStorage] = resource.MustParse(l.EphemeralStorage) 44 | } 45 | return m 46 | } 47 | 48 | // Request represents Kubernetes ResourceList with Requests 49 | type Request struct { 50 | CPU string 51 | Memory string 52 | Storage string 53 | EphemeralStorage string 54 | } 55 | 56 | // toK8S converts Request to Kubernetes client object 57 | func (r *Request) toK8S() v1.ResourceList { 58 | m := map[v1.ResourceName]resource.Quantity{} 59 | if len(r.CPU) > 0 { 60 | m[v1.ResourceCPU] = resource.MustParse(r.CPU) 61 | } 62 | if len(r.Memory) > 0 { 63 | m[v1.ResourceMemory] = resource.MustParse(r.Memory) 64 | } 65 | if len(r.Storage) > 0 { 66 | m[v1.ResourceStorage] = resource.MustParse(r.Storage) 67 | } 68 | if len(r.EphemeralStorage) > 0 { 69 | m[v1.ResourceEphemeralStorage] = resource.MustParse(r.EphemeralStorage) 70 | } 71 | return m 72 | } 73 | -------------------------------------------------------------------------------- /pkg/k8s/containers/security.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // SecurityContext represents Kubernetes SecurityContext 6 | type SecurityContext struct { 7 | AllowPrivilegeEscalation bool 8 | Capabilities Capabilities 9 | Privileged bool 10 | ProcMount string 11 | ReadOnlyRootFilesystem bool 12 | RunAsGroup int64 13 | RunAsNonRoot bool 14 | RunAsUser int64 15 | SELinuxOptions SELinuxOptions 16 | WindowsOptions WindowsOptions 17 | } 18 | 19 | // toK8S converts SecurityContext to Kubernetes client object 20 | func (sc *SecurityContext) toK8S() *v1.SecurityContext { 21 | return &v1.SecurityContext{ 22 | AllowPrivilegeEscalation: &sc.AllowPrivilegeEscalation, 23 | Capabilities: sc.Capabilities.toK8S(), 24 | Privileged: &sc.Privileged, 25 | ProcMount: func() *v1.ProcMountType { 26 | p := v1.ProcMountType(sc.ProcMount) 27 | return &p 28 | }(), 29 | ReadOnlyRootFilesystem: &sc.ReadOnlyRootFilesystem, 30 | RunAsGroup: &sc.RunAsGroup, 31 | RunAsNonRoot: &sc.RunAsNonRoot, 32 | RunAsUser: &sc.RunAsUser, 33 | SELinuxOptions: sc.SELinuxOptions.toK8S(), 34 | WindowsOptions: sc.WindowsOptions.toK8S(), 35 | } 36 | } 37 | 38 | // Capabilities represents Kubernetes Capabilities 39 | type Capabilities struct { 40 | Add []string 41 | Drop []string 42 | } 43 | 44 | // toK8S converts Capabilities to Kubernetes client object 45 | func (cap *Capabilities) toK8S() *v1.Capabilities { 46 | if cap.Add == nil && cap.Drop == nil { 47 | return nil 48 | } 49 | 50 | caps := v1.Capabilities{} 51 | for _, a := range cap.Add { 52 | caps.Add = append(caps.Add, v1.Capability(a)) 53 | } 54 | for _, d := range cap.Drop { 55 | caps.Drop = append(caps.Drop, v1.Capability(d)) 56 | } 57 | return &caps 58 | } 59 | 60 | // SELinuxOptions represents Kubernetes SELinuxOptions 61 | type SELinuxOptions struct { 62 | User string 63 | Role string 64 | Type string 65 | Level string 66 | } 67 | 68 | // toK8S converts SELinuxOptions to Kubernetes client object 69 | func (se *SELinuxOptions) toK8S() *v1.SELinuxOptions { 70 | return &v1.SELinuxOptions{ 71 | User: se.User, 72 | Role: se.Role, 73 | Type: se.Type, 74 | Level: se.Level, 75 | } 76 | } 77 | 78 | // WindowsOptions represents Kubernetes WindowsSecurityContextOptions 79 | type WindowsOptions struct { 80 | GMSACredentialSpecName string 81 | GMSACredentialSpec string 82 | RunAsUserName string 83 | } 84 | 85 | func (wo *WindowsOptions) toK8S() *v1.WindowsSecurityContextOptions { 86 | if wo.GMSACredentialSpecName == "" && wo.GMSACredentialSpec == "" && wo.RunAsUserName == "" { 87 | return nil 88 | } 89 | return &v1.WindowsSecurityContextOptions{ 90 | GMSACredentialSpecName: &wo.GMSACredentialSpecName, 91 | GMSACredentialSpec: &wo.GMSACredentialSpec, 92 | RunAsUserName: &wo.RunAsUserName, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/k8s/containers/volume.go: -------------------------------------------------------------------------------- 1 | package containers 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // VolumeDevices represents Kubernetes VolumeDevices 6 | type VolumeDevices []VolumeDevice 7 | 8 | // toK8S converts VolumeDevices to Kubernetes client objects 9 | func (vds VolumeDevices) toK8S() (l []v1.VolumeDevice) { 10 | if len(vds) > 0 { 11 | l = make([]v1.VolumeDevice, 0, len(vds)) 12 | for _, vd := range vds { 13 | l = append(l, vd.toK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // VolumeDevice represents Kubernetes VolumeDevice 20 | type VolumeDevice struct { 21 | Name string 22 | DevicePath string 23 | } 24 | 25 | // toK8S converts VolumeDevice to Kubernetes client object 26 | func (vd *VolumeDevice) toK8S() v1.VolumeDevice { 27 | return v1.VolumeDevice{ 28 | Name: vd.Name, 29 | DevicePath: vd.DevicePath, 30 | } 31 | } 32 | 33 | // VolumeMounts represents Kubernetes VolumeMounts 34 | type VolumeMounts []VolumeMount 35 | 36 | // toK8S converts VolumeMounts to Kubernetes client objects 37 | func (vms VolumeMounts) toK8S() (l []v1.VolumeMount) { 38 | if len(vms) > 0 { 39 | l = make([]v1.VolumeMount, 0, len(vms)) 40 | for _, vm := range vms { 41 | l = append(l, vm.toK8S()) 42 | } 43 | } 44 | return 45 | } 46 | 47 | // VolumeMount represents Kubernetes VolumeMount 48 | type VolumeMount struct { 49 | Name string 50 | MountPath string 51 | SubPath string 52 | ReadOnly bool 53 | } 54 | 55 | // toK8S converts VolumeMount to Kubernetes client object 56 | func (v *VolumeMount) toK8S() v1.VolumeMount { 57 | return v1.VolumeMount{ 58 | Name: v.Name, 59 | MountPath: v.MountPath, 60 | SubPath: v.SubPath, 61 | ReadOnly: v.ReadOnly, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/k8s/customresource/ingressroute/config.go: -------------------------------------------------------------------------------- 1 | package ingressroute 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | "k8s.io/client-go/kubernetes/scheme" 8 | "k8s.io/client-go/rest" 9 | ) 10 | 11 | type Interface interface { 12 | IngressRoutes(namespace string) IngressRouteInterface 13 | } 14 | 15 | type CustomResourceClient struct { 16 | restClient rest.Interface 17 | } 18 | 19 | func NewForConfig(c *rest.Config) (*CustomResourceClient, error) { 20 | config := *c 21 | config.ContentConfig.GroupVersion = &schema.GroupVersion{Group: GroupName, Version: GroupVersion} 22 | config.APIPath = "/apis" 23 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 24 | config.UserAgent = rest.DefaultKubernetesUserAgent() 25 | client, err := rest.RESTClientFor(&config) 26 | if err != nil { 27 | return nil, fmt.Errorf("create rest client failed: %w", err) 28 | } 29 | 30 | err = AddToScheme(scheme.Scheme) 31 | if err != nil { 32 | return nil, fmt.Errorf("register type definitions failed: %w", err) 33 | } 34 | 35 | return &CustomResourceClient{restClient: client}, nil 36 | } 37 | 38 | func (c *CustomResourceClient) IngressRoutes(namespace string) IngressRouteInterface { 39 | return &ingressRouteClient{ 40 | restClient: c.restClient, 41 | ns: namespace, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/k8s/customresource/ingressroute/register.go: -------------------------------------------------------------------------------- 1 | package ingressroute 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 ( 10 | GroupName = "traefik.containo.us" 11 | GroupVersion = "v1alpha1" 12 | ) 13 | 14 | // SchemeGroupVersion is group version used to register these objects 15 | var SchemeGroupVersion = schema.GroupVersion{ 16 | Group: GroupName, 17 | Version: GroupVersion, 18 | } 19 | 20 | var ( 21 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 22 | AddToScheme = SchemeBuilder.AddToScheme 23 | ) 24 | 25 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind. 26 | func Kind(kind string) schema.GroupKind { 27 | return SchemeGroupVersion.WithKind(kind).GroupKind() 28 | } 29 | 30 | // Resource takes an unqualified resource and returns a Group 31 | // qualified GroupResource. 32 | func Resource(resource string) schema.GroupResource { 33 | return SchemeGroupVersion.WithResource(resource).GroupResource() 34 | } 35 | 36 | // Adds the list of known types to Scheme. 37 | func addKnownTypes(scheme *runtime.Scheme) error { 38 | scheme.AddKnownTypes(SchemeGroupVersion, 39 | &IngressRoute{}, 40 | &IngressRouteList{}, 41 | ) 42 | 43 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/k8s/customresource/ingressroute/types.go: -------------------------------------------------------------------------------- 1 | package ingressroute 2 | 3 | import ( 4 | "regexp" 5 | 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | 8 | "k8s.io/apimachinery/pkg/runtime" 9 | ) 10 | 11 | var ( 12 | _ runtime.Object = (*IngressRoute)(nil) 13 | _ runtime.Object = (*IngressRouteList)(nil) 14 | ) 15 | 16 | type IngressRouteSpec struct { 17 | Routes []Route `json:"routes"` 18 | } 19 | 20 | type IngressRoute struct { 21 | metav1.TypeMeta `json:",inline"` 22 | metav1.ObjectMeta `json:"metadata,omitempty"` 23 | 24 | Spec IngressRouteSpec `json:"spec"` 25 | } 26 | 27 | type IngressRouteList struct { 28 | metav1.TypeMeta `json:",inline"` 29 | metav1.ListMeta `json:"metadata,omitempty"` 30 | 31 | Items []IngressRoute `json:"items"` 32 | } 33 | 34 | type Route struct { 35 | Kind string `json:"kind"` 36 | Match string `json:"match"` 37 | Services []Service `json:"services"` 38 | } 39 | 40 | type Service struct { 41 | Kind string `json:"kind"` 42 | Name string `json:"name"` 43 | Namespace string `json:"namespace"` 44 | Port string `json:"port"` 45 | } 46 | 47 | // DeepCopyObject implements runtime.Object 48 | func (in *IngressRouteList) DeepCopyObject() runtime.Object { 49 | out := IngressRouteList{} 50 | out.TypeMeta = in.TypeMeta 51 | out.ListMeta = in.ListMeta 52 | 53 | if in.Items != nil { 54 | out.Items = make([]IngressRoute, len(in.Items)) 55 | for i := range in.Items { 56 | in.Items[i].DeepCopyInto(&out.Items[i]) 57 | } 58 | } 59 | 60 | return &out 61 | } 62 | 63 | // DeepCopyObject implements runtime.Object 64 | func (ir *IngressRoute) DeepCopyObject() runtime.Object { 65 | out := IngressRoute{} 66 | ir.DeepCopyInto(&out) 67 | return &out 68 | } 69 | 70 | // DeepCopyInto copies all properties of this object into another object of the 71 | // same type that is provided as a pointer. 72 | func (ir *IngressRoute) DeepCopyInto(out *IngressRoute) { 73 | out.TypeMeta = ir.TypeMeta 74 | out.ObjectMeta = ir.ObjectMeta 75 | out.Spec = ir.Spec 76 | copy(out.Spec.Routes, ir.Spec.Routes) 77 | } 78 | 79 | func (r *Route) GetHost() string { 80 | re := regexp.MustCompile(`Host\("([^"]+)"\)`) 81 | match := re.FindStringSubmatch(r.Match) 82 | if len(match) == 2 { 83 | return match[1] 84 | } 85 | return "" 86 | } 87 | -------------------------------------------------------------------------------- /pkg/k8s/ingress/backend.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | v1 "k8s.io/api/networking/v1" 5 | ) 6 | 7 | // Backend represents Kubernetes IngressBackend 8 | type Backend struct { 9 | ServiceName string 10 | ServicePortName string 11 | } 12 | 13 | // toK8S converts Backend to Kubernetes client object 14 | func (b *Backend) toK8S() v1.IngressBackend { 15 | return v1.IngressBackend{ 16 | Service: &v1.IngressServiceBackend{ 17 | Name: b.ServiceName, 18 | Port: v1.ServiceBackendPort{ 19 | Name: b.ServicePortName, 20 | }, 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/k8s/ingress/client.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/logging" 8 | v1 "k8s.io/api/networking/v1" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/kubernetes" 12 | ) 13 | 14 | // Client manages communication with the Kubernetes Ingress. 15 | type Client struct { 16 | clientset kubernetes.Interface 17 | logger logging.Logger 18 | } 19 | 20 | // NewClient constructs a new Client. 21 | func NewClient(clientset kubernetes.Interface, log logging.Logger) *Client { 22 | return &Client{ 23 | clientset: clientset, 24 | logger: log, 25 | } 26 | } 27 | 28 | // Options holds optional parameters for the Client. 29 | type Options struct { 30 | Annotations map[string]string 31 | Labels map[string]string 32 | Spec Spec 33 | } 34 | 35 | // NodeInfo 36 | type NodeInfo struct { 37 | Name string 38 | Host string 39 | } 40 | 41 | // Set updates Ingress or creates it if it does not exist 42 | func (c *Client) Set(ctx context.Context, name, namespace string, o Options) (ing *v1.Ingress, err error) { 43 | spec := &v1.Ingress{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: name, 46 | Namespace: namespace, 47 | Annotations: o.Annotations, 48 | Labels: o.Labels, 49 | }, 50 | Spec: o.Spec.toK8S(), 51 | } 52 | 53 | ing, err = c.clientset.NetworkingV1().Ingresses(namespace).Update(ctx, spec, metav1.UpdateOptions{}) 54 | if err != nil { 55 | if errors.IsNotFound(err) { 56 | ing, err = c.clientset.NetworkingV1().Ingresses(namespace).Create(ctx, spec, metav1.CreateOptions{}) 57 | if err != nil { 58 | return nil, fmt.Errorf("creating ingress %s in namespace %s: %w", name, namespace, err) 59 | } 60 | } else { 61 | return nil, fmt.Errorf("updating ingress %s in namespace %s: %w", name, namespace, err) 62 | } 63 | } 64 | 65 | return 66 | } 67 | 68 | // Delete deletes Ingress 69 | func (c *Client) Delete(ctx context.Context, name, namespace string) (err error) { 70 | err = c.clientset.NetworkingV1().Ingresses(namespace).Delete(ctx, name, metav1.DeleteOptions{}) 71 | if err != nil { 72 | if errors.IsNotFound(err) { 73 | return nil 74 | } 75 | return fmt.Errorf("deleting ingress %s in namespace %s: %w", name, namespace, err) 76 | } 77 | 78 | return 79 | } 80 | 81 | // GetNodes list Ingresses hosts using label as selector, for the given namespace. If label is empty, all Ingresses are listed. 82 | func (c *Client) GetNodes(ctx context.Context, namespace, label string) (nodes []NodeInfo, err error) { 83 | c.logger.Debugf("listing Ingresses in namespace %s, label selector %s", namespace, label) 84 | ingreses, err := c.clientset.NetworkingV1().Ingresses(namespace).List(ctx, metav1.ListOptions{ 85 | LabelSelector: label, 86 | }) 87 | if err != nil { 88 | if errors.IsNotFound(err) { 89 | return nil, nil 90 | } 91 | return nil, fmt.Errorf("list ingresses in namespace %s: %w", namespace, err) 92 | } 93 | 94 | c.logger.Debugf("found %d ingresses in namespace %s", len(ingreses.Items), namespace) 95 | 96 | for _, ingress := range ingreses.Items { 97 | for _, rule := range ingress.Spec.Rules { 98 | if rule.Host != "" { 99 | nodes = append(nodes, NodeInfo{ 100 | Name: ingress.Name, 101 | Host: rule.Host, 102 | }) 103 | c.logger.Tracef("found Ingress %s in namespace %s with host %s", ingress.Name, namespace, rule.Host) 104 | } 105 | } 106 | } 107 | 108 | return nodes, nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/k8s/ingress/ingress.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import v1 "k8s.io/api/networking/v1" 4 | 5 | // Spec represents Kubernetes IngressSpec 6 | type Spec struct { 7 | Class string 8 | TLS TLSs 9 | Rules Rules 10 | } 11 | 12 | // toK8S converts IngressSpec to Kubernetes client object 13 | func (s *Spec) toK8S() v1.IngressSpec { 14 | return v1.IngressSpec{ 15 | IngressClassName: func(class string) *string { 16 | if class != "" { 17 | return &class 18 | } 19 | return nil 20 | }(s.Class), 21 | Rules: s.Rules.toK8S(), 22 | TLS: s.TLS.toK8S(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/k8s/ingress/rule.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import v1 "k8s.io/api/networking/v1" 4 | 5 | // Rules represents Kubernetes IngressRules 6 | type Rules []Rule 7 | 8 | // toK8S converts Rules to Kubernetes client objects 9 | func (rs Rules) toK8S() (l []v1.IngressRule) { 10 | if len(rs) > 0 { 11 | l = make([]v1.IngressRule, 0, len(rs)) 12 | 13 | for _, r := range rs { 14 | l = append(l, r.toK8S()) 15 | } 16 | } 17 | return 18 | } 19 | 20 | // Rule represents Kubernetes IngressRule 21 | type Rule struct { 22 | Host string 23 | Paths Paths 24 | } 25 | 26 | // toK8S converts Rule to Kubernetes client object 27 | func (r *Rule) toK8S() (rule v1.IngressRule) { 28 | return v1.IngressRule{ 29 | Host: r.Host, 30 | IngressRuleValue: v1.IngressRuleValue{ 31 | HTTP: &v1.HTTPIngressRuleValue{ 32 | Paths: r.Paths.toK8S(), 33 | }, 34 | }, 35 | } 36 | } 37 | 38 | // Paths represents service's HTTPIngressPaths 39 | type Paths []Path 40 | 41 | // toK8S converts Paths to Kubernetes client objects 42 | func (ps Paths) toK8S() (l []v1.HTTPIngressPath) { 43 | if len(ps) > 0 { 44 | l = make([]v1.HTTPIngressPath, 0, len(ps)) 45 | 46 | for _, p := range ps { 47 | l = append(l, p.toK8S()) 48 | } 49 | } 50 | return 51 | } 52 | 53 | // Path represents service's HTTPIngressPath 54 | type Path struct { 55 | Backend Backend 56 | Path string 57 | PathType string 58 | } 59 | 60 | // toK8S converts Path to Kubernetes client object 61 | func (p *Path) toK8S() (h v1.HTTPIngressPath) { 62 | pt := v1.PathType(p.PathType) 63 | return v1.HTTPIngressPath{ 64 | Backend: p.Backend.toK8S(), 65 | Path: p.Path, 66 | PathType: &pt, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/k8s/ingress/tls.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import v1 "k8s.io/api/networking/v1" 4 | 5 | // TLSs represents service's IngressTLSs 6 | type TLSs []TLS 7 | 8 | // toK8S converts TLSs to Kubernetes client objects 9 | func (ts TLSs) toK8S() (l []v1.IngressTLS) { 10 | if len(ts) > 0 { 11 | l = make([]v1.IngressTLS, 0, len(ts)) 12 | 13 | for _, t := range ts { 14 | l = append(l, t.toK8S()) 15 | } 16 | } 17 | return 18 | } 19 | 20 | // TLS represents service's IngressTLS 21 | type TLS struct { 22 | Hosts []string 23 | SecretName string 24 | } 25 | 26 | // toK8S converts TLS to Kubernetes client object 27 | func (t *TLS) toK8S() (tls v1.IngressTLS) { 28 | return v1.IngressTLS{ 29 | Hosts: t.Hosts, 30 | SecretName: t.SecretName, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/k8s/namespace/namespace.go: -------------------------------------------------------------------------------- 1 | package namespace 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/ethersphere/beekeeper" 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | // Client manages communication with the Kubernetes Namespace. 14 | type Client struct { 15 | clientset kubernetes.Interface 16 | } 17 | 18 | // NewClient constructs a new Client. 19 | func NewClient(clientset kubernetes.Interface) *Client { 20 | return &Client{ 21 | clientset: clientset, 22 | } 23 | } 24 | 25 | // Options holds optional parameters for the Client. 26 | type Options struct { 27 | Annotations map[string]string 28 | Labels map[string]string 29 | } 30 | 31 | // Create creates namespace 32 | func (c *Client) Create(ctx context.Context, name string) (*v1.Namespace, error) { 33 | spec := &v1.Namespace{ 34 | ObjectMeta: metav1.ObjectMeta{ 35 | Name: name, 36 | Annotations: map[string]string{ 37 | "created-by": fmt.Sprintf("beekeeper:%s", beekeeper.Version), 38 | }, 39 | Labels: map[string]string{ 40 | "app.kubernetes.io/managed-by": "beekeeper", 41 | }, 42 | }, 43 | } 44 | 45 | return c.clientset.CoreV1().Namespaces().Create(ctx, spec, metav1.CreateOptions{}) 46 | } 47 | 48 | // Update updates namespace 49 | func (c *Client) Update(ctx context.Context, name string, o Options) (*v1.Namespace, error) { 50 | spec := &v1.Namespace{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: name, 53 | Annotations: o.Annotations, 54 | Labels: o.Labels, 55 | }, 56 | } 57 | 58 | return c.clientset.CoreV1().Namespaces().Update(ctx, spec, metav1.UpdateOptions{}) 59 | } 60 | 61 | // Delete deletes namespace 62 | func (c *Client) Delete(ctx context.Context, name string) (err error) { 63 | n, err := c.clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | managedBy, ok := n.Labels["app.kubernetes.io/managed-by"] 69 | if !ok || managedBy != "beekeeper" { 70 | return fmt.Errorf("namespace %s is not managed by beekeeper, try kubectl", name) 71 | } 72 | 73 | err = c.clientset.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{}) 74 | if err != nil { 75 | return fmt.Errorf("deleting namespace %s: %w", name, err) 76 | } 77 | 78 | return 79 | } 80 | -------------------------------------------------------------------------------- /pkg/k8s/persistentvolumeclaim/accessmode.go: -------------------------------------------------------------------------------- 1 | package persistentvolumeclaim 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // AccessModes represents Kubernetes AccessModes 6 | type AccessModes []AccessMode 7 | 8 | // toK8S converts AccessModes to Kubernetes client objects 9 | func (ams AccessModes) toK8S() (l []v1.PersistentVolumeAccessMode) { 10 | if len(ams) > 0 { 11 | l = make([]v1.PersistentVolumeAccessMode, 0, len(ams)) 12 | for _, am := range ams { 13 | l = append(l, am.toK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // AccessMode represents Kubernetes AccessMode 20 | type AccessMode string 21 | 22 | // toK8S converts AccessMode to Kubernetes client object 23 | func (a *AccessMode) toK8S() v1.PersistentVolumeAccessMode { 24 | return v1.PersistentVolumeAccessMode(*a) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/k8s/persistentvolumeclaim/client.go: -------------------------------------------------------------------------------- 1 | package persistentvolumeclaim 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | // Client manages communication with the Kubernetes PersistentVolumeClaims. 14 | type Client struct { 15 | clientset kubernetes.Interface 16 | } 17 | 18 | // NewClient constructs a new Client. 19 | func NewClient(clientset kubernetes.Interface) *Client { 20 | return &Client{ 21 | clientset: clientset, 22 | } 23 | } 24 | 25 | // Options holds optional parameters for the Client. 26 | type Options struct { 27 | Annotations map[string]string 28 | Labels map[string]string 29 | Spec Spec 30 | } 31 | 32 | // Set updates PersistentVolumeClaim or it creates it if it does not exist 33 | func (c *Client) Set(ctx context.Context, name, namespace string, o Options) (pvc *v1.PersistentVolumeClaim, err error) { 34 | spec := &v1.PersistentVolumeClaim{ 35 | ObjectMeta: metav1.ObjectMeta{ 36 | Name: name, 37 | Namespace: namespace, 38 | Annotations: o.Annotations, 39 | Labels: o.Labels, 40 | }, 41 | Spec: o.Spec.toK8S(), 42 | } 43 | 44 | pvc, err = c.clientset.CoreV1().PersistentVolumeClaims(namespace).Update(ctx, spec, metav1.UpdateOptions{}) 45 | if err != nil { 46 | if errors.IsNotFound(err) { 47 | pvc, err = c.clientset.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, spec, metav1.CreateOptions{}) 48 | if err != nil { 49 | return nil, fmt.Errorf("creating pvc %s in namespace %s: %w", name, namespace, err) 50 | } 51 | } else { 52 | return nil, fmt.Errorf("updating pvc %s in namespace %s: %w", name, namespace, err) 53 | } 54 | } 55 | 56 | return 57 | } 58 | 59 | // Delete deletes PersistentVolumeClaim 60 | func (c *Client) Delete(ctx context.Context, name, namespace string) (err error) { 61 | err = c.clientset.CoreV1().PersistentVolumeClaims(namespace).Delete(ctx, name, metav1.DeleteOptions{}) 62 | if err != nil { 63 | if errors.IsNotFound(err) { 64 | return nil 65 | } 66 | return fmt.Errorf("deleting pvc %s in namespace %s: %w", name, namespace, err) 67 | } 68 | 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /pkg/k8s/persistentvolumeclaim/datasource.go: -------------------------------------------------------------------------------- 1 | package persistentvolumeclaim 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // DataSource represents Kubernetes DataSource 6 | type DataSource struct { 7 | APIGroup string 8 | Kind string 9 | Name string 10 | } 11 | 12 | // toK8S converts DataSource to Kubernetes client object 13 | func (d *DataSource) toK8S() *v1.TypedLocalObjectReference { 14 | return &v1.TypedLocalObjectReference{ 15 | APIGroup: &d.APIGroup, 16 | Kind: d.Kind, 17 | Name: d.Name, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pkg/k8s/persistentvolumeclaim/persistentvolumeclaim.go: -------------------------------------------------------------------------------- 1 | package persistentvolumeclaim 2 | 3 | import ( 4 | "strings" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/api/resource" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | // PersistentVolumeClaims represents Kubernetes PersistentVolumeClaims 12 | type PersistentVolumeClaims []PersistentVolumeClaim 13 | 14 | // ToK8S converts PersistentVolumeClaims to Kubernetes client objects 15 | func (ps PersistentVolumeClaims) ToK8S() (l []v1.PersistentVolumeClaim) { 16 | if len(ps) > 0 { 17 | l = make([]v1.PersistentVolumeClaim, 0, len(ps)) 18 | for _, p := range ps { 19 | l = append(l, p.toK8S()) 20 | } 21 | } 22 | return 23 | } 24 | 25 | // PersistentVolumeClaim represents Kubernetes PersistentVolumeClaim 26 | type PersistentVolumeClaim struct { 27 | Name string 28 | Namespace string 29 | Annotations map[string]string 30 | Labels map[string]string 31 | Spec Spec 32 | } 33 | 34 | // toK8S converts PersistentVolumeClaim to Kubernetes client object 35 | func (pvc PersistentVolumeClaim) toK8S() v1.PersistentVolumeClaim { 36 | return v1.PersistentVolumeClaim{ 37 | ObjectMeta: metav1.ObjectMeta{ 38 | Name: pvc.Name, 39 | Namespace: pvc.Namespace, 40 | Annotations: pvc.Annotations, 41 | Labels: pvc.Labels, 42 | }, 43 | Spec: pvc.Spec.toK8S(), 44 | } 45 | } 46 | 47 | // Spec represents Kubernetes Spec 48 | type Spec struct { 49 | Name string 50 | AccessModes AccessModes 51 | DataSource DataSource 52 | RequestStorage string 53 | Selector Selector 54 | StorageClass string 55 | VolumeMode string 56 | VolumeName string 57 | } 58 | 59 | // toK8S converts PersistentVolumeClaimSpec to Kubernetes client object 60 | func (pvcs Spec) toK8S() v1.PersistentVolumeClaimSpec { 61 | return v1.PersistentVolumeClaimSpec{ 62 | AccessModes: pvcs.AccessModes.toK8S(), 63 | DataSource: pvcs.DataSource.toK8S(), 64 | Resources: v1.VolumeResourceRequirements{ 65 | Requests: func() (m v1.ResourceList) { 66 | if len(pvcs.RequestStorage) > 0 { 67 | m = make(map[v1.ResourceName]resource.Quantity) 68 | m[v1.ResourceStorage] = resource.MustParse(pvcs.RequestStorage) 69 | } 70 | return m 71 | }(), 72 | }, 73 | Selector: pvcs.Selector.toK8S(), 74 | StorageClassName: &pvcs.StorageClass, 75 | VolumeName: pvcs.VolumeName, 76 | VolumeMode: func() *v1.PersistentVolumeMode { 77 | if strings.EqualFold(pvcs.VolumeMode, "block") { 78 | m := v1.PersistentVolumeBlock 79 | return &m 80 | } 81 | m := v1.PersistentVolumeFilesystem 82 | return &m 83 | }(), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkg/k8s/persistentvolumeclaim/selector.go: -------------------------------------------------------------------------------- 1 | package persistentvolumeclaim 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // Selector represents Kubernetes LabelSelector 8 | type Selector struct { 9 | MatchLabels map[string]string 10 | MatchExpressions LabelSelectorRequirements 11 | } 12 | 13 | // toK8S converts Selector to Kubernetes client object 14 | func (s *Selector) toK8S() *metav1.LabelSelector { 15 | return &metav1.LabelSelector{ 16 | MatchLabels: s.MatchLabels, 17 | MatchExpressions: s.MatchExpressions.toK8S(), 18 | } 19 | } 20 | 21 | // LabelSelectorRequirements represents Kubernetes LabelSelectorRequirements 22 | type LabelSelectorRequirements []LabelSelectorRequirement 23 | 24 | // toK8S converts LabelSelectorRequirements to Kubernetes client object 25 | func (lsrs LabelSelectorRequirements) toK8S() (l []metav1.LabelSelectorRequirement) { 26 | if len(lsrs) > 0 { 27 | l = make([]metav1.LabelSelectorRequirement, 0, len(lsrs)) 28 | for _, lsr := range lsrs { 29 | l = append(l, lsr.toK8S()) 30 | } 31 | } 32 | return 33 | } 34 | 35 | // LabelSelectorRequirement represents Kubernetes LabelSelectorRequirement 36 | type LabelSelectorRequirement struct { 37 | Key string 38 | Operator string 39 | Values []string 40 | } 41 | 42 | // toK8S converts LabelSelectorRequirement to Kubernetes client object 43 | func (l *LabelSelectorRequirement) toK8S() metav1.LabelSelectorRequirement { 44 | return metav1.LabelSelectorRequirement{ 45 | Key: l.Key, 46 | Operator: metav1.LabelSelectorOperator(l.Operator), 47 | Values: l.Values, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/k8s/pod/dns.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // PodDNSConfig represents Kubernetes Volume 6 | type PodDNSConfig struct { 7 | Nameservers []string 8 | Searches []string 9 | Options PodDNSConfigOptions 10 | } 11 | 12 | // toK8S converts PodDNSConfig to Kubernetes client object 13 | func (pdc *PodDNSConfig) toK8S() *v1.PodDNSConfig { 14 | return &v1.PodDNSConfig{ 15 | Nameservers: pdc.Nameservers, 16 | Searches: pdc.Searches, 17 | Options: pdc.Options.toK8S(), 18 | } 19 | } 20 | 21 | // PodDNSConfigOptions represents Kubernetes PodDNSConfigOptions 22 | type PodDNSConfigOptions []PodDNSConfigOption 23 | 24 | // toK8S converts Items to Kubernetes client object 25 | func (pdcos PodDNSConfigOptions) toK8S() (l []v1.PodDNSConfigOption) { 26 | if len(pdcos) > 0 { 27 | l = make([]v1.PodDNSConfigOption, 0, len(pdcos)) 28 | for _, p := range pdcos { 29 | l = append(l, p.toK8S()) 30 | } 31 | } 32 | return 33 | } 34 | 35 | // PodDNSConfigOption represents Kubernetes PodDNSConfigOption 36 | type PodDNSConfigOption struct { 37 | Name string 38 | Value string 39 | } 40 | 41 | // toK8S converts PodDNSConfigOption to Kubernetes client object 42 | func (pdco *PodDNSConfigOption) toK8S() v1.PodDNSConfigOption { 43 | return v1.PodDNSConfigOption{ 44 | Name: pdco.Name, 45 | Value: &pdco.Value, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/k8s/pod/host.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // HostAliases represents Kubernetes HostAliases 6 | type HostAliases []HostAlias 7 | 8 | // toK8S converts HostAliases to Kubernetes client objects 9 | func (has HostAliases) toK8S() (l []v1.HostAlias) { 10 | if len(has) > 0 { 11 | l = make([]v1.HostAlias, 0, len(has)) 12 | for _, h := range has { 13 | l = append(l, h.toK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // HostAlias represents Kubernetes HostAliase 20 | type HostAlias struct { 21 | IP string 22 | Hostnames []string 23 | } 24 | 25 | // toK8S converts HostAliase to Kubernetes client object 26 | func (ha *HostAlias) toK8S() v1.HostAlias { 27 | return v1.HostAlias{ 28 | IP: ha.IP, 29 | Hostnames: ha.Hostnames, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/k8s/pod/readiness.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // PodReadinessGates represents Kubernetes PodReadinessGates 6 | type PodReadinessGates []PodReadinessGate 7 | 8 | // toK8S converts PodReadinessGates to Kubernetes client objects 9 | func (prgs PodReadinessGates) toK8S() (l []v1.PodReadinessGate) { 10 | if len(prgs) > 0 { 11 | l = make([]v1.PodReadinessGate, 0, len(prgs)) 12 | for _, g := range prgs { 13 | l = append(l, g.toK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // PodReadinessGate represents Kubernetes PodReadinessGate 20 | type PodReadinessGate struct { 21 | ConditionType string 22 | } 23 | 24 | // toK8S converts PodReadinessGate to Kubernetes client object 25 | func (prg *PodReadinessGate) toK8S() v1.PodReadinessGate { 26 | return v1.PodReadinessGate{ 27 | ConditionType: v1.PodConditionType(prg.ConditionType), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/k8s/pod/security.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // PodSecurityContext represents Kubernetes PodSecurityContext 8 | type PodSecurityContext struct { 9 | FSGroup int64 10 | FSGroupChangePolicy string 11 | RunAsGroup int64 12 | RunAsNonRoot bool 13 | RunAsUser int64 14 | SELinuxOptions SELinuxOptions 15 | SupplementalGroups []int64 16 | Sysctls Sysctls 17 | WindowsOptions WindowsOptions 18 | } 19 | 20 | // toK8S converts PodSecurityContext to Kubernetes client object 21 | func (psc *PodSecurityContext) toK8S() *v1.PodSecurityContext { 22 | return &v1.PodSecurityContext{ 23 | FSGroup: &psc.FSGroup, 24 | FSGroupChangePolicy: func() *v1.PodFSGroupChangePolicy { 25 | if len(psc.FSGroupChangePolicy) == 0 { 26 | return nil 27 | } 28 | f := v1.PodFSGroupChangePolicy(psc.FSGroupChangePolicy) 29 | return &f 30 | }(), 31 | RunAsGroup: &psc.RunAsGroup, 32 | RunAsNonRoot: &psc.RunAsNonRoot, 33 | RunAsUser: &psc.RunAsUser, 34 | SELinuxOptions: psc.SELinuxOptions.toK8S(), 35 | SupplementalGroups: psc.SupplementalGroups, 36 | Sysctls: psc.Sysctls.toK8S(), 37 | WindowsOptions: psc.WindowsOptions.toK8S(), 38 | } 39 | } 40 | 41 | // SELinuxOptions represents Kubernetes SELinuxOptions 42 | type SELinuxOptions struct { 43 | User string 44 | Role string 45 | Type string 46 | Level string 47 | } 48 | 49 | // toK8S converts SELinuxOptions to Kubernetes client object 50 | func (se *SELinuxOptions) toK8S() *v1.SELinuxOptions { 51 | return &v1.SELinuxOptions{ 52 | User: se.User, 53 | Role: se.Role, 54 | Type: se.Type, 55 | Level: se.Level, 56 | } 57 | } 58 | 59 | // Sysctls represents Kubernetes Sysctls 60 | type Sysctls []Sysctl 61 | 62 | // toK8S converts Sysctls to Kubernetes client objects 63 | func (scs Sysctls) toK8S() (l []v1.Sysctl) { 64 | if len(scs) > 0 { 65 | l = make([]v1.Sysctl, 0, len(scs)) 66 | for _, s := range scs { 67 | l = append(l, s.toK8S()) 68 | } 69 | } 70 | return 71 | } 72 | 73 | // Sysctl represents Kubernetes Sysctl 74 | type Sysctl struct { 75 | Name string 76 | Value string 77 | } 78 | 79 | func (sc *Sysctl) toK8S() v1.Sysctl { 80 | return v1.Sysctl{ 81 | Name: sc.Name, 82 | Value: sc.Value, 83 | } 84 | } 85 | 86 | // WindowsOptions represents Kubernetes WindowsSecurityContextOptions 87 | type WindowsOptions struct { 88 | GMSACredentialSpecName string 89 | GMSACredentialSpec string 90 | RunAsUserName string 91 | } 92 | 93 | func (wo *WindowsOptions) toK8S() *v1.WindowsSecurityContextOptions { 94 | if wo.GMSACredentialSpecName == "" && wo.GMSACredentialSpec == "" && wo.RunAsUserName == "" { 95 | return nil 96 | } 97 | return &v1.WindowsSecurityContextOptions{ 98 | GMSACredentialSpecName: &wo.GMSACredentialSpecName, 99 | GMSACredentialSpec: &wo.GMSACredentialSpec, 100 | RunAsUserName: &wo.RunAsUserName, 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pkg/k8s/pod/toleration.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | // Tolerations represents Kubernetes Tolerations 6 | type Tolerations []Toleration 7 | 8 | // toK8S converts Tolerations to Kubernetes client object 9 | func (ts Tolerations) toK8S() (l []v1.Toleration) { 10 | if len(ts) > 0 { 11 | l = make([]v1.Toleration, 0, len(ts)) 12 | for _, p := range ts { 13 | l = append(l, p.toK8S()) 14 | } 15 | } 16 | return 17 | } 18 | 19 | // Toleration represents Kubernetes Toleration 20 | type Toleration struct { 21 | Key string 22 | Operator string 23 | Value string 24 | Effect string 25 | TolerationSeconds int64 26 | } 27 | 28 | // toK8S converts Toleration to Kubernetes client object 29 | func (t *Toleration) toK8S() v1.Toleration { 30 | return v1.Toleration{ 31 | Key: t.Key, 32 | Operator: v1.TolerationOperator(t.Operator), 33 | Value: t.Value, 34 | Effect: v1.TaintEffect(t.Effect), 35 | TolerationSeconds: &t.TolerationSeconds, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/k8s/pod/topology.go: -------------------------------------------------------------------------------- 1 | package pod 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // TopologySpreadConstraints represents Kubernetes TopologySpreadConstraints 9 | type TopologySpreadConstraints []TopologySpreadConstraint 10 | 11 | // toK8S converts TopologySpreadConstraints to Kubernetes client objects 12 | func (tscs TopologySpreadConstraints) toK8S() (l []v1.TopologySpreadConstraint) { 13 | if len(tscs) > 0 { 14 | l = make([]v1.TopologySpreadConstraint, 0, len(tscs)) 15 | for _, t := range tscs { 16 | l = append(l, t.toK8S()) 17 | } 18 | } 19 | return 20 | } 21 | 22 | // TopologySpreadConstraint represents Kubernetes TopologySpreadConstraint 23 | type TopologySpreadConstraint struct { 24 | MaxSkew int32 25 | TopologyKey string 26 | WhenUnsatisfiable string 27 | LabelSelector map[string]string 28 | } 29 | 30 | // toK8S converts TopologySpreadConstraint to Kubernetes client object 31 | func (tsc *TopologySpreadConstraint) toK8S() v1.TopologySpreadConstraint { 32 | return v1.TopologySpreadConstraint{ 33 | MaxSkew: tsc.MaxSkew, 34 | TopologyKey: tsc.TopologyKey, 35 | WhenUnsatisfiable: v1.UnsatisfiableConstraintAction(tsc.WhenUnsatisfiable), 36 | LabelSelector: &metav1.LabelSelector{MatchLabels: tsc.LabelSelector}, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/k8s/secret/secret.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | // Client manages communication with the Kubernetes Secret. 14 | type Client struct { 15 | clientset kubernetes.Interface 16 | } 17 | 18 | // NewClient constructs a new Client. 19 | func NewClient(clientset kubernetes.Interface) *Client { 20 | return &Client{ 21 | clientset: clientset, 22 | } 23 | } 24 | 25 | // Options holds optional parameters for the Client. 26 | type Options struct { 27 | Annotations map[string]string 28 | Labels map[string]string 29 | Immutable bool 30 | Data map[string][]byte 31 | StringData map[string]string 32 | Type string 33 | } 34 | 35 | // Set updates Secret of creates it if it does not exist 36 | func (c *Client) Set(ctx context.Context, name, namespace string, o Options) (sc *v1.Secret, err error) { 37 | spec := &v1.Secret{ 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Name: name, 40 | Namespace: namespace, 41 | Annotations: o.Annotations, 42 | Labels: o.Labels, 43 | }, 44 | Immutable: &o.Immutable, 45 | Data: o.Data, 46 | StringData: o.StringData, 47 | Type: v1.SecretType(o.Type), 48 | } 49 | 50 | sc, err = c.clientset.CoreV1().Secrets(namespace).Update(ctx, spec, metav1.UpdateOptions{}) 51 | if err != nil { 52 | if errors.IsNotFound(err) { 53 | sc, err = c.clientset.CoreV1().Secrets(namespace).Create(ctx, spec, metav1.CreateOptions{}) 54 | if err != nil { 55 | return nil, fmt.Errorf("creating secret %s in namespace %s: %w", name, namespace, err) 56 | } 57 | } else { 58 | return nil, fmt.Errorf("updating secret %s in namespace %s: %w", name, namespace, err) 59 | } 60 | } 61 | 62 | return 63 | } 64 | 65 | // Delete deletes Secret 66 | func (c *Client) Delete(ctx context.Context, name, namespace string) (err error) { 67 | err = c.clientset.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{}) 68 | if err != nil { 69 | if errors.IsNotFound(err) { 70 | return nil 71 | } 72 | return fmt.Errorf("deleting secret %s in namespace %s: %w", name, namespace, err) 73 | } 74 | 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /pkg/k8s/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/util/intstr" 6 | ) 7 | 8 | // Spec represents Kubernetes ServiceSpec 9 | type Spec struct { 10 | ClusterIP string 11 | ExternalIPs []string 12 | ExternalName string 13 | ExternalTrafficPolicy string 14 | LoadBalancerIP string 15 | LoadBalancerSourceRanges []string 16 | Ports Ports 17 | PublishNotReadyAddresses bool 18 | Selector map[string]string 19 | SessionAffinity string 20 | SessionAffinityTimeoutSeconds int32 21 | Type string 22 | } 23 | 24 | // ToK8S converts ServiceSpec to Kubernetes client object 25 | func (s *Spec) ToK8S() v1.ServiceSpec { 26 | return v1.ServiceSpec{ 27 | ClusterIP: s.ClusterIP, 28 | ExternalIPs: s.ExternalIPs, 29 | ExternalName: s.ExternalName, 30 | ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(s.ExternalTrafficPolicy), 31 | LoadBalancerIP: s.LoadBalancerIP, 32 | LoadBalancerSourceRanges: s.LoadBalancerSourceRanges, 33 | Ports: s.Ports.toK8S(), 34 | PublishNotReadyAddresses: s.PublishNotReadyAddresses, 35 | Selector: s.Selector, 36 | SessionAffinity: v1.ServiceAffinity(s.SessionAffinity), 37 | SessionAffinityConfig: &v1.SessionAffinityConfig{ 38 | ClientIP: &v1.ClientIPConfig{ 39 | TimeoutSeconds: &s.SessionAffinityTimeoutSeconds, 40 | }, 41 | }, 42 | Type: v1.ServiceType(s.Type), 43 | } 44 | } 45 | 46 | // Ports represents service's ports 47 | type Ports []Port 48 | 49 | // toK8S converts Ports to Kubernetes client objects 50 | func (ps Ports) toK8S() (l []v1.ServicePort) { 51 | l = make([]v1.ServicePort, 0, len(ps)) 52 | 53 | for _, p := range ps { 54 | l = append(l, p.toK8S()) 55 | } 56 | 57 | return 58 | } 59 | 60 | // Port represents service's port 61 | type Port struct { 62 | Name string 63 | AppProtocol string 64 | Nodeport int32 65 | Port int32 66 | Protocol string 67 | TargetPort string 68 | } 69 | 70 | // toK8S converts Port to Kubernetes client object 71 | func (p *Port) toK8S() v1.ServicePort { 72 | return v1.ServicePort{ 73 | Name: p.Name, 74 | AppProtocol: &p.AppProtocol, 75 | NodePort: p.Nodeport, 76 | Port: p.Port, 77 | Protocol: v1.Protocol(p.Protocol), 78 | TargetPort: intstr.FromString(p.TargetPort), 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pkg/k8s/serviceaccount/serviceaccount.go: -------------------------------------------------------------------------------- 1 | package serviceaccount 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | // Client manages communication with the Kubernetes ServiceAccount. 14 | type Client struct { 15 | clientset kubernetes.Interface 16 | } 17 | 18 | // NewClient constructs a new Client. 19 | func NewClient(clientset kubernetes.Interface) *Client { 20 | return &Client{ 21 | clientset: clientset, 22 | } 23 | } 24 | 25 | // Options holds optional parameters for the Client. 26 | type Options struct { 27 | Annotations map[string]string 28 | Labels map[string]string 29 | AutomountServiceAccountToken bool 30 | ImagePullSecrets []string 31 | Secrets []string 32 | } 33 | 34 | // Set updates ServiceAccount or creates it if it does not exist 35 | func (c *Client) Set(ctx context.Context, name, namespace string, o Options) (sa *v1.ServiceAccount, err error) { 36 | spec := &v1.ServiceAccount{ 37 | ObjectMeta: metav1.ObjectMeta{ 38 | Name: name, 39 | Namespace: namespace, 40 | Annotations: o.Annotations, 41 | Labels: o.Labels, 42 | }, 43 | AutomountServiceAccountToken: &o.AutomountServiceAccountToken, 44 | ImagePullSecrets: func() (l []v1.LocalObjectReference) { 45 | for _, s := range o.ImagePullSecrets { 46 | l = append(l, v1.LocalObjectReference{Name: s}) 47 | } 48 | return 49 | }(), 50 | Secrets: func() (l []v1.ObjectReference) { 51 | for _, s := range o.Secrets { 52 | l = append(l, v1.ObjectReference{Name: s}) 53 | } 54 | return 55 | }(), 56 | } 57 | 58 | sa, err = c.clientset.CoreV1().ServiceAccounts(namespace).Update(ctx, spec, metav1.UpdateOptions{}) 59 | if err != nil { 60 | if errors.IsNotFound(err) { 61 | sa, err = c.clientset.CoreV1().ServiceAccounts(namespace).Create(ctx, spec, metav1.CreateOptions{}) 62 | if err != nil { 63 | return nil, fmt.Errorf("creating service account %s in namespace %s: %w", name, namespace, err) 64 | } 65 | } else { 66 | return nil, fmt.Errorf("updating service account %s in namespace %s: %w", name, namespace, err) 67 | } 68 | } 69 | 70 | return 71 | } 72 | 73 | // Delete deletes ServiceAccount 74 | func (c *Client) Delete(ctx context.Context, name, namespace string) (err error) { 75 | err = c.clientset.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{}) 76 | if err != nil { 77 | if errors.IsNotFound(err) { 78 | return nil 79 | } 80 | return fmt.Errorf("deleting service account %s in namespace %s: %w", name, namespace, err) 81 | } 82 | 83 | return 84 | } 85 | -------------------------------------------------------------------------------- /pkg/k8s/statefulset/statefulset.go: -------------------------------------------------------------------------------- 1 | package statefulset 2 | 3 | import ( 4 | pvc "github.com/ethersphere/beekeeper/pkg/k8s/persistentvolumeclaim" 5 | "github.com/ethersphere/beekeeper/pkg/k8s/pod" 6 | appsv1 "k8s.io/api/apps/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | const ( 11 | UpdateStrategyOnDelete = "OnDelete" 12 | UpdateStrategyRolling = "RollingUpdate" 13 | ) 14 | 15 | // StatefulSetSpec represents Kubernetes StatefulSetSpec 16 | type StatefulSetSpec struct { 17 | PodManagementPolicy string 18 | Replicas int32 19 | RevisionHistoryLimit int32 20 | Selector map[string]string 21 | ServiceName string 22 | Template pod.PodTemplateSpec 23 | UpdateStrategy UpdateStrategy 24 | VolumeClaimTemplates pvc.PersistentVolumeClaims 25 | } 26 | 27 | // ToK8S converts StatefulSetSpec to Kubernetes client object 28 | func (s *StatefulSetSpec) ToK8S() appsv1.StatefulSetSpec { 29 | return appsv1.StatefulSetSpec{ 30 | PodManagementPolicy: appsv1.PodManagementPolicyType(s.PodManagementPolicy), 31 | Replicas: &s.Replicas, 32 | RevisionHistoryLimit: &s.RevisionHistoryLimit, 33 | Selector: &metav1.LabelSelector{MatchLabels: s.Selector}, 34 | ServiceName: s.ServiceName, 35 | Template: s.Template.ToK8S(), 36 | UpdateStrategy: s.UpdateStrategy.toK8S(), 37 | VolumeClaimTemplates: s.VolumeClaimTemplates.ToK8S(), 38 | } 39 | } 40 | 41 | // UpdateStrategy represents Kubernetes StatefulSetUpdateStrategy 42 | type UpdateStrategy struct { 43 | Type string 44 | RollingUpdatePartition int32 45 | } 46 | 47 | func newUpdateStrategy(us appsv1.StatefulSetUpdateStrategy) UpdateStrategy { 48 | if us.Type == appsv1.OnDeleteStatefulSetStrategyType { 49 | return UpdateStrategy{ 50 | Type: UpdateStrategyOnDelete, 51 | } 52 | } 53 | 54 | return UpdateStrategy{ 55 | Type: UpdateStrategyRolling, 56 | RollingUpdatePartition: *us.RollingUpdate.Partition, 57 | } 58 | } 59 | 60 | // toK8S converts UpdateStrategy to Kubernetes client object 61 | func (u *UpdateStrategy) toK8S() appsv1.StatefulSetUpdateStrategy { 62 | if u.Type == UpdateStrategyOnDelete { 63 | return appsv1.StatefulSetUpdateStrategy{ 64 | Type: appsv1.OnDeleteStatefulSetStrategyType, 65 | } 66 | } 67 | 68 | return appsv1.StatefulSetUpdateStrategy{ 69 | Type: appsv1.RollingUpdateStatefulSetStrategyType, 70 | RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ 71 | Partition: &u.RollingUpdatePartition, 72 | }, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/k8s/transport.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/logging" 7 | "k8s.io/client-go/rest" 8 | "k8s.io/client-go/util/flowcontrol" 9 | ) 10 | 11 | // customTransport is an example custom transport that wraps the default transport 12 | // and adds some custom behavior. 13 | type customTransport struct { 14 | base http.RoundTripper 15 | semaphore chan struct{} 16 | rateLimiter flowcontrol.RateLimiter 17 | logger logging.Logger 18 | } 19 | 20 | func NewCustomTransport(config *rest.Config, semaphore chan struct{}, logger logging.Logger) *customTransport { 21 | return &customTransport{ 22 | semaphore: semaphore, 23 | rateLimiter: config.RateLimiter, 24 | logger: logger, 25 | } 26 | } 27 | 28 | func (t *customTransport) SetBaseTransport(base http.RoundTripper) *customTransport { 29 | t.base = base 30 | return t 31 | } 32 | 33 | func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) { 34 | // Acquire the semaphore to limit the number of concurrent requests. 35 | t.semaphore <- struct{}{} 36 | defer func() { 37 | <-t.semaphore 38 | }() 39 | 40 | t.rateLimiter.Accept() 41 | 42 | // Forward the request to the base transport. 43 | resp, err := t.base.RoundTrip(req) 44 | 45 | return resp, err 46 | } 47 | -------------------------------------------------------------------------------- /pkg/logging/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package logging provides the logger interface abstraction 6 | // and implementation for Bee. It uses logrus under the hood. 7 | package logging 8 | 9 | import ( 10 | "io" 11 | "net/http" 12 | 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type Logger interface { 17 | Tracef(format string, args ...interface{}) 18 | Trace(args ...interface{}) 19 | Debugf(format string, args ...interface{}) 20 | Debug(args ...interface{}) 21 | Infof(format string, args ...interface{}) 22 | Info(args ...interface{}) 23 | Warningf(format string, args ...interface{}) 24 | Warning(args ...interface{}) 25 | Errorf(format string, args ...interface{}) 26 | Error(args ...interface{}) 27 | Fatalf(format string, args ...interface{}) 28 | Fatal(args ...interface{}) 29 | WithField(key string, value interface{}) *logrus.Entry 30 | WithFields(fields logrus.Fields) *logrus.Entry 31 | WriterLevel(logrus.Level) *io.PipeWriter 32 | NewEntry() *logrus.Entry 33 | GetLevel() string 34 | } 35 | 36 | type logger struct { 37 | *logrus.Logger 38 | metrics metrics 39 | } 40 | 41 | type LoggerOption func(*logger) 42 | 43 | // New initializes a new logger instance with given options. 44 | func New(w io.Writer, level logrus.Level, opts ...LoggerOption) Logger { 45 | l := logrus.New() 46 | l.SetOutput(w) 47 | l.SetLevel(level) 48 | l.Formatter = &logrus.TextFormatter{FullTimestamp: true} 49 | 50 | loggerInstance := &logger{Logger: l} 51 | 52 | for _, option := range opts { 53 | option(loggerInstance) 54 | } 55 | 56 | return loggerInstance 57 | } 58 | 59 | func (l *logger) NewEntry() *logrus.Entry { 60 | return logrus.NewEntry(l.Logger) 61 | } 62 | 63 | func (l *logger) GetLevel() string { 64 | return l.Level.String() 65 | } 66 | 67 | // WithLokiOption sets the hook for Loki logging. 68 | func WithLokiOption(lokiEndpoint string, httpClient *http.Client) LoggerOption { 69 | return func(l *logger) { 70 | if lokiEndpoint != "" { 71 | l.Logger.AddHook(newLoki(lokiEndpoint, httpClient)) 72 | } 73 | } 74 | } 75 | 76 | // WithMetricsOption sets the hook for metrics logging. 77 | func WithMetricsOption() LoggerOption { 78 | return func(l *logger) { 79 | l.Logger.AddHook(newMetrics()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pkg/logging/logrusloki.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "github.com/ethersphere/beekeeper/pkg/logging/loki" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type LokiHook struct { 17 | hostname string 18 | lokiEndpoint string 19 | httpclient *http.Client 20 | } 21 | 22 | func newLoki(lokiEndpoint string, httpClient *http.Client) LokiHook { 23 | hostname, err := os.Hostname() 24 | if err != nil { 25 | hostname = "unknown" 26 | } 27 | 28 | if httpClient == nil { 29 | httpClient = &http.Client{Timeout: 5 * time.Second} 30 | } 31 | 32 | return LokiHook{ 33 | hostname: hostname, 34 | lokiEndpoint: lokiEndpoint, 35 | httpclient: httpClient, 36 | } 37 | } 38 | 39 | func (l LokiHook) Levels() []logrus.Level { 40 | return []logrus.Level{ 41 | logrus.ErrorLevel, 42 | logrus.WarnLevel, 43 | logrus.InfoLevel, 44 | logrus.DebugLevel, 45 | logrus.TraceLevel, 46 | } 47 | } 48 | 49 | func (l LokiHook) Fire(entry *logrus.Entry) error { 50 | stream := loki.NewStream() 51 | 52 | msg, err := entry.Logger.Formatter.Format(entry) 53 | if err != nil { 54 | return fmt.Errorf("loki format failed: %s", err.Error()) 55 | } 56 | stream.AddEntry(entry.Time, string(msg)) 57 | stream.AddLabel("hostname", l.hostname) 58 | 59 | batch := loki.NewBatch() 60 | batch.AddStream(stream) 61 | 62 | err = l.executeHTTPRequest(batch) 63 | if err != nil { 64 | return fmt.Errorf("loki request failed: %s", err.Error()) 65 | } 66 | return nil 67 | } 68 | 69 | func (l LokiHook) executeHTTPRequest(batch *loki.Batch) error { 70 | data, err := json.Marshal(batch) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | req, err := http.NewRequest(http.MethodPost, l.lokiEndpoint, bytes.NewReader(data)) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | req.Header.Set("Content-Type", "application/json") 81 | 82 | resp, err := l.httpclient.Do(req) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | defer resp.Body.Close() 88 | 89 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { 90 | data, err := io.ReadAll(resp.Body) 91 | if err != nil { 92 | return fmt.Errorf("error reading response (%s): %s", resp.Status, err.Error()) 93 | } 94 | 95 | return fmt.Errorf("error posting loki batch (%s): %s", resp.Status, string(data)) 96 | } 97 | 98 | return err 99 | } 100 | -------------------------------------------------------------------------------- /pkg/logging/loki/batch.go: -------------------------------------------------------------------------------- 1 | package loki 2 | 3 | type Batch struct { 4 | Streams []Stream `json:"streams"` 5 | } 6 | 7 | func NewBatch() *Batch { 8 | return &Batch{[]Stream{}} 9 | } 10 | 11 | func (b *Batch) AddStream(s *Stream) { 12 | b.Streams = append(b.Streams, *s) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/logging/loki/stream.go: -------------------------------------------------------------------------------- 1 | package loki 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | type Stream struct { 9 | Labels map[string]string `json:"stream"` 10 | Entries [][]string `json:"values"` 11 | } 12 | 13 | func NewStream() *Stream { 14 | return &Stream{ 15 | map[string]string{}, 16 | [][]string{}, 17 | } 18 | } 19 | 20 | func (s *Stream) AddLabel(key string, value string) { 21 | s.Labels[key] = value 22 | } 23 | 24 | func (s *Stream) AddEntry(t time.Time, entry string) { 25 | timeStr := strconv.FormatInt(t.UnixNano(), 10) 26 | s.Entries = append(s.Entries, []string{timeStr, entry}) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/logging/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | m "github.com/ethersphere/beekeeper/pkg/metrics" 9 | "github.com/prometheus/client_golang/prometheus" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type metrics struct { 14 | // all metrics fields must be exported 15 | // to be able to return them by Metrics() 16 | // using reflection 17 | ErrorCount prometheus.Counter 18 | WarnCount prometheus.Counter 19 | InfoCount prometheus.Counter 20 | DebugCount prometheus.Counter 21 | TraceCount prometheus.Counter 22 | } 23 | 24 | func newMetrics() metrics { 25 | subsystem := "log" 26 | 27 | return metrics{ 28 | ErrorCount: prometheus.NewCounter(prometheus.CounterOpts{ 29 | Namespace: m.Namespace, 30 | Subsystem: subsystem, 31 | Name: "error_count", 32 | Help: "Number ERROR log messages.", 33 | }), 34 | WarnCount: prometheus.NewCounter(prometheus.CounterOpts{ 35 | Namespace: m.Namespace, 36 | Subsystem: subsystem, 37 | Name: "warn_count", 38 | Help: "Number WARN log messages.", 39 | }), 40 | InfoCount: prometheus.NewCounter(prometheus.CounterOpts{ 41 | Namespace: m.Namespace, 42 | Subsystem: subsystem, 43 | Name: "info_count", 44 | Help: "Number INFO log messages.", 45 | }), 46 | DebugCount: prometheus.NewCounter(prometheus.CounterOpts{ 47 | Namespace: m.Namespace, 48 | Subsystem: subsystem, 49 | Name: "debug_count", 50 | Help: "Number DEBUG log messages.", 51 | }), 52 | TraceCount: prometheus.NewCounter(prometheus.CounterOpts{ 53 | Namespace: m.Namespace, 54 | Subsystem: subsystem, 55 | Name: "trace_count", 56 | Help: "Number TRACE log messages.", 57 | }), 58 | } 59 | } 60 | 61 | func (l *logger) Report() []prometheus.Collector { 62 | return m.PrometheusCollectorsFromFields(l.metrics) 63 | } 64 | 65 | func (m metrics) Levels() []logrus.Level { 66 | return []logrus.Level{ 67 | logrus.ErrorLevel, 68 | logrus.WarnLevel, 69 | logrus.InfoLevel, 70 | logrus.DebugLevel, 71 | logrus.TraceLevel, 72 | } 73 | } 74 | 75 | func (m metrics) Fire(e *logrus.Entry) error { 76 | switch e.Level { 77 | case logrus.ErrorLevel: 78 | m.ErrorCount.Inc() 79 | case logrus.WarnLevel: 80 | m.WarnCount.Inc() 81 | case logrus.InfoLevel: 82 | m.InfoCount.Inc() 83 | case logrus.DebugLevel: 84 | m.DebugCount.Inc() 85 | case logrus.TraceLevel: 86 | m.TraceCount.Inc() 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | "github.com/prometheus/client_golang/prometheus/push" 8 | ) 9 | 10 | // Namespace is prefixed before every metric. If it is changed, it must be done 11 | // before any metrics collector is registered. 12 | var Namespace = "beekeeper" 13 | 14 | type Reporter interface { 15 | Report() []prometheus.Collector 16 | } 17 | 18 | func PrometheusCollectorsFromFields(i interface{}) (cs []prometheus.Collector) { 19 | v := reflect.Indirect(reflect.ValueOf(i)) 20 | for i := 0; i < v.NumField(); i++ { 21 | if !v.Field(i).CanInterface() { 22 | continue 23 | } 24 | if u, ok := v.Field(i).Interface().(prometheus.Collector); ok { 25 | cs = append(cs, u) 26 | } 27 | } 28 | return cs 29 | } 30 | 31 | func RegisterCollectors(p *push.Pusher, c ...prometheus.Collector) { 32 | for _, cc := range c { 33 | p.Collector(cc) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/orchestration/k8s/node.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/bee" 7 | "github.com/ethersphere/beekeeper/pkg/logging" 8 | "github.com/ethersphere/beekeeper/pkg/orchestration" 9 | ) 10 | 11 | // compile check whether client implements interface 12 | var _ orchestration.Node = (*Node)(nil) 13 | 14 | // Node represents Bee node 15 | type Node struct { 16 | orchestration.NodeOrchestrator 17 | name string 18 | client *bee.Client 19 | opts orchestration.NodeOptions 20 | log logging.Logger 21 | } 22 | 23 | // NewNode returns Bee node 24 | func NewNode(name string, client *bee.Client, opts orchestration.NodeOptions, no orchestration.NodeOrchestrator, log logging.Logger) (n *Node) { 25 | return &Node{ 26 | NodeOrchestrator: no, 27 | name: name, 28 | client: client, 29 | opts: opts, 30 | log: log, 31 | } 32 | } 33 | 34 | // Name returns node's name 35 | func (n Node) Name() string { 36 | return n.name 37 | } 38 | 39 | // Client returns node's name 40 | func (n Node) Client() *bee.Client { 41 | return n.client 42 | } 43 | 44 | // Config returns node's config 45 | func (n Node) Config() *orchestration.Config { 46 | return n.opts.Config 47 | } 48 | 49 | // LibP2PKey returns node's libP2PKey 50 | func (n Node) LibP2PKey() string { 51 | return n.opts.LibP2PKey 52 | } 53 | 54 | // SwarmKey returns node's swarmKey 55 | func (n Node) SwarmKey() *orchestration.EncryptedKey { 56 | return n.opts.SwarmKey 57 | } 58 | 59 | // SetSwarmKey sets node's Swarm key 60 | func (n Node) SetSwarmKey(key *orchestration.EncryptedKey) orchestration.Node { 61 | n.opts.SwarmKey = key 62 | return n 63 | } 64 | 65 | // Create implements orchestration.Node. 66 | // Subtle: this method shadows the method (NodeOrchestrator).Create of Node.NodeOrchestrator. 67 | func (n Node) Create(ctx context.Context, o orchestration.CreateOptions) (err error) { 68 | return n.NodeOrchestrator.Create(ctx, o) 69 | } 70 | 71 | // Delete implements orchestration.Node. 72 | // Subtle: this method shadows the method (NodeOrchestrator).Delete of Node.NodeOrchestrator. 73 | func (n Node) Delete(ctx context.Context, namespace string) (err error) { 74 | return n.NodeOrchestrator.Delete(ctx, n.name, namespace) 75 | } 76 | 77 | // Ready implements orchestration.Node. 78 | // Subtle: this method shadows the method (NodeOrchestrator).Ready of Node.NodeOrchestrator. 79 | func (n Node) Ready(ctx context.Context, namespace string) (ready bool, err error) { 80 | return n.NodeOrchestrator.Ready(ctx, n.name, namespace) 81 | } 82 | 83 | // Start implements orchestration.Node. 84 | // Subtle: this method shadows the method (NodeOrchestrator).Start of Node.NodeOrchestrator. 85 | func (n Node) Start(ctx context.Context, namespace string) (err error) { 86 | return n.NodeOrchestrator.Start(ctx, n.name, namespace) 87 | } 88 | 89 | // Stop implements orchestration.Node. 90 | // Subtle: this method shadows the method (NodeOrchestrator).Stop of Node.NodeOrchestrator. 91 | func (n Node) Stop(ctx context.Context, namespace string) (err error) { 92 | return n.NodeOrchestrator.Stop(ctx, n.name, namespace) 93 | } 94 | -------------------------------------------------------------------------------- /pkg/orchestration/notset/bee.go: -------------------------------------------------------------------------------- 1 | package notset 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/orchestration" 7 | ) 8 | 9 | var _ orchestration.NodeOrchestrator = (*BeeClient)(nil) 10 | 11 | // BeeClient represents not implemented Kubernetes Bee client 12 | type BeeClient struct{} 13 | 14 | // Create creates Bee node in the cluster 15 | func (c *BeeClient) Create(ctx context.Context, o orchestration.CreateOptions) (err error) { 16 | return orchestration.ErrNotSet 17 | } 18 | 19 | // Delete deletes Bee node from the cluster 20 | func (c *BeeClient) Delete(ctx context.Context, name string, namespace string) (err error) { 21 | return orchestration.ErrNotSet 22 | } 23 | 24 | // Ready gets Bee node's readiness 25 | func (c *BeeClient) Ready(ctx context.Context, name string, namespace string) (ready bool, err error) { 26 | return false, orchestration.ErrNotSet 27 | } 28 | 29 | // Start starts Bee node in the cluster 30 | func (c *BeeClient) Start(ctx context.Context, name string, namespace string) (err error) { 31 | return orchestration.ErrNotSet 32 | } 33 | 34 | // Stop stops Bee node in the cluster 35 | func (c *BeeClient) Stop(ctx context.Context, name string, namespace string) (err error) { 36 | return orchestration.ErrNotSet 37 | } 38 | 39 | // RunningNodes implements orchestration.NodeOrchestrator. 40 | func (c *BeeClient) RunningNodes(ctx context.Context, namespace string) (running []string, err error) { 41 | return nil, orchestration.ErrNotSet 42 | } 43 | 44 | // StoppedNodes implements orchestration.NodeOrchestrator. 45 | func (c *BeeClient) StoppedNodes(ctx context.Context, namespace string) (stopped []string, err error) { 46 | return nil, orchestration.ErrNotSet 47 | } 48 | -------------------------------------------------------------------------------- /pkg/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | crand "crypto/rand" 5 | "encoding/binary" 6 | "log" 7 | "math/rand" 8 | ) 9 | 10 | // PseudoGenerator returns *rand.Rand 11 | func PseudoGenerator(seed int64) (g *rand.Rand) { 12 | rnd := rand.New(rand.NewSource(seed)) 13 | return rand.New(rand.NewSource(rnd.Int63())) 14 | } 15 | 16 | // PseudoGenerators returns list of n *rand.Rand. 17 | // This is needed in cases where random number generators are used in different 18 | // goroutines, so that predictability of the generators can be maintained. 19 | func PseudoGenerators(seed int64, n int) (g []*rand.Rand) { 20 | rnd := rand.New(rand.NewSource(seed)) 21 | for i := 0; i < n; i++ { 22 | g = append(g, rand.New(rand.NewSource(rnd.Int63()))) 23 | } 24 | return 25 | } 26 | 27 | // Int64 returns random int64 28 | func Int64() int64 { 29 | var src CryptoSource 30 | rnd := rand.New(src) 31 | return rnd.Int63() 32 | } 33 | 34 | // CryptoSource is used to create random source 35 | type CryptoSource struct{} 36 | 37 | func (s CryptoSource) Seed(_ int64) {} 38 | 39 | func (s CryptoSource) Int63() int64 { 40 | return int64(s.Uint64() & ^uint64(1<<63)) 41 | } 42 | 43 | func (s CryptoSource) Uint64() (v uint64) { 44 | err := binary.Read(crand.Reader, binary.BigEndian, &v) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | return v 49 | } 50 | -------------------------------------------------------------------------------- /pkg/restart/restart.go: -------------------------------------------------------------------------------- 1 | package restart 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/k8s" 8 | "github.com/ethersphere/beekeeper/pkg/k8s/statefulset" 9 | "github.com/ethersphere/beekeeper/pkg/logging" 10 | "github.com/ethersphere/beekeeper/pkg/orchestration" 11 | ) 12 | 13 | type Client struct { 14 | k8sClient *k8s.Client 15 | logger logging.Logger 16 | } 17 | 18 | // NewClient creates a new restart client 19 | func NewClient(k8sClient *k8s.Client, l logging.Logger) *Client { 20 | return &Client{ 21 | k8sClient: k8sClient, 22 | logger: l, 23 | } 24 | } 25 | 26 | func (c *Client) RestartPods(ctx context.Context, ns, labelSelector string) error { 27 | c.logger.Debugf("starting pod restart in namespace %s", ns) 28 | count, err := c.k8sClient.Pods.DeletePods(ctx, ns, labelSelector) 29 | if err != nil { 30 | return fmt.Errorf("restarting pods in namespace %s: %w", ns, err) 31 | } 32 | 33 | c.logger.Infof("restarted %d pods in namespace %s", count, ns) 34 | 35 | return nil 36 | } 37 | 38 | func (c *Client) RestartCluster(ctx context.Context, cluster orchestration.Cluster, ns, image string, nodeGroups []string) (err error) { 39 | nodes := c.getNodeList(cluster, nodeGroups) 40 | for _, node := range nodes { 41 | if err := c.updateOrDeleteNode(ctx, node, ns, image); err != nil { 42 | return fmt.Errorf("error handling node %s in namespace %s: %w", node, ns, err) 43 | } 44 | c.logger.Debugf("node %s in namespace %s restarted", node, ns) 45 | } 46 | return nil 47 | } 48 | 49 | func (c *Client) getNodeList(cluster orchestration.Cluster, nodeGroups []string) []string { 50 | if len(nodeGroups) == 0 { 51 | return cluster.NodeNames() 52 | } 53 | 54 | nodeGroupsMap := cluster.NodeGroups() 55 | var nodes []string 56 | 57 | for _, nodeGroup := range nodeGroups { 58 | group, ok := nodeGroupsMap[nodeGroup] 59 | if !ok { 60 | c.logger.Debugf("node group %s not found in cluster %s", nodeGroup, cluster.Name()) 61 | continue 62 | } 63 | nodes = append(nodes, group.NodesSorted()...) 64 | } 65 | 66 | return nodes 67 | } 68 | 69 | func (c *Client) updateOrDeleteNode(ctx context.Context, node, ns, image string) error { 70 | if image == "" { 71 | return c.deletePod(ctx, node, ns) 72 | } 73 | 74 | strategy, err := c.k8sClient.StatefulSet.GetUpdateStrategy(ctx, node, ns) 75 | if err != nil { 76 | return fmt.Errorf("getting update strategy for node %s: %w", node, err) 77 | } 78 | 79 | if err = c.k8sClient.StatefulSet.UpdateImage(ctx, node, ns, image); err != nil { 80 | return fmt.Errorf("updating image for node %s: %w", node, err) 81 | } 82 | 83 | if strategy.Type == statefulset.UpdateStrategyOnDelete { 84 | return c.deletePod(ctx, node, ns) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (c *Client) deletePod(ctx context.Context, node, ns string) error { 91 | podName := fmt.Sprintf("%s-0", node) // Suffix "-0" added as StatefulSet names pods based on replica count. 92 | ok, err := c.k8sClient.Pods.Delete(ctx, podName, ns) 93 | if err != nil { 94 | return fmt.Errorf("deleting pod %s: %w", podName, err) 95 | } 96 | if !ok { 97 | return fmt.Errorf("failed to delete pod %s", podName) 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /pkg/scheduler/duration.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/ethersphere/beekeeper/pkg/logging" 9 | ) 10 | 11 | // DurationExecutor executes a task and then stops immediately after the specified duration. 12 | type DurationExecutor struct { 13 | duration time.Duration 14 | log logging.Logger 15 | } 16 | 17 | // NewDurationExecutor creates a new DurationExecutor with the given duration and logger. 18 | func NewDurationExecutor(duration time.Duration, log logging.Logger) *DurationExecutor { 19 | return &DurationExecutor{ 20 | duration: duration, 21 | log: log, 22 | } 23 | } 24 | 25 | // Run executes the given task and waits for the specified duration before stopping. 26 | func (de *DurationExecutor) Run(ctx context.Context, task func(ctx context.Context) error) error { 27 | if task == nil { 28 | return errors.New("task cannot be nil") 29 | } 30 | 31 | ctx, cancel := context.WithCancel(ctx) 32 | defer cancel() 33 | 34 | doneCh := make(chan error, 1) 35 | 36 | go func() { 37 | select { 38 | case <-ctx.Done(): 39 | case doneCh <- task(ctx): 40 | } 41 | }() 42 | 43 | select { 44 | case <-ctx.Done(): 45 | return ctx.Err() 46 | case <-time.After(de.duration): 47 | de.log.Infof("Duration of %s expired, stopping executor", de.duration) 48 | return nil 49 | case err := <-doneCh: 50 | return err 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/ethersphere/beekeeper/pkg/logging" 8 | "resenje.org/x/shutdown" 9 | ) 10 | 11 | type PeriodicExecutor struct { 12 | ticker *time.Ticker 13 | interval time.Duration 14 | log logging.Logger 15 | shutdown *shutdown.Graceful 16 | } 17 | 18 | func NewPeriodicExecutor(interval time.Duration, log logging.Logger) *PeriodicExecutor { 19 | return &PeriodicExecutor{ 20 | ticker: time.NewTicker(interval), 21 | interval: interval, 22 | log: log, 23 | shutdown: shutdown.NewGraceful(), 24 | } 25 | } 26 | 27 | func (pe *PeriodicExecutor) Start(ctx context.Context, task func(ctx context.Context) error) { 28 | pe.shutdown.Add(1) 29 | go func() { 30 | defer pe.shutdown.Done() 31 | ctx = pe.shutdown.Context(ctx) 32 | 33 | if err := task(ctx); err != nil { 34 | pe.log.Errorf("Task execution failed: %v", err) 35 | } 36 | 37 | for { 38 | select { 39 | case <-pe.ticker.C: 40 | pe.log.Debugf("Executing task after %s interval", pe.interval) 41 | if err := task(ctx); err != nil { 42 | pe.log.Errorf("Task execution failed: %v", err) 43 | } 44 | case <-pe.shutdown.Quit(): 45 | return 46 | case <-ctx.Done(): 47 | return 48 | } 49 | } 50 | }() 51 | } 52 | 53 | func (pe *PeriodicExecutor) Close() error { 54 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 55 | defer cancel() 56 | pe.ticker.Stop() 57 | return pe.shutdown.Shutdown(ctx) 58 | } 59 | -------------------------------------------------------------------------------- /pkg/simulate/pushsync/metrics.go: -------------------------------------------------------------------------------- 1 | package pushsync 2 | 3 | import ( 4 | m "github.com/ethersphere/beekeeper/pkg/metrics" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metrics struct { 9 | UploadedChunks *prometheus.CounterVec 10 | DownloadedChunks *prometheus.CounterVec 11 | DownloadCount *prometheus.CounterVec 12 | } 13 | 14 | func newMetrics(runID string) metrics { 15 | subsystem := "simulation_pushsync" 16 | 17 | return metrics{ 18 | UploadedChunks: prometheus.NewCounterVec( 19 | prometheus.CounterOpts{ 20 | Namespace: m.Namespace, 21 | Subsystem: subsystem, 22 | ConstLabels: prometheus.Labels{ 23 | "run": runID, 24 | }, 25 | Name: "chunks_uploaded_count", 26 | Help: "Number of uploaded chunks.", 27 | }, 28 | []string{"node"}, 29 | ), 30 | DownloadedChunks: prometheus.NewCounterVec( 31 | prometheus.CounterOpts{ 32 | Namespace: m.Namespace, 33 | Subsystem: subsystem, 34 | ConstLabels: prometheus.Labels{ 35 | "run": runID, 36 | }, 37 | Name: "chunks_downloaded_count", 38 | Help: "Number of downloaded chunks.", 39 | }, 40 | []string{"node"}, 41 | ), 42 | DownloadCount: prometheus.NewCounterVec( 43 | prometheus.CounterOpts{ 44 | Namespace: m.Namespace, 45 | Subsystem: subsystem, 46 | ConstLabels: prometheus.Labels{ 47 | "run": runID, 48 | }, 49 | Name: "download_node_count", 50 | Help: "Number of nodes used for downloading.", 51 | }, 52 | []string{"node"}, 53 | ), 54 | } 55 | } 56 | 57 | func (s *Simulation) Report() []prometheus.Collector { 58 | return m.PrometheusCollectorsFromFields(s.metrics) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/simulate/pushsync/pushsync_test.go: -------------------------------------------------------------------------------- 1 | package pushsync_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ethersphere/beekeeper/pkg/simulate/pushsync" 7 | ) 8 | 9 | func TestGetIPFromUnderlays(t *testing.T) { 10 | ips := []string{ 11 | "/ip4/127.0.0.1/udp/9090/quic", 12 | "/ip6/::1/tcp/3217", 13 | "/ip4/10.0.0.0/tcp/80/http/baz.jpg", 14 | } 15 | ip := pushsync.GetIPFromUnderlays(ips) 16 | 17 | if ip != "10.0.0.0" { 18 | t.Fatalf("want %s got %s", ip, "10.0.0.0") 19 | } 20 | } 21 | 22 | func TestTobuckets(t *testing.T) { 23 | for _, tc := range []struct { 24 | name string 25 | base []string 26 | buckets [][]string 27 | leftover []string 28 | start float64 29 | end float64 30 | step float64 31 | }{ 32 | { 33 | name: "0.1-0.8-0.1", 34 | base: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, 35 | buckets: [][]string{{"1"}, {"2"}, {"3"}, {"4"}, {"5"}, {"6"}, {"7"}, {"8"}, {"9", "10"}}, 36 | leftover: []string{"9", "10"}, 37 | start: 0.1, 38 | end: 0.8, 39 | step: 0.1, 40 | }, 41 | { 42 | name: "0.2-0.9-0.1", 43 | base: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, 44 | buckets: [][]string{{"1", "2"}, {"3"}, {"4"}, {"5"}, {"6"}, {"7"}, {"8"}, {"9"}, {"10"}}, 45 | leftover: []string{"10"}, 46 | start: 0.2, 47 | end: 0.9, 48 | step: 0.1, 49 | }, 50 | { 51 | name: "0.2-1.0-0.1", 52 | base: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, 53 | buckets: [][]string{{"1", "2"}, {"3"}, {"4"}, {"5"}, {"6"}, {"7"}, {"8"}, {"9"}, {"10"}, {}}, 54 | leftover: []string{}, 55 | start: 0.2, 56 | end: 1.0, 57 | step: 0.1, 58 | }, 59 | } { 60 | t.Run(tc.name, func(t *testing.T) { 61 | buckets := pushsync.ToBuckets(tc.base, tc.start, tc.end, tc.step) 62 | leftover := buckets[len(buckets)-1] 63 | 64 | if !isArrSame(leftover, tc.leftover) { 65 | t.Fatal("leftovers do not match") 66 | } 67 | 68 | if len(buckets) != len(tc.buckets) { 69 | t.Fatal("len of bucks do not match") 70 | } 71 | 72 | for i := range buckets { 73 | if !isArrSame(buckets[i], tc.buckets[i]) { 74 | t.Fatal("buckets do not match") 75 | } 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func isArrSame(a, b []string) bool { 82 | if len(a) != len(b) { 83 | return false 84 | } 85 | 86 | for i := 0; i < len(a); i++ { 87 | if a[i] != b[i] { 88 | return false 89 | } 90 | } 91 | 92 | return true 93 | } 94 | -------------------------------------------------------------------------------- /pkg/swap/cache.go: -------------------------------------------------------------------------------- 1 | package swap 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type cache struct { 8 | blockTime int64 9 | m sync.Mutex 10 | } 11 | 12 | func newCache() *cache { 13 | return &cache{} 14 | } 15 | 16 | func (c *cache) SetBlockTime(blockTime int64) { 17 | c.m.Lock() 18 | c.blockTime = blockTime 19 | c.m.Unlock() 20 | } 21 | 22 | func (c *cache) BlockTime() int64 { 23 | c.m.Lock() 24 | defer c.m.Unlock() 25 | return c.blockTime 26 | } 27 | -------------------------------------------------------------------------------- /pkg/swap/http-errors.go: -------------------------------------------------------------------------------- 1 | package swap 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // BadRequestError holds list of errors from http response that represent 9 | // invalid data submitted by the user. 10 | type BadRequestError struct { 11 | errors []string 12 | } 13 | 14 | // NewBadRequestError constructs a new BadRequestError with provided errors. 15 | func NewBadRequestError(errors ...string) (err *BadRequestError) { 16 | return &BadRequestError{ 17 | errors: errors, 18 | } 19 | } 20 | 21 | func (e *BadRequestError) Error() (s string) { 22 | return strings.Join(e.errors, " ") 23 | } 24 | 25 | // Errors returns a list of error messages. 26 | func (e *BadRequestError) Errors() (errs []string) { 27 | return e.errors 28 | } 29 | 30 | // Errors that are returned by the API. 31 | var ( 32 | ErrUnauthorized = errors.New("unauthorized") 33 | ErrForbidden = errors.New("forbidden") 34 | ErrNotFound = errors.New("not found") 35 | ErrMethodNotAllowed = errors.New("method not allowed") 36 | ErrTooManyRequests = errors.New("too many requests") 37 | ErrInternalServerError = errors.New("internal server error") 38 | ErrServiceUnavailable = errors.New("service unavailable") 39 | ) 40 | -------------------------------------------------------------------------------- /pkg/swap/notset.go: -------------------------------------------------------------------------------- 1 | package swap 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | // ErrNotSet represents error when Swap client is not set 9 | var ErrNotSet = errors.New("swap client not initialized because geth-url is missing") 10 | 11 | // compile check whether NotSet implements Swap interface 12 | var _ Client = (*NotSet)(nil) 13 | 14 | type NotSet struct{} 15 | 16 | // SendETH makes ETH deposit 17 | func (n *NotSet) SendETH(ctx context.Context, to string, amount float64) (tx string, err error) { 18 | return "", ErrNotSet 19 | } 20 | 21 | // SendBZZ makes BZZ token deposit 22 | func (n *NotSet) SendBZZ(ctx context.Context, to string, amount float64) (tx string, err error) { 23 | return "", ErrNotSet 24 | } 25 | 26 | // SendGBZZ makes gBZZ token deposit 27 | func (n *NotSet) SendGBZZ(ctx context.Context, to string, amount float64) (tx string, err error) { 28 | return "", ErrNotSet 29 | } 30 | 31 | func (n *NotSet) AttestOverlayEthAddress(ctx context.Context, ethAddr string) (tx string, err error) { 32 | return "", ErrNotSet 33 | } 34 | 35 | // FetchBlockTime(ctx context.Context) (blockTime int64, err error) 36 | func (n *NotSet) FetchBlockTime(ctx context.Context, opts ...Option) (blockTime int64, err error) { 37 | return 0, ErrNotSet 38 | } 39 | -------------------------------------------------------------------------------- /pkg/swap/swap.go: -------------------------------------------------------------------------------- 1 | package swap 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | const ( 8 | BzzTokenAddress = "0x6aab14fe9cccd64a502d23842d916eb5321c26e7" 9 | EthAccount = "0x62cab2b3b55f341f10348720ca18063cdb779ad5" 10 | GasPrice int64 = 10000000000 11 | BzzGasLimit = 100000 12 | EthGasLimit = 21000 13 | mintBzz = "0x40c10f19" 14 | transferBzz = "0xa9059cbb" 15 | ) 16 | 17 | // Client defines Client interface 18 | type Client interface { 19 | SendETH(ctx context.Context, to string, amount float64) (tx string, err error) 20 | SendBZZ(ctx context.Context, to string, amount float64) (tx string, err error) 21 | SendGBZZ(ctx context.Context, to string, amount float64) (tx string, err error) 22 | AttestOverlayEthAddress(ctx context.Context, ethAddr string) (tx string, err error) 23 | BlockTimeFetcher 24 | } 25 | 26 | type BlockTimeFetcher interface { 27 | FetchBlockTime(ctx context.Context, opts ...Option) (blockTime int64, err error) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/test/case.go: -------------------------------------------------------------------------------- 1 | package bee 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/ethersphere/bee/v2/pkg/swarm" 9 | "github.com/ethersphere/beekeeper/pkg/bee" 10 | "github.com/ethersphere/beekeeper/pkg/logging" 11 | "github.com/ethersphere/beekeeper/pkg/orchestration" 12 | "github.com/ethersphere/beekeeper/pkg/random" 13 | ) 14 | 15 | type CheckCase struct { 16 | ctx context.Context 17 | clients map[string]*bee.Client 18 | cluster orchestration.Cluster 19 | overlays orchestration.ClusterOverlays 20 | logger logging.Logger 21 | 22 | nodes []BeeV2 23 | 24 | options CaseOptions 25 | rnd *rand.Rand 26 | } 27 | 28 | type CaseOptions struct { 29 | FileName string 30 | FileSize int64 31 | GasPrice string 32 | PostageTTL time.Duration 33 | PostageLabel string 34 | Seed int64 35 | PostageDepth uint64 36 | } 37 | 38 | func NewCheckCase(ctx context.Context, cluster orchestration.Cluster, caseOpts CaseOptions, logger logging.Logger) (*CheckCase, error) { 39 | clients, err := cluster.NodesClients(ctx) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | overlays, err := cluster.Overlays(ctx) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | logger.Infof("Seed: %d", caseOpts.Seed) 50 | 51 | rnd := random.PseudoGenerator(caseOpts.Seed) 52 | 53 | flatOverlays, err := cluster.FlattenOverlays(ctx) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | rnds := random.PseudoGenerators(caseOpts.Seed, len(flatOverlays)) 59 | 60 | var ( 61 | nodes []BeeV2 62 | count int 63 | ) 64 | 65 | for name, addr := range flatOverlays { 66 | nodes = append(nodes, BeeV2{ 67 | name: name, 68 | Addr: addr, 69 | client: clients[name], 70 | rnd: rnds[count], 71 | opts: caseOpts, 72 | logger: logger, 73 | }) 74 | count++ 75 | } 76 | 77 | return &CheckCase{ 78 | ctx: ctx, 79 | clients: clients, 80 | cluster: cluster, 81 | overlays: overlays, 82 | nodes: nodes, 83 | rnd: rnd, 84 | options: caseOpts, 85 | logger: logger, 86 | }, nil 87 | } 88 | 89 | func (c *CheckCase) RandomBee() *BeeV2 { 90 | _, nodeName, overlay := c.overlays.Random(c.rnd) 91 | 92 | return &BeeV2{ 93 | opts: c.options, 94 | name: nodeName, 95 | overlay: overlay, 96 | client: c.clients[nodeName], 97 | logger: c.logger, 98 | } 99 | } 100 | 101 | type File struct { 102 | address swarm.Address 103 | name string 104 | hash []byte 105 | rand *rand.Rand 106 | size int64 107 | } 108 | 109 | func (c *CheckCase) LastBee() *BeeV2 { 110 | return &c.nodes[len(c.nodes)-1] 111 | } 112 | 113 | func (c *CheckCase) Bee(index int) *BeeV2 { 114 | return &c.nodes[index] 115 | } 116 | 117 | func (c *CheckCase) Balances(ctx context.Context) (balances orchestration.NodeGroupBalances, err error) { 118 | return c.cluster.FlattenBalances(ctx) 119 | } 120 | -------------------------------------------------------------------------------- /pkg/test/chunk.go: -------------------------------------------------------------------------------- 1 | package bee 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/ethersphere/bee/v2/pkg/swarm" 7 | ) 8 | 9 | type chunkV2 struct { 10 | addr swarm.Address 11 | data []byte 12 | } 13 | 14 | func (c *chunkV2) Addr() swarm.Address { 15 | return c.addr 16 | } 17 | 18 | func (c *chunkV2) AddrString() string { 19 | return c.addr.String() 20 | } 21 | 22 | func (c *chunkV2) Equals(data []byte) bool { 23 | return bytes.Equal(c.data, data) 24 | } 25 | 26 | func (c *chunkV2) Contains(data []byte) bool { 27 | return bytes.Contains(c.data, data) 28 | } 29 | 30 | func (c *chunkV2) Size() int { 31 | return len(c.data) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/test/funcs.go: -------------------------------------------------------------------------------- 1 | package bee 2 | 3 | type Bees []BeeV2 4 | 5 | func (c *CheckCase) Bees() Bees { 6 | return Bees(c.nodes) 7 | } 8 | 9 | type FilterFunc func(b *BeeV2) bool 10 | 11 | func (bb Bees) Filter(f FilterFunc) (out Bees) { 12 | for _, b := range bb { 13 | if f(&b) { 14 | out = append(out, b) 15 | } 16 | } 17 | 18 | return 19 | } 20 | 21 | type ConsumeFunc func(b *BeeV2) error 22 | 23 | func (bb Bees) ForEach(c ConsumeFunc) error { 24 | for _, b := range bb { 25 | if err := c(&b); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /pkg/test/uploader.go: -------------------------------------------------------------------------------- 1 | package bee 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | 8 | "github.com/ethersphere/beekeeper/pkg/bee" 9 | "github.com/ethersphere/beekeeper/pkg/bee/api" 10 | "github.com/ethersphere/beekeeper/pkg/logging" 11 | ) 12 | 13 | type ChunkUploader struct { 14 | ctx context.Context 15 | rnd *rand.Rand 16 | name string 17 | client *bee.Client 18 | batchID string 19 | Overlay string 20 | logger logging.Logger 21 | } 22 | 23 | func (c *ChunkUploader) Name() string { 24 | return c.name 25 | } 26 | 27 | func (cu *ChunkUploader) UploadRandomChunk() (*chunkV2, error) { 28 | chunk, err := bee.NewRandomChunk(cu.rnd, cu.logger) 29 | if err != nil { 30 | return nil, fmt.Errorf("node %s: %w", cu.name, err) 31 | } 32 | 33 | ref, err := cu.client.UploadChunk(cu.ctx, chunk.Data(), api.UploadOptions{BatchID: cu.batchID}) 34 | if err != nil { 35 | return nil, fmt.Errorf("node %s: %w", cu.name, err) 36 | } 37 | 38 | return &chunkV2{ 39 | addr: ref, 40 | data: chunk.Data(), 41 | }, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/tracing/tracing.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/uber/jaeger-client-go" 8 | jconfig "github.com/uber/jaeger-client-go/config" 9 | 10 | "github.com/opentracing/opentracing-go" 11 | ) 12 | 13 | type Options struct { 14 | Enabled bool 15 | Endpoint string 16 | ServiceName string 17 | } 18 | 19 | // NewTracer creates a new Tracer and returns a closer which needs to be closed 20 | // when the Tracer is no longer used to flush remaining traces. 21 | func NewTracer(o *Options) (opentracing.Tracer, io.Closer, error) { 22 | if o == nil { 23 | o = new(Options) 24 | } 25 | 26 | cfg := jconfig.Configuration{ 27 | Disabled: !o.Enabled, 28 | ServiceName: o.ServiceName, 29 | Sampler: &jconfig.SamplerConfig{ 30 | Type: jaeger.SamplerTypeConst, 31 | Param: 1, 32 | }, 33 | Reporter: &jconfig.ReporterConfig{ 34 | LogSpans: true, 35 | BufferFlushInterval: 1 * time.Second, 36 | LocalAgentHostPort: o.Endpoint, 37 | }, 38 | } 39 | 40 | t, closer, err := cfg.NewTracer() 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | return t, closer, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/wslistener/wslistener.go: -------------------------------------------------------------------------------- 1 | package wslistener 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "sync" 8 | "time" 9 | 10 | "github.com/ethersphere/beekeeper/pkg/bee" 11 | "github.com/ethersphere/beekeeper/pkg/logging" 12 | "github.com/gorilla/websocket" 13 | ) 14 | 15 | // ListenWebSocket listens for messages on a websocket connection. 16 | func ListenWebSocket(ctx context.Context, client *bee.Client, endpoint string, logger logging.Logger) (<-chan string, func(), error) { 17 | dialer := &websocket.Dialer{ 18 | Proxy: http.ProxyFromEnvironment, 19 | HandshakeTimeout: 45 * time.Second, 20 | } 21 | 22 | ws, _, err := dialer.DialContext(ctx, fmt.Sprintf("ws://%s%s", client.Host(), endpoint), http.Header{}) 23 | if err != nil { 24 | return nil, nil, err 25 | } 26 | 27 | ch := make(chan string) 28 | readCh := make(chan []byte) 29 | errCh := make(chan error) 30 | done := make(chan struct{}) 31 | 32 | go func() { 33 | defer close(readCh) 34 | defer close(errCh) 35 | 36 | for { 37 | _, data, err := ws.ReadMessage() 38 | if err != nil { 39 | errCh <- err 40 | return 41 | } 42 | select { 43 | case readCh <- data: 44 | case <-done: 45 | return 46 | case <-ctx.Done(): 47 | return 48 | } 49 | } 50 | }() 51 | 52 | go func() { 53 | defer close(ch) 54 | 55 | for { 56 | select { 57 | case data := <-readCh: 58 | logger.WithField("node", client.Name()).Infof("websocket received message: %s", string(data)) 59 | select { 60 | case ch <- string(data): 61 | case <-done: 62 | return 63 | } 64 | case err := <-errCh: 65 | logger.Errorf("websocket error: %v", err) 66 | return 67 | case <-ctx.Done(): 68 | logger.Info("context canceled, closing websocket") 69 | return 70 | case <-done: 71 | return 72 | } 73 | } 74 | }() 75 | 76 | var once sync.Once 77 | closer := func() { 78 | once.Do(func() { 79 | deadline := time.Now().Add(5 * time.Second) 80 | msg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") 81 | if err := ws.WriteControl(websocket.CloseMessage, msg, deadline); err != nil { 82 | logger.Errorf("failed to send close message: %v", err) 83 | } 84 | 85 | close(done) 86 | if err := ws.Close(); err != nil { 87 | logger.Errorf("failed to close websocket: %v", err) 88 | } 89 | }) 90 | } 91 | 92 | return ch, closer, nil 93 | } 94 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package beekeeper 2 | 3 | var ( 4 | version string // automatically set semantic version number 5 | commit string // automatically set git commit hash 6 | 7 | // Version TODO 8 | Version = func() string { 9 | if commit != "" { 10 | return version + "-" + commit 11 | } 12 | return version + "-dev" 13 | }() 14 | ) 15 | --------------------------------------------------------------------------------