From e74bba9092ade45135373a9519ba6018bbc1f81a Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 10 Jul 2020 14:08:16 +0800 Subject: [PATCH] ADD test devops --- .../dev_ops/cloud_accounts_controller.rb | 64 +++++++++++++++++++ .../dev_ops/create_cloud_account_form.rb | 9 +++ app/helpers/dev_ops_helper.rb | 2 + app/libs/custom_regexp.rb | 3 +- app/libs/dev_ops/drone/ci.rb | 23 +++++++ app/libs/dev_ops/drone/client.rb | 39 +++++++++++ app/libs/dev_ops/drone/server.rb | 59 +++++++++++++++++ app/libs/dev_ops/drone/start.rb | 22 +++++++ app/models/devops_cloud_account.rb | 25 ++++++++ app/models/oauth.rb | 5 ++ app/models/project.rb | 2 +- app/services/gitea/client_service.rb | 3 - app/services/gitea/oauth2/create_service.rb | 41 ++++++++++++ config/routes.rb | 6 +- ...0708114354_create_devops_cloud_accounts.rb | 14 ++++ ...gitea_oauth_id_and_project_id_to_oauths.rb | 6 ++ 16 files changed, 317 insertions(+), 6 deletions(-) create mode 100644 app/controllers/dev_ops/cloud_accounts_controller.rb create mode 100644 app/forms/dev_ops/create_cloud_account_form.rb create mode 100644 app/helpers/dev_ops_helper.rb create mode 100644 app/libs/dev_ops/drone/ci.rb create mode 100644 app/libs/dev_ops/drone/client.rb create mode 100644 app/libs/dev_ops/drone/server.rb create mode 100644 app/libs/dev_ops/drone/start.rb create mode 100644 app/models/devops_cloud_account.rb create mode 100644 app/models/oauth.rb create mode 100644 app/services/gitea/oauth2/create_service.rb create mode 100644 db/migrate/20200708114354_create_devops_cloud_accounts.rb create mode 100644 db/migrate/20200709061656_add_gitea_oauth_id_and_project_id_to_oauths.rb diff --git a/app/controllers/dev_ops/cloud_accounts_controller.rb b/app/controllers/dev_ops/cloud_accounts_controller.rb new file mode 100644 index 000000000..0cc63ced8 --- /dev/null +++ b/app/controllers/dev_ops/cloud_accounts_controller.rb @@ -0,0 +1,64 @@ +class DevOps::CloudAccountsController < ApplicationController + before_action :require_login + before_action :find_project + + def create + ActiveRecord::Base.transaction do + DevOps::CreateCloudAccountForm.new(devops_params).validate! + logger.info "######### devops_params: #{devops_params}" + logger.info "######### ......: #{(IPAddr.new devops_params[:ip_num]).to_i}" + logger.info "######### ......: #{DevopsCloudAccount.encrypted_secret(devops_params[:secret])}" + # 1. 保存华为云服务器帐号 + logger.info "######### ......ff: #{devops_params.merge(ip_num: IPAddr.new(devops_params[:ip_num]).to_i, secret: DevopsCloudAccount.encrypted_secret(devops_params[:secret]))}" + create_params = devops_params.merge(ip_num: IPAddr.new(devops_params[:ip_num]).to_i, secret: DevopsCloudAccount.encrypted_secret(devops_params[:secret])) + logger.info "######### create_params: #{create_params}" + cloud_account = DevopsCloudAccount.new(create_params) + cloud_account.user = current_user + cloud_account.save + # 2. 生成oauth2应用程序的client_id和client_secrete + gitea_oauth = Gitea::Oauth2::CreateService.call(current_user.gitea_token, {name: "pipeline", redirect_uris: [cloud_account.drone_url]}) + 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: devops_params[:project_id]) + oauth.save + + rpc_secret = SecureRandom.hex 16 + logger.info "######### rpc_secret: #{rpc_secret}" + # 3. 创建drone server + drone_server_cmd = DevOps::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 = DevOps::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 = DevOps::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 = "#{Gitea.gitea_config[:domain]}/login/oauth/authorize?client_id=#{oauth.client_id}&redirect_uri=#{cloud_account.drone_url}/login&response_type=code" + if result + render_ok(redirect_url: redirect_url) + else + render_error('激活失败') + end + end + rescue Exception => ex + render_error(ex.message) + end + + private + def devops_params + params.permit(:account, :secret, :ip_num, :project_id) + end + + def find_project + @project = Project.find_by_id params[:project_id] + render_not_found("未找到project_id为:#{params[:project_id]}相关的项目") if @project.blank? + end +end diff --git a/app/forms/dev_ops/create_cloud_account_form.rb b/app/forms/dev_ops/create_cloud_account_form.rb new file mode 100644 index 000000000..2d1752b74 --- /dev/null +++ b/app/forms/dev_ops/create_cloud_account_form.rb @@ -0,0 +1,9 @@ +class DevOps::CreateCloudAccountForm + include ActiveModel::Model + + attr_accessor :project_id, :ip_num, :account, :secret + + validates :project_id, :account, :secret, presence: true + validates :ip_num, presence: true, format: { with: CustomRegexp::IP, multiline: true, message: 'IP 地址格式不对' } + +end diff --git a/app/helpers/dev_ops_helper.rb b/app/helpers/dev_ops_helper.rb new file mode 100644 index 000000000..cb4d7e9b8 --- /dev/null +++ b/app/helpers/dev_ops_helper.rb @@ -0,0 +1,2 @@ +module DevOpsHelper +end diff --git a/app/libs/custom_regexp.rb b/app/libs/custom_regexp.rb index 497c26cae..4aa6fac2d 100644 --- a/app/libs/custom_regexp.rb +++ b/app/libs/custom_regexp.rb @@ -5,4 +5,5 @@ module CustomRegexp NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/ PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/ URL = /\Ahttps?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]\z/ -end \ No newline at end of file + IP = /^((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/ +end diff --git a/app/libs/dev_ops/drone/ci.rb b/app/libs/dev_ops/drone/ci.rb new file mode 100644 index 000000000..62bdd69ac --- /dev/null +++ b/app/libs/dev_ops/drone/ci.rb @@ -0,0 +1,23 @@ +class DevOps::Drone::Ci + attr_reader :host, :username, :password + + # host: drone server's ip + # username: drone server's account + # password: drone server's password + # eq: + # DevOps::Drone::Ci.new(@cloud_account.drone_ip, @cloud_account.account, @cloud_account.visible_secret).get_token + def initialize(host, username, password) + @host = host + @username = username + @password = password + end + + def get_token + `sshpass -p #{password} ssh -o "StrictHostKeyChecking no" #{username}@#{host} "#{cmd}"` + end + + private + def cmd + "cd ..; cd var/lib/drone/; sqlite3 database.sqlite; .dump; select user_hash from users where user_login=#{username} " + end +end diff --git a/app/libs/dev_ops/drone/client.rb b/app/libs/dev_ops/drone/client.rb new file mode 100644 index 000000000..8e479b09c --- /dev/null +++ b/app/libs/dev_ops/drone/client.rb @@ -0,0 +1,39 @@ +class DevOps::Drone::Client + attr_reader :client_id, :drone_ip, :rpc_secret + + # client_id: user's client_id from oauth + # drone_ip: 云服务器IP地址, eq: 173.65.32.21 + # eq: + # DevOps::Drone::Client.new(current_user.oauth.client_id, 'drone_ip').generate_cmd + def initialize(client_id, drone_ip, rpc_secret) + @client_id = client_id + @drone_ip = drone_ip + @rpc_secret = rpc_secret + end + + def run + `docker run -d \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e DRONE_RPC_SERVER=drone-server-#{client_id}:9000 \ + -e DRONE_RPC_SECRET=#{rpc_secret} \ + -e DRONE_RUNNER_NAME=#{drone_ip} \ + --restart always \ + --name drone-agent--#{client_id} \ + --net="bridge" \ + drone/drone-runner-docker:1 + ` + end + + def generate_cmd + "docker run -d \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e DRONE_RPC_SERVER=drone-server-#{client_id}:9000 \ + -e DRONE_RPC_SECRET=#{rpc_secret} \ + -e DRONE_RUNNER_NAME=#{drone_ip} \ + --restart always \ + --name drone-agent--#{client_id} \ + --net='bridge' \ + drone/drone-runner-docker:1 + " + end +end diff --git a/app/libs/dev_ops/drone/server.rb b/app/libs/dev_ops/drone/server.rb new file mode 100644 index 000000000..22e864c69 --- /dev/null +++ b/app/libs/dev_ops/drone/server.rb @@ -0,0 +1,59 @@ +class DevOps::Drone::Server + attr_reader :client_id, :client_secret, :drone_host, :rpc_secret + + # client_id: user's client_id from oauth + # client_secret: user's client_id from oauth + # drone_host: 云服务器地址,eq: 173.53.21.31:80 + # eg: + # DevOps::Drone::Server.new(current_user.oauth.client_id, current_user.oauth.client_secret, 'drone_host').generate_cmd + def initialize(client_id, client_secret, drone_host, rpc_secret) + @client_id = client_id + @drone_host = drone_host + @rpc_secret = rpc_secret + @client_secret = client_secret + end + + def run + ` + docker run \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e DRONE_GITEA_SERVER=#{gitea_url} \ + -e DRONE_GITEA_CLIENT_ID=#{client_id} \ + -e DRONE_GITEA_CLIENT_SECRET=#{client_secret} \ + -e DRONE_RPC_SECRET=#{rpc_secret} \ + -e DRONE_SERVER_HOST=#{drone_host} \ + -e DRONE_SERVER_PROTO=http \ + -p "80:80" \ + -p "9000:9000" \ + --restart=always \ + --detach=true \ + --name=drone-server-#{client_id} \ + --net="bridge" \ + drone/drone:1 + ` + end + + def generate_cmd + "service docker start; docker run \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e DRONE_GITEA_SERVER=#{gitea_url} \ + -e DRONE_GITEA_CLIENT_ID=#{client_id} \ + -e DRONE_GITEA_CLIENT_SECRET=#{client_secret} \ + -e DRONE_RPC_SECRET=#{rpc_secret} \ + -e DRONE_SERVER_HOST=#{drone_host} \ + -e DRONE_SERVER_PROTO=http \ + -p '80:80' \ + -p '9000:9000' \ + --restart=always \ + --detach=true \ + --name=drone-server-#{client_id} \ + --net='bridge' \ + drone/drone:1 + " + end + + private + def gitea_url + Gitea.gitea_config[:domain] + end +end diff --git a/app/libs/dev_ops/drone/start.rb b/app/libs/dev_ops/drone/start.rb new file mode 100644 index 000000000..29c3324f6 --- /dev/null +++ b/app/libs/dev_ops/drone/start.rb @@ -0,0 +1,22 @@ +class DevOps::Drone::Start + attr_reader :drone_username, :drone_password, :drone_host, :drone_server_cmd, :drone_client_cmd + + # drone_username="XXXX" 云服务器登录用户名 + # drone_password="XXXXX" 云服务器用户密码 + # drone_host="" 云服务器地址 + # eq: + # drone_server_cmd = DevOps::Drone::Server.new('client_id', 'client_secret', 'drone_url').generate_cmd + # drone_client_cmd = DevOps::Drone::Client.new('client_id', 'server_url').generate_cmd + # DevOps::Drone::Start.new(drone_username, drone_password, 'drone_host', drone_server_cmd, drone_client_cmd).run + def initialize(drone_username, drone_password, drone_host, drone_server_cmd, drone_client_cmd) + @drone_username = drone_username + @drone_password = drone_password + @drone_host = drone_host + @drone_server_cmd = drone_server_cmd + @drone_client_cmd = drone_client_cmd + end + + def run + `sshpass -p #{drone_password} ssh -o "StrictHostKeyChecking no" #{drone_username}@#{drone_host} "#{drone_server_cmd} && #{drone_client_cmd}"` + end +end diff --git a/app/models/devops_cloud_account.rb b/app/models/devops_cloud_account.rb new file mode 100644 index 000000000..a77e3ff8a --- /dev/null +++ b/app/models/devops_cloud_account.rb @@ -0,0 +1,25 @@ +class DevopsCloudAccount < ApplicationRecord + belongs_to :project + belongs_to :user + + + def drone_host + [drone_ip, ":80"].join + end + + def drone_ip + IPAddr.new(self.ip_num, Socket::AF_INET).to_s + end + + def drone_url + ["http://", drone_host].join + end + + def visible_secret + Base64.decode64(secret) + end + + def self.encrypted_secret(str) + Base64.encode64(str.strip).gsub(/\n/, '') + end +end diff --git a/app/models/oauth.rb b/app/models/oauth.rb new file mode 100644 index 000000000..563ed0906 --- /dev/null +++ b/app/models/oauth.rb @@ -0,0 +1,5 @@ +# for oauth2 application +class Oauth < ApplicationRecord + belongs_to :project + belongs_to :user +end diff --git a/app/models/project.rb b/app/models/project.rb index b288c0cbd..8a07bc5a0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -161,7 +161,7 @@ class Project < ApplicationRecord member&.roles&.last&.name || permission end - def fork_project + def fork_project Project.find_by(id: self.forked_from_project_id) end diff --git a/app/services/gitea/client_service.rb b/app/services/gitea/client_service.rb index 584276378..7fd990c6f 100644 --- a/app/services/gitea/client_service.rb +++ b/app/services/gitea/client_service.rb @@ -21,9 +21,6 @@ class Gitea::ClientService < ApplicationService def post(url, params={}) puts "[gitea] request params: #{params}" request_url = [api_url, url].join('').freeze - Rails.logger.info("######_____api____request_url_______###############{request_url}") - Rails.logger.info("######_____api____request_params_______###############{params}") - auth_token = authen_params(params[:token]) response = conn(auth_token).post do |req| req.url "#{request_url}" diff --git a/app/services/gitea/oauth2/create_service.rb b/app/services/gitea/oauth2/create_service.rb new file mode 100644 index 000000000..ba87e960c --- /dev/null +++ b/app/services/gitea/oauth2/create_service.rb @@ -0,0 +1,41 @@ +# creates a new OAuth2 application +class Gitea::Oauth2::CreateService < Gitea::ClientService + attr_reader :token, :params + + # params: + # { + # "name": "string", + # "redirect_uris": [ + # "string" + # ] + # } + # ep: Gitea::OAuth2::CreateService.call(current_user.gitea_token, {name: 'oauth_name', redirect_uris: ['url']}) + # return values example: + # { + # "client_id": "string", + # "client_secret": "string", + # "created": "2020-07-08T03:12:49.960Z", + # "id": 0, + # "name": "string", + # "redirect_uris": [ + # "string" + # ] + # } + def initialize(token, params) + @token = token + @params = params + end + + def call + post(url, request_params) + end + + private + def url + "/user/applications/oauth2".freeze + end + + def request_params + params.merge(token: token, data: params).compact + end +end diff --git a/config/routes.rb b/config/routes.rb index ba9efd909..25626273e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - + require 'sidekiq/web' require 'admin_constraint' @@ -16,6 +16,10 @@ Rails.application.routes.draw do resources :edu_settings scope '/api' do + namespace :dev_ops do + resources :cloud_accounts, only: [:create] + end + resources :composes do resources :compose_projects, only: [:create, :destroy] end diff --git a/db/migrate/20200708114354_create_devops_cloud_accounts.rb b/db/migrate/20200708114354_create_devops_cloud_accounts.rb new file mode 100644 index 000000000..3607968f7 --- /dev/null +++ b/db/migrate/20200708114354_create_devops_cloud_accounts.rb @@ -0,0 +1,14 @@ +class CreateDevopsCloudAccounts < ActiveRecord::Migration[5.2] + def change + create_table :devops_cloud_accounts do |t| + t.integer :project_id, null: false + t.integer :user_id, null: false + t.integer :ip_num, null: false + t.string :account, null: false + t.string :secret, null: false + + t.timestamps + end + add_index :devops_cloud_accounts, [:project_id, :user_id, :ip_num] + end +end diff --git a/db/migrate/20200709061656_add_gitea_oauth_id_and_project_id_to_oauths.rb b/db/migrate/20200709061656_add_gitea_oauth_id_and_project_id_to_oauths.rb new file mode 100644 index 000000000..6208601b1 --- /dev/null +++ b/db/migrate/20200709061656_add_gitea_oauth_id_and_project_id_to_oauths.rb @@ -0,0 +1,6 @@ +class AddGiteaOauthIdAndProjectIdToOauths < ActiveRecord::Migration[5.2] + def change + add_column :oauths, :gitea_oauth_id, :integer + add_column :oauths, :project_id, :integer + end +end