diff --git a/api/swagger/docs.go b/api/swagger/docs.go index 5750fa77..dc74409b 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -4725,7 +4725,6 @@ const docTemplate = `{ "domain.CreateStackRequest": { "type": "object", "required": [ - "cloudAccountId", "cloudService", "name", "stackTemplateId" diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 21551c4a..10e9108f 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -4718,7 +4718,6 @@ "domain.CreateStackRequest": { "type": "object", "required": [ - "cloudAccountId", "cloudService", "name", "stackTemplateId" diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index 4ef1d136..165b8875 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -723,7 +723,6 @@ definitions: tksUserNodeType: type: string required: - - cloudAccountId - cloudService - name - stackTemplateId diff --git a/internal/delivery/http/cluster.go b/internal/delivery/http/cluster.go index 62b51dca..2acd1efb 100644 --- a/internal/delivery/http/cluster.go +++ b/internal/delivery/http/cluster.go @@ -164,10 +164,20 @@ func (h *ClusterHandler) CreateCluster(w http.ResponseWriter, r *http.Request) { log.InfoWithContext(r.Context(), dto.Conf) //txHandle := r.Context().Value("txHandle").(*gorm.DB) - clusterId, err := h.usecase.Create(r.Context(), dto) - if err != nil { - ErrorJSON(w, r, err) - return + clusterId := domain.ClusterId("") + if input.CloudService == domain.CloudService_BYOH { + clusterId, err = h.usecase.CreateByoh(r.Context(), dto) + if err != nil { + ErrorJSON(w, r, err) + return + } + } else { + clusterId, err = h.usecase.Create(r.Context(), dto) + if err != nil { + ErrorJSON(w, r, err) + return + } + } var out domain.CreateClusterResponse diff --git a/internal/delivery/http/stack.go b/internal/delivery/http/stack.go index dbe3f8d3..dc6d213b 100644 --- a/internal/delivery/http/stack.go +++ b/internal/delivery/http/stack.go @@ -49,13 +49,6 @@ func (h *StackHandler) CreateStack(w http.ResponseWriter, r *http.Request) { return } - if input.CloudService == domain.CloudService_BYOH { - if input.AdminClusterUrl == "" { - ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid adminClusterUrl"), "C_INVALID_ADMINCLUSTER_URL", "")) - return - } - } - var dto domain.Stack if err = serializer.Map(input, &dto); err != nil { log.InfoWithContext(r.Context(), err) @@ -65,10 +58,23 @@ func (h *StackHandler) CreateStack(w http.ResponseWriter, r *http.Request) { } dto.OrganizationId = organizationId - stackId, err := h.usecase.Create(r.Context(), dto) - if err != nil { - ErrorJSON(w, r, err) - return + stackId := domain.StackId("") + if input.CloudService == domain.CloudService_BYOH { + if input.AdminClusterUrl == "" { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid adminClusterUrl"), "C_INVALID_ADMINCLUSTER_URL", "")) + return + } + stackId, err = h.usecase.CreateByoh(r.Context(), dto) + if err != nil { + ErrorJSON(w, r, err) + return + } + } else { + stackId, err = h.usecase.Create(r.Context(), dto) + if err != nil { + ErrorJSON(w, r, err) + return + } } out := domain.CreateStackResponse{ diff --git a/internal/usecase/cluster.go b/internal/usecase/cluster.go index 63fba610..e9ab621b 100644 --- a/internal/usecase/cluster.go +++ b/internal/usecase/cluster.go @@ -26,6 +26,7 @@ type IClusterUsecase interface { Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.Cluster, error) FetchByCloudAccountId(ctx context.Context, cloudAccountId uuid.UUID, pg *pagination.Pagination) (out []domain.Cluster, err error) Create(ctx context.Context, dto domain.Cluster) (clusterId domain.ClusterId, err error) + CreateByoh(ctx context.Context, dto domain.Cluster) (clusterId domain.ClusterId, err error) Get(ctx context.Context, clusterId domain.ClusterId) (out domain.Cluster, err error) GetClusterSiteValues(ctx context.Context, clusterId domain.ClusterId) (out domain.ClusterSiteValuesResponse, err error) Delete(ctx context.Context, clusterId domain.ClusterId) (err error) @@ -159,42 +160,74 @@ func (u *ClusterUsecase) Create(ctx context.Context, dto domain.Cluster) (cluste return "", errors.Wrap(err, "Failed to create cluster") } - workflowId := "" - if dto.CloudService == domain.CloudService_BYOH { - workflowId, err = u.argo.SumbitWorkflowFromWftpl( - "bootstrap-tks-usercluster", - argowf.SubmitOptions{ - Parameters: []string{ - fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")), - "contract_id=" + dto.OrganizationId, - "cluster_id=" + clusterId.String(), - "site_name=" + clusterId.String(), - "template_name=" + stackTemplate.Template, - "git_account=" + viper.GetString("git-account"), - "creator=" + user.GetUserId().String(), - "cloud_account_id=" + tksCloudAccountId, - "base_repo_branch=" + viper.GetString("revision"), - //"manifest_repo_url=" + viper.GetString("git-base-url") + "/" + viper.GetString("git-account") + "/" + clusterId + "-manifests", - }, - }) - } else { - workflowId, err = u.argo.SumbitWorkflowFromWftpl( - "create-tks-usercluster", - argowf.SubmitOptions{ - Parameters: []string{ - fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")), - "contract_id=" + dto.OrganizationId, - "cluster_id=" + clusterId.String(), - "site_name=" + clusterId.String(), - "template_name=" + stackTemplate.Template, - "git_account=" + viper.GetString("git-account"), - "creator=" + user.GetUserId().String(), - "cloud_account_id=" + tksCloudAccountId, - "base_repo_branch=" + viper.GetString("revision"), - //"manifest_repo_url=" + viper.GetString("git-base-url") + "/" + viper.GetString("git-account") + "/" + clusterId + "-manifests", - }, - }) + workflowId, err := u.argo.SumbitWorkflowFromWftpl( + "create-tks-usercluster", + argowf.SubmitOptions{ + Parameters: []string{ + fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")), + "contract_id=" + dto.OrganizationId, + "cluster_id=" + clusterId.String(), + "site_name=" + clusterId.String(), + "template_name=" + stackTemplate.Template, + "git_account=" + viper.GetString("git-account"), + "creator=" + user.GetUserId().String(), + "cloud_account_id=" + tksCloudAccountId, + "base_repo_branch=" + viper.GetString("revision"), + //"manifest_repo_url=" + viper.GetString("git-base-url") + "/" + viper.GetString("git-account") + "/" + clusterId + "-manifests", + }, + }) + if err != nil { + log.ErrorWithContext(ctx, "failed to submit argo workflow template. err : ", err) + return "", err + } + log.InfoWithContext(ctx, "Successfully submited workflow: ", workflowId) + + if err := u.repo.InitWorkflow(clusterId, workflowId, domain.ClusterStatus_INSTALLING); err != nil { + return "", errors.Wrap(err, "Failed to initialize status") + } + + return clusterId, nil +} + +func (u *ClusterUsecase) CreateByoh(ctx context.Context, dto domain.Cluster) (clusterId domain.ClusterId, err error) { + user, ok := request.UserFrom(ctx) + if !ok { + return "", httpErrors.NewBadRequestError(fmt.Errorf("Invalid token"), "", "") } + + _, err = u.repo.GetByName(dto.OrganizationId, dto.Name) + if err == nil { + return "", httpErrors.NewBadRequestError(httpErrors.DuplicateResource, "", "") + } + + stackTemplate, err := u.stackTemplateRepo.Get(dto.StackTemplateId) + if err != nil { + return "", httpErrors.NewBadRequestError(errors.Wrap(err, "Invalid stackTemplateId"), "", "") + } + + userId := user.GetUserId() + dto.CreatorId = &userId + clusterId, err = u.repo.Create(dto) + if err != nil { + return "", errors.Wrap(err, "Failed to create cluster") + } + + workflowId := "" + workflowId, err = u.argo.SumbitWorkflowFromWftpl( + "bootstrap-tks-usercluster", + argowf.SubmitOptions{ + Parameters: []string{ + fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")), + "contract_id=" + dto.OrganizationId, + "cluster_id=" + clusterId.String(), + "site_name=" + clusterId.String(), + "template_name=" + stackTemplate.Template, + "git_account=" + viper.GetString("git-account"), + "creator=" + user.GetUserId().String(), + "cloud_account_id=NULL", + "base_repo_branch=" + viper.GetString("revision"), + }, + }) if err != nil { log.ErrorWithContext(ctx, "failed to submit argo workflow template. err : ", err) return "", err diff --git a/internal/usecase/stack.go b/internal/usecase/stack.go index a8e1c3d4..efc8d80e 100644 --- a/internal/usecase/stack.go +++ b/internal/usecase/stack.go @@ -27,6 +27,7 @@ type IStackUsecase interface { GetByName(ctx context.Context, organizationId string, name string) (domain.Stack, error) Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.Stack, error) Create(ctx context.Context, dto domain.Stack) (stackId domain.StackId, err error) + CreateByoh(ctx context.Context, dto domain.Stack) (stackId domain.StackId, err error) Update(ctx context.Context, dto domain.Stack) error Delete(ctx context.Context, dto domain.Stack) error GetKubeConfig(ctx context.Context, stackId domain.StackId) (kubeConfig string, err error) @@ -96,16 +97,87 @@ func (u *StackUsecase) Create(ctx context.Context, dto domain.Stack) (stackId do log.InfoWithContext(ctx, err) } - workflow := "" - if dto.CloudService == "AWS" { - workflow = "tks-stack-create-aws" - } else if dto.CloudService == "BYOH" { - workflow = "tks-stack-create-byoh" - } else { - log.ErrorWithContext(ctx, "Invalid cloud service : ", dto.CloudService) - return "", httpErrors.NewInternalServerError(fmt.Errorf("Invalid cloud service. %s", dto.CloudService), "", "") + workflow := "tks-stack-create-aws" + workflowId, err := u.argo.SumbitWorkflowFromWftpl(workflow, argowf.SubmitOptions{ + Parameters: []string{ + fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")), + "cluster_name=" + dto.Name, + "description=" + dto.Description, + "organization_id=" + dto.OrganizationId, + "cloud_account_id=" + dto.CloudAccountId.String(), + "stack_template_id=" + dto.StackTemplateId.String(), + "creator=" + user.GetUserId().String(), + "base_repo_branch=" + viper.GetString("revision"), + "infra_conf=" + strings.Replace(helper.ModelToJson(stackConf), "\"", "\\\"", -1), + "cloud_service=" + dto.CloudService, + }, + }) + if err != nil { + log.ErrorWithContext(ctx, err) + return "", httpErrors.NewInternalServerError(err, "S_FAILED_TO_CALL_WORKFLOW", "") + } + log.DebugWithContext(ctx, "Submitted workflow: ", workflowId) + + // wait & get clusterId ( max 1min ) + dto.ID = domain.StackId("") + for i := 0; i < 60; i++ { + time.Sleep(time.Second * 5) + workflow, err := u.argo.GetWorkflow("argo", workflowId) + if err != nil { + return "", err + } + + log.DebugWithContext(ctx, "workflow ", workflow) + if workflow.Status.Phase != "" && workflow.Status.Phase != "Running" { + return "", fmt.Errorf("Invalid workflow status [%s]", workflow.Status.Phase) + } + + cluster, err := u.clusterRepo.GetByName(dto.OrganizationId, dto.Name) + if err != nil { + continue + } + if cluster.Name == dto.Name { + dto.ID = domain.StackId(cluster.ID) + break + } + } + + return dto.ID, nil +} + +func (u *StackUsecase) CreateByoh(ctx context.Context, dto domain.Stack) (stackId domain.StackId, err error) { + user, ok := request.UserFrom(ctx) + if !ok { + return "", httpErrors.NewUnauthorizedError(fmt.Errorf("Invalid token"), "A_INVALID_TOKEN", "") + } + + _, err = u.GetByName(ctx, dto.OrganizationId, dto.Name) + if err == nil { + return "", httpErrors.NewBadRequestError(httpErrors.DuplicateResource, "S_CREATE_ALREADY_EXISTED_NAME", "") + } + + _, err = u.stackTemplateRepo.Get(dto.StackTemplateId) + if err != nil { + return "", httpErrors.NewInternalServerError(errors.Wrap(err, "Invalid stackTemplateId"), "S_INVALID_STACK_TEMPLATE", "") + } + + clusters, err := u.clusterRepo.FetchByOrganizationId(dto.OrganizationId, nil) + if err != nil { + return "", httpErrors.NewInternalServerError(errors.Wrap(err, "Failed to get clusters"), "S_FAILED_GET_CLUSTERS", "") + } + isPrimary := false + if len(clusters) == 0 { + isPrimary = true + } + log.DebugWithContext(ctx, "isPrimary ", isPrimary) + + // Make stack nodes + var stackConf domain.StackConfResponse + if err = domain.Map(dto.Conf, &stackConf); err != nil { + log.InfoWithContext(ctx, err) } + workflow := "tks-stack-create-byoh" workflowId, err := u.argo.SumbitWorkflowFromWftpl(workflow, argowf.SubmitOptions{ Parameters: []string{ fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")), diff --git a/pkg/domain/cluster.go b/pkg/domain/cluster.go index 7558d0d5..0d00cb75 100644 --- a/pkg/domain/cluster.go +++ b/pkg/domain/cluster.go @@ -139,7 +139,7 @@ type CreateClusterRequest struct { StackTemplateId string `json:"stackTemplateId" validate:"required"` Name string `json:"name" validate:"required,name"` Description string `json:"description"` - CloudAccountId string `json:"cloudAccountId" validate:"required"` + CloudAccountId string `json:"cloudAccountId"` ClusterType string `json:"clusterType"` TksCpNode int `json:"tksCpNode"` TksCpNodeMax int `json:"tksCpNodeMax,omitempty"` diff --git a/pkg/domain/stack.go b/pkg/domain/stack.go index 8a416c7a..ffa7c711 100644 --- a/pkg/domain/stack.go +++ b/pkg/domain/stack.go @@ -147,7 +147,7 @@ type CreateStackRequest struct { Description string `json:"description"` CloudService string `json:"cloudService" validate:"required,oneof=AWS BYOH"` StackTemplateId string `json:"stackTemplateId" validate:"required"` - CloudAccountId string `json:"cloudAccountId" validate:"required"` + CloudAccountId string `json:"cloudAccountId"` TksCpNode int `json:"tksCpNode"` TksCpNodeMax int `json:"tksCpNodeMax,omitempty"` TksCpNodeType string `json:"tksCpNodeType,omitempty"`