Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Botkube CLI login, migrate commands and fix E2E migration tests #1128

Merged
merged 3 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/branch-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ on:
push:
branches:
- main
- migration-improvements
pkosiec marked this conversation as resolved.
Show resolved Hide resolved

env:
HELM_VERSION: v3.9.0
K3D_VERSION: v5.4.6
IMAGE_REGISTRY: "ghcr.io"
IMAGE_REPOSITORY: "kubeshop/botkube"
MIGRATOR_IMAGE_REPOSITORY: "kubeshop/botkube-migration"
CFG_EXPORTER_IMAGE_REPOSITORY: "kubeshop/botkube-config-exporter"
IMAGE_TAG: v9.99.9-dev # TODO: Use commit hash tag to make the predictable builds for each commit on branch

jobs:
Expand Down Expand Up @@ -82,7 +83,7 @@ jobs:
run: make release-snapshot-cli
- name: Add botkube alias
run: |
echo BOTKUBE_BIN="$PWD/dist/botkube-cli_linux_amd64_v1/botkube" >> $GITHUB_ENV
echo BOTKUBE_BINARY_PATH="$PWD/dist/botkube-cli_linux_amd64_v1/botkube" >> $GITHUB_ENV
- name: Install Helm
uses: azure/setup-helm@v1
with:
Expand All @@ -94,7 +95,7 @@ jobs:
- name: Run e2e tests for botkube client
env:
DISCORD_BOT_ID: ${{ secrets.DISCORD_BOT_ID }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_TESTER_APP_TOKEN }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
DISCORD_TESTER_APP_TOKEN: ${{ secrets.DISCORD_TESTER_APP_TOKEN }}
BOTKUBE_CLOUD_DEV_GQL_ENDPOINT: ${{ secrets.BOTKUBE_CLOUD_DEV_GQL_ENDPOINT }}
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/pr-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ env:
PR_NUMBER: ${{ github.event.pull_request.number }}
IMAGE_REGISTRY: "ghcr.io"
IMAGE_REPOSITORY: "kubeshop/pr/botkube"
CFG_EXPORTER_IMAGE_REPOSITORY: "kubeshop/pr/botkube-config-exporter"
IMAGE_TAG: ${{ github.event.pull_request.number }}-PR
IMAGE_SAVE_LOAD_DIR: /tmp/botkube-images

Expand Down Expand Up @@ -69,7 +70,7 @@ jobs:
retention-days: 1

push-image:
name: Push Botkube image
name: Push images
runs-on: ubuntu-latest
needs: [ save-image ]

Expand Down
42 changes: 33 additions & 9 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ env:
- IMAGE_REGISTRY={{ if index .Env "IMAGE_REGISTRY" }}{{ .Env.IMAGE_REGISTRY }}{{ else }}ghcr.io{{ end }}
- IMAGE_REPOSITORY={{ if index .Env "IMAGE_REPOSITORY" }}{{ .Env.IMAGE_REPOSITORY }}{{ else }}kubeshop/botkube{{ end }}
- IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}{{ .Tag }}{{ end }}
- MIGRATOR_IMAGE_REPOSITORY={{ if index .Env "MIGRATOR_IMAGE_REPOSITORY" }}{{ .Env.MIGRATOR_IMAGE_REPOSITORY }}{{ else }}kubeshop/botkube-migration{{ end }}
- CFG_EXPORTER_IMAGE_REPOSITORY={{ if index .Env "CFG_EXPORTER_IMAGE_REPOSITORY" }}{{ .Env.CFG_EXPORTER_IMAGE_REPOSITORY }}{{ else }}kubeshop/botkube-config-exporter{{ end }}
- ANALYTICS_API_KEY={{ if index .Env "ANALYTICS_API_KEY" }}{{ .Env.ANALYTICS_API_KEY }}{{ else }}{{ end }}
- HOMEBREW_REPO_OWNER={{ if index .Env "HOMEBREW_REPO_OWNER" }}{{ .Env.HOMEBREW_REPO_OWNER }}{{ else }}kubeshop{{ end }}
- HOMEBREW_REPO_NAME={{ if index .Env "HOMEBREW_REPO_NAME" }}{{ .Env.HOMEBREW_REPO_NAME }}{{ else }}homebrew-botkube{{ end }}
Expand Down Expand Up @@ -35,7 +35,10 @@ builds:
main: cmd/cli/main.go
ldflags:
- -s -w
-X github.com/kubeshop/botkube/internal/cli/migrate.Tag={{ .Env.IMAGE_TAG }}
-X github.com/kubeshop/botkube/cmd/cli/cmd/migrate.DefaultImageTag={{ .Env.IMAGE_TAG }}
-X go.szostok.io/version.version={{.Version}}
-X go.szostok.io/version.buildDate={{.Date}}
-X go.szostok.io/version.name=botkube
env:
- CGO_ENABLED=0
goos:
Expand All @@ -47,8 +50,8 @@ builds:
- arm64
goarm:
- "7"
- id: botkube-cloud-migration
binary: botkube-cloud-migration
- id: botkube-config-exporter
binary: botkube-config-exporter
main: cmd/config-exporter/main.go
env:
- CGO_ENABLED=0
Expand Down Expand Up @@ -83,6 +86,7 @@ changelog:
skip: false

