This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 7b64c9e8 feat: multi-data connections support for ae (#2208)
7b64c9e8 is described below

commit 7b64c9e8f7643d8c65a2bcfb497eedb9fc2e761e
Author: mappjzc <[email protected]>
AuthorDate: Mon Jun 20 22:34:28 2022 +0800

    feat: multi-data connections support for ae (#2208)
    
    * feat: multi-data connections support for ae
    
    Add multi-data support for AE
    Mv randomstr to core
    Add TestConnection for AE
    
    Nddtfjiang <[email protected]>
    
    * refactor: change test api to projects
    
    Change test api to projects from projects/
    Move GetSign from api to models.
    Remove Tasks of AeOptions.
    
    Nddtfjiang <[email protected]>
    
    * refactor: spread out aeconnection at migrationscripts
    
    spread out aeconnection at migrationscripts
    
    Nddtfjiang <[email protected]>
    
    * fix: fix services init early than main
    
    Change services init to Init and call it on main
    
    Nddtfjiang <[email protected]>
    
    * refactor: change connction test from param not db
    
    Change connction test to use param not db.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix e2e test lost init
    
    Add init to e2e test TestNewTask
    
    Nddtfjiang <[email protected]>
---
 main.go                                            |   2 +
 plugins/ae/ae.go                                   |  38 +++++-
 plugins/ae/api/connection.go                       | 135 ++++++++++++++-------
 plugins/ae/models/connection.go                    |  54 ++++++++-
 .../{ => migrationscripts/archived}/connection.go  |  29 +++--
 .../migrationscripts/updateSchemas20220615.go}     |  28 +++--
 plugins/ae/tasks/api_client.go                     |  71 ++---------
 plugins/ae/tasks/task_data.go                      |   4 +-
 plugins/core/plugin_utils.go                       |  14 +++
 plugins/helper/connection.go                       |   5 +
 services/init.go                                   |   2 +-
 test/api/task/task_test.go                         |  27 +++--
 12 files changed, 263 insertions(+), 146 deletions(-)

diff --git a/main.go b/main.go
index 7c3001f7..2ffe728d 100644
--- a/main.go
+++ b/main.go
@@ -21,6 +21,7 @@ import (
        "github.com/apache/incubator-devlake/api"
        "github.com/apache/incubator-devlake/config"
        "github.com/apache/incubator-devlake/plugins/core"
+       "github.com/apache/incubator-devlake/services"
        _ "github.com/apache/incubator-devlake/version"
 )
 
@@ -36,5 +37,6 @@ func main() {
                        panic(err)
                }
        }
+       services.Init()
        api.CreateApiService()
 }
