Commit d4fa672c authored by Shinya Maeda's avatar Shinya Maeda

Create Kubernetes cluster on GKE from k8s service

parent e2b195b2
module GoogleApi
class AuthorizationsController < ApplicationController
# callback_google_api_authorizations GET|POST /google_api/authorizations/callback(.:format) google_api/authorizations#callback
##
# TODO:
# - Is it ok to use both "http://localhost:3000/google_api/authorizations/callback"(For login) and "http://localhost:3000/google_api/authorizations/callback"(For API token)
def callback
session[access_token_key] = api_client.get_token(params[:code])
if params[:state]
redirect_to params[:state]
else
redirect_to root_url
end
end
def api_client
@api_client ||=
GoogleApi::Authentication.new(nil, callback_google_api_authorizations_url)
end
def access_token_key
# :"#{api_client.scope}_access_token"
:"hoge_access_token" # TODO:
end
end
end
class Projects::ClustersController < Projects::ApplicationController
# before_action :authenticate_google_api
before_action :cluster
# before_action :authorize_admin_clusters! # TODO: Authentication
def index
if cluster
redirect_to action: 'edit'
else
redirect_to action: 'new'
end
end
##
# TODO:
# - Show form for "Create on Google Container Engine"
# - Show form for "Use existing kubernets cluster"
# - If user has not authroized yet, Show "Sign in with Google" button
# - If user has already authroized, Skip "Sign in with Google" button
# - user.is_authenticated_for_gcp?
# - user.authenticate_for_gcp!
# - Create this module which can be used from view
def new
unless session[access_token_key]
@authorize_url = api_client.authorize_url
end
end
##
# TODO:
# - If create on GKE, Use Google::Apis::ContainerV1::ContainerService
# - If create manually, save in db (Prob, Project > Setting)
# - Dry up with Service
def create
redirect_to action: 'index'
end
# TODO: Show results/status. Edits Swtich for enable/disable.
# If created with GKE, non-editable form. enable/disable switch.
# If created manually, editable form. enable/disable switch.
# GKE params are on-off swtich
# Manul params are on-off swtich, Endpoint, CACert, k8s Token, Proj namespace.
def edit
unless session[access_token_key]
@authorize_url = api_client.authorize_url
end
end
def update
cluster.update(schedule_params)
render :edit
end
# In presenter
# TODO: Generate a link to the cluster on GKE
def gcp_projects
# api_client.blah
# TODO: Return all avaiable GCP Projects.
# TODO: Return json
# TODO: Dry with concern
end
def gke_zones
# api_client.blah
# TODO: Return all avaiable zones on GKE.
# TODO: Return json
# TODO: Dry with concern
end
private
# def authenticate_google_api
# if cluster&.on_gke? && session[access_token_key].blank?
# redirect_to api_client.authorize_url(callback_import_url)
# end
# end
def cluster
# Each project has only one cluster, for now. In the future iteraiton, we'll support multiple clusters
@cluster ||= project.clusters.first
end
def cluster_params
params.require(:cluster).permit(:aaa)
end
def api_client
@api_client ||=
GoogleApi::CloudPlatform::Client.new(
session[access_token_key],
callback_google_api_authorizations_url,
state: namespace_project_clusters_url.to_s
)
end
def access_token_key
# :"#{api_client.scope}_access_token"
:"hoge_access_token" # TODO:
end
end
module Ci
class Cluster < ActiveRecord::Base
extend Gitlab::Ci::Model
belongs_to :project
belongs_to :owner, class_name: 'User'
enum creation_type: {
unknown: nil,
on_gke: 1,
manual: 2
}
end
end
...@@ -171,6 +171,7 @@ class Project < ActiveRecord::Base ...@@ -171,6 +171,7 @@ 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
......
edit/show cluster
= @cluster.inspect
Create a new cluster
%br
- if @authorize_url
I have not authenticated yet. I can authenticate from
= link_to("authenticate from here", @authorize_url)
- else
I have already authenticated.
%br
Avaiable GCP project lists
%br
Avaiable zones
%br
= link_to "Create on Google Container Engine", namespace_project_clusters_path(@project.namespace, @project, param1: 'value1', param2: 'value2'), method: :post
= link_to "Use existing kubernets cluster", namespace_project_clusters_path(@project.namespace, @project, param1: 'value1', param2: 'value2'), method: :post
...@@ -87,6 +87,7 @@ Rails.application.routes.draw do ...@@ -87,6 +87,7 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update] resources :issues, module: :boards, only: [:index, :update]
end end
draw :google_api
draw :import draw :import
draw :uploads draw :uploads
draw :explore draw :explore
......
namespace :google_api do
resource :authorizations, only: [], controller: :authorizations do
match :callback, via: [:get, :post]
end
end
...@@ -183,6 +183,13 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -183,6 +183,13 @@ constraints(ProjectUrlConstrainer.new) do
end end
end end
resources :clusters, except: [:show, :destroy] do
collection do
get :gcp_projects # TODO: This doesn't belong here. Grape or under user. Hint. Serilizer
get :gke_zones
end
end
resources :environments, except: [:destroy] do resources :environments, except: [:destroy] do
member do member do
post :stop post :stop
......
class CreateCiClusters < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :ci_clusters do |t|
t.integer :project_id
t.integer :owner_id
t.datetime_with_timezone :created_at, null: false
t.datetime_with_timezone :updated_at, null: false
t.boolean :enabled, default: true
t.string :end_point
t.text :ca_cert # Base64?
t.string :token
t.string :username
t.string :password
t.string :project_namespace
t.integer :creation_type # manual or on_gke
end
# TODO: fk, index, encypt
add_foreign_key :ci_clusters, :projects
add_foreign_key :ci_clusters, :users, column: :owner_id
end
def down
drop_table :ci_clusters
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: 20170921115009) do ActiveRecord::Schema.define(version: 20170924094327) 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"
...@@ -32,8 +32,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -32,8 +32,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.text "description", null: false t.text "description", null: false
t.string "header_logo" t.string "header_logo"
t.string "logo" t.string "logo"
t.datetime_with_timezone "created_at", null: false t.datetime "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime "updated_at", null: false
t.text "description_html" t.text "description_html"
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
end end
...@@ -101,10 +101,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -101,10 +101,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.text "help_page_text_html" t.text "help_page_text_html"
t.text "shared_runners_text_html" t.text "shared_runners_text_html"
t.text "after_sign_up_text_html" t.text "after_sign_up_text_html"
t.integer "rsa_key_restriction", default: 0, null: false
t.integer "dsa_key_restriction", default: 0, null: false
t.integer "ecdsa_key_restriction", default: 0, null: false
t.integer "ed25519_key_restriction", default: 0, null: false
t.boolean "housekeeping_enabled", default: true, null: false t.boolean "housekeeping_enabled", default: true, null: false
t.boolean "housekeeping_bitmaps_enabled", default: true, null: false t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
t.integer "housekeeping_incremental_repack_period", default: 10, null: false t.integer "housekeeping_incremental_repack_period", default: 10, null: false
...@@ -132,6 +128,10 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -132,6 +128,10 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.boolean "password_authentication_enabled" t.boolean "password_authentication_enabled"
t.integer "performance_bar_allowed_group_id" t.integer "performance_bar_allowed_group_id"
t.boolean "hashed_storage_enabled", default: false, null: false t.boolean "hashed_storage_enabled", default: false, null: false
t.integer "rsa_key_restriction", default: 0, null: false
t.integer "dsa_key_restriction", default: 0, null: false
t.integer "ecdsa_key_restriction", default: 0, null: false
t.integer "ed25519_key_restriction", default: 0, null: false
t.boolean "project_export_enabled", default: true, null: false t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: false, null: false t.boolean "auto_devops_enabled", default: false, null: false
end end
...@@ -256,7 +256,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -256,7 +256,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["id"], name: "index_for_ci_builds_retried_migration", where: "(retried IS NULL)", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
...@@ -267,6 +266,21 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -267,6 +266,21 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
create_table "ci_clusters", force: :cascade do |t|
t.integer "project_id"
t.integer "owner_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "enabled", default: true
t.string "end_point"
t.text "ca_cert"
t.string "token"
t.string "username"
t.string "password"
t.string "project_namespace"
t.integer "creation_type"
end
create_table "ci_group_variables", force: :cascade do |t| create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
...@@ -275,8 +289,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -275,8 +289,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.string "encrypted_value_iv" t.string "encrypted_value_iv"
t.integer "group_id", null: false t.integer "group_id", null: false
t.boolean "protected", default: false, null: false t.boolean "protected", default: false, null: false
t.datetime_with_timezone "created_at", null: false t.datetime "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime "updated_at", null: false
end end
add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
...@@ -288,8 +302,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -288,8 +302,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.string "encrypted_value_salt" t.string "encrypted_value_salt"
t.string "encrypted_value_iv" t.string "encrypted_value_iv"
t.integer "pipeline_schedule_id", null: false t.integer "pipeline_schedule_id", null: false
t.datetime_with_timezone "created_at" t.datetime "created_at"
t.datetime_with_timezone "updated_at" t.datetime "updated_at"
end end
add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
...@@ -341,12 +355,14 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -341,12 +355,14 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.integer "auto_canceled_by_id" t.integer "auto_canceled_by_id"
t.integer "pipeline_schedule_id" t.integer "pipeline_schedule_id"
t.integer "source" t.integer "source"
t.integer "config_source"
t.boolean "protected" t.boolean "protected"
t.integer "iid"
t.integer "config_source"
end end
add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
add_index "ci_pipelines", ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree add_index "ci_pipelines", ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
add_index "ci_pipelines", ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, using: :btree
add_index "ci_pipelines", ["project_id", "ref", "status"], name: "index_ci_pipelines_on_project_id_and_ref_and_status", using: :btree add_index "ci_pipelines", ["project_id", "ref", "status"], name: "index_ci_pipelines_on_project_id_and_ref_and_status", using: :btree
add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree
...@@ -538,8 +554,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -538,8 +554,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.integer "project_id" t.integer "project_id"
t.integer "author_id", null: false t.integer "author_id", null: false
t.integer "target_id" t.integer "target_id"
t.datetime_with_timezone "created_at", null: false t.datetime "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime "updated_at", null: false
t.integer "action", limit: 2, null: false t.integer "action", limit: 2, null: false
t.string "target_type" t.string "target_type"
end end
...@@ -577,8 +593,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -577,8 +593,8 @@ ActiveRecord::Schema.define(version: 20170921115009) 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 "gpg_keys", force: :cascade do |t| create_table "gpg_keys", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime "updated_at", null: false
t.integer "user_id" t.integer "user_id"
t.binary "primary_keyid" t.binary "primary_keyid"
t.binary "fingerprint" t.binary "fingerprint"
...@@ -590,8 +606,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -590,8 +606,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "gpg_keys", ["user_id"], name: "index_gpg_keys_on_user_id", using: :btree add_index "gpg_keys", ["user_id"], name: "index_gpg_keys_on_user_id", using: :btree
create_table "gpg_signatures", force: :cascade do |t| create_table "gpg_signatures", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime "updated_at", null: false
t.integer "project_id" t.integer "project_id"
t.integer "gpg_key_id" t.integer "gpg_key_id"
t.binary "commit_sha" t.binary "commit_sha"
...@@ -789,8 +805,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -789,8 +805,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
create_table "merge_request_diff_commits", id: false, force: :cascade do |t| create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
t.datetime_with_timezone "authored_date" t.datetime "authored_date"
t.datetime_with_timezone "committed_date" t.datetime "committed_date"
t.integer "merge_request_diff_id", null: false t.integer "merge_request_diff_id", null: false
t.integer "relative_order", null: false t.integer "relative_order", null: false
t.binary "sha", null: false t.binary "sha", null: false
...@@ -1113,8 +1129,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -1113,8 +1129,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
create_table "project_auto_devops", force: :cascade do |t| create_table "project_auto_devops", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.datetime_with_timezone "created_at", null: false t.datetime "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "enabled" t.boolean "enabled"
t.string "domain" t.string "domain"
end end
...@@ -1204,7 +1220,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -1204,7 +1220,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.string "repository_storage", default: "default", null: false t.string "repository_storage", default: "default", null: false
t.boolean "request_access_enabled", default: false, null: false t.boolean "request_access_enabled", default: false, null: false
t.boolean "has_external_wiki" t.boolean "has_external_wiki"
t.string "ci_config_path"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
t.boolean "only_allow_merge_if_all_discussions_are_resolved" t.boolean "only_allow_merge_if_all_discussions_are_resolved"
...@@ -1212,8 +1227,9 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -1212,8 +1227,9 @@ ActiveRecord::Schema.define(version: 20170921115009) do
t.integer "auto_cancel_pending_pipelines", default: 1, null: false t.integer "auto_cancel_pending_pipelines", default: 1, null: false
t.string "import_jid" t.string "import_jid"
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
t.text "delete_error"
t.datetime "last_repository_updated_at" t.datetime "last_repository_updated_at"
t.string "ci_config_path"
t.text "delete_error"
t.integer "storage_version", limit: 2 t.integer "storage_version", limit: 2
t.boolean "resolve_outdated_diff_discussions" t.boolean "resolve_outdated_diff_discussions"
end end
...@@ -1685,6 +1701,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do ...@@ -1685,6 +1701,8 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_clusters", "projects"
add_foreign_key "ci_clusters", "users", column: "owner_id"
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
......
module GoogleApi
class Authentication
attr_reader :access_token, :redirect_uri, :state
def initialize(access_token, redirect_uri, state: nil)
@access_token = access_token
@redirect_uri = redirect_uri
@state = state
end
def client
return @client if defined?(@client)
unless config
raise 'OAuth configuration for google_oauth2 missing.'
end
@client = ::OAuth2::Client.new(
config.app_id,
config.app_secret,
site: 'https://accounts.google.com',
token_url: '/o/oauth2/token',
authorize_url: '/o/oauth2/auth'
)
end
def authorize_url
client.auth_code.authorize_url(
redirect_uri: redirect_uri,
scope: scope,
state: state # This is used for arbitary redirection
)
end
def get_token(code)
client.auth_code.get_token(code, redirect_uri: redirect_uri).token
end
protected
def scope
raise NotImplementedError
end
private
def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "google_oauth2" }
end
end
end
module GoogleApi
module CloudPlatform
class Client < GoogleApi::Authentication
# Google::Apis::ContainerV1::ContainerService.new
def scope
'https://www.googleapis.com/auth/cloud-platform'
end
def projects_zones_clusters_get
# TODO:
# service = Google::Apis::ContainerV1::ContainerService.new
# service.authorization = access_token
# project_id = params['project_id']
# ...
# response = service.list_zone_clusters(project_id, zone)
response
end
def projects_zones_clusters_create
# TODO
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