-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial logic for checking connection with cloud
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
1 parent
cd9ef80
commit 4463682
Showing
13 changed files
with
308 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
Oops, something went wrong.