diff --git a/plugins/ae/ae.go b/plugins/ae/ae.go
index e2bad02f..4ca20b24 100644
--- a/plugins/ae/ae.go
+++ b/plugins/ae/ae.go
@@ -22,9 +22,11 @@ import (
 
        "github.com/apache/incubator-devlake/migration"
        "github.com/apache/incubator-devlake/plugins/ae/api"
+       "github.com/apache/incubator-devlake/plugins/ae/models"
        "github.com/apache/incubator-devlake/plugins/ae/models/migrationscripts"
        "github.com/apache/incubator-devlake/plugins/ae/tasks"
        "github.com/apache/incubator-devlake/plugins/core"
+       "github.com/apache/incubator-devlake/plugins/helper"
        "github.com/apache/incubator-devlake/runner"
        "github.com/mitchellh/mapstructure"
        "github.com/spf13/cobra"
@@ -41,6 +43,7 @@ var _ core.Migratable = (*AE)(nil)
 type AE struct{}
 
 func (plugin AE) Init(config *viper.Viper, logger core.Logger, db *gorm.DB) 
error {
+       api.Init(config, logger, db)
        return nil
 }
 
@@ -67,10 +70,26 @@ func (plugin AE) PrepareTaskData(taskCtx core.TaskContext, 
options map[string]in
        if op.ProjectId <= 0 {
                return nil, fmt.Errorf("projectId is required")
        }
-       apiClient, err := tasks.CreateApiClient(taskCtx)
+
+       connection := &models.AeConnection{}
+       connectionHelper := helper.NewConnectionHelper(
+               taskCtx,
+               nil,
+       )
+       if err != nil {
+               return nil, err
+       }
+
+       err = connectionHelper.FirstById(connection, op.ConnectionId)
        if err != nil {
                return nil, err
        }
+
+       apiClient, err := tasks.CreateApiClient(taskCtx, connection)
+       if err != nil {
+               return nil, err
+       }
+
        return &tasks.AeTaskData{
                Options:   &op,
                ApiClient: apiClient,
@@ -82,7 +101,10 @@ func (plugin AE) RootPkgPath() string {
 }
 
 func (plugin AE) MigrationScripts() []migration.Script {
-       return []migration.Script{new(migrationscripts.InitSchemas)}
+       return []migration.Script{
+               new(migrationscripts.InitSchemas),
+               new(migrationscripts.UpdateSchemas20220615),
+       }
 }
 
 func (plugin AE) ApiResources() map[string]map[string]core.ApiResourceHandler {
@@ -91,11 +113,13 @@ func (plugin AE) ApiResources() 
map[string]map[string]core.ApiResourceHandler {
                        "GET": api.TestConnection,
                },
                "connections": {
-                       "GET": api.ListConnections,
+                       "GET":  api.ListConnections,
+                       "POST": api.PostConnections,
                },
                "connections/:connectionId": {
-                       "GET":   api.GetConnection,
-                       "PATCH": api.PatchConnection,
+                       "GET":    api.GetConnection,
+                       "PATCH":  api.PatchConnection,
+                       "DELETE": api.DeleteConnection,
                },
        }
 }
@@ -105,11 +129,13 @@ var PluginEntry AE //nolint
 
 func main() {
        aeCmd := &cobra.Command{Use: "ae"}
+       connectionId := aeCmd.Flags().Uint64P("Connection-id", "c", 0, "ae 
connection id")
        projectId := aeCmd.Flags().IntP("project-id", "p", 0, "ae project id")
        _ = aeCmd.MarkFlagRequired("project-id")
        aeCmd.Run = func(cmd *cobra.Command, args []string) {
                runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
-                       "projectId": *projectId,
+                       "connectionId": *connectionId,
+                       "projectId":    *projectId,
                })
        }
        runner.RunCmd(aeCmd)
diff --git a/plugins/ae/api/connection.go b/plugins/ae/api/connection.go
index c66f9e11..9dc2119b 100644
--- a/plugins/ae/api/connection.go
+++ b/plugins/ae/api/connection.go
@@ -18,88 +18,141 @@ limitations under the License.
 package api
 
 import (
-       "github.com/apache/incubator-devlake/config"
+       "fmt"
+       "net/http"
+       "time"
+
        "github.com/apache/incubator-devlake/plugins/ae/models"
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
-       "net/http"
+       "github.com/go-playground/validator/v10"
+       "github.com/mitchellh/mapstructure"
+       "github.com/spf13/viper"
+       "gorm.io/gorm"
 )
 
 type ApiMeResponse struct {
        Name string `json:"name"`
 }
 
-/*
-GET /plugins/ae/test
-*/
-func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
-       // TODO: implement test connection
-       return &core.ApiResourceOutput{Body: true}, nil
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+
+func Init(config *viper.Viper, logger core.Logger, database *gorm.DB) {
+       basicRes := helper.NewDefaultBasicRes(config, logger, database)
+       vld = validator.New()
+       connectionHelper = helper.NewConnectionHelper(
+               basicRes,
+               vld,
+       )
 }
 
 /*
-PATCH /plugins/ae/connections/:connectionId
+GET /plugins/ae/test/
 */
-func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
-       v := config.GetConfig()
-       connection := &models.AeConnection{}
-       err := helper.EncodeStruct(v, connection, "env")
+func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       // decode
+       var err error
+       var connection models.TestConnectionRequest
+       err = mapstructure.Decode(input.Body, &connection)
        if err != nil {
                return nil, err
        }
-       // update from request and save to .env
-       err = helper.DecodeStruct(v, connection, input.Body, "env")
+       // validate
+       err = vld.Struct(connection)
        if err != nil {
                return nil, err
        }
-       err = config.WriteConfig(v)
+
+       // load and process cconfiguration
+       endpoint := connection.Endpoint
+       appId := connection.AppId
+       secretKey := connection.SecretKey
+       proxy := connection.Proxy
+
+       apiClient, err := helper.NewApiClient(endpoint, nil, 3*time.Second, 
proxy, nil)
+       if err != nil {
+               return nil, err
+       }
+       apiClient.SetBeforeFunction(func(req *http.Request) error {
+               nonceStr := core.RandLetterBytes(8)
+               timestamp := fmt.Sprintf("%v", time.Now().Unix())
+               sign := models.GetSign(req.URL.Query(), appId, secretKey, 
nonceStr, timestamp)
+               req.Header.Set("x-ae-app-id", appId)
+               req.Header.Set("x-ae-timestamp", timestamp)
+               req.Header.Set("x-ae-nonce-str", nonceStr)
+               req.Header.Set("x-ae-sign", sign)
+               return nil
+       })
+       res, err := apiClient.Get("projects", nil, nil)
        if err != nil {
                return nil, err
        }
-       response := models.AeResponse{
-               AeConnection: *connection,
-               Name:         "Ae",
-               ID:           1,
+
+       switch res.StatusCode {
+       case 200: // right StatusCode
+               return &core.ApiResourceOutput{Body: true, Status: 200}, nil
+       case 401: // error secretKey or nonceStr
+               return &core.ApiResourceOutput{Body: false, Status: 
res.StatusCode}, nil
+       default: // unknow what happen , back to user
+               return &core.ApiResourceOutput{Body: res.Body, Status: 
res.StatusCode}, nil
        }
-       return &core.ApiResourceOutput{Body: response, Status: http.StatusOK}, 
nil
 }
 
 /*
-GET /plugins/ae/connections
+POST /plugins/ae/connections
 */
-func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
-       // RETURN ONLY 1 SOURCE (FROM ENV) until multi-connection is developed.
-       v := config.GetConfig()
+func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
        connection := &models.AeConnection{}
-
-       err := helper.EncodeStruct(v, connection, "env")
+       err := connectionHelper.Create(connection, input)
        if err != nil {
                return nil, err
        }
-       response := models.AeResponse{
-               AeConnection: *connection,
-               Name:         "Ae",
-               ID:           1,
-       }
+       return &core.ApiResourceOutput{Body: connection, Status: 
http.StatusOK}, nil
+}
 
-       return &core.ApiResourceOutput{Body: []models.AeResponse{response}}, nil
+/*
+GET /plugins/ae/connections
+*/
+func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       var connections []models.AeConnection
+       err := connectionHelper.List(&connections)
+       if err != nil {
+               return nil, err
+       }
+       return &core.ApiResourceOutput{Body: connections, Status: 
http.StatusOK}, nil
 }
 
 /*
 GET /plugins/ae/connections/:connectionId
 */
 func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
