Commit 807bc4cc authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch...

Merge branch 'feature/sm/35954-create-kubernetes-cluster-on-gke-from-k8s-service' of https://gitlab.com/gitlab-org/gitlab-ce into feature/sm/35954-create-kubernetes-cluster-on-gke-from-k8s-service

* 'feature/sm/35954-create-kubernetes-cluster-on-gke-from-k8s-service' of https://gitlab.com/gitlab-org/gitlab-ce:
  PollingInterval, rename to gke_clusters, has_one :cluster
  Use expires_in for access_token validation
parents cddab09b 34e66c42
module GoogleApi module GoogleApi
class AuthorizationsController < ApplicationController class AuthorizationsController < ApplicationController
def callback def callback
session[GoogleApi::CloudPlatform::Client.session_key_for_token] = token, expires_at = GoogleApi::CloudPlatform::Client
GoogleApi::CloudPlatform::Client.new(nil, callback_google_api_authorizations_url) .new(nil, callback_google_api_authorizations_url)
.get_token(params[:code]) .get_token(params[:code])
session[GoogleApi::CloudPlatform::Client.session_key_for_token] = token
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
expires_at.to_s
if params[:state] if params[:state]
redirect_to params[:state] redirect_to params[:state]
......
...@@ -6,25 +6,24 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -6,25 +6,24 @@ class Projects::ClustersController < Projects::ApplicationController
def login def login
begin begin
@authorize_url = GoogleApi::CloudPlatform::Client.new( @authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, nil, callback_google_api_authorizations_url,
callback_google_api_authorizations_url,
state: namespace_project_clusters_url.to_s state: namespace_project_clusters_url.to_s
).authorize_url ).authorize_url
rescue GoogleApi::Auth::ConfigMissingError rescue GoogleApi::Auth::ConfigMissingError
# Show an alert message that gitlab.yml is not configured properly # no-op
end end
end end
def index def index
if project.clusters.any? if project.cluster
redirect_to edit_project_cluster_path(project, project.clusters.last.id) redirect_to edit_project_cluster_path(project, project.cluster)
else else
redirect_to new_project_cluster_path(project) redirect_to new_project_cluster_path(project)
end end
end end
def new def new
@cluster = project.clusters.new @cluster = project.build_cluster
end end
def create def create
...@@ -43,6 +42,8 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -43,6 +42,8 @@ class Projects::ClustersController < Projects::ApplicationController
def status def status
respond_to do |format| respond_to do |format|
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: { render json: {
status: cluster.status, # The current status of the operation. status: cluster.status, # The current status of the operation.
status_reason: cluster.status_reason # If an error has occurred, a textual description of the error. status_reason: cluster.status_reason # If an error has occurred, a textual description of the error.
...@@ -73,7 +74,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -73,7 +74,7 @@ class Projects::ClustersController < Projects::ApplicationController
private private
def cluster def cluster
@cluster ||= project.clusters.find(params[:id]) @cluster ||= project.cluster
end end
def cluster_params def cluster_params
...@@ -83,12 +84,19 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -83,12 +84,19 @@ class Projects::ClustersController < Projects::ApplicationController
end end
def authorize_google_api def authorize_google_api
unless token_in_session unless GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
.validate_token(expires_at_in_session)
redirect_to action: 'login' redirect_to action: 'login'
end end
end end
def token_in_session def token_in_session
@token_in_session ||= session[GoogleApi::CloudPlatform::Client.session_key_for_token] @token_in_session ||=
session[GoogleApi::CloudPlatform::Client.session_key_for_token]
end
def expires_at_in_session
@expires_at_in_session ||=
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
end end
end end
module Ci module Gcp
class Cluster < ActiveRecord::Base class Cluster < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Gcp::Model
belongs_to :project belongs_to :project, inverse_of: :cluster
belongs_to :user belongs_to :user
belongs_to :service belongs_to :service
attr_encrypted :password, attr_encrypted :password,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv,
insecure_mode: true, insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
attr_encrypted :kubernetes_token, attr_encrypted :kubernetes_token,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv,
insecure_mode: true, insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
attr_encrypted :gcp_token, attr_encrypted :gcp_token,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv,
insecure_mode: true, insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
...@@ -54,6 +54,21 @@ module Ci ...@@ -54,6 +54,21 @@ module Ci
save!(validate: false) save!(validate: false)
end end
def created!(endpoint, ca_cert, kubernetes_token, username, password)
self.status = :created
self.enabled = true
self.endpoint = endpoint
self.ca_cert = ca_cert
self.kubernetes_token = kubernetes_token
self.username = username
self.password = password
self.service = project.find_or_initialize_service('kubernetes')
self.gcp_token = nil
self.gcp_operation_id = nil
save!
end
def on_creation? def on_creation?
scheduled? || creating? scheduled? || creating?
end end
......
...@@ -163,6 +163,7 @@ class Project < ActiveRecord::Base ...@@ -163,6 +163,7 @@ class Project < ActiveRecord::Base
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
has_one :project_feature, inverse_of: :project has_one :project_feature, inverse_of: :project
has_one :statistics, class_name: 'ProjectStatistics' has_one :statistics, class_name: 'ProjectStatistics'
has_one :cluster, class_name: 'Gcp::Cluster', inverse_of: :project
# Container repositories need to remove data from the container registry, # Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy # which is not managed by the DB. Hence we're still using dependent: :destroy
...@@ -171,7 +172,6 @@ class Project < ActiveRecord::Base ...@@ -171,7 +172,6 @@ class Project < ActiveRecord::Base
has_many :commit_statuses has_many :commit_statuses
has_many :pipelines, class_name: 'Ci::Pipeline' has_many :pipelines, class_name: 'Ci::Pipeline'
has_many :clusters, class_name: 'Ci::Cluster'
# Ci::Build objects store data on the file system such as artifact files and # Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in # build traces. Currently there's no efficient way of removing this data in
......
...@@ -5,9 +5,9 @@ module Ci ...@@ -5,9 +5,9 @@ module Ci
params['machine_type'] = GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE params['machine_type'] = GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE
end end
project.clusters.create( project.create_cluster(
params.merge(user: current_user, params.merge(user: current_user,
status: Ci::Cluster.statuses[:scheduled], status: Gcp::Cluster.statuses[:scheduled],
gcp_token: access_token)) gcp_token: access_token))
end end
end end
......
module Ci module Ci
class IntegrateClusterService class IntegrateClusterService
def execute(cluster, endpoint, ca_cert, token, username, password) def execute(cluster, endpoint, ca_cert, token, username, password)
Ci::Cluster.transaction do Gcp::Cluster.transaction do
kubernetes_service ||= cluster.created!(endpoint, ca_cert, token, username, password)
cluster.project.find_or_initialize_service('kubernetes')
cluster.update!( cluster.service.update!(
enabled: true,
service: kubernetes_service,
username: username,
password: password,
kubernetes_token: token,
ca_cert: ca_cert,
endpoint: endpoint,
gcp_token: nil,
gcp_operation_id: nil,
status: Ci::Cluster.statuses[:created])
kubernetes_service.update!(
active: true, active: true,
api_url: cluster.api_url, api_url: cluster.api_url,
ca_pem: ca_cert, ca_pem: ca_cert,
......
module Ci module Ci
class UpdateClusterService < BaseService class UpdateClusterService < BaseService
def execute(cluster) def execute(cluster)
Ci::Cluster.transaction do Gcp::Cluster.transaction do
cluster.update!(enabled: params['enabled']) cluster.update!(enabled: params['enabled'])
if params['enabled'] == 'true' if params['enabled'] == 'true'
...@@ -12,7 +12,7 @@ module Ci ...@@ -12,7 +12,7 @@ module Ci
namespace: cluster.project_namespace, namespace: cluster.project_namespace,
token: cluster.kubernetes_token) token: cluster.kubernetes_token)
else else
cluster.service.update(active: false) cluster.service.update!(active: false)
end end
end end
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
......
...@@ -3,7 +3,7 @@ class ClusterCreationWorker ...@@ -3,7 +3,7 @@ class ClusterCreationWorker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
def perform(cluster_id) def perform(cluster_id)
cluster = Ci::Cluster.find_by_id(cluster_id) cluster = Gcp::Cluster.find_by_id(cluster_id)
unless cluster unless cluster
return Rails.logger.error "Cluster object is not found; #{cluster_id}" return Rails.logger.error "Cluster object is not found; #{cluster_id}"
......
...@@ -7,7 +7,7 @@ class WaitForClusterCreationWorker ...@@ -7,7 +7,7 @@ class WaitForClusterCreationWorker
TIMEOUT = 20.minutes TIMEOUT = 20.minutes
def perform(cluster_id) def perform(cluster_id)
cluster = Ci::Cluster.find_by_id(cluster_id) cluster = Gcp::Cluster.find_by_id(cluster_id)
unless cluster unless cluster
return Rails.logger.error "Cluster object is not found; #{cluster_id}" return Rails.logger.error "Cluster object is not found; #{cluster_id}"
...@@ -56,7 +56,7 @@ class WaitForClusterCreationWorker ...@@ -56,7 +56,7 @@ class WaitForClusterCreationWorker
username = gke_cluster.master_auth.username username = gke_cluster.master_auth.username
password = gke_cluster.master_auth.password password = gke_cluster.master_auth.password
rescue Exception => e rescue Exception => e
return cluster.errored!("Can not extract the extected data; #{e}") return cluster.errored!("Can not extract the expected data; #{e}")
end end
kubernetes_token = Ci::FetchKubernetesTokenService.new( kubernetes_token = Ci::FetchKubernetesTokenService.new(
......
class CreateCiClusters < ActiveRecord::Migration class CreateGcpClusters < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
def up def change
create_table :ci_clusters do |t| create_table :gcp_clusters do |t|
t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.references :user, null: false, foreign_key: true t.references :user, null: false, foreign_key: true
t.references :service, foreign_key: true t.references :service, foreign_key: true
...@@ -41,8 +41,4 @@ class CreateCiClusters < ActiveRecord::Migration ...@@ -41,8 +41,4 @@ class CreateCiClusters < ActiveRecord::Migration
t.datetime_with_timezone :updated_at, null: false t.datetime_with_timezone :updated_at, null: false
end end
end end
def down
drop_table :ci_clusters
end
end end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170924094327) do ActiveRecord::Schema.define(version: 20170928100231) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -608,6 +608,38 @@ ActiveRecord::Schema.define(version: 20170924094327) do ...@@ -608,6 +608,38 @@ ActiveRecord::Schema.define(version: 20170924094327) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
create_table "gcp_clusters", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
t.integer "service_id"
t.boolean "enabled", default: true
t.integer "status"
t.string "status_reason"
t.string "project_namespace"
t.string "endpoint"
t.text "ca_cert"
t.string "encrypted_kubernetes_token"
t.string "encrypted_kubernetes_token_salt"
t.string "encrypted_kubernetes_token_iv"
t.string "username"
t.string "encrypted_password"
t.string "encrypted_password_salt"
t.string "encrypted_password_iv"
t.string "gcp_project_id", null: false
t.string "cluster_zone", null: false
t.string "cluster_name", null: false
t.integer "cluster_size", null: false
t.string "machine_type"
t.string "gcp_operation_id"
t.string "encrypted_gcp_token"
t.string "encrypted_gcp_token_salt"
t.string "encrypted_gcp_token_iv"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "gcp_clusters", ["project_id"], name: "index_gcp_clusters_on_project_id", unique: true, using: :btree
create_table "gpg_keys", force: :cascade do |t| create_table "gpg_keys", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
...@@ -1742,6 +1774,9 @@ ActiveRecord::Schema.define(version: 20170924094327) do ...@@ -1742,6 +1774,9 @@ ActiveRecord::Schema.define(version: 20170924094327) do
add_foreign_key "events", "projects", on_delete: :cascade add_foreign_key "events", "projects", on_delete: :cascade
add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "gcp_clusters", "projects", on_delete: :cascade
add_foreign_key "gcp_clusters", "services"
add_foreign_key "gcp_clusters", "users"
add_foreign_key "gpg_keys", "users", on_delete: :cascade add_foreign_key "gpg_keys", "users", on_delete: :cascade
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
......
module Gitlab
module Gcp
module Model
def table_name_prefix
"gcp_"
end
def model_name
@model_name ||= ActiveModel::Name.new(self, nil, self.name.split("::").last)
end
end
end
end
...@@ -19,7 +19,8 @@ module GoogleApi ...@@ -19,7 +19,8 @@ module GoogleApi
end end
def get_token(code) def get_token(code)
client.auth_code.get_token(code, redirect_uri: redirect_uri).token ret = client.auth_code.get_token(code, redirect_uri: redirect_uri)
return ret.token, ret.expires_at
end end
protected protected
......
...@@ -9,12 +9,28 @@ module GoogleApi ...@@ -9,12 +9,28 @@ module GoogleApi
def session_key_for_token def session_key_for_token
:cloud_platform_access_token :cloud_platform_access_token
end end
def session_key_for_expires_at
:cloud_platform_expires_at
end
end end
def scope def scope
'https://www.googleapis.com/auth/cloud-platform' 'https://www.googleapis.com/auth/cloud-platform'
end end
def validate_token(expires_at)
return false unless access_token
return false unless expires_at
# Making sure that the token will have been still alive during the cluster creation.
unless DateTime.strptime(expires_at, '%s').to_time > Time.now + 10.minutes
return false
end
true
end
def projects_zones_clusters_get(project_id, zone, cluster_id) def projects_zones_clusters_get(project_id, zone, cluster_id)
service = Google::Apis::ContainerV1::ContainerService.new service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token service.authorization = access_token
...@@ -70,7 +86,7 @@ module GoogleApi ...@@ -70,7 +86,7 @@ module GoogleApi
end end
def parse_operation_id(self_link) def parse_operation_id(self_link)
self_link.match(/projects\/.*\/zones\/.*\/operations\/(.*)/)[1] self_link.match(%r{projects/.*/zones/.*/operations/(.*)})[1]
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment