You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2023/02/17 05:16:08 UTC

[incubator-devlake] branch main updated: feat: bamboo connection (#4435)

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

warren 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 641607837 feat: bamboo connection (#4435)
641607837 is described below

commit 64160783720e08d2c5f2bea4fa795f94832775cf
Author: mappjzc <zh...@merico.dev>
AuthorDate: Fri Feb 17 13:16:03 2023 +0800

    feat: bamboo connection (#4435)
    
    * feat: bamboo connection
    
    Add connection for bamboo
    
    Nddtfjiang <zh...@merico.dev>
    
    * fix: fix for review
    
    fix for review
    
    Nddtfjiang <zh...@merico.dev>
---
 backend/plugins/bamboo/api/connection.go           | 149 ++++++++++++++++++++
 backend/plugins/bamboo/api/init.go                 |  37 +++++
 backend/plugins/bamboo/bamboo.go                   |  42 ++++++
 backend/plugins/bamboo/impl/impl.go                | 150 +++++++++++++++++++++
 backend/plugins/bamboo/models/connection.go        | 128 ++++++++++++++++++
 .../migrationscripts/20230216_add_init_tables.go   |  42 ++++++
 .../models/migrationscripts/archived/connection.go |  77 +++++++++++
 .../bamboo/models/migrationscripts/register.go     |  29 ++++
 backend/plugins/bamboo/tasks/api_client.go         |  72 ++++++++++
 backend/plugins/bamboo/tasks/task_data.go          |  52 +++++++
 10 files changed, 778 insertions(+)

diff --git a/backend/plugins/bamboo/api/connection.go b/backend/plugins/bamboo/api/connection.go
new file mode 100644
index 000000000..c516c3206
--- /dev/null
+++ b/backend/plugins/bamboo/api/connection.go
@@ -0,0 +1,149 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/bamboo/models"
+	"github.com/apache/incubator-devlake/server/api/shared"
+)
+
+type BambooTestConnResponse struct {
+	shared.ApiBody
+	Connection *models.BambooConn
+}
+
+// @Summary test bamboo connection
+// @Description Test bamboo Connection
+// @Tags plugins/bamboo
+// @Param body body models.BambooConn true "json body"
+// @Success 200  {object} BambooTestConnResponse "Success"
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /plugins/Bamboo/test [POST]
+func TestConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	// decode
+	var err errors.Error
+	var connection models.BambooConn
+	if err = api.Decode(input.Body, &connection, vld); err != nil {
+		return nil, err
+	}
+
+	// test connection
+	_, err = api.NewApiClientFromConnection(
+		context.TODO(),
+		basicRes,
+		&connection,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	body := BambooTestConnResponse{}
+	body.Success = true
+	body.Message = "success"
+	body.Connection = &connection
+
+	return &plugin.ApiResourceOutput{Body: body, Status: http.StatusOK}, nil
+}
+
+// @Summary create bamboo connection
+// @Description Create bamboo connection
+// @Tags plugins/bamboo
+// @Param body body models.BambooConnection true "json body"
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections [POST]
+func PostConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	// update from request and save to database
+	connection := &models.BambooConnection{}
+	err := connectionHelper.Create(connection, input)
+	if err != nil {
+		return nil, err
+	}
+	return &plugin.ApiResourceOutput{Body: connection, Status: http.StatusOK}, nil
+}
+
+// @Summary patch bamboo connection
+// @Description Patch bamboo connection
+// @Tags plugins/bamboo
+// @Param body body models.BambooConnection true "json body"
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections/{connectionId} [PATCH]
+func PatchConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	connection := &models.BambooConnection{}
+	err := connectionHelper.Patch(connection, input)
+	if err != nil {
+		return nil, err
+	}
+	return &plugin.ApiResourceOutput{Body: connection}, nil
+}
+
+// @Summary delete a bamboo connection
+// @Description Delete a bamboo connection
+// @Tags plugins/bamboo
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections/{connectionId} [DELETE]
+func DeleteConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	connection := &models.BambooConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	if err != nil {
+		return nil, err
+	}
+	err = connectionHelper.Delete(connection)
+	return &plugin.ApiResourceOutput{Body: connection}, err
+}
+
+// @Summary get all bamboo connections
+// @Description Get all bamboo connections
+// @Tags plugins/bamboo
+// @Success 200  {object} []models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections [GET]
+func ListConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	var connections []models.BambooConnection
+	err := connectionHelper.List(&connections)
+	if err != nil {
+		return nil, err
+	}
+	return &plugin.ApiResourceOutput{Body: connections, Status: http.StatusOK}, nil
+}
+
+// @Summary get bamboo connection detail
+// @Description Get bamboo connection detail
+// @Tags plugins/bamboo
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections/{connectionId} [GET]
+func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	connection := &models.BambooConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	return &plugin.ApiResourceOutput{Body: connection}, err
+}
diff --git a/backend/plugins/bamboo/api/init.go b/backend/plugins/bamboo/api/init.go
new file mode 100644
index 000000000..cd019a36f
--- /dev/null
+++ b/backend/plugins/bamboo/api/init.go
@@ -0,0 +1,37 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"github.com/apache/incubator-devlake/core/context"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/go-playground/validator/v10"
+)
+
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+var basicRes context.BasicRes
+
+func Init(br context.BasicRes) {
+	basicRes = br
+	vld = validator.New()
+	connectionHelper = helper.NewConnectionHelper(
+		basicRes,
+		vld,
+	)
+}
diff --git a/backend/plugins/bamboo/bamboo.go b/backend/plugins/bamboo/bamboo.go
new file mode 100644
index 000000000..076323e69
--- /dev/null
+++ b/backend/plugins/bamboo/bamboo.go
@@ -0,0 +1,42 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"github.com/apache/incubator-devlake/core/runner"
+	"github.com/apache/incubator-devlake/plugins/bamboo/impl"
+	"github.com/spf13/cobra"
+)
+
+// PluginEntry exports for Framework to search and load
+var PluginEntry impl.Bamboo //nolint
+
+// standalone mode for debugging
+func main() {
+	bambooCmd := &cobra.Command{Use: "bamboo"}
+	connectionId := bambooCmd.Flags().Uint64P("Connection-id", "c", 0, "bamboo connection id")
+	projectId := bambooCmd.Flags().IntP("project-id", "p", 0, "bamboo project id")
+	_ = bambooCmd.MarkFlagRequired("project-id")
+	bambooCmd.Run = func(cmd *cobra.Command, args []string) {
+		runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
+			"connectionId": *connectionId,
+			"projectId":    *projectId,
+		})
+	}
+	runner.RunCmd(bambooCmd)
+}
diff --git a/backend/plugins/bamboo/impl/impl.go b/backend/plugins/bamboo/impl/impl.go
new file mode 100644
index 000000000..a7c922d88
--- /dev/null
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -0,0 +1,150 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package impl
+
+import (
+	"fmt"
+
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/bamboo/api"
+	"github.com/apache/incubator-devlake/plugins/bamboo/models"
+	"github.com/apache/incubator-devlake/plugins/bamboo/models/migrationscripts"
+	"github.com/apache/incubator-devlake/plugins/bamboo/tasks"
+)
+
+// make sure interface is implemented
+var _ interface {
+	plugin.PluginMeta
+	plugin.PluginInit
+	plugin.PluginTask
+	plugin.PluginModel
+	plugin.PluginMigration
+	plugin.PluginBlueprintV100
+	plugin.DataSourcePluginBlueprintV200
+	plugin.CloseablePluginTask
+	plugin.PluginSource
+} = (*Bamboo)(nil)
+
+type Bamboo struct{}
+
+func (p Bamboo) Init(br context.BasicRes) errors.Error {
+	api.Init(br)
+	return nil
+}
+
+func (p Bamboo) Connection() interface{} {
+	return &models.BambooConnection{}
+}
+
+func (p Bamboo) Scope() interface{} {
+	return nil
+}
+
+func (p Bamboo) TransformationRule() interface{} {
+	return nil
+}
+
+func (p Bamboo) MakeDataSourcePipelinePlanV200(connectionId uint64, scopes []*plugin.BlueprintScopeV200, syncPolicy plugin.BlueprintSyncPolicy) (plugin.PipelinePlan, []plugin.Scope, errors.Error) {
+	//return api.MakePipelinePlanV200(p.SubTaskMetas(), connectionId, scopes, &syncPolicy)
+	return nil, nil, nil
+}
+
+func (p Bamboo) GetTablesInfo() []dal.Tabler {
+	return []dal.Tabler{
+		&models.BambooConnection{},
+	}
+}
+
+func (p Bamboo) Description() string {
+	return "collect some Bamboo data"
+}
+
+func (p Bamboo) SubTaskMetas() []plugin.SubTaskMeta {
+	// TODO add your sub task here
+	return []plugin.SubTaskMeta{}
+}
+
+func (p Bamboo) PrepareTaskData(taskCtx plugin.TaskContext, options map[string]interface{}) (interface{}, errors.Error) {
+	op, err := tasks.DecodeAndValidateTaskOptions(options)
+	if err != nil {
+		return nil, err
+	}
+	connectionHelper := helper.NewConnectionHelper(
+		taskCtx,
+		nil,
+	)
+	connection := &models.BambooConnection{}
+	err = connectionHelper.FirstById(connection, op.ConnectionId)
+	if err != nil {
+		return nil, errors.Default.Wrap(err, "unable to get Bamboo connection by the given connection ID")
+	}
+
+	apiClient, err := tasks.NewBambooApiClient(taskCtx, connection)
+	if err != nil {
+		return nil, errors.Default.Wrap(err, "unable to get Bamboo API client instance")
+	}
+
+	return &tasks.BambooTaskData{
+		Options:   op,
+		ApiClient: apiClient,
+	}, nil
+}
+
+// PkgPath information lost when compiled as plugin(.so)
+func (p Bamboo) RootPkgPath() string {
+	return "github.com/apache/incubator-devlake/plugins/bamboo"
+}
+
+func (p Bamboo) MigrationScripts() []plugin.MigrationScript {
+	return migrationscripts.All()
+}
+
+func (p Bamboo) ApiResources() map[string]map[string]plugin.ApiResourceHandler {
+	return map[string]map[string]plugin.ApiResourceHandler{
+		"test": {
+			"POST": api.TestConnection,
+		},
+		"connections": {
+			"POST": api.PostConnections,
+			"GET":  api.ListConnections,
+		},
+		"connections/:connectionId": {
+			"GET":    api.GetConnection,
+			"PATCH":  api.PatchConnection,
+			"DELETE": api.DeleteConnection,
+		},
+	}
+}
+
+func (p Bamboo) MakePipelinePlan(connectionId uint64, scope []*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) {
+	//return api.MakePipelinePlan(p.SubTaskMetas(), connectionId, scope)
+	return nil, nil
+}
+
+func (p Bamboo) Close(taskCtx plugin.TaskContext) errors.Error {
+	data, ok := taskCtx.GetData().(*tasks.BambooTaskData)
+	if !ok {
+		return errors.Default.New(fmt.Sprintf("GetData failed when try to close %+v", taskCtx))
+	}
+	data.ApiClient.Release()
+	return nil
+}
diff --git a/backend/plugins/bamboo/models/connection.go b/backend/plugins/bamboo/models/connection.go
new file mode 100644
index 000000000..88520d463
--- /dev/null
+++ b/backend/plugins/bamboo/models/connection.go
@@ -0,0 +1,128 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+	"encoding/xml"
+	"fmt"
+	"net/http"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
+)
+
+type BambooConnection struct {
+	api.BaseConnection `mapstructure:",squash"`
+	BambooConn         `mapstructure:",squash"`
+}
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type BambooConn struct {
+	api.RestConnection `mapstructure:",squash"`
+	//TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+	api.AccessToken `mapstructure:",squash"`
+}
+
+// PrepareApiClient test api and set the IsPrivateToken,version,UserId and so on.
+func (conn *BambooConn) PrepareApiClient(apiClient apihelperabstract.ApiClientAbstract) errors.Error {
+	header := http.Header{}
+	header.Set("Authorization", fmt.Sprintf("Bearer %v", conn.Token))
+
+	res, err := apiClient.Get("", nil, header)
+	if err != nil {
+		return errors.HttpStatus(400).New(fmt.Sprintf("Get failed %s", err.Error()))
+	}
+	resources := &ApiXMLResourcesResponse{}
+
+	if res.StatusCode != http.StatusOK {
+		return errors.HttpStatus(res.StatusCode).New(fmt.Sprintf("unexpected status code: %d", res.StatusCode))
+	}
+
+	err = api.UnmarshalResponseXML(res, resources)
+
+	if err != nil {
+		return errors.HttpStatus(500).New(fmt.Sprintf("UnmarshalResponse failed %s", err.Error()))
+	}
+
+	if resources.Link.Href != conn.Endpoint {
+		return errors.HttpStatus(400).New(fmt.Sprintf("Response Data error for connection endpoint: %s, it should like: http://{domain}/rest/api/latest/", conn.Endpoint))
+	}
+
+	res, err = apiClient.Get("repository", nil, header)
+	if err != nil {
+		return errors.HttpStatus(400).New(fmt.Sprintf("Get failed %s", err.Error()))
+	}
+	repo := &ApiRepository{}
+
+	if res.StatusCode != http.StatusOK {
+		return errors.HttpStatus(res.StatusCode).New(fmt.Sprintf("unexpected status code: %d", res.StatusCode))
+	}
+
+	err = api.UnmarshalResponse(res, repo)
+
+	if err != nil {
+		return errors.HttpStatus(400).New(fmt.Sprintf("UnmarshalResponse repository failed %s", err.Error()))
+	}
+
+	return nil
+}
+
+// This object conforms to what the frontend currently expects.
+type BambooResponse struct {
+	Name string `json:"name"`
+	ID   int    `json:"id"`
+	BambooConnection
+}
+
+type ApiRepository struct {
+	Size          int         `json:"size"`
+	SearchResults interface{} `json:"searchResults"`
+	StartIndex    int         `json:"start-index"`
+	MaxResult     int         `json:"max-result"`
+}
+
+type ApiXMLLink struct {
+	XMLName xml.Name `json:"xml_name" xml:"link"`
+	Href    string   `json:"href" xml:"href,attr"`
+}
+
+type ApiXMLResource struct {
+	XMLName xml.Name `json:"xml_name" xml:"resource"`
+	Name    string   `json:"name" xml:"name,attr"`
+}
+
+type ApiXMLResources struct {
+	XMLName   xml.Name         `json:"xml_name" xml:"resources"`
+	Size      string           `json:"size" xml:"size,attr"`
+	Resources []ApiXMLResource `json:"resource" xml:"resource"`
+}
+
+// Using User because it requires authentication.
+type ApiXMLResourcesResponse struct {
+	XMLName xml.Name `json:"xml_name" xml:"resources"`
+	Expand  string   `json:"expand" xml:"expand,attr"`
+
+	Link      ApiXMLLink      `json:"link" xml:"link"`
+	Resources ApiXMLResources `json:"resources" xml:"resources"`
+}
+
+func (BambooConnection) TableName() string {
+	return "_tool_bamboo_connections"
+}
diff --git a/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go b/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
new file mode 100644
index 000000000..d43bd53b3
--- /dev/null
+++ b/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
@@ -0,0 +1,42 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/helpers/migrationhelper"
+	"github.com/apache/incubator-devlake/plugins/bamboo/models/migrationscripts/archived"
+)
+
+type addInitTables struct{}
+
+func (u *addInitTables) Up(baseRes context.BasicRes) errors.Error {
+	return migrationhelper.AutoMigrateTables(
+		baseRes,
+		archived.BambooConnection{},
+	)
+}
+
+func (*addInitTables) Version() uint64 {
+	return 20230216205024
+}
+
+func (*addInitTables) Name() string {
+	return "bamboo init schemas"
+}
diff --git a/backend/plugins/bamboo/models/migrationscripts/archived/connection.go b/backend/plugins/bamboo/models/migrationscripts/archived/connection.go
new file mode 100644
index 000000000..e99545ebe
--- /dev/null
+++ b/backend/plugins/bamboo/models/migrationscripts/archived/connection.go
@@ -0,0 +1,77 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package archived
+
+import "time"
+
+type Model struct {
+	ID        uint64    `gorm:"primaryKey" json:"id"`
+	CreatedAt time.Time `json:"createdAt"`
+	UpdatedAt time.Time `json:"updatedAt"`
+}
+type BaseConnection struct {
+	Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"`
+	Model
+}
+
+// RestConnection implements the ApiConnection interface
+type RestConnection struct {
+	Endpoint         string `mapstructure:"endpoint" validate:"required" json:"endpoint"`
+	Proxy            string `mapstructure:"proxy" json:"proxy"`
+	RateLimitPerHour int    `comment:"api request rate limit per hour" json:"rateLimitPerHour"`
+}
+
+// AccessToken FIXME ...
+type AccessToken struct {
+	Token string `mapstructure:"token" validate:"required" json:"token" encrypt:"yes"`
+}
+
+type BambooConn struct {
+	RestConnection `mapstructure:",squash"`
+	//TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+	AccessToken `mapstructure:",squash"`
+}
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type BambooConnection struct {
+	BaseConnection `mapstructure:",squash"`
+	BambooConn     `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+	Endpoint    string `json:"endpoint"`
+	Proxy       string `json:"proxy"`
+	AccessToken `mapstructure:",squash"`
+}
+
+// This object conforms to what the frontend currently expects.
+type BambooResponse struct {
+	Name string `json:"name"`
+	ID   int    `json:"id"`
+	BambooConnection
+}
+
+// Using User because it requires authentication.
+type ApiResourcesResponse struct {
+	Resources string `json:"resources" xml:"resources"`
+}
+
+func (BambooConnection) TableName() string {
+	return "_tool_bamboo_connections"
+}
diff --git a/backend/plugins/bamboo/models/migrationscripts/register.go b/backend/plugins/bamboo/models/migrationscripts/register.go
new file mode 100644
index 000000000..ec054748c
--- /dev/null
+++ b/backend/plugins/bamboo/models/migrationscripts/register.go
@@ -0,0 +1,29 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+	"github.com/apache/incubator-devlake/core/plugin"
+)
+
+// All return all the migration scripts
+func All() []plugin.MigrationScript {
+	return []plugin.MigrationScript{
+		new(addInitTables),
+	}
+}
diff --git a/backend/plugins/bamboo/tasks/api_client.go b/backend/plugins/bamboo/tasks/api_client.go
new file mode 100644
index 000000000..6efbb21a5
--- /dev/null
+++ b/backend/plugins/bamboo/tasks/api_client.go
@@ -0,0 +1,72 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/bamboo/models"
+)
+
+func CreateBambooAsyncApiClient(
+	taskCtx plugin.TaskContext,
+	apiClient *api.ApiClient,
+	connection *models.BambooConnection,
+) (*api.ApiAsyncClient, errors.Error) {
+	// create rate limit calculator
+	rateLimiter := &api.ApiRateLimitCalculator{
+		UserRateLimitPerHour: connection.RateLimitPerHour,
+		DynamicRateLimit: func(res *http.Response) (int, time.Duration, errors.Error) {
+			rateLimitHeader := res.Header.Get("RateLimit-Limit")
+			if rateLimitHeader == "" {
+				// use default
+				return 0, 0, nil
+			}
+			rateLimit, err := strconv.Atoi(rateLimitHeader)
+			if err != nil {
+				return 0, 0, errors.Default.Wrap(err, "failed to parse RateLimit-Limit header")
+			}
+			// seems like {{ .plugin-ame }} rate limit is on minute basis
+			return rateLimit, 1 * time.Minute, nil
+		},
+	}
+	asyncApiClient, err := api.CreateAsyncApiClient(
+		taskCtx,
+		apiClient,
+		rateLimiter,
+	)
+	if err != nil {
+		return nil, err
+	}
+	return asyncApiClient, nil
+}
+
+func NewBambooApiClient(taskCtx plugin.TaskContext, connection *models.BambooConnection) (*api.ApiAsyncClient, errors.Error) {
+	// create synchronize api client so we can calculate api rate limit dynamically
+	apiClient, err := api.NewApiClientFromConnection(taskCtx.GetContext(), taskCtx, connection)
+	if err != nil {
+		return nil, err
+	}
+
+	return CreateBambooAsyncApiClient(taskCtx, apiClient, connection)
+}
diff --git a/backend/plugins/bamboo/tasks/task_data.go b/backend/plugins/bamboo/tasks/task_data.go
new file mode 100644
index 000000000..ea5c62bce
--- /dev/null
+++ b/backend/plugins/bamboo/tasks/task_data.go
@@ -0,0 +1,52 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/core/errors"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+type BambooApiParams struct {
+}
+
+type BambooOptions struct {
+	// TODO add some custom options here if necessary
+	// options means some custom params required by plugin running.
+	// Such As How many rows do your want
+	// You can use it in sub tasks and you need pass it in main.go and pipelines.
+	ConnectionId uint64   `json:"connectionId"`
+	Tasks        []string `json:"tasks,omitempty"`
+	Since        string
+}
+
+type BambooTaskData struct {
+	Options   *BambooOptions
+	ApiClient *helper.ApiAsyncClient
+}
+
+func DecodeAndValidateTaskOptions(options map[string]interface{}) (*BambooOptions, errors.Error) {
+	var op BambooOptions
+	if err := helper.Decode(options, &op, nil); err != nil {
+		return nil, err
+	}
+	if op.ConnectionId == 0 {
+		return nil, errors.Default.New("connectionId is invalid")
+	}
+	return &op, nil
+}