-       //  RETURN ONLY 1 SOURCE FROM ENV (Ignore ID until multi-connection is 
developed.)
-       v := config.GetConfig()
        connection := &models.AeConnection{}
-       err := helper.EncodeStruct(v, connection, "env")
+       err := connectionHelper.First(connection, input.Params)
+       return &core.ApiResourceOutput{Body: connection}, err
+}
+
+/*
+PATCH /plugins/ae/connections/:connectionId
+*/
+func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       connection := &models.AeConnection{}
+       err := connectionHelper.Patch(connection, input)
        if err != nil {
                return nil, err
        }
-       response := &models.AeResponse{
-               AeConnection: *connection,
-               Name:         "Ae",
-               ID:           1,
+       return &core.ApiResourceOutput{Body: connection, Status: 
http.StatusOK}, nil
+}
+
+/*
+DELETE /plugins/ae/connections/:connectionId
+*/
+func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       connection := &models.AeConnection{}
+       err := connectionHelper.First(connection, input.Params)
+       if err != nil {
+               return nil, err
        }
-       return &core.ApiResourceOutput{Body: response}, nil
+       err = connectionHelper.Delete(connection)
+       return &core.ApiResourceOutput{Body: connection}, err
 }
diff --git a/plugins/ae/models/connection.go b/plugins/ae/models/connection.go
index 99624b56..96ae9b1c 100644
--- a/plugins/ae/models/connection.go
+++ b/plugins/ae/models/connection.go
@@ -17,11 +17,26 @@ limitations under the License.
 
 package models
 
