diff --git a/pkg/apis/iam/v1alpha2/register.go b/pkg/apis/iam/v1alpha2/register.go index d2e8e9406e0e821dae043c4f2fc22b9cf0f15680..bb3ff24605c1dad3bd72bac33fab09f77d8f6114 100644 --- a/pkg/apis/iam/v1alpha2/register.go +++ b/pkg/apis/iam/v1alpha2/register.go @@ -20,11 +20,14 @@ package v1alpha2 import ( "github.com/emicklei/go-restful" "github.com/emicklei/go-restful-openapi" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/apiserver/iam" "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/errors" "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/iam/policy" + "net/http" ) const GroupName = "iam.kubesphere.io" @@ -40,181 +43,220 @@ func addWebService(c *restful.Container) error { tags := []string{"IAM"} ws := runtime.NewWebService(GroupVersion) + ok := "ok" + pageableUserList := struct { + Items []models.User `json:"items"` + TotalCount int `json:"total_count"` + }{} + ws.Route(ws.POST("/authenticate"). To(iam.TokenReviewHandler). - Doc("Token review"). + Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver."). Reads(iam.TokenReview{}). - Writes(iam.TokenReview{}). - Doc("k8s token review"). + Returns(http.StatusOK, ok, iam.TokenReview{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.POST("/login"). To(iam.LoginHandler). - Doc("User login"). + Doc("KubeSphere APIs support token-based authentication via the Authtoken request header. The POST Login API is used to retrieve the authentication token. After the authentication token is obtained, it must be inserted into the Authtoken header for all requests."). Reads(iam.LoginRequest{}). - Writes(models.Token{}). + Returns(http.StatusOK, ok, models.Token{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/users/{username}"). To(iam.DescribeUser). - Doc("User detail"). + Doc("Describes the specified user."). Param(ws.PathParameter("username", "username")). - Writes(models.User{}). + Returns(http.StatusOK, ok, models.User{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.POST("/users"). To(iam.CreateUser). + Doc("Create a user account."). Reads(models.User{}). - Writes(errors.Error{}). - Doc("Create user"). + Returns(http.StatusOK, ok, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.DELETE("/users/{name}"). To(iam.DeleteUser). + Doc("Remove a specified user."). Param(ws.PathParameter("name", "username")). - Doc("Delete user"). - Writes(errors.Error{}). + Returns(http.StatusOK, ok, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.PUT("/users/{name}"). To(iam.UpdateUser). + Doc("Updates information about the specified user."). Param(ws.PathParameter("name", "username")). Reads(models.User{}). - Writes(errors.Error{}). - Doc("Update user"). + Returns(http.StatusOK, ok, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.GET("/users/{name}/log"). To(iam.UserLoginLog). + Doc("This method is used to retrieve the \"login logs\" for the specified user."). Param(ws.PathParameter("name", "username")). - Doc("User login log"). - Writes([]map[string]string{}). + Returns(http.StatusOK, ok, struct { + LoginTime string `json:"login_time"` + LoginIP string `json:"login_ip"` + }{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/users"). To(iam.ListUsers). - Doc("User list"). - Writes(models.PageableResponse{}). + Doc("List all users."). + Returns(http.StatusOK, ok, pageableUserList). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/groups"). To(iam.ListGroups). - Writes([]models.Group{}). - Doc("User group list"). + Doc("List all user groups."). + Returns(http.StatusOK, ok, []models.Group{}). Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.GET("/groups/{path}"). To(iam.DescribeGroup). - Param(ws.PathParameter("path", "group path")). - Doc("User group detail"). + Doc("Describes the specified user group."). + Param(ws.PathParameter("path", "user group path separated by colon.")). + Returns(http.StatusOK, ok, models.Group{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/groups/{path}/users"). To(iam.ListGroupUsers). - Param(ws.PathParameter("path", "group path")). - Doc("Group user list"). + Doc("List all users in the specified user group."). + Param(ws.PathParameter("path", "user group path separated by colon.")). + Returns(http.StatusOK, ok, []models.User{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.POST("/groups"). To(iam.CreateGroup). + Doc("Create a user group."). Reads(models.Group{}). - Doc("Create user group"). + Returns(http.StatusOK, ok, models.Group{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.DELETE("/groups/{path}"). To(iam.DeleteGroup). - Param(ws.PathParameter("path", "group path")). - Doc("Delete user group"). + Doc("Delete a user group."). + Param(ws.PathParameter("path", "user group path separated by colon.")). + Returns(http.StatusOK, ok, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.PUT("/groups/{path}"). To(iam.UpdateGroup). - Param(ws.PathParameter("path", "group path")). - Doc("Update user group"). + Doc("Updates information about the user group."). + Param(ws.PathParameter("path", "user group path separated by colon.")). + Reads(models.Group{}). + Returns(http.StatusOK, ok, models.Group{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/users/{username}/roles"). To(iam.ListUserRoles). + Doc("This method is used to retrieve all the roles that are assigned to the user."). Param(ws.PathParameter("username", "username")). - Doc("Get user role list"). + Returns(http.StatusOK, ok, iam.RoleList{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/namespaces/{namespace}/roles"). To(iam.ListRoles). - Param(ws.PathParameter("namespace", "namespace")). - Doc("Get role list"). + Doc("This method is used to retrieve the roles that are assigned to the user in the specified namespace."). + Param(ws.PathParameter("namespace", "kubernetes namespace")). + Returns(http.StatusOK, ok, struct { + Items []rbacv1.Role `json:"items"` + TotalCount int `json:"total_count"` + }{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/clusterroles"). To(iam.ListClusterRoles). - Doc("Get cluster role list"). + Doc("List all cluster roles."). + Returns(http.StatusOK, ok, struct { + Items []rbacv1.ClusterRole `json:"items"` + TotalCount int `json:"total_count"` + }{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users"). To(iam.ListRoleUsers). - Param(ws.PathParameter("namespace", "namespace")). + Doc("This method is used to retrieve the users that are bind the role in the specified namespace."). + Param(ws.PathParameter("namespace", "kubernetes namespace")). Param(ws.PathParameter("role", "role name")). - Doc("Get user list by role"). + Returns(http.StatusOK, ok, []models.User{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/namespaces/{namespace}/users"). To(iam.ListNamespaceUsers). - Param(ws.PathParameter("namespace", "namespace")). - Doc("Get user list by namespace"). + Doc("List all users in the specified namespace"). + Param(ws.PathParameter("namespace", "kubernetes namespace")). + Returns(http.StatusOK, ok, []models.User{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/clusterroles/{clusterrole}/users"). To(iam.ListClusterRoleUsers). + Doc("List all users that are bind the cluster role."). Param(ws.PathParameter("clusterrole", "cluster role name")). - Doc("Get user list by cluster role"). + Returns(http.StatusOK, ok, pageableUserList). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/clusterroles/{clusterrole}/rules"). To(iam.ListClusterRoleRules). + Doc("List all policy rules of the specified cluster role."). Param(ws.PathParameter("clusterrole", "cluster role name")). - Doc("Get cluster role detail"). + Returns(http.StatusOK, ok, []models.SimpleRule{}). + Metadata(restfulspec.KeyOpenAPITags, tags)) + ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/rules"). + To(iam.ListRoleRules). + Doc("List all policy rules of the specified role."). + Param(ws.PathParameter("namespace", "kubernetes namespace")). + Param(ws.PathParameter("role", "role name")). + Returns(http.StatusOK, ok, []models.SimpleRule{}). + Metadata(restfulspec.KeyOpenAPITags, tags)) + ws.Route(ws.GET("/devops/{devops}/roles/{role}/rules"). + To(iam.ListDevopsRoleRules). + Doc("List all policy rules of the specified role."). + Param(ws.PathParameter("devops", "devops project id")). + Param(ws.PathParameter("role", "devops role name")). + Returns(http.StatusOK, ok, []models.SimpleRule{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/rulesmapping/clusterroles"). To(iam.ClusterRulesMapping). - Doc("Get cluster role policy rules mapping"). + Doc("Get the mapping relationships between cluster roles and policy rules."). + Returns(http.StatusOK, ok, policy.ClusterRoleRuleMapping). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/rulesmapping/roles"). To(iam.RulesMapping). - Doc("Get role policy rules mapping"). + Doc("Get the mapping relationships between namespaced roles and policy rules."). + Returns(http.StatusOK, ok, policy.RoleRuleMapping). Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.GET("/workspaces/{workspace}/roles"). To(iam.ListWorkspaceRoles). + Doc("List all workspace roles."). Param(ws.PathParameter("workspace", "workspace name")). - Doc("List workspace role"). + Returns(http.StatusOK, ok, struct { + Items []rbacv1.ClusterRole `json:"items"` + TotalCount int `json:"total_count"` + }{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}"). To(iam.DescribeWorkspaceRole). + Doc("Describes the workspace role."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("role", "workspace role name")). - Doc("Describe workspace role"). + Returns(http.StatusOK, ok, rbacv1.ClusterRole{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}/rules"). To(iam.ListWorkspaceRoleRules). + Doc("List all policy rules of the specified workspace role."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("role", "workspace role name")). - Doc("Get workspace role policy rules"). + Returns(http.StatusOK, ok, []models.SimpleRule{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/workspaces/{workspace}/members"). To(iam.ListWorkspaceUsers). + Doc("List all members in the specified workspace."). Param(ws.PathParameter("workspace", "workspace name")). - Doc("Get workspace member list"). + Returns(http.StatusOK, ok, pageableUserList). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.POST("/workspaces/{workspace}/members"). To(iam.InviteUser). + Doc("Invite members to a workspace."). Param(ws.PathParameter("workspace", "workspace name")). - Doc("Add user to workspace"). + Reads(models.User{}). + Returns(http.StatusOK, ok, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.DELETE("/workspaces/{workspace}/members/{username}"). To(iam.RemoveUser). + Doc("Remove members from workspace."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("name", "username")). - Doc("Remove user from workspace"). + Returns(http.StatusOK, ok, errors.Error{}). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/workspaces/{workspace}/members/{username}"). To(iam.DescribeWorkspaceUser). + Doc("Describes the specified user."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("username", "username")). - Doc("Describe user in workspace"). - Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/rules"). - To(iam.ListRoleRules). - Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("role", "role name")). - Doc("Get namespace role policy rules"). - Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.GET("/devops/{devops}/roles/{role}/rules"). - To(iam.ListDevopsRoleRules). - Param(ws.PathParameter("devops", "devops project id")). - Param(ws.PathParameter("role", "devops role name")). - Doc("Get devops role policy rules"). + Returns(http.StatusOK, ok, models.User{}). Metadata(restfulspec.KeyOpenAPITags, tags)) c.Add(ws) return nil diff --git a/pkg/apiserver/iam/am.go b/pkg/apiserver/iam/am.go index 59175a22af694e0caf73924a4e7553450daefe09..a869665933d364b930b2c4d480549829714af1a0 100644 --- a/pkg/apiserver/iam/am.go +++ b/pkg/apiserver/iam/am.go @@ -30,7 +30,7 @@ import ( "kubesphere.io/kubesphere/pkg/models/iam/policy" ) -type roleList struct { +type RoleList struct { ClusterRoles []*v1.ClusterRole `json:"clusterRole" protobuf:"bytes,2,rep,name=clusterRoles"` Roles []*v1.Role `json:"roles" protobuf:"bytes,2,rep,name=roles"` } @@ -132,7 +132,7 @@ func ListUserRoles(req *restful.Request, resp *restful.Response) { return } - roleList := roleList{} + roleList := RoleList{} roleList.Roles = roles roleList.ClusterRoles = clusterRoles diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 8e0bee9e56f511dbe920734cf3acfe3e71ce32cb..81917aa447a0e0d09f91ff39f6204894ddd142ad 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -35,6 +35,8 @@ import ( "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/utils/k8sutil" + "kubesphere.io/kubesphere/pkg/utils/sliceutil" + "math" "reflect" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -44,6 +46,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" + "time" ) const ( @@ -115,25 +118,47 @@ func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (reconcile.Res if errors.IsNotFound(err) { // Object not found, return. Created objects are automatically garbage collected. // For additional cleanup logic use finalizers. + // The object is being deleted + // our finalizer is present, so lets handle our external dependency return reconcile.Result{}, nil } // Error reading the object - requeue the request. return reconcile.Result{}, err } - if !instance.ObjectMeta.DeletionTimestamp.IsZero() { - // The object is being deleted + // name of your custom finalizer + finalizer := "finalizers.kubesphere.io/namespaces" - if err := r.deleteRouter(instance.Name); err != nil { - return reconcile.Result{}, err + if instance.ObjectMeta.DeletionTimestamp.IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. + if !sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) { + instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, finalizer) + if err := r.Update(context.Background(), instance); err != nil { + return reconcile.Result{}, err + } } + } else { + // The object is being deleted + if sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) { + if err := r.deleteRouter(instance.Name); err != nil { + return reconcile.Result{}, err + } - if err := r.deleteRuntime(instance); err != nil { - // if fail to delete the external dependency here, return with error - // so that it can be retried - return reconcile.Result{}, err + // delete runtime in the background, retry 3 times + go r.deleteRuntime(instance) + + // remove our finalizer from the list and update it. + instance.ObjectMeta.Finalizers = sliceutil.RemoveString(instance.ObjectMeta.Finalizers, func(item string) bool { + return item == finalizer + }) + + if err := r.Update(context.Background(), instance); err != nil { + return reconcile.Result{}, err + } } + // Our finalizer has finished, so the reconciler can do nothing. return reconcile.Result{}, nil } @@ -347,9 +372,17 @@ func (r *ReconcileNamespace) checkAndCreateRuntime(namespace *corev1.Namespace) func (r *ReconcileNamespace) deleteRuntime(namespace *corev1.Namespace) error { if runtimeId := namespace.Annotations[constants.OpenPitrixRuntimeAnnotationKey]; runtimeId != "" { - log.Info("Deleting openpitrix runtime", "namespace", namespace.Name, "runtime", runtimeId) - if err := openpitrix.Client().DeleteRuntime(runtimeId); err != nil { - return err + maxRetries := float64(3) + for i := float64(0); i < maxRetries; i++ { + time.Sleep(time.Duration(i*math.Pow(2, i)) * time.Second) + log.Info("Deleting openpitrix runtime", "namespace", namespace.Name, "runtime", runtimeId) + err := openpitrix.Client().DeleteRuntime(runtimeId) + + if err == nil || openpitrix.IsNotFound(err) || openpitrix.IsDeleted(err) { + return nil + } + + log.Error(err, fmt.Sprintf("Deleteing openpitrix runtime failed: %v times left", maxRetries-i-1)) } } diff --git a/pkg/models/iam/im.go b/pkg/models/iam/im.go index ea8757214032dcdd4f334185abb6a7a865a584e2..0ab9cae4ee977832a42309af105cbe5d37c6da6a 100644 --- a/pkg/models/iam/im.go +++ b/pkg/models/iam/im.go @@ -284,7 +284,7 @@ func ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limi defer conn.Close() - pageControl := ldap.NewControlPaging(80) + pageControl := ldap.NewControlPaging(1000) users := make([]models.User, 0) diff --git a/pkg/models/iam/policy/policy.go b/pkg/models/iam/policy/policy.go index 109bb6f236224c56ae0aef86d818e3c79edf023d..bce92c8f6510406414dbe5514c67dffbbc6146d1 100644 --- a/pkg/models/iam/policy/policy.go +++ b/pkg/models/iam/policy/policy.go @@ -905,7 +905,7 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"get", "list"}, - APIGroups: []string{"resources.kubesphere.io"}, + APIGroups: []string{"resources.kubesphere.io", "app.k8s.io"}, Resources: []string{"applications"}, }, { Verbs: []string{"get", "list"}, @@ -927,7 +927,7 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"create", "update", "patch"}, - APIGroups: []string{"resources.kubesphere.io"}, + APIGroups: []string{"resources.kubesphere.io", "app.k8s.io"}, Resources: []string{"applications"}, }, { Verbs: []string{"create", "update", "patch"}, @@ -940,7 +940,7 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"delete"}, - APIGroups: []string{"resources.kubesphere.io"}, + APIGroups: []string{"resources.kubesphere.io", "app.k8s.io"}, Resources: []string{"applications"}, }, { diff --git a/pkg/simple/client/openpitrix/openpitrixclient.go b/pkg/simple/client/openpitrix/openpitrixclient.go index b9844d96b095fe6c76c1b357be7188b167ac9105..5456f22de1fa1da8d700c19f482a39cbe51259de 100644 --- a/pkg/simple/client/openpitrix/openpitrixclient.go +++ b/pkg/simple/client/openpitrix/openpitrixclient.go @@ -24,7 +24,9 @@ import ( "fmt" "github.com/golang/glog" "io/ioutil" + "kubesphere.io/kubesphere/pkg/utils/sliceutil" "net/http" + "strings" "sync" ) @@ -48,6 +50,10 @@ type Interface interface { CreateRuntime(runtime *RunTime) error DeleteRuntime(runtimeId string) error } +type cluster struct { + Status string `json:"status"` + ClusterId string `json:"cluster_id"` +} type Error struct { status int @@ -78,14 +84,12 @@ func (c client) CreateRuntime(runtime *RunTime) error { data, err := json.Marshal(runtime) if err != nil { - glog.Error(err) return err } req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/runtimes", openpitrixAPIServer), bytes.NewReader(data)) if err != nil { - glog.Error(err) return err } req.Header.Add("Content-Type", "application/json") @@ -106,30 +110,46 @@ func (c client) CreateRuntime(runtime *RunTime) error { } if resp.StatusCode > http.StatusOK { - return Error{resp.StatusCode, string(data)} + err = Error{resp.StatusCode, string(data)} + glog.Error(err) + return err } return nil } -func (c client) DeleteRuntime(runtimeId string) error { - data := []byte(fmt.Sprintf(`{"runtime_id":"%s"}`, runtimeId)) - req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/v1/runtimes", openpitrixAPIServer), bytes.NewReader(data)) +func (c client) deleteClusters(clusters []cluster) error { + clusterId := make([]string, 0) - if err != nil { - glog.Error(err) + for _, cluster := range clusters { + if cluster.Status != "deleted" && cluster.Status != "deleting" && !sliceutil.HasString(clusterId, cluster.ClusterId) { + clusterId = append(clusterId, cluster.ClusterId) + } + } - return err + if len(clusterId) == 0 { + return nil + } + + deleteRequest := struct { + ClusterId []string `json:"cluster_id"` + }{ + ClusterId: clusterId, } + data, _ := json.Marshal(deleteRequest) + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/clusters/delete", openpitrixAPIServer), bytes.NewReader(data)) + if err != nil { + return err + } req.Header.Add("Authorization", openpitrixProxyToken) resp, err := c.client.Do(req) - if err != nil { glog.Error(err) return err } + defer resp.Body.Close() data, err = ioutil.ReadAll(resp.Body) @@ -139,8 +159,110 @@ func (c client) DeleteRuntime(runtimeId string) error { } if resp.StatusCode > http.StatusOK { - return Error{resp.StatusCode, string(data)} + err = Error{resp.StatusCode, string(data)} + glog.Error(err) + return err } return nil } + +func (c client) listClusters(runtimeId string) ([]cluster, error) { + limit := 200 + offset := 0 + clusters := make([]cluster, 0) + for { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v1/clusters?runtime_id=%s&limit=%d&offset=%d", openpitrixAPIServer, runtimeId, limit, offset), nil) + + if err != nil { + glog.Error(err) + return nil, err + } + + req.Header.Add("Authorization", openpitrixProxyToken) + + resp, err := c.client.Do(req) + + if err != nil { + glog.Error(err) + return nil, err + } + + data, err := ioutil.ReadAll(resp.Body) + + if err != nil { + glog.Error(err) + return nil, err + } + + resp.Body.Close() + + if resp.StatusCode > http.StatusOK { + err = Error{resp.StatusCode, string(data)} + glog.Error(err) + return nil, err + } + listClusterResponse := struct { + TotalCount int `json:"total_count"` + ClusterSet []cluster `json:"cluster_set"` + }{} + err = json.Unmarshal(data, &listClusterResponse) + + if err != nil { + glog.Error(err) + return nil, err + } + + clusters = append(clusters, listClusterResponse.ClusterSet...) + + if listClusterResponse.TotalCount <= limit+offset { + break + } + + offset += limit + } + + return clusters, nil +} + +func (c client) DeleteRuntime(runtimeId string) error { + clusters, err := c.listClusters(runtimeId) + + if err != nil { + glog.Error(err) + return err + } + + err = c.deleteClusters(clusters) + + if err != nil { + glog.Error(err) + return err + } + + return nil +} + +func IsNotFound(err error) bool { + if e, ok := err.(Error); ok { + if e.status == http.StatusNotFound { + return true + } + if strings.Contains(e.message, "not exist") { + return true + } + if strings.Contains(e.message, "not found") { + return true + } + } + return false +} + +func IsDeleted(err error) bool { + if e, ok := err.(Error); ok { + if strings.Contains(e.message, "is [deleted]") { + return true + } + } + return false +}