Skip to content

Commit

Permalink
Add initial logic for checking connection with cloud
Browse files Browse the repository at this point in the history
MOD handle / display errors

ADD authorized executor
ADD with retry connection to cloud

MOD init auth executor for doctor and flux plugin

ADD auth source for source plugins

FIX prometheus
  • Loading branch information
madebyrogal authored and mszostok committed Feb 22, 2024
1 parent cd9ef80 commit 4463682
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 14 deletions.
1 change: 1 addition & 0 deletions cmd/executor/helm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/hashicorp/go-plugin"

"github.com/kubeshop/botkube-cloud-plugins/internal/executor/helm"

"github.com/kubeshop/botkube/pkg/api/executor"
)

Expand Down
39 changes: 39 additions & 0 deletions internal/executor/authorized/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package authorized

import (
"context"

"github.com/kubeshop/botkube-cloud-plugins/internal/remote"

"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/executor"
)

type unauthorizedExecutor struct {
metadata api.MetadataOutput
}

func NewExecutor(exec executor.Executor) executor.Executor {
deployClient := remote.NewDeploymentClient(remote.NewDefaultGqlClient())
ok := deployClient.IsConnectedWithCould()
if !ok {
metadata, _ := exec.Metadata(context.Background())
return &unauthorizedExecutor{
metadata: metadata,
}
}

return exec
}

func (e unauthorizedExecutor) Execute(context.Context, executor.ExecuteInput) (executor.ExecuteOutput, error) {
panic("couldn't run the plugin: invalid license")
}

func (e unauthorizedExecutor) Metadata(context.Context) (api.MetadataOutput, error) {
return e.metadata, nil
}

func (e unauthorizedExecutor) Help(context.Context) (api.Message, error) {
panic("couldn't run the plugin: invalid license")
}
7 changes: 5 additions & 2 deletions internal/executor/doctor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/sirupsen/logrus"
stringsutil "k8s.io/utils/strings"

"github.com/kubeshop/botkube-cloud-plugins/internal/executor/authorized"

"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/executor"
"github.com/kubeshop/botkube/pkg/config"
Expand Down Expand Up @@ -53,10 +55,11 @@ type Executor struct {
}