+import (
+       "crypto/md5"
+       "encoding/hex"
+       "fmt"
+       "net/url"
+       "sort"
+       "strings"
+
+       "github.com/apache/incubator-devlake/plugins/helper"
+)
+
 type AeConnection struct {
-       AppId    string `mapstructure:"appId" env:"AE_APP_ID" json:"appId"`
-       Sign     string `mapstructure:"sign" env:"AE_SIGN" json:"sign"`
-       NonceStr string `mapstructure:"nonceStr" env:"AE_NONCE_STR" 
json:"nonceStr"`
-       Endpoint string `mapstructure:"endpoint" env:"AE_ENDPOINT" 
json:"endpoint"`
+       helper.RestConnection `mapstructure:",squash"`
+       helper.AppKey         `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+       Endpoint      string `json:"endpoint"`
+       Proxy         string `json:"proxy"`
+       helper.AppKey `mapstructure:",squash"`
 }
 
 // This object conforms to what the frontend currently expects.
@@ -30,3 +45,34 @@ type AeResponse struct {
        Name string `json:"name"`
        ID   int    `json:"id"`
 }
+
+func (AeConnection) TableName() string {
+       return "_tool_ae_connections"
+}
+
+func GetSign(query url.Values, appId, secretKey, nonceStr, timestamp string) 
string {
+       // clone query because we need to add items
+       kvs := make([]string, 0, len(query)+3)
+       kvs = append(kvs, fmt.Sprintf("app_id=%s", appId))
+       kvs = append(kvs, fmt.Sprintf("timestamp=%s", timestamp))
+       kvs = append(kvs, fmt.Sprintf("nonce_str=%s", nonceStr))
+       for key, values := range query {
+               for _, value := range values {
+                       kvs = append(kvs, fmt.Sprintf("%s=%s", 
url.QueryEscape(key), url.QueryEscape(value)))
+               }
+       }
+
+       // sort by alphabetical order
+       sort.Strings(kvs)
+
+       // generate text for signature
+       querystring := fmt.Sprintf("%s&key=%s", strings.Join(kvs, "&"), 
url.QueryEscape(secretKey))
+
+       // sign it
+       hasher := md5.New()
+       _, err := hasher.Write([]byte(querystring))
+       if err != nil {
+               return ""
+       }
+       return strings.ToUpper(hex.EncodeToString(hasher.Sum(nil)))
+}
diff --git a/plugins/ae/models/connection.go 
b/plugins/ae/models/migrationscripts/archived/connection.go
similarity index 52%
copy from plugins/ae/models/connection.go
copy to plugins/ae/models/migrationscripts/archived/connection.go
index 99624b56..6cbfe39e 100644
--- a/plugins/ae/models/connection.go
+++ b/plugins/ae/models/migrationscripts/archived/connection.go
@@ -15,18 +15,27 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package models
+package archived
+
+import (
+       "time"
+)
 
 type AeConnection struct {
-       AppId    string `mapstructure:"appId" env:"AE_APP_ID" json:"appId"`
-       Sign     string `mapstructure:"sign" env:"AE_SIGN" json:"sign"`
-       NonceStr string `mapstructure:"nonceStr" env:"AE_NONCE_STR" 
json:"nonceStr"`
-       Endpoint string `mapstructure:"endpoint" env:"AE_ENDPOINT" 
json:"endpoint"`
+       Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" 
validate:"required"`
+
+       ID        uint64    `gorm:"primaryKey" json:"id"`
+       CreatedAt time.Time `json:"createdAt"`
+       UpdatedAt time.Time `json:"updatedAt"`
+
+       Endpoint  string `mapstructure:"endpoint" validate:"required" 
json:"endpoint"`
+       Proxy     string `mapstructure:"proxy" json:"proxy"`
+       RateLimit int    `comment:"api request rate limit per hour" 
json:"rateLimit"`
+
+       AppId     string `mapstructure:"app_id" validate:"required" 
json:"app_id"`
+       SecretKey string `mapstructure:"secret_key" validate:"required" 
json:"secret_key" encrypt:"yes"`
 }
 
