diff --git a/capten/agent/internal/api/agent.go b/capten/agent/internal/api/agent.go index ff878d25..2183b752 100644 --- a/capten/agent/internal/api/agent.go +++ b/capten/agent/internal/api/agent.go @@ -6,13 +6,13 @@ import ( "github.com/intelops/go-common/logging" "github.com/kube-tarian/kad/capten/agent/internal/config" - "github.com/kube-tarian/kad/capten/agent/internal/temporalclient" captenstore "github.com/kube-tarian/kad/capten/common-pkg/capten-store" "github.com/kube-tarian/kad/capten/common-pkg/pb/agentpb" "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" "github.com/kube-tarian/kad/capten/common-pkg/pb/clusterpluginspb" "github.com/kube-tarian/kad/capten/common-pkg/pb/pluginstorepb" pluginstore "github.com/kube-tarian/kad/capten/common-pkg/plugin-store" + "github.com/kube-tarian/kad/capten/common-pkg/temporalclient" ) var _ agentpb.AgentServer = &Agent{} @@ -26,6 +26,9 @@ type pluginStore interface { GetPluginValues(storeType pluginstorepb.StoreType, pluginName, version string) ([]byte, error) DeployPlugin(storeType pluginstorepb.StoreType, pluginName, version string, values []byte) error UnDeployPlugin(storeType pluginstorepb.StoreType, pluginName string) error + + DeployClusterPlugin(ctx context.Context, pluginData *clusterpluginspb.Plugin) error + UnDeployClusterPlugin(ctx context.Context, request *clusterpluginspb.UnDeployClusterPluginRequest) error } type Agent struct { @@ -58,7 +61,7 @@ func NewAgent(log logging.Logger, cfg *config.SericeConfig, log: log, } - agent.plugin, err = pluginstore.NewPluginStore(log, as, agent) + agent.plugin, err = pluginstore.NewPluginStore(log, as, tc) if err != nil { return nil, err } diff --git a/capten/agent/internal/api/agent_auth.go b/capten/agent/internal/api/agent_auth.go index b2e1cb86..f407f828 100644 --- a/capten/agent/internal/api/agent_auth.go +++ b/capten/agent/internal/api/agent_auth.go @@ -18,7 +18,7 @@ func (a *Agent) AuthInterceptor(ctx context.Context, req interface{}, info *grpc a.log.Errorf("error occured while extracting oauth token, oryurl, and ory pat token error: %v", err.Error()) return nil, status.Error(codes.Unauthenticated, "invalid or missing token") } - oryApiClient := newOryAPIClient(a.log, oryUrl) + oryApiClient := newOryAPIClient(oryUrl) isValid, err := verifyToken(a.log, oryPat, tk, oryApiClient) if err != nil || !isValid { return nil, status.Error(codes.Unauthenticated, "invalid or missing token") @@ -27,7 +27,7 @@ func (a *Agent) AuthInterceptor(ctx context.Context, req interface{}, info *grpc return handler(ctx, req) } -func newOryAPIClient(log logging.Logger, oryURL string) *ory.APIClient { +func newOryAPIClient(oryURL string) *ory.APIClient { config := ory.NewConfiguration() config.Servers = ory.ServerConfigurations{{ URL: oryURL, diff --git a/capten/agent/internal/api/app_config_sso.go b/capten/agent/internal/api/app_config_sso.go index 9a06eef9..556868e2 100644 --- a/capten/agent/internal/api/app_config_sso.go +++ b/capten/agent/internal/api/app_config_sso.go @@ -3,9 +3,9 @@ package api import ( "context" - "github.com/kube-tarian/kad/capten/agent/internal/workers" "github.com/kube-tarian/kad/capten/common-pkg/credential" "github.com/kube-tarian/kad/capten/common-pkg/pb/agentpb" + "github.com/kube-tarian/kad/capten/common-pkg/workers" "github.com/kube-tarian/kad/capten/model" "github.com/pkg/errors" "gopkg.in/yaml.v2" diff --git a/capten/agent/internal/api/cluster_plugin_apis.go b/capten/agent/internal/api/cluster_plugin_apis.go index c1fd7543..b9f96b87 100644 --- a/capten/agent/internal/api/cluster_plugin_apis.go +++ b/capten/agent/internal/api/cluster_plugin_apis.go @@ -1,14 +1,9 @@ package api import ( - "bytes" "context" - "html/template" - "github.com/kube-tarian/kad/capten/agent/internal/workers" "github.com/kube-tarian/kad/capten/common-pkg/pb/clusterpluginspb" - "github.com/kube-tarian/kad/capten/model" - "gopkg.in/yaml.v2" ) func (a *Agent) GetClusterPlugins(ctx context.Context, request *clusterpluginspb.GetClusterPluginsRequest) ( @@ -43,27 +38,15 @@ func (a *Agent) DeployClusterPlugin(ctx context.Context, request *clusterplugins *clusterpluginspb.DeployClusterPluginResponse, error) { a.log.Infof("Recieved Plugin Deploy request for plugin %s, version %+v", request.Plugin.PluginName, request.Plugin.Version) - values, err := replaceTemplateValuesInByteData(request.Plugin.Values, request.Plugin.OverrideValues) + err := a.plugin.DeployClusterPlugin(ctx, request.Plugin) if err != nil { - a.log.Errorf("failed to derive template values for plugin %s, %v", request.Plugin.PluginName, err) + a.log.Errorf("failed to deploy plugin [%s], %v", request.Plugin.PluginName, err) return &clusterpluginspb.DeployClusterPluginResponse{ Status: clusterpluginspb.StatusCode_INTERNRAL_ERROR, - StatusMessage: "failed to prepare plugin values", - }, nil - } - - request.Plugin.Values = values - request.Plugin.InstallStatus = string(model.AppIntallingStatus) - if err := a.as.UpsertClusterPluginConfig(request.Plugin); err != nil { - a.log.Errorf("failed to update plugin config data for plugin %s, %v", request.Plugin.PluginName, err) - return &clusterpluginspb.DeployClusterPluginResponse{ - Status: clusterpluginspb.StatusCode_INTERNRAL_ERROR, - StatusMessage: "failed to update plugin config data", - }, nil + StatusMessage: "failed to deploy plugin", + }, err } - go a.deployPluginWithWorkflow(request.Plugin) - a.log.Infof("Triggerred plugin [%s] install", request.Plugin.PluginName) return &clusterpluginspb.DeployClusterPluginResponse{ Status: clusterpluginspb.StatusCode_OK, @@ -75,89 +58,18 @@ func (a *Agent) UnDeployClusterPlugin(ctx context.Context, request *clusterplugi *clusterpluginspb.UnDeployClusterPluginResponse, error) { a.log.Infof("Recieved Plugin UnInstall request %+v", request) - if request.PluginName == "" { - a.log.Errorf("release name is empty") - return &clusterpluginspb.UnDeployClusterPluginResponse{ - Status: clusterpluginspb.StatusCode_INTERNRAL_ERROR, - StatusMessage: "release name is missing in request", - }, nil - } - - pluginConfigdata, err := a.as.GetClusterPluginConfig(request.PluginName) + err := a.plugin.UnDeployClusterPlugin(ctx, request) if err != nil { - a.log.Errorf("failed to fetch plugin config record %s, %v", request.PluginName, err) - return &clusterpluginspb.UnDeployClusterPluginResponse{ - Status: clusterpluginspb.StatusCode_INTERNRAL_ERROR, - StatusMessage: "failed to fetch plugin config", - }, nil - } - - pluginConfigdata.InstallStatus = string(model.AppUnInstallingStatus) - if err := a.as.UpsertClusterPluginConfig(pluginConfigdata); err != nil { - a.log.Errorf("failed to update plugin config status with UnInstalling for plugin %s, %v", pluginConfigdata.PluginName, err) + a.log.Errorf("failed to undeploy plugin [%s], %v", request.PluginName, err) return &clusterpluginspb.UnDeployClusterPluginResponse{ Status: clusterpluginspb.StatusCode_INTERNRAL_ERROR, - StatusMessage: "failed to undeploy the plugin", - }, nil + StatusMessage: "failed to undeploy plugin", + }, err } - go a.unInstallPluginWithWorkflow(request, pluginConfigdata) - a.log.Infof("Triggerred plugin [%s] un install", request.PluginName) return &clusterpluginspb.UnDeployClusterPluginResponse{ Status: clusterpluginspb.StatusCode_OK, StatusMessage: "plugin is successfully undeployed", }, nil } - -func (a *Agent) deployPluginWithWorkflow(plugin *clusterpluginspb.Plugin) { - wd := workers.NewDeployment(a.tc, a.log) - _, err := wd.SendEventV2(context.TODO(), wd.GetPluginWorkflowName(), string(model.AppInstallAction), plugin) - if err != nil { - // pluginConfig.InstallStatus = string(model.AppIntallFailedStatus) - // if err := a.pas.UpsertPluginConfig(pluginConfig); err != nil { - // a.log.Errorf("failed to update plugin config for plugin %s, %v", pluginConfig.PluginName, err) - // return - // } - a.log.Errorf("sendEventV2 failed, plugin: %s, reason: %v", plugin.PluginName, err) - return - } - // TODO: workflow will update the final status - // Write a periodic scheduler which will go through all apps not in installed status and check the status till either success or failed. - // Make SendEventV2 asynchrounous so that periodic scheduler will take care of monitoring. -} - -func (a *Agent) unInstallPluginWithWorkflow(request *clusterpluginspb.UnDeployClusterPluginRequest, plugin *clusterpluginspb.Plugin) { - wd := workers.NewDeployment(a.tc, a.log) - _, err := wd.SendDeleteEvent(context.TODO(), wd.GetPluginWorkflowName(), string(model.AppUnInstallAction), request) - if err != nil { - a.log.Errorf("failed to send delete event to workflow for plugin %s, %v", request.PluginName, err) - - plugin.InstallStatus = string(model.AppUnUninstallFailedStatus) - if err := a.as.UpsertClusterPluginConfig(plugin); err != nil { - a.log.Errorf("failed to update plugin config status with Installed for plugin %s, %v", request.PluginName, err) - } - } -} - -func replaceTemplateValuesInByteData(data []byte, - values []byte) (transformedData []byte, err error) { - tmpl, err := template.New("templateVal").Parse(string(data)) - if err != nil { - return - } - - mapValues := map[string]any{} - if err = yaml.Unmarshal(values, &mapValues); err != nil { - return - } - - var buf bytes.Buffer - err = tmpl.Execute(&buf, mapValues) - if err != nil { - return - } - - transformedData = buf.Bytes() - return -} diff --git a/capten/agent/internal/api/plugin_crossplane_project_apis.go b/capten/agent/internal/api/plugin_crossplane_project_apis.go index 29673ea8..1518b91a 100644 --- a/capten/agent/internal/api/plugin_crossplane_project_apis.go +++ b/capten/agent/internal/api/plugin_crossplane_project_apis.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/kube-tarian/kad/capten/agent/internal/workers" "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" + "github.com/kube-tarian/kad/capten/common-pkg/workers" "github.com/kube-tarian/kad/capten/model" ) diff --git a/capten/agent/internal/api/plugin_tekton_apis.go b/capten/agent/internal/api/plugin_tekton_apis.go index 7ee96a40..1f850d6f 100644 --- a/capten/agent/internal/api/plugin_tekton_apis.go +++ b/capten/agent/internal/api/plugin_tekton_apis.go @@ -3,8 +3,8 @@ package api import ( "context" - "github.com/kube-tarian/kad/capten/agent/internal/workers" "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" + "github.com/kube-tarian/kad/capten/common-pkg/workers" "github.com/kube-tarian/kad/capten/model" ) diff --git a/capten/agent/internal/app/app.go b/capten/agent/internal/app/app.go index 038cd063..ecfd8ed8 100644 --- a/capten/agent/internal/app/app.go +++ b/capten/agent/internal/app/app.go @@ -11,9 +11,9 @@ import ( ginapiserver "github.com/kube-tarian/kad/capten/agent/gin-api-server" agentapi "github.com/kube-tarian/kad/capten/agent/internal/api" "github.com/kube-tarian/kad/capten/agent/internal/config" - "github.com/kube-tarian/kad/capten/agent/internal/crossplane" "github.com/kube-tarian/kad/capten/agent/internal/job" captenstore "github.com/kube-tarian/kad/capten/common-pkg/capten-store" + "github.com/kube-tarian/kad/capten/common-pkg/crossplane" "github.com/kube-tarian/kad/capten/common-pkg/pb/agentpb" "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" "github.com/kube-tarian/kad/capten/common-pkg/pb/clusterpluginspb" diff --git a/capten/agent/internal/job/crossplane_resources_sync.go b/capten/agent/internal/job/crossplane_resources_sync.go index 1989b827..36078db1 100644 --- a/capten/agent/internal/job/crossplane_resources_sync.go +++ b/capten/agent/internal/job/crossplane_resources_sync.go @@ -2,8 +2,8 @@ package job import ( "github.com/intelops/go-common/logging" - "github.com/kube-tarian/kad/capten/agent/internal/crossplane" captenstore "github.com/kube-tarian/kad/capten/common-pkg/capten-store" + "github.com/kube-tarian/kad/capten/common-pkg/crossplane" ) type CrossplaneResourcesSync struct { diff --git a/capten/agent/internal/temporalclient/client.go b/capten/agent/internal/temporalclient/client.go deleted file mode 100644 index 4a0c934a..00000000 --- a/capten/agent/internal/temporalclient/client.go +++ /dev/null @@ -1,118 +0,0 @@ -package temporalclient - -import ( - "context" - "time" - - "github.com/intelops/go-common/logging" - "github.com/kelseyhightower/envconfig" - - "go.temporal.io/api/workflowservice/v1" - "go.temporal.io/sdk/client" -) - -type Configuration struct { - TemporalServiceAddress string `envconfig:"TEMPORAL_SERVICE_URL" default:"localhost:7233"` - EncryptionKey string `envconfig:"ENCRYPTIONKEY" default:"00000000~secretGoesHere~00000000"` - Secure bool `envconfig:"SECURE" default:"false"` -} - -type Client struct { - conf *Configuration - TemporalClient client.Client - log logging.Logger -} - -func NewClient(log logging.Logger) (*Client, error) { - cfg, err := fetchConfiguration() - if err != nil { - return nil, err - } - - clnt := &Client{ - conf: cfg, - log: log, - } - - err = clnt.newTemporalClient() - if err != nil { - return nil, err - } - - return clnt, nil -} - -func (c *Client) newTemporalClient() (err error) { - opts := client.Options{ - Namespace: "default", - HostPort: c.conf.TemporalServiceAddress, - Logger: c.log, - } - - if err := createNamespace(opts, c.log); err != nil { - return err - } - - if c.conf.Secure { - encryptedDataConverter, err := NewEncryptDataConverterV1(Options{ - EncryptionKey: []byte(c.conf.EncryptionKey), - }) - if err != nil { - return err - } - - opts.DataConverter = encryptedDataConverter - } - c.TemporalClient, err = client.Dial(opts) - return -} - -func createNamespace(opts client.Options, log logging.Logger) error { - namespaceClient, err := client.NewNamespaceClient(opts) - if err != nil { - log.Errorf("failed to create the namespace client", err) - return err - } - - response, err := namespaceClient.Describe(context.Background(), "default") - if err != nil { - log.Errorf("failed to get the namespace", err) - return err - } - - if response.NamespaceInfo.Name == opts.Namespace { - log.Debugf("namespace %s exists, skipping namespace creation", opts.Namespace) - return nil - } - - retention := time.Hour * 72 - err = namespaceClient.Register(context.Background(), &workflowservice.RegisterNamespaceRequest{ - Namespace: "default", - WorkflowExecutionRetentionPeriod: &retention, - }) - - if err != nil { - log.Errorf("failed to create the namespace", err) - return err - } - return nil -} - -func (c *Client) Close() { - c.TemporalClient.Close() -} - -func (c *Client) ExecuteWorkflow( - ctx context.Context, - options client.StartWorkflowOptions, - workflowName string, - args ...interface{}, -) (client.WorkflowRun, error) { - return c.TemporalClient.ExecuteWorkflow(ctx, options, workflowName, args...) -} - -func fetchConfiguration() (*Configuration, error) { - cfg := &Configuration{} - err := envconfig.Process("", cfg) - return cfg, err -} diff --git a/capten/agent/internal/temporalclient/dataconverter.go b/capten/agent/internal/temporalclient/dataconverter.go deleted file mode 100644 index 274c4f80..00000000 --- a/capten/agent/internal/temporalclient/dataconverter.go +++ /dev/null @@ -1,224 +0,0 @@ -package temporalclient - -import ( - "errors" - "fmt" - - commonpb "go.temporal.io/api/common/v1" - "go.temporal.io/sdk/converter" -) - -const ( - metadataEncryptionKey = "encryption" - metadataEncryptedAESV1 = "AES-GCM-V1" -) - -type Options struct { - // EncryptionKey is the encryption key used to encrypt the payloads - // this key must be 16, 24, 32 characters in length - EncryptionKey []byte -} - -type encryptDataConverterV1 struct { - encryptionService *AESEncryptionServiceV1 - payloadConverters map[string]converter.PayloadConverter - orderedEncodings []string -} - -type nightfallTemporalEncodings struct { - encoding string - isAESV1 bool -} - -var ( - - // ErrMetadataIsNotSet is returned when metadata is not set. - ErrMetadataIsNotSet = errors.New("metadata is not set") - // ErrEncodingIsNotSet is returned when payload encoding metadata is not set. - ErrEncodingIsNotSet = errors.New("payload encoding metadata is not set") - // ErrEncodingIsNotSupported is returned when payload encoding is not supported. - ErrEncodingIsNotSupported = errors.New("payload encoding is not supported") - //ErrUnableToFindConverter is return when payload converter is not found - ErrUnableToFindConverter = errors.New("payload converter is not found") -) - -// NewEncryptDataConverterV1 - Temporal provides a default unencrypted DataConverter however -// for some of our needs we need a DataConverter to encrypt maybe sensitive information -// into workflows. EncryptDataConverterV1 allows the ability to encrypt maybe sensitive -// workflows without compromising sensitive info we send to our temporal service. -func NewEncryptDataConverterV1(opts Options) (converter.DataConverter, error) { - defaultTemporalPayloadConverters := []converter.PayloadConverter{ - converter.NewNilPayloadConverter(), - converter.NewByteSlicePayloadConverter(), - - // Order is important here. Both ProtoJsonPayload and ProtoPayload converters check for the same proto.Message - // interface. The first match (ProtoJsonPayload in this case) will always be used for serialization. - // Deserialization is controlled by metadata, therefore both converters can deserialize corresponding data format - // (JSON or binary proto). - converter.NewProtoJSONPayloadConverter(), - converter.NewProtoPayloadConverter(), - - converter.NewJSONPayloadConverter(), - } - encryptionService, err := newAESEncryptionServiceV1(opts) - if err != nil { - return nil, err - } - dc := &encryptDataConverterV1{ - payloadConverters: make(map[string]converter.PayloadConverter, len(defaultTemporalPayloadConverters)), - orderedEncodings: make([]string, len(defaultTemporalPayloadConverters)), - encryptionService: encryptionService, - } - for i, payloadConverter := range defaultTemporalPayloadConverters { - dc.payloadConverters[payloadConverter.Encoding()] = payloadConverter - dc.orderedEncodings[i] = payloadConverter.Encoding() - } - return dc, nil -} - -func (dc *encryptDataConverterV1) ToPayloads(values ...interface{}) (*commonpb.Payloads, error) { - if len(values) == 0 { - return nil, nil - } - - result := &commonpb.Payloads{ - Payloads: make([]*commonpb.Payload, len(values)), - } - for i := range values { - payload, err := dc.ToPayload(values[i]) - if err != nil { - return nil, fmt.Errorf("values[%d]: %w", i, err) - } - result.Payloads[i] = payload - } - - return result, nil -} - -// FromPayloads converts to a list of values of different types. -func (dc *encryptDataConverterV1) FromPayloads(payloads *commonpb.Payloads, valuePtrs ...interface{}) error { - if payloads == nil { - return nil - } - - for i, payload := range payloads.GetPayloads() { - if i >= len(valuePtrs) { - break - } - - err := dc.FromPayload(payload, valuePtrs[i]) - if err != nil { - return fmt.Errorf("payload item %d: %w", i, err) - } - } - - return nil -} - -// ToPayload converts single value to payload. -func (dc *encryptDataConverterV1) ToPayload(value interface{}) (*commonpb.Payload, error) { - for _, enc := range dc.orderedEncodings { - unencryptedPayload, err := dc.payloadConverters[enc].ToPayload(value) - if err != nil { - return nil, err - } - if unencryptedPayload != nil { - return dc.encryptPayload(unencryptedPayload) - } - } - - return nil, fmt.Errorf("value: %v of type: %T: %w", value, value, ErrUnableToFindConverter) -} - -// FromPayload converts single value from payload. -func (dc *encryptDataConverterV1) FromPayload(payload *commonpb.Payload, valuePtr interface{}) error { - if payload == nil { - return nil - } - nightfallEncodings, err := dc.decryptPayload(payload) - if err != nil { - return err - } - payloadConverter, ok := dc.payloadConverters[nightfallEncodings.encoding] - if !ok { - return fmt.Errorf("encoding %s: %w", nightfallEncodings.encoding, ErrEncodingIsNotSupported) - } - - return payloadConverter.FromPayload(payload, valuePtr) -} - -// ToString converts payload object into human readable string. -func (dc *encryptDataConverterV1) ToString(payload *commonpb.Payload) string { - if payload == nil { - return "" - } - nightfallEncodings, err := dc.decryptPayload(payload) - if err != nil { - return err.Error() - } - payloadConverter, ok := dc.payloadConverters[nightfallEncodings.encoding] - if !ok { - return fmt.Errorf("encoding %s: %w", nightfallEncodings.encoding, ErrEncodingIsNotSupported).Error() - } - - return payloadConverter.ToString(payload) -} - -// ToStrings converts payloads object into human-readable strings. -func (dc *encryptDataConverterV1) ToStrings(payloads *commonpb.Payloads) []string { - if payloads == nil { - return nil - } - - result := make([]string, len(payloads.GetPayloads())) - for idx := range payloads.GetPayloads() { - result[idx] = dc.ToString(payloads.GetPayloads()[idx]) - } - - return result -} -func (dc *encryptDataConverterV1) encryptPayload(unencryptedPayload *commonpb.Payload) (*commonpb.Payload, error) { - newMetadata := unencryptedPayload.GetMetadata() - if newMetadata == nil { - newMetadata = make(map[string][]byte) - } - newMetadata[metadataEncryptionKey] = []byte(metadataEncryptedAESV1) - encryptedBytes, err := dc.encryptionService.Encrypt(unencryptedPayload.GetData()) - if err != nil { - return &commonpb.Payload{}, err - } - - return &commonpb.Payload{ - Metadata: newMetadata, - Data: encryptedBytes, - }, nil -} - -// decryptPayload figures out from metadata whether the payload needs to be decrypted -func (dc *encryptDataConverterV1) decryptPayload(payload *commonpb.Payload) (nightfallTemporalEncodings, error) { - nightfallEncodings, err := encoding(payload) - if err != nil { - return nightfallTemporalEncodings{}, err - } - if nightfallEncodings.isAESV1 { - if payload.Data, err = dc.encryptionService.Decrypt(payload.GetData()); err != nil { - return nightfallTemporalEncodings{}, err - } - } - return nightfallEncodings, nil -} - -func encoding(payload *commonpb.Payload) (nightfallTemporalEncodings, error) { - metadata := payload.GetMetadata() - if metadata == nil { - return nightfallTemporalEncodings{}, ErrMetadataIsNotSet - } - encryptionType, hasEncryption := metadata[metadataEncryptionKey] - if encoding, ok := metadata[converter.MetadataEncoding]; ok { - return nightfallTemporalEncodings{ - encoding: string(encoding), - isAESV1: hasEncryption && (string(encryptionType) == metadataEncryptedAESV1), - }, nil - } - return nightfallTemporalEncodings{}, ErrEncodingIsNotSet -} diff --git a/capten/agent/internal/temporalclient/encrypt.go b/capten/agent/internal/temporalclient/encrypt.go deleted file mode 100644 index 80200657..00000000 --- a/capten/agent/internal/temporalclient/encrypt.go +++ /dev/null @@ -1,65 +0,0 @@ -package temporalclient - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "fmt" - "io" -) - -type AESEncryptionServiceV1 struct { - Cipher cipher.AEAD -} - -func newAESEncryptionServiceV1(opts Options) (*AESEncryptionServiceV1, error) { - // must be 16, 24, 32 byte length - // this is your encryption key - // will fail to initialize if length requirements are not met - cipherBlock, err := aes.NewCipher(opts.EncryptionKey) - if err != nil { - // likely invalid key length if errors here - return nil, err - } - gcm, err := cipher.NewGCM(cipherBlock) - if err != nil { - return nil, err - } - return &AESEncryptionServiceV1{ - Cipher: gcm, - }, nil -} - -// Encrypt takes a byte array and returns an encrypted byte array -// as base64 encoded -func (a AESEncryptionServiceV1) Encrypt(unencryptedBytes []byte) ([]byte, error) { - if len(unencryptedBytes) == 0 { // prevent err on empty byte arrays - "cipher: message authentication failed" - return []byte(""), nil - } - nonce := make([]byte, a.Cipher.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - encryptedBytes := a.Cipher.Seal(nonce, nonce, unencryptedBytes, nil) - encryptedEncodedData := make([]byte, base64.RawURLEncoding.EncodedLen(len(encryptedBytes))) - base64.RawURLEncoding.Encode(encryptedEncodedData, encryptedBytes) - return encryptedEncodedData, nil -} - -// Decrypt takes an encrypted base64 byte array then -// returns an unencrypted byte array if same key was used to encrypt it -func (a AESEncryptionServiceV1) Decrypt(encryptedBytes []byte) ([]byte, error) { - if len(encryptedBytes) == 0 { - return []byte(""), nil - } - decodedEncryptedBytes := make([]byte, base64.RawURLEncoding.DecodedLen(len(encryptedBytes))) - if _, err := base64.RawURLEncoding.Decode(decodedEncryptedBytes, encryptedBytes); err != nil { - return nil, err - } - nonceSize := a.Cipher.NonceSize() - if len(encryptedBytes) < nonceSize { - return nil, fmt.Errorf("ciphertext too short: %v", len(encryptedBytes)) - } - return a.Cipher.Open(nil, decodedEncryptedBytes[:nonceSize], decodedEncryptedBytes[nonceSize:], nil) -} diff --git a/capten/agent/internal/crossplane/cluster_claims.go b/capten/common-pkg/crossplane/cluster_claims.go similarity index 96% rename from capten/agent/internal/crossplane/cluster_claims.go rename to capten/common-pkg/crossplane/cluster_claims.go index 8e9fbc85..8fdc6838 100644 --- a/capten/agent/internal/crossplane/cluster_claims.go +++ b/capten/common-pkg/crossplane/cluster_claims.go @@ -9,9 +9,9 @@ import ( "github.com/google/uuid" "github.com/intelops/go-common/logging" - "github.com/kube-tarian/kad/capten/agent/internal/temporalclient" - "github.com/kube-tarian/kad/capten/agent/internal/workers" captenstore "github.com/kube-tarian/kad/capten/common-pkg/capten-store" + "github.com/kube-tarian/kad/capten/common-pkg/temporalclient" + "github.com/kube-tarian/kad/capten/common-pkg/workers" "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" diff --git a/capten/agent/internal/crossplane/package_providers.go b/capten/common-pkg/crossplane/package_providers.go similarity index 95% rename from capten/agent/internal/crossplane/package_providers.go rename to capten/common-pkg/crossplane/package_providers.go index f004b921..1dee3d4d 100644 --- a/capten/agent/internal/crossplane/package_providers.go +++ b/capten/common-pkg/crossplane/package_providers.go @@ -8,9 +8,9 @@ import ( "sync" "github.com/intelops/go-common/logging" - "github.com/kube-tarian/kad/capten/agent/internal/temporalclient" - "github.com/kube-tarian/kad/capten/agent/internal/workers" captenstore "github.com/kube-tarian/kad/capten/common-pkg/capten-store" + "github.com/kube-tarian/kad/capten/common-pkg/temporalclient" + "github.com/kube-tarian/kad/capten/common-pkg/workers" "github.com/kube-tarian/kad/capten/common-pkg/k8s" "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" diff --git a/capten/agent/internal/crossplane/watchers.go b/capten/common-pkg/crossplane/watchers.go similarity index 100% rename from capten/agent/internal/crossplane/watchers.go rename to capten/common-pkg/crossplane/watchers.go diff --git a/capten/common-pkg/plugin-store/model.go b/capten/common-pkg/plugin-store/model.go index f4d40dd4..5accea58 100644 --- a/capten/common-pkg/plugin-store/model.go +++ b/capten/common-pkg/plugin-store/model.go @@ -4,11 +4,6 @@ const ( tmpGitProjectCloneStr = "clone*" gitProjectAccessTokenAttribute = "accessToken" gitProjectUserId = "userID" - - uiSSOCapabilityName = "ui-sso-oauth" - oAuthBaseURLName = "oAuthBaseURL" - oAuthClientIdName = "oAuthClientId" - oAuthClientSecretName = "oAuthClientSecret" ) var ( diff --git a/capten/common-pkg/plugin-store/plugin_deployment_handler.go b/capten/common-pkg/plugin-store/plugin_deployment_handler.go new file mode 100644 index 00000000..82b2b333 --- /dev/null +++ b/capten/common-pkg/plugin-store/plugin_deployment_handler.go @@ -0,0 +1,101 @@ +package pluginstore + +import ( + "bytes" + "context" + "fmt" + "text/template" + + "github.com/kube-tarian/kad/capten/common-pkg/pb/clusterpluginspb" + "github.com/kube-tarian/kad/capten/common-pkg/workers" + "github.com/kube-tarian/kad/capten/model" + "gopkg.in/yaml.v2" +) + +func (p *PluginStore) DeployClusterPlugin(ctx context.Context, pluginData *clusterpluginspb.Plugin) error { + p.log.Infof("Recieved Plugin Deploy request for plugin %s, version %+v", pluginData.PluginName, pluginData.Version) + + values, err := replaceTemplateValueBytesInByteData(pluginData.Values, pluginData.OverrideValues) + if err != nil { + return fmt.Errorf("failed to derive template values for plugin %s, %v", pluginData.PluginName, err) + } + + pluginData.Values = values + pluginData.InstallStatus = string(model.AppIntallingStatus) + if err := p.dbStore.UpsertClusterPluginConfig(pluginData); err != nil { + return fmt.Errorf("failed to update plugin config data for plugin %s, %v", pluginData.PluginName, err) + } + + go p.deployPluginWithWorkflow(pluginData) + p.log.Infof("Triggerred plugin [%s] install", pluginData.PluginName) + return nil +} + +func (p *PluginStore) UnDeployClusterPlugin(ctx context.Context, request *clusterpluginspb.UnDeployClusterPluginRequest) error { + pluginConfigdata, err := p.dbStore.GetClusterPluginConfig(request.PluginName) + if err != nil { + return fmt.Errorf("failed to fetch plugin config record %s, %v", request.PluginName, err) + } + + pluginConfigdata.InstallStatus = string(model.AppUnInstallingStatus) + if err := p.dbStore.UpsertClusterPluginConfig(pluginConfigdata); err != nil { + return fmt.Errorf("failed to update plugin config status with UnInstalling for plugin %s, %v", request.PluginName, err) + } + + go p.unInstallPluginWithWorkflow(request, pluginConfigdata) + + p.log.Infof("Triggerred plugin [%s] un install", request.PluginName) + return nil +} + +func (p *PluginStore) deployPluginWithWorkflow(pluginData *clusterpluginspb.Plugin) { + wd := workers.NewDeployment(p.tc, p.log) + _, err := wd.SendEventV2(context.TODO(), wd.GetPluginWorkflowName(), string(model.AppInstallAction), pluginData) + if err != nil { + // pluginConfig.InstallStatus = string(model.AppIntallFailedStatus) + // if err := a.pas.UpsertPluginConfig(pluginConfig); err != nil { + // a.log.Errorf("failed to update plugin config for plugin %s, %v", pluginConfig.PluginName, err) + // return + // } + p.log.Errorf("sendEventV2 failed, plugin: %s, reason: %v", pluginData.PluginName, err) + return + } + // TODO: workflow will update the final status + // Write a periodic scheduler which will go through all apps not in installed status and check the status till either success or failed. + // Make SendEventV2 asynchrounous so that periodic scheduler will take care of monitoring. +} + +func (p *PluginStore) unInstallPluginWithWorkflow(request *clusterpluginspb.UnDeployClusterPluginRequest, plugin *clusterpluginspb.Plugin) { + wd := workers.NewDeployment(p.tc, p.log) + _, err := wd.SendDeleteEvent(context.TODO(), wd.GetPluginWorkflowName(), string(model.AppUnInstallAction), request) + if err != nil { + p.log.Errorf("failed to send delete event to workflow for plugin %s, %v", request.PluginName, err) + + plugin.InstallStatus = string(model.AppUnUninstallFailedStatus) + if err := p.dbStore.UpsertClusterPluginConfig(plugin); err != nil { + p.log.Errorf("failed to update plugin config status with Installed for plugin %s, %v", request.PluginName, err) + } + } +} + +func replaceTemplateValueBytesInByteData(data []byte, + values []byte) (transformedData []byte, err error) { + tmpl, err := template.New("templateVal").Parse(string(data)) + if err != nil { + return + } + + mapValues := map[string]any{} + if err = yaml.Unmarshal(values, &mapValues); err != nil { + return + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, mapValues) + if err != nil { + return + } + + transformedData = buf.Bytes() + return +} diff --git a/capten/common-pkg/plugin-store/plugin_store_handler.go b/capten/common-pkg/plugin-store/plugin_store_handler.go index 1813bfc7..6492f095 100644 --- a/capten/common-pkg/plugin-store/plugin_store_handler.go +++ b/capten/common-pkg/plugin-store/plugin_store_handler.go @@ -15,6 +15,7 @@ import ( "github.com/kube-tarian/kad/capten/common-pkg/pb/captenpluginspb" "github.com/kube-tarian/kad/capten/common-pkg/pb/clusterpluginspb" "github.com/kube-tarian/kad/capten/common-pkg/pb/pluginstorepb" + "github.com/kube-tarian/kad/capten/common-pkg/temporalclient" "github.com/pkg/errors" "gopkg.in/yaml.v2" @@ -29,31 +30,29 @@ type captenStore interface { DeletePluginStoreData(storeType pluginstorepb.StoreType, gitProjectId, pluginName string) error UpsertPluginStoreData(gitProjectId string, plugin *pluginstorepb.PluginData) error GetPluginStoreData(storeType pluginstorepb.StoreType, gitProjectId, pluginName string) (*pluginstorepb.PluginData, error) -} -type pluginDeployHandler interface { - DeployClusterPlugin(context.Context, *clusterpluginspb.DeployClusterPluginRequest) (*clusterpluginspb.DeployClusterPluginResponse, error) - UnDeployClusterPlugin(context.Context, *clusterpluginspb.UnDeployClusterPluginRequest) (*clusterpluginspb.UnDeployClusterPluginResponse, error) + UpsertClusterPluginConfig(plugin *clusterpluginspb.Plugin) error + GetClusterPluginConfig(pluginName string) (*clusterpluginspb.Plugin, error) } type PluginStore struct { - log logging.Logger - cfg *Config - dbStore captenStore - pluginHandler pluginDeployHandler + log logging.Logger + cfg *Config + dbStore captenStore + tc *temporalclient.Client } -func NewPluginStore(log logging.Logger, dbStore captenStore, pluginHandler pluginDeployHandler) (*PluginStore, error) { +func NewPluginStore(log logging.Logger, dbStore captenStore, tc *temporalclient.Client) (*PluginStore, error) { cfg := &Config{} if err := envconfig.Process("", cfg); err != nil { return nil, err } return &PluginStore{ - log: log, - cfg: cfg, - dbStore: dbStore, - pluginHandler: pluginHandler, + log: log, + cfg: cfg, + dbStore: dbStore, + tc: tc, }, nil } @@ -346,7 +345,7 @@ func (p *PluginStore) DeployPlugin(storeType pluginstorepb.StoreType, } p.log.Infof("Sending plugin %s deploy request", pluginName) - _, err = p.pluginHandler.DeployClusterPlugin(context.Background(), &clusterpluginspb.DeployClusterPluginRequest{Plugin: plugin}) + err = p.DeployClusterPlugin(context.Background(), plugin) if err != nil { return err } @@ -354,7 +353,7 @@ func (p *PluginStore) DeployPlugin(storeType pluginstorepb.StoreType, } func (p *PluginStore) UnDeployPlugin(storeType pluginstorepb.StoreType, pluginName string) error { - _, err := p.pluginHandler.UnDeployClusterPlugin(context.Background(), + err := p.UnDeployClusterPlugin(context.Background(), &clusterpluginspb.UnDeployClusterPluginRequest{StoreType: clusterpluginspb.StoreType(storeType), PluginName: pluginName}) if err != nil { return err @@ -410,15 +409,6 @@ func filterSupporttedCapabilties(pluginCapabilties []string) (validCapabilties, return } -func isUISSOCapabilitySupported(pluginCapabilties []string) bool { - for _, pluginCapability := range pluginCapabilties { - if pluginCapability == uiSSOCapabilityName { - return true - } - } - return false -} - func (p *PluginStore) getPluginDataAPIValues(pluginConfig *PluginConfig, overrideValues map[string]string) (string, string, error) { apiEndpoint, err := replaceTemplateValuesInString(pluginConfig.ApiEndpoint, overrideValues) if err != nil { diff --git a/capten/agent/internal/workers/configure.go b/capten/common-pkg/workers/configure.go similarity index 98% rename from capten/agent/internal/workers/configure.go rename to capten/common-pkg/workers/configure.go index d2ec0c2a..56f78409 100644 --- a/capten/agent/internal/workers/configure.go +++ b/capten/common-pkg/workers/configure.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/intelops/go-common/logging" - "github.com/kube-tarian/kad/capten/agent/internal/temporalclient" + "github.com/kube-tarian/kad/capten/common-pkg/temporalclient" "github.com/kube-tarian/kad/capten/model" "go.temporal.io/sdk/client" ) diff --git a/capten/agent/internal/workers/deployment.go b/capten/common-pkg/workers/deployment.go similarity index 98% rename from capten/agent/internal/workers/deployment.go rename to capten/common-pkg/workers/deployment.go index 3f380743..81902094 100644 --- a/capten/agent/internal/workers/deployment.go +++ b/capten/common-pkg/workers/deployment.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/intelops/go-common/logging" - "github.com/kube-tarian/kad/capten/agent/internal/temporalclient" + "github.com/kube-tarian/kad/capten/common-pkg/temporalclient" "github.com/kube-tarian/kad/capten/model" "go.temporal.io/sdk/client" ) diff --git a/capten/agent/internal/workers/types.go b/capten/common-pkg/workers/types.go similarity index 100% rename from capten/agent/internal/workers/types.go rename to capten/common-pkg/workers/types.go