// NewExecutor returns a new Executor instance.
func NewExecutor(ver string) *Executor {
return &Executor{
func NewExecutor(ver string) executor.Executor {
exec := &Executor{
pluginVersion: ver,
}
return authorized.NewExecutor(exec)
}

// Metadata returns details about the Doctor plugin.
Expand Down
7 changes: 5 additions & 2 deletions internal/executor/flux/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"github.com/allegro/bigcache/v3"

"github.com/kubeshop/botkube-cloud-plugins/internal/executor/authorized"

"github.com/kubeshop/botkube-cloud-plugins/internal/executor/flux/commands"
"github.com/kubeshop/botkube-cloud-plugins/internal/executor/x"
"github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/output"
Expand Down Expand Up @@ -35,12 +37,13 @@ type Executor struct {
}

// NewExecutor returns a new Executor instance.
func NewExecutor(cache *bigcache.BigCache, ver string) *Executor {
func NewExecutor(cache *bigcache.BigCache, ver string) executor.Executor {
x.BuiltinCmdPrefix = "" // we don't need them
return &Executor{
exec := &Executor{
pluginVersion: ver,
cache: cache,
}
return authorized.NewExecutor(exec)
}

// Metadata returns details about the Flux plugin.
Expand Down
6 changes: 4 additions & 2 deletions internal/executor/helm/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
_ "embed"
"fmt"
"github.com/kubeshop/botkube-cloud-plugins/internal/executor/authorized"
"os"

"github.com/alexflint/go-arg"
Expand Down Expand Up @@ -55,11 +56,12 @@ type Executor struct {
}

// NewExecutor returns a new Executor instance.
func NewExecutor(ver string) *Executor {
return &Executor{
func NewExecutor(ver string) executor.Executor {
exec := &Executor{
pluginVersion: ver,
executeCommand: pluginx.ExecuteCommand,
}
return authorized.NewExecutor(exec)
}

// Metadata returns details about Helm plugin.
Expand Down
32 changes: 32 additions & 0 deletions internal/remote/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package remote

import (
"os"
)

const (
// ProviderEndpointEnvKey holds config provider endpoint.
//nolint:gosec // Potential hardcoded credentials
ProviderEndpointEnvKey = "CONFIG_PROVIDER_ENDPOINT"
// ProviderIdentifierEnvKey holds config provider identifier.
ProviderIdentifierEnvKey = "CONFIG_PROVIDER_IDENTIFIER"
// ProviderAPIKeyEnvKey holds config provider API key.
//nolint:gosec // warns us about 'Potential hardcoded credentials' but there is no security issue here
ProviderAPIKeyEnvKey = "CONFIG_PROVIDER_API_KEY"
)

// Config holds configuration for remote configuration.
type Config struct {
Endpoint string
Identifier string
APIKey string
}

// GetConfig returns remote configuration if it is set.
func GetConfig() Config {
return Config{
Endpoint: os.Getenv(ProviderEndpointEnvKey),
Identifier: os.Getenv(ProviderIdentifierEnvKey),
APIKey: os.Getenv(ProviderAPIKeyEnvKey),
}
}
64 changes: 64 additions & 0 deletions internal/remote/deploy_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package remote

import (
"context"
"time"

"github.com/avast/retry-go/v4"
"github.com/hasura/go-graphql-client"
)

// GraphQLClient defines GraphQL client.
type GraphQLClient interface {
Client() *graphql.Client
DeploymentID() string
}

// DeploymentClient defines GraphQL client for Deployment.
type DeploymentClient struct {
client GraphQLClient
}

// NewDeploymentClient initializes GraphQL client.
func NewDeploymentClient(client GraphQLClient) *DeploymentClient {
return &DeploymentClient{client: client}
}

// Deployment returns deployment with Botkube configuration.
type Deployment struct {
ID string
}

// IsConnectedWithCould returns whether connected to Botkube Cloud
func (d *DeploymentClient) IsConnectedWithCould() bool {
var query struct {
Deployment struct {
ID string
} `graphql:"deployment(id: $id)"`
}
deployID := d.client.DeploymentID()
variables := map[string]interface{}{
"id": graphql.ID(deployID),
}
err := d.withRetries(context.Background(), 5, func() error {
return d.client.Client().Query(context.Background(), &query, variables)
})
if err != nil {
return false
}

return query.Deployment.ID == d.client.DeploymentID()
}

func (d *DeploymentClient) withRetries(ctx context.Context, maxRetries int, fn func() error) error {
return retry.Do(
func() error {
return fn()
},
retry.DelayType(func(uint, error, *retry.Config) time.Duration {
return 200 * time.Microsecond
}),
retry.Attempts(uint(maxRetries)), // infinite, we cancel that by our own
retry.Context(ctx),
)
}
103 changes: 103 additions & 0 deletions internal/remote/gql_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package remote

import (
"net/http"
"time"

"github.com/hasura/go-graphql-client"
)

const (
defaultTimeout = 30 * time.Second
//nolint:gosec // warns us about 'Potential hardcoded credentials' but there is no security issue here
apiKeyHeaderName = "X-API-Key"
)

// Option define GraphQL client option.
type Option func(*Gql)

// WithEndpoint configures ApiURL for GraphQL endpoint.
func WithEndpoint(url string) Option {
return func(client *Gql) {
client.endpoint = url
}
}

// WithAPIKey configures API key for GraphQL endpoint.
func WithAPIKey(apiKey string) Option {
return func(client *Gql) {
client.apiKey = apiKey
}
}

// WithDeploymentID configures deployment id for GraphQL endpoint.
func WithDeploymentID(id string) Option {
return func(client *Gql) {
client.deployID = id
}
}

// Gql defines GraphQL client data structure.
type Gql struct {
client *graphql.Client
endpoint string
apiKey string
deployID string
}

// NewGqlClient initializes GraphQL client.
func NewGqlClient(options ...Option) *Gql {
c := &Gql{}
for _, opt := range options {
opt(c)
}

httpCli := &http.Client{
Transport: newAPIKeySecuredTransport(c.apiKey),
Timeout: defaultTimeout,
}

c.client = graphql.NewClient(c.endpoint, httpCli)
return c
}

// NewDefaultGqlClient initializes GraphQL client with default options.
func NewDefaultGqlClient() *Gql {
remoteCfg := GetConfig()

return NewGqlClient(
WithEndpoint(remoteCfg.Endpoint),
WithAPIKey(remoteCfg.APIKey),
WithDeploymentID(remoteCfg.Identifier),
)
}

// DeploymentID returns deployment ID.
func (g *Gql) DeploymentID() string {
return g.deployID
}

// Client returns GraphQL client.
func (g *Gql) Client() *graphql.Client {
return g.client
}

type apiKeySecuredTransport struct {
apiKey string
transport *http.Transport
}

func newAPIKeySecuredTransport(apiKey string) *apiKeySecuredTransport {
return &apiKeySecuredTransport{
apiKey: apiKey,
transport: http.DefaultTransport.(*http.Transport).Clone(),
}
}

// RoundTrip adds API key to request header and executes RoundTrip for the underlying transport.
func (t *apiKeySecuredTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.apiKey != "" {
req.Header.Set(apiKeyHeaderName, t.apiKey)
}
return t.transport.RoundTrip(req)
}
6 changes: 4 additions & 2 deletions internal/source/argocd/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"

"github.com/kubeshop/botkube-cloud-plugins/internal/source/authorized"
"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/source"
"github.com/kubeshop/botkube/pkg/config"
Expand Down Expand Up @@ -56,11 +57,12 @@ type Source struct {
}

// NewSource returns a new instance of Source.
func NewSource(version string) *Source {
return &Source{
func NewSource(version string) source.Source {
src := &Source{
pluginVersion: version,
cfgs: sync.Map{},
}
return authorized.NewSource(src)
}

type subscription struct {
Expand Down
39 changes: 39 additions & 0 deletions internal/source/authorized/source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package authorized

import (
"context"

"github.com/kubeshop/botkube-cloud-plugins/internal/remote"

"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/source"
)

type unauthorizedSource struct {
metadata api.MetadataOutput
}

func NewSource(source source.Source) source.Source {
deployClient := remote.NewDeploymentClient(remote.NewDefaultGqlClient())
ok := deployClient.IsConnectedWithCould()
if !ok {
metadata, _ := source.Metadata(context.Background())
return &unauthorizedSource{
metadata: metadata,
}
}

return source
}

func (s unauthorizedSource) Stream(context.Context, source.StreamInput) (source.StreamOutput, error) {
panic("couldn't run the plugin: invalid license")
}

func (s unauthorizedSource) Metadata(context.Context) (api.MetadataOutput, error) {
return s.metadata, nil
}

func (s unauthorizedSource) HandleExternalRequest(context.Context, source.ExternalRequestInput) (source.ExternalRequestOutput, error) {
panic("couldn't run the plugin: invalid license")
}
Loading

0 comments on commit 4463682

Please sign in to comment.