-// This object conforms to what the frontend currently expects.
-type AeResponse struct {
-       AeConnection
-       Name string `json:"name"`
-       ID   int    `json:"id"`
+func (AeConnection) TableName() string {
+       return "_tool_ae_connections"
 }
diff --git a/plugins/ae/tasks/task_data.go 
b/plugins/ae/models/migrationscripts/updateSchemas20220615.go
similarity index 60%
copy from plugins/ae/tasks/task_data.go
copy to plugins/ae/models/migrationscripts/updateSchemas20220615.go
index 1ea21557..07c604a0 100644
--- a/plugins/ae/tasks/task_data.go
+++ b/plugins/ae/models/migrationscripts/updateSchemas20220615.go
@@ -15,19 +15,27 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package tasks
+package migrationscripts
 
-import "github.com/apache/incubator-devlake/plugins/helper"
+import (
+       "context"
 
-type AeOptions struct {
-       ProjectId int
-       Tasks     []string `json:"tasks,omitempty"`
+       
"github.com/apache/incubator-devlake/plugins/ae/models/migrationscripts/archived"
+       "gorm.io/gorm"
+)
+
+type UpdateSchemas20220615 struct{}
+
+func (*UpdateSchemas20220615) Up(ctx context.Context, db *gorm.DB) error {
+       return db.Migrator().AutoMigrate(
+               &archived.AeConnection{},
+       )
 }
 
-type AeTaskData struct {
-       Options   *AeOptions
-       ApiClient *helper.ApiAsyncClient
+func (*UpdateSchemas20220615) Version() uint64 {
+       return 20220615181010
 }
-type AeApiParams struct {
-       ProjectId int
+
+func (*UpdateSchemas20220615) Name() string {
+       return "create tables:" + archived.AeConnection{}.TableName()
 }
diff --git a/plugins/ae/tasks/api_client.go b/plugins/ae/tasks/api_client.go
index da38e2c1..4ce1642e 100644
--- a/plugins/ae/tasks/api_client.go
+++ b/plugins/ae/tasks/api_client.go
@@ -18,85 +18,30 @@ limitations under the License.
 package tasks
 
 import (
-       "crypto/md5"
-       "encoding/hex"
        "fmt"
-       "math/rand"
        "net/http"
-       "net/url"
-       "sort"
-       "strings"
        "time"
 
+       "github.com/apache/incubator-devlake/plugins/ae/models"
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
 )
 
-const letterBytes = 
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-
-func init() {
-       rand.Seed(time.Now().UnixNano())
-}
-
-func RandString(n int) string {
-       b := make([]byte, n)
-       for i := range b {
-               b[i] = letterBytes[rand.Intn(len(letterBytes))]
-       }
-       return string(b)
-}
-
-func getSign(query url.Values, appId, secretKey, nonceStr, timestamp string) 
string {
-       // clone query because we need to add items
-       kvs := make([]string, 0, len(query)+3)
-       kvs = append(kvs, fmt.Sprintf("app_id=%s", appId))
-       kvs = append(kvs, fmt.Sprintf("timestamp=%s", timestamp))
-       kvs = append(kvs, fmt.Sprintf("nonce_str=%s", nonceStr))
-       for key, values := range query {
-               for _, value := range values {
-                       kvs = append(kvs, fmt.Sprintf("%s=%s", 
url.QueryEscape(key), url.QueryEscape(value)))
-               }
-       }
-
-       // sort by alphabetical order
-       sort.Strings(kvs)
-
-       // generate text for signature
-       querystring := fmt.Sprintf("%s&key=%s", strings.Join(kvs, "&"), 
url.QueryEscape(secretKey))
-
-       // sign it
-       hasher := md5.New()
-       _, err := hasher.Write([]byte(querystring))
-       if err != nil {
-               return ""
-       }
-       return strings.ToUpper(hex.EncodeToString(hasher.Sum(nil)))
-}
-
-func CreateApiClient(taskCtx core.TaskContext) (*helper.ApiAsyncClient, error) 
{
+func CreateApiClient(taskCtx core.TaskContext, connection 
*models.AeConnection) (*helper.ApiAsyncClient, error) {
        // load and process cconfiguration
-       endpoint := taskCtx.GetConfig("AE_ENDPOINT")
-       if endpoint == "" {
-               return nil, fmt.Errorf("invalid AE_ENDPOINT")
-       }
-       appId := taskCtx.GetConfig("AE_APP_ID")
-       if appId == "" {
-               return nil, fmt.Errorf("invalid AE_APP_ID")
-       }
-       secretKey := taskCtx.GetConfig("AE_SECRET_KEY")
-       if secretKey == "" {
-               return nil, fmt.Errorf("invalid AE_SECRET_KEY")
-       }
-       proxy := taskCtx.GetConfig("AE_PROXY")
+       endpoint := connection.Endpoint
+       appId := connection.AppId
+       secretKey := connection.SecretKey
+       proxy := connection.Proxy
 
        apiClient, err := helper.NewApiClient(endpoint, nil, 0, proxy, 
taskCtx.GetContext())
        if err != nil {
                return nil, err
        }
        apiClient.SetBeforeFunction(func(req *http.Request) error {
-               nonceStr := RandString(8)
+               nonceStr := core.RandLetterBytes(8)
                timestamp := fmt.Sprintf("%v", time.Now().Unix())
-               sign := getSign(req.URL.Query(), appId, secretKey, nonceStr, 
timestamp)
+               sign := models.GetSign(req.URL.Query(), appId, secretKey, 
nonceStr, timestamp)
                req.Header.Set("x-ae-app-id", appId)
                req.Header.Set("x-ae-timestamp", timestamp)
                req.Header.Set("x-ae-nonce-str", nonceStr)
diff --git a/plugins/ae/tasks/task_data.go b/plugins/ae/tasks/task_data.go
index 1ea21557..0bff2af5 100644
--- a/plugins/ae/tasks/task_data.go
+++ b/plugins/ae/tasks/task_data.go
@@ -20,8 +20,8 @@ package tasks
 import "github.com/apache/incubator-devlake/plugins/helper"
 
 type AeOptions struct {
-       ProjectId int
-       Tasks     []string `json:"tasks,omitempty"`
+       ConnectionId uint64 `json:"connectionId"`
+       ProjectId    int
 }
 
 type AeTaskData struct {
diff --git a/plugins/core/plugin_utils.go b/plugins/core/plugin_utils.go
index 95df35b6..b3f64380 100644
--- a/plugins/core/plugin_utils.go
+++ b/plugins/core/plugin_utils.go
@@ -30,6 +30,10 @@ import (
 
 const EncodeKeyEnvStr = "ENCODE_KEY"
 
+func init() {
+       rand.Seed(time.Now().UnixNano())
+}
+
 // TODO: maybe move encryption/decryption into helper?
 // AES + Base64 encryption using ENCODE_KEY in .env as key
 func Encrypt(encKey, plainText string) (string, error) {
@@ -152,3 +156,13 @@ func RandomCapsStr(len int) string {
 func RandomEncKey() string {
        return RandomCapsStr(128)
 }
+
+const letterBytes = 
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+func RandLetterBytes(n int) string {
+       b := make([]byte, n)
+       for i := range b {
+               b[i] = letterBytes[rand.Intn(len(letterBytes))]
+       }
+       return string(b)
+}
diff --git a/plugins/helper/connection.go b/plugins/helper/connection.go
index f0c3f8d8..14d66a65 100644
--- a/plugins/helper/connection.go
+++ b/plugins/helper/connection.go
@@ -49,6 +49,11 @@ type AccessToken struct {
        Token string `mapstructure:"token" validate:"required" json:"token" 
encrypt:"yes"`
 }
 
+type AppKey struct {
+       AppId     string `mapstructure:"app_id" validate:"required" 
json:"app_id"`
+       SecretKey string `mapstructure:"secret_key" validate:"required" 
json:"secret_key" encrypt:"yes"`
+}
+
 type RestConnection struct {
        BaseConnection `mapstructure:",squash"`
        Endpoint       string `mapstructure:"endpoint" validate:"required" 
json:"endpoint"`
diff --git a/services/init.go b/services/init.go
index dd70c17b..63a69292 100644
--- a/services/init.go
+++ b/services/init.go
@@ -39,7 +39,7 @@ var db *gorm.DB
 var cronManager *cron.Cron
 var log core.Logger
 
-func init() {
+func Init() {
        var err error
        cfg = config.GetConfig()
        log = logger.Global
diff --git a/test/api/task/task_test.go b/test/api/task/task_test.go
index 54d88471..1cc712d4 100644
--- a/test/api/task/task_test.go
+++ b/test/api/task/task_test.go
@@ -25,24 +25,33 @@ import (
        "testing"
 
        "github.com/apache/incubator-devlake/api"
+       "github.com/apache/incubator-devlake/config"
        "github.com/apache/incubator-devlake/models"
+       "github.com/apache/incubator-devlake/plugins/core"
+       "github.com/apache/incubator-devlake/services"
        "github.com/gin-gonic/gin"
        "github.com/magiconair/properties/assert"
-       "github.com/stretchr/testify/mock"
 )
 
+func init() {
+       v := config.GetConfig()
+       encKey := v.GetString(core.EncodeKeyEnvStr)
+       if encKey == "" {
+               // Randomly generate a bunch of encryption keys and set them to 
config
+               encKey = core.RandomEncKey()
+               v.Set(core.EncodeKeyEnvStr, encKey)
+               err := config.WriteConfig(v)
+               if err != nil {
+                       panic(err)
+               }
+       }
+       services.Init()
+}
+
 func TestNewTask(t *testing.T) {
        r := gin.Default()
        api.RegisterRouter(r)
 
-       type services struct {
-               mock.Mock
-       }
-
-       // fakeTask := models.Task{}
-       testObj := new(services)
-       testObj.On("CreateTask").Return(true, nil)
-
        w := httptest.NewRecorder()
        params := strings.NewReader(`{"name": "hello", "tasks": [[{ "plugin": 
"jira", "options": { "host": "www.jira.com" } }]]}`)
        req, _ := http.NewRequest("POST", "/pipelines", params)

Reply via email to