dockers:
# Botkube Agent
- image_templates:
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-amd64"
use: buildx
Expand All @@ -107,22 +111,42 @@ dockers:
build_flag_templates:
- "--platform=linux/arm"
- "--build-arg=botkube_version={{ .Env.IMAGE_TAG }}"
# Config Exporter
- image_templates:
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.MIGRATOR_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}"
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-amd64"
use: buildx
dockerfile: "build/Dockerfile.migration"
dockerfile: "build/Dockerfile.config_exporter"
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=botkube_cloud_migration_version={{ .Env.IMAGE_TAG }}"
- "--build-arg=botkube_config_exporter_version={{ .Env.IMAGE_TAG }}"
- image_templates:
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-arm64"
use: buildx
goarch: arm64
dockerfile: "build/Dockerfile.config_exporter"
build_flag_templates:
- "--platform=linux/arm64"
- "--build-arg=botkube_config_exporter_version={{ .Env.IMAGE_TAG }}"
- image_templates:
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-armv7"
use: buildx
goarch: arm
goarm: 7
dockerfile: "build/Dockerfile.config_exporter"
build_flag_templates:
- "--platform=linux/arm"
- "--build-arg=botkube_config_exporter_version={{ .Env.IMAGE_TAG }}"
docker_manifests:
- name_template: "{{.Env.IMAGE_REGISTRY}}/{{.Env.IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}"
image_templates:
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-amd64"
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-arm64"
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-armv7"
- name_template: "{{.Env.IMAGE_REGISTRY}}/{{.Env.MIGRATOR_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}"
- name_template: "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}"
image_templates:
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.MIGRATOR_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}"
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-amd64"
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-arm64"
- "{{.Env.IMAGE_REGISTRY}}/{{.Env.CFG_EXPORTER_IMAGE_REPOSITORY}}:{{ .Env.IMAGE_TAG }}-armv7"

archives:
- id: botkube-cli
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test-integration-discord: system-check
@go test -v -tags=integration -race -count=1 ./test/e2e/... -run "TestDiscord"

test-migration-tool: system-check
@go test -v -tags=e2e -race -count=1 ./test/migration/e2e/...
@go test -v -tags=migration -race -count=1 ./test/e2e/...

# Build the binary
build: pre-build
Expand Down Expand Up @@ -93,7 +93,7 @@ gen-plugins-index: build-plugins

