diff --git a/README.md b/README.md index 4698acc5e..61970b40f 100644 --- a/README.md +++ b/README.md @@ -2424,7 +2424,7 @@ http://localhost:3000/api/jasder/forgeplus/ci_authorize.json | jq |参数名|类型|说明| |-|-|-| -|step |int|初始化devops流程步骤; 0: 标识未开启devops,1: 标识用户已填写了云服务器相关信息,但并未开启认证, 2: 标识用户已开启了CI服务端的认证, 3: 标识用户ci服务已初始化| +|step |int|初始化devops流程步骤; 0: 标识未开启devops,1: 标识用户已填写了云服务器相关信息,但并未开启认证, 2: 标识用户已开启了CI服务端的认证| |account |string|你的云服务器帐号| |ip |string|你的云服务器帐号ip| |secret |string|你的云服务器登录密码| @@ -3108,3 +3108,182 @@ http://localhost:3000/api/dev_ops/builds/2/logs/1/1 | jq ] ``` --- + +#### 获取CI服务器配置信息 +``` +GET /api/users/ci/cloud_account +``` + +*示例* +``` +curl -X GET \ +http://localhost:3000/api/users/ci/cloud_account | jq +``` + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|step |int|0: 未绑定;1: 未认证(已绑定),2: 已认证| +|ip |string|ci服务器ip| +|redirect_url |string|认证地址| + +返回值 +```json +{ + "step": 0, + "cloud_account": { + "ip": "xxx.xxx.xxx.x", + "redirect_url": "http://localhost:3000/login", + } +} +``` +--- + +#### 绑定CI服务器 +``` +POST /api/users/ci/cloud_account/bind +``` + +*示例* +``` +curl -X POST \ +-d "account=xx" \ +-d "secret=xxx" \ +-d "ip_num=xx.xx.xx.xx" \ +https://localhost:3000/api/users/ci/cloud_account/bind.json | jq +``` + +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|account |是|string |云服务器ssh连接登录用户名 | +|secret |是|string |云服务器ssh连接登录秘密 | +|ip_num |否|string |云服务器公网IP | + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|step |int|0: 未绑定;1: 未认证(已绑定),2: 已认证| +|ip |string|ci服务器ip| +|redirect_url |string|认证地址| + +返回值 +```json +{ + "step": 0, + "cloud_account": { + "ip": "xxx.xxx.xxx.x", + "redirect_url": "http://localhost:3000/login", + } +} +``` +--- + + +### 解除CI服务器绑定 +``` +DELETE /api/users/ci/cloud_account/unbind +``` + +*示例* +``` +curl -X DELETE \ +http://localhost:3000/api/users/ci/cloud_account/unbind.json | jq +``` + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|status |int|状态码, 0: 成功,-1: 失败| +|message |string|返回信息说明| + +返回值 +```json +{ + "status": 0, + "message": "success" +} +``` +--- + +### 项目列表 +``` +GET /api/users/:login/projects +``` + +*示例* +``` +curl -X GET \ +-d "page=1" \ +-d "limit=20" \ +http://localhost:3000/api/users/Jason/projects.json | jq +``` + +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|page |否|int |页数,第几页 | +|limit |否|int |每页多少条数据,默认20条 | + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|total_count |int |项目总条数 | +|id |string |项目id | +|name |string|项目名称| +|description |string|项目简介| +|open_devops |boolean|激活状态,true: 激活; false:未激活| +|visits |int|流量数| +|forked_count |int|被fork的数量| +|praises_count |int|star数量| +|is_public |boolean|是否公开, true:公开,false:未公开| +|mirror_url |string|镜像url| +|last_update_time|int|最后更新时间,为UNIX格式的时间戳| +|author |object|项目创建者| +|-- name |string|用户名,也是用户标识| +|category |object|项目类别| +|-- id |int|项目类型id| +|-- name |string|项目类型名称| +|language |object|项目语言| +|-- id |int|项目语言id| +|-- name |string|项目语言名称| + + +返回值 +``` +{ + "total_count": 3096, + "projects": [ + { + "id": 1, + "name": "hnfl_demo1", + "description": "my first project", + "visits": 0, + "praises_count": 0, + "forked_count": 0, + "is_public": true, + "mirror_url": null, + "last_update_time": 1577697461, + "author": { + "name": "18816895620", + "image_url": "avatars/User/b" + }, + "category": { + "id": 1, + "name": "大数据" + }, + "language": { + "id": 2, + "name": "C" + } + } + ] +} +``` +--- diff --git a/app/controllers/ci/cloud_accounts_controller.rb b/app/controllers/ci/cloud_accounts_controller.rb index 85b0e7ac8..46fd02e76 100644 --- a/app/controllers/ci/cloud_accounts_controller.rb +++ b/app/controllers/ci/cloud_accounts_controller.rb @@ -10,52 +10,12 @@ class Ci::CloudAccountsController < Ci::BaseController ActiveRecord::Base.transaction do Ci::CreateCloudAccountForm.new(devops_params).validate! - # 1. 保存华为云服务器帐号 - create_params = devops_params.merge(ip_num: IPAddr.new(devops_params[:ip_num]).to_i, secret: Ci::CloudAccount.encrypted_secret(devops_params[:secret])) - if current_user&.ci_cloud_account.present? - return render_error('该仓库已绑定了云帐号.') - else - cloud_account = Ci::CloudAccount.new(create_params) - cloud_account.user = current_user - cloud_account.save! - end - - # 2. 生成oauth2应用程序的client_id和client_secrete - gitea_oauth = Gitea::Oauth2::CreateService.call(current_user.gitea_token, {name: "pipeline", redirect_uris: ["#{cloud_account.drone_url}/login"]}) - logger.info "######### gitea_oauth: #{gitea_oauth}" - oauth = Oauth.new(client_id: gitea_oauth['client_id'], - client_secret: gitea_oauth['client_secret'], - redirect_uri: gitea_oauth['redirect_uris'], - gitea_oauth_id: gitea_oauth['id'], - user_id: current_user.id, - project_id: @project.id) - oauth.save - - rpc_secret = SecureRandom.hex 16 - logger.info "######### rpc_secret: #{rpc_secret}" - - # 3. 创建drone server - drone_server_cmd = Ci::Drone::Server.new(oauth.client_id, oauth.client_secret, cloud_account.drone_host, rpc_secret).generate_cmd - logger.info "######### drone_server_cmd: #{drone_server_cmd}" - - # 4. 创建drone client - drone_client_cmd = Ci::Drone::Client.new(oauth.client_id, cloud_account.drone_ip, rpc_secret).generate_cmd - logger.info "######### drone_client_cmd: #{drone_client_cmd}" - - # 5. 登录远程服务器,启动drone服务 - result = Ci::Drone::Start.new(cloud_account.account, cloud_account.visible_secret, cloud_account.drone_ip, drone_server_cmd, drone_client_cmd).run - logger.info "######### result: #{result}" - - - redirect_url = "#{cloud_account.drone_url}/login" - logger.info "######### redirect_url: #{redirect_url}" - - if result && !result.blank? - current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) - render_ok(redirect_url: redirect_url) - else + @cloud_account = bind_account(current_user) + if @cloud_account.blank? render_error('激活失败, 请检查你的云服务器信息是否正确.') raise ActiveRecord::Rollback + else + current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) end end rescue Exception => ex @@ -79,6 +39,37 @@ class Ci::CloudAccountsController < Ci::BaseController end end + def show + @cloud_account = current_user.ci_cloud_account + end + + + def bind + Ci::CreateCloudAccountForm.new(devops_params).validate! + + ActiveRecord::Base.transaction do + @cloud_account = bind_account(current_user) + if @cloud_account.blank? + render_error('激活失败, 请检查你的云服务器信息是否正确.') + raise ActiveRecord::Rollback + else + current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) + end + end + rescue Exception => ex + render_error(ex.message) + end + + def unbind + ActiveRecord::Base.transaction do + unbind_account!(current_user) + render_ok + end + rescue Exception => ex + render_error(ex.message) + end + + private def devops_params params.permit(:account, :secret, :ip_num) @@ -87,4 +78,61 @@ class Ci::CloudAccountsController < Ci::BaseController def find_cloud_account @cloud_account = Ci::CloudAccount.find params[:id] end + + def bind_account(user) + # 1. 保存华为云服务器帐号 + create_params = devops_params.merge(ip_num: IPAddr.new(devops_params[:ip_num]).to_i, secret: Ci::CloudAccount.encrypted_secret(devops_params[:secret])) + + return render_error('你已绑定了云帐号.') if user.ci_cloud_account.blank? + + cloud_account = Ci::CloudAccount.new(create_params) + cloud_account.user = user + cloud_account.save! + + # 2. 生成oauth2应用程序的client_id和client_secrete + gitea_oauth = Gitea::Oauth2::CreateService.call(user.gitea_token, {name: "pipeline", redirect_uris: ["#{cloud_account.drone_url}/login"]}) + logger.info "######### gitea_oauth: #{gitea_oauth}" + oauth = Oauth.new(client_id: gitea_oauth['client_id'], + client_secret: gitea_oauth['client_secret'], + redirect_uri: gitea_oauth['redirect_uris'], + gitea_oauth_id: gitea_oauth['id'], + user_id: current_user.id) + oauth.save + + rpc_secret = SecureRandom.hex 16 + logger.info "######### rpc_secret: #{rpc_secret}" + + # 3. 创建drone server + drone_server_cmd = Ci::Drone::Server.new(oauth.client_id, oauth.client_secret, cloud_account.drone_host, rpc_secret).generate_cmd + logger.info "######### drone_server_cmd: #{drone_server_cmd}" + + # 4. 创建drone client + drone_client_cmd = Ci::Drone::Client.new(oauth.client_id, cloud_account.drone_ip, rpc_secret).generate_cmd + logger.info "######### drone_client_cmd: #{drone_client_cmd}" + + # 5. 登录远程服务器,启动drone服务 + result = Ci::Drone::Start.new(cloud_account.account, cloud_account.visible_secret, cloud_account.drone_ip, drone_server_cmd, drone_client_cmd).run + logger.info "######### result: #{result}" + + + redirect_url = "#{cloud_account.drone_url}/login" + logger.info "######### redirect_url: #{redirect_url}" + + result && !result.blank? ? cloud_account : nil + end + + def unbind_account!(user) + cloud_account = user.ci_cloud_account + + case user.devops_step + when User::DEVOPS_UNINIT + return render_error('你还未绑定CI服务器') + when User::DEVOPS_UNVERIFIED + cloud_account.destroy + when User::DEVOPS_CERTIFICATION + cloud_account.ci_user.destroy + end + user.projects.update_all(open_devops: false) + user.set_drone_step!(User::DEVOPS_UNINIT) + end end diff --git a/app/models/ci/cloud_account.rb b/app/models/ci/cloud_account.rb index f54db49d9..ca8524c9a 100644 --- a/app/models/ci/cloud_account.rb +++ b/app/models/ci/cloud_account.rb @@ -1,5 +1,6 @@ class Ci::CloudAccount < Ci::LocalBase belongs_to :user + belongs_to :ci_user, class_name: 'Ci::User', foreign_key: :ci_user_id def drone_host [drone_ip, ":80"].join diff --git a/app/models/ci/log.rb b/app/models/ci/log.rb new file mode 100644 index 000000000..e509425dd --- /dev/null +++ b/app/models/ci/log.rb @@ -0,0 +1,4 @@ +class Ci::Log < Ci::RemoteBase + self.primary_key = 'log_id' + +end diff --git a/app/models/ci/perm.rb b/app/models/ci/perm.rb new file mode 100644 index 000000000..ae16d5880 --- /dev/null +++ b/app/models/ci/perm.rb @@ -0,0 +1,4 @@ +class Ci::Perm < Ci::RemoteBase + belongs_to :user, foreign_key: :perm_user_id + +end diff --git a/app/models/ci/user.rb b/app/models/ci/user.rb index 90387b492..fecf7dccd 100644 --- a/app/models/ci/user.rb +++ b/app/models/ci/user.rb @@ -2,5 +2,7 @@ class Ci::User < Ci::RemoteBase self.primary_key = 'user_id' has_many :repos, foreign_key: "repo_user_id", dependent: :destroy + has_many :perms, foreign_key: "perm_user_id", dependent: :destroy + has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', foreign_key: :ci_user_id end diff --git a/app/models/oauth.rb b/app/models/oauth.rb index 563ed0906..bf7860515 100644 --- a/app/models/oauth.rb +++ b/app/models/oauth.rb @@ -1,5 +1,4 @@ # for oauth2 application class Oauth < ApplicationRecord - belongs_to :project belongs_to :user end diff --git a/app/models/user.rb b/app/models/user.rb index 9acd369e8..9df853b4e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,9 +10,10 @@ class User < ApplicationRecord # include Searchable::Dependents::User # devops step - # devops_step column: 0: 未填写服务器信息;1: 已填写服务器信息(未认证); + # devops_step column: 0: 未填写服务器信息;1: 已填写服务器信息(未认证);2: 已认证 DEVOPS_UNINIT = 0 DEVOPS_UNVERIFIED = 1 + DEVOPS_CERTIFICATION = 2 # Account statuses STATUS_ANONYMOUS = 0 diff --git a/app/views/ci/ucloud_accounts/bind.json.jbuilder b/app/views/ci/ucloud_accounts/bind.json.jbuilder new file mode 100644 index 000000000..230f4672f --- /dev/null +++ b/app/views/ci/ucloud_accounts/bind.json.jbuilder @@ -0,0 +1,9 @@ +json.step current_user.devops_step +json.cloud_account do + if @cloud_account && !current_user.devops_uninit? + json.ip @cloud_account.drone_ip + json.redirect_url "#{@cloud_account.drone_url}/login" if current_user.devops_unverified? + else + json.nil! + end +end diff --git a/app/views/ci/ucloud_accounts/show.json.jbuilder b/app/views/ci/ucloud_accounts/show.json.jbuilder new file mode 100644 index 000000000..230f4672f --- /dev/null +++ b/app/views/ci/ucloud_accounts/show.json.jbuilder @@ -0,0 +1,9 @@ +json.step current_user.devops_step +json.cloud_account do + if @cloud_account && !current_user.devops_uninit? + json.ip @cloud_account.drone_ip + json.redirect_url "#{@cloud_account.drone_url}/login" if current_user.devops_unverified? + else + json.nil! + end +end diff --git a/app/views/ci/ucloud_accounts/unbind.json.jbuilder b/app/views/ci/ucloud_accounts/unbind.json.jbuilder new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/projects/_project_detail.json.jbuilder b/app/views/projects/_project_detail.json.jbuilder index d7cd196e2..b14a237e7 100644 --- a/app/views/projects/_project_detail.json.jbuilder +++ b/app/views/projects/_project_detail.json.jbuilder @@ -16,6 +16,7 @@ json.type project&.numerical_for_project_type json.last_update_time render_unix_time(project.updated_on) json.time_ago time_from_now(project.updated_on) json.forked_from_project_id project.forked_from_project_id +json.open_devops project.open_devops? json.author do json.name user.try(:show_real_name) json.login user.login diff --git a/config/routes.rb b/config/routes.rb index c2f608afd..595692371 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,6 +26,7 @@ Rails.application.routes.draw do get :common end end + # resources :repos, only: :index do # collection do # get 'get_trustie_pipeline', to: 'builds#get_trustie_pipeline', as: 'get_trustie_pipeline' @@ -144,6 +145,28 @@ Rails.application.routes.draw do post :sync_salt get :trustie_projects get :trustie_related_projects + + scope '/ci', module: :ci do + scope do + post( + '/cloud_account/bind', + to: 'cloud_accounts#bind', + as: :bind_cloud_acclount + ) + + get( + '/cloud_account', + to: 'cloud_accounts#show', + as: :get_cloud_account + ) + + delete( + '/cloud_account/unbind', + to: 'cloud_accounts#unbind', + as: :unbind_cloud_acclount + ) + end + end end scope module: :users do