gen-docs-cli:
rm -f ./cmd/cli/docs/*
go run cmd/cli/main.go gen-usage-docs
go run -ldflags="-X go.szostok.io/version.name=botkube" cmd/cli/main.go gen-usage-docs
.PHONY: gen-docs-cli

# Pre-build checks
Expand Down
14 changes: 14 additions & 0 deletions build/Dockerfile.config_exporter
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM gcr.io/distroless/static:latest

ARG botkube_config_exporter_version="dev"

LABEL org.opencontainers.image.source="git@github.com:kubeshop/botkube.git" \
org.opencontainers.image.title="Botkube Config Exporter" \
org.opencontainers.image.version="${botkube_config_exporter_version}" \
org.opencontainers.image.description="Botkube Config Exporter fetches the Botkube configuration and stores it in a ConfigMap." \
org.opencontainers.image.documentation="https://docs.botkube.io" \
org.opencontainers.image.licenses="MIT"

COPY botkube-config-exporter /usr/local/bin/botkube-config-exporter

CMD ["botkube-config-exporter"]
14 changes: 0 additions & 14 deletions build/Dockerfile.migration

This file was deleted.

6 changes: 3 additions & 3 deletions cmd/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ The server is stopped after the callback is received.

### Migration

Once logged in, we create a pod in the same namespace as the Botkube instance that mounts the same
secrets and config maps as the Botkube pod and generates and stores the entire configuration in a
config map `botkube-migration`.
Once user is logged in, Botkube CLI creates a Pod in the same namespace where Botkube resides. Then, it mounts the same
Secrets and ConfigMaps as the Botkube Pod, and pulls entire configuration to a
ConfigMap `botkube-config-exporter`.

Once we have the configuration, we can turn it into a API call and create identical
resources in Botkube Cloud.
14 changes: 10 additions & 4 deletions cmd/cli/cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ func New() (*Config, error) {
return c, nil
}

const (
dirPerms = 0755
filePerms = 0644
)

// Save saves Config to local FS
func (c *Config) Save() error {
if _, err := os.Stat(filepath.Clean(filepath.Dir(configFilePath))); os.IsNotExist(err) {
// #nosec G301
err := os.MkdirAll(filepath.Clean(filepath.Dir(configFilePath)), 0755)
cfgFileDir := filepath.Clean(filepath.Dir(configFilePath))
cfgFilePath := filepath.Clean(configFilePath)
if _, err := os.Stat(cfgFileDir); os.IsNotExist(err) {
err = os.MkdirAll(cfgFileDir, dirPerms)
if err != nil {
return fmt.Errorf("failed to create config directory: %v", err)
}
Expand All @@ -48,7 +54,7 @@ func (c *Config) Save() error {
}

// #nosec G306
err = os.WriteFile(filepath.Clean(configFilePath), data, 0644)
err = os.WriteFile(cfgFilePath, data, filePerms)
if err != nil {
return fmt.Errorf("failed to write config: %v", err)
}
Expand Down
88 changes: 8 additions & 80 deletions cmd/cli/cmd/login.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
package cmd

import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"

"github.com/fatih/color"
"github.com/pkg/browser"
"github.com/spf13/cobra"

"github.com/kubeshop/botkube/cmd/cli/cmd/config"
"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/cli/heredoc"
)

const (
srvAddress = "localhost:8085"
loginURL = "http://localhost:3000/cli/login?cli_server_login=http://localhost:8085/login_redirect"
redirectURLSuccess = "http://localhost:3000/cli/login?success=true"
"github.com/kubeshop/botkube/internal/cli/login"
)

// NewLogin returns a cobra.Command for logging into a Botkube Cloud.
func NewLogin() *cobra.Command {
var opts login.Options

login := &cobra.Command{
Use: "login [OPTIONS]",
Short: "Login to a Botkube Cloud",
Expand All @@ -33,74 +22,13 @@ func NewLogin() *cobra.Command {
<cli> login
`, cli.Name),
RunE: func(cmd *cobra.Command, args []string) error {
return runLogin(cmd.Context(), os.Stdout)
return login.Run(cmd.Context(), os.Stdout, opts)
},
}

return login
}

func runLogin(_ context.Context, w io.Writer) error {
t, err := fetchToken(srvAddress, loginURL)
if err != nil {
return err
}

c := config.Config{Token: t.Token}
if err := c.Save(); err != nil {
return err
}

okCheck := color.New(color.FgGreen).FprintlnFunc()
okCheck(w, "Login Succeeded")

return nil
}

type tokenResp struct {
Token string `json:"token"`
}

func fetchToken(addr, authUrl string) (*tokenResp, error) {
ch := make(chan *tokenResp)
errCh := make(chan error)

mux := http.NewServeMux()
mux.HandleFunc("/login_redirect", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, redirectURLSuccess, http.StatusFound)

ch <- &tokenResp{
Token: r.URL.Query().Get("token"),
}
})

s := http.Server{
Addr: addr,
Handler: mux,
ReadHeaderTimeout: 2 * time.Second,
}

go func() {
err := s.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
errCh <- err
}
}()

fmt.Println(heredoc.Docf(`
Your browser has been opened to visit:
%s
`, authUrl))
err := browser.OpenURL(authUrl)
if err != nil {
return nil, fmt.Errorf("failed to open page: %v", err)
}
flags := login.Flags()
flags.StringVar(&opts.CloudDashboardURL, "cloud-dashboard-url", "https://app.botkube.io", "Botkube Cloud URL")
flags.StringVar(&opts.LocalServerAddress, "local-server-addr", "localhost:8085", "Address of a local server which is used for the login flow")

select {
case token := <-ch:
_ = s.Shutdown(context.Background())
return token, nil
case err = <-errCh:
return nil, err
}
return login
}
Loading