From 8a1ac8f4ce0d8e96234ef32cd032adaf7cc57b1a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 May 2018 16:29:55 +0300 Subject: Add Applications::Jupyter class sceleton Signed-off-by: Dmitriy Zaporozhets --- app/models/clusters/applications/jupyter.rb | 28 ++++++++++++++++++++++++++++ app/models/clusters/cluster.rb | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 app/models/clusters/applications/jupyter.rb diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb new file mode 100644 index 00000000000..ec75c120dac --- /dev/null +++ b/app/models/clusters/applications/jupyter.rb @@ -0,0 +1,28 @@ +module Clusters + module Applications + class Jupyter < ActiveRecord::Base + VERSION = '0.0.1'.freeze + + self.table_name = 'clusters_applications_jupyters' + + include ::Clusters::Concerns::ApplicationCore + include ::Clusters::Concerns::ApplicationStatus + include ::Clusters::Concerns::ApplicationData + + default_value_for :version, VERSION + + def chart + # TODO: publish jupyterhub charts that we can use for our installation + # and provide path to it here. + end + + def install_command + Gitlab::Kubernetes::Helm::InstallCommand.new( + name, + chart: chart, + values: values + ) + end + end + end +end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 77947d515c1..92e5da77066 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -8,7 +8,8 @@ module Clusters Applications::Helm.application_name => Applications::Helm, Applications::Ingress.application_name => Applications::Ingress, Applications::Prometheus.application_name => Applications::Prometheus, - Applications::Runner.application_name => Applications::Runner + Applications::Runner.application_name => Applications::Runner, + Applications::Jupyter.application_name => Applications::Jupyter }.freeze DEFAULT_ENVIRONMENT = '*'.freeze -- cgit v1.2.1 From 4220e914db356f4a55c771a7ad7f559e2507dd56 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 16 May 2018 12:01:13 +0300 Subject: Add support for Jupyter in GitLab via Kubernetes Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/clusters/clusters_bundle.js | 2 + .../clusters/components/applications.vue | 99 +++++++++++++++++++++- app/assets/javascripts/clusters/constants.js | 1 + .../clusters/services/clusters_service.js | 10 ++- .../javascripts/clusters/stores/clusters_store.js | 12 ++- app/assets/stylesheets/pages/clusters.scss | 2 +- .../projects/clusters/applications_controller.rb | 1 + app/models/clusters/applications/jupyter.rb | 28 +++++- app/models/clusters/cluster.rb | 4 +- app/serializers/cluster_application_entity.rb | 1 + .../clusters/applications/install_service.rb | 4 +- app/views/projects/clusters/show.html.haml | 1 + ...1131058_create_clusters_applications_jupyter.rb | 22 +++++ db/schema.rb | 60 +++++++------ spec/factories/clusters/applications/helm.rb | 1 + spec/fixtures/api/schemas/cluster_status.json | 3 +- spec/models/clusters/cluster_spec.rb | 3 +- vendor/jupyter/values.yaml | 16 ++++ 18 files changed, 234 insertions(+), 36 deletions(-) create mode 100644 db/migrate/20180511131058_create_clusters_applications_jupyter.rb create mode 100644 vendor/jupyter/values.yaml diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 01aec4f36af..6bf9dca1112 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -31,6 +31,7 @@ export default class Clusters { installHelmPath, installIngressPath, installRunnerPath, + installJupyterPath, installPrometheusPath, managePrometheusPath, clusterStatus, @@ -51,6 +52,7 @@ export default class Clusters { installIngressEndpoint: installIngressPath, installRunnerEndpoint: installRunnerPath, installPrometheusEndpoint: installPrometheusPath, + installJupyterEndpoint: installJupyterPath, }); this.installApplication = this.installApplication.bind(this); diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 9c12b89240c..e03db7b8974 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -37,6 +37,11 @@ export default { default: '', }, }, + data() { + return { + jupyterSuggestHostnameValue: '', + }; + }, computed: { generalApplicationDescription() { return sprintf( @@ -121,6 +126,20 @@ export default { false, ); }, + jupyterInstalled() { + return this.applications.jupyter.status === APPLICATION_INSTALLED; + }, + jupyterHostname() { + return this.applications.jupyter.hostname; + }, + jupyterSuggestHostname() { + return `jupyter.${this.applications.ingress.externalIp}.xip.io`; + }, + }, + watch: { + jupyterSuggestHostname() { + this.jupyterSuggestHostnameValue = this.jupyterSuggestHostname; + }, }, }; @@ -278,11 +297,89 @@ export default { applications to production.`) }} + +
+

+ {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns, + manages, and proxies multiple instances of the single-user + Jupyter notebook server. JupyterHub can be used to serve + notebooks to a class of students, a corporate data science group, + or a scientific research group.`) }} +

+ + +
+
- + diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index b7179f52bb3..371f71fde44 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -11,3 +11,4 @@ export const REQUEST_LOADING = 'request-loading'; export const REQUEST_SUCCESS = 'request-success'; export const REQUEST_FAILURE = 'request-failure'; export const INGRESS = 'ingress'; +export const JUPYTER = 'jupyter'; diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js index 13468578f4f..e49db9c2f4f 100644 --- a/app/assets/javascripts/clusters/services/clusters_service.js +++ b/app/assets/javascripts/clusters/services/clusters_service.js @@ -1,4 +1,5 @@ import axios from '../../lib/utils/axios_utils'; +import { JUPYTER } from '../constants'; export default class ClusterService { constructor(options = {}) { @@ -8,6 +9,7 @@ export default class ClusterService { ingress: this.options.installIngressEndpoint, runner: this.options.installRunnerEndpoint, prometheus: this.options.installPrometheusEndpoint, + jupyter: this.options.installJupyterEndpoint, }; } @@ -16,7 +18,13 @@ export default class ClusterService { } installApplication(appId) { - return axios.post(this.appInstallEndpointMap[appId]); + const data = {}; + + if (appId === JUPYTER) { + data.hostname = document.getElementById('jupyter-hostname').value; + } + + return axios.post(this.appInstallEndpointMap[appId], data); } static updateCluster(endpoint, data) { diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 348bbec3b25..f609b425190 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -1,5 +1,5 @@ import { s__ } from '../../locale'; -import { INGRESS } from '../constants'; +import { INGRESS, JUPYTER } from '../constants'; export default class ClusterStore { constructor() { @@ -38,6 +38,14 @@ export default class ClusterStore { requestStatus: null, requestReason: null, }, + jupyter: { + title: s__('ClusterIntegration|JupyterHub'), + status: null, + statusReason: null, + requestStatus: null, + requestReason: null, + hostname: null, + }, }, }; } @@ -83,6 +91,8 @@ export default class ClusterStore { if (appId === INGRESS) { this.state.applications.ingress.externalIp = serverAppEntry.external_ip; + } else if (appId === JUPYTER) { + this.state.applications.jupyter.hostname = serverAppEntry.hostname; } }); } diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 3fd13078131..cfcce91f514 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -6,7 +6,7 @@ .cluster-applications-table { // Wait for the Vue to kick-in and render the applications block - min-height: 400px; + min-height: 500px; } .clusters-dropdown-menu { diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb index 35885543622..9198a66b73d 100644 --- a/app/controllers/projects/clusters/applications_controller.rb +++ b/app/controllers/projects/clusters/applications_controller.rb @@ -6,6 +6,7 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll def create application = @application_class.find_or_create_by!(cluster: @cluster) + application.update(hostname: params[:hostname]) if application.respond_to?(:hostname) Clusters::Applications::ScheduleInstallationService.new(project, current_user).execute(application) diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index ec75c120dac..ef62be34abd 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -12,17 +12,39 @@ module Clusters default_value_for :version, VERSION def chart - # TODO: publish jupyterhub charts that we can use for our installation - # and provide path to it here. + "#{name}/jupyterhub" + end + + def repository + 'https://jupyterhub.github.io/helm-chart/' + end + + def values + content_values.to_yaml end def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name, chart: chart, - values: values + values: values, + repository: repository ) end + + private + + def specification + { + "ingress" => { "hosts" => [hostname] }, + "hub" => { "cookieSecret" => SecureRandom.hex(32) }, + "proxy" => { "secretToken" => SecureRandom.hex(32) } + } + end + + def content_values + YAML.load_file(chart_values_file).deep_merge!(specification) + end end end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 92e5da77066..d99f858e0c0 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -27,6 +27,7 @@ module Clusters has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus' has_one :application_runner, class_name: 'Clusters::Applications::Runner' + has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter' accepts_nested_attributes_for :provider_gcp, update_only: true accepts_nested_attributes_for :platform_kubernetes, update_only: true @@ -75,7 +76,8 @@ module Clusters application_helm || build_application_helm, application_ingress || build_application_ingress, application_prometheus || build_application_prometheus, - application_runner || build_application_runner + application_runner || build_application_runner, + application_jupyter || build_application_jupyter ] end diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb index b22a0b666ef..77fc3336521 100644 --- a/app/serializers/cluster_application_entity.rb +++ b/app/serializers/cluster_application_entity.rb @@ -3,4 +3,5 @@ class ClusterApplicationEntity < Grape::Entity expose :status_name, as: :status expose :status_reason expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) } + expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) } end diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb index 4c25a09814b..7ec3a9baa6e 100644 --- a/app/services/clusters/applications/install_service.rb +++ b/app/services/clusters/applications/install_service.rb @@ -12,8 +12,8 @@ module Clusters ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) rescue Kubeclient::HttpError => ke app.make_errored!("Kubernetes error: #{ke.message}") - rescue StandardError - app.make_errored!("Can't start installation process") + rescue StandardError => e + app.make_errored!("Can't start installation process. #{e.message}") end end end diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index 4c510293204..08d2deff6f8 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -11,6 +11,7 @@ install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress), install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus), install_runner_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :runner), + install_jupyter_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :jupyter), toggle_status: @cluster.enabled? ? 'true': 'false', cluster_status: @cluster.status_name, cluster_status_reason: @cluster.status_reason, diff --git a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb new file mode 100644 index 00000000000..5fd39f24d98 --- /dev/null +++ b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb @@ -0,0 +1,22 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateClustersApplicationsJupyter < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :clusters_applications_jupyters do |t| + t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade } + + t.integer :status, null: false + t.string :version, null: false + t.string :hostname + + t.text :status_reason + + t.timestamps_with_timezone null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 37d336b9928..3a57f9ecbd2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -135,16 +135,13 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.boolean "clientside_sentry_enabled", default: false, null: false t.string "clientside_sentry_dsn" t.boolean "prometheus_metrics_enabled", default: true, null: false + t.boolean "authorized_keys_enabled", default: true, null: false t.boolean "help_page_hide_commercial_content", default: false t.string "help_page_support_url" t.integer "performance_bar_allowed_group_id" t.boolean "hashed_storage_enabled", default: false, null: false t.boolean "project_export_enabled", default: true, null: false t.boolean "auto_devops_enabled", default: false, null: false - t.integer "circuitbreaker_failure_count_threshold", default: 3 - t.integer "circuitbreaker_failure_reset_time", default: 1800 - t.integer "circuitbreaker_storage_timeout", default: 15 - t.integer "circuitbreaker_access_retries", default: 3 t.boolean "throttle_unauthenticated_enabled", default: false, null: false t.integer "throttle_unauthenticated_requests_per_period", default: 3600, null: false t.integer "throttle_unauthenticated_period_in_seconds", default: 3600, null: false @@ -154,13 +151,16 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.boolean "throttle_authenticated_web_enabled", default: false, null: false t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false - t.integer "circuitbreaker_check_interval", default: 1, null: false - t.boolean "password_authentication_enabled_for_web" - t.boolean "password_authentication_enabled_for_git", default: true + t.integer "circuitbreaker_failure_count_threshold", default: 3 + t.integer "circuitbreaker_failure_reset_time", default: 1800 + t.integer "circuitbreaker_storage_timeout", default: 15 + t.integer "circuitbreaker_access_retries", default: 3 t.integer "gitaly_timeout_default", default: 55, null: false t.integer "gitaly_timeout_medium", default: 30, null: false t.integer "gitaly_timeout_fast", default: 10, null: false - t.boolean "authorized_keys_enabled", default: true, null: false + t.boolean "password_authentication_enabled_for_web" + t.boolean "password_authentication_enabled_for_git", default: true, null: false + t.integer "circuitbreaker_check_interval", default: 1, null: false t.string "auto_devops_domain" t.boolean "pages_domain_verification_enabled", default: true, null: false t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false @@ -375,12 +375,12 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.integer "project_id", null: false t.integer "job_id", null: false t.integer "file_type", null: false - t.integer "file_store" t.integer "size", limit: 8 t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" + t.integer "file_store" t.binary "file_sha256" end @@ -448,8 +448,8 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.integer "auto_canceled_by_id" t.integer "pipeline_schedule_id" t.integer "source" - t.integer "config_source" t.boolean "protected" + t.integer "config_source" t.integer "failure_reason" end @@ -495,8 +495,8 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.boolean "run_untagged", default: true, null: false t.boolean "locked", default: false, null: false t.integer "access_level", default: 0, null: false - t.string "ip_address" t.integer "maximum_timeout" + t.string "ip_address" t.integer "runner_type", limit: 2, null: false end @@ -635,6 +635,16 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.string "external_ip" end + create_table "clusters_applications_jupyters", force: :cascade do |t| + t.integer "cluster_id", null: false + t.integer "status", null: false + t.string "version", null: false + t.string "hostname" + t.text "status_reason" + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + end + create_table "clusters_applications_prometheus", force: :cascade do |t| t.integer "cluster_id", null: false t.integer "status", null: false @@ -904,8 +914,8 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_index "gpg_signatures", ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree create_table "group_custom_attributes", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false t.integer "group_id", null: false t.string "key", null: false t.string "value", null: false @@ -987,6 +997,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_index "issues", ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree add_index "issues", ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state", using: :btree add_index "issues", ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)", using: :btree + add_index "issues", ["project_id", "due_date", "id", "state"], name: "index_issues_on_project_id_and_due_date_and_id_and_state", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree add_index "issues", ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state", using: :btree add_index "issues", ["relative_position"], name: "index_issues_on_relative_position", using: :btree @@ -1203,6 +1214,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.boolean "merge_when_pipeline_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" + t.string "rebase_commit_sha" t.string "in_progress_merge_commit_sha" t.integer "lock_version" t.text "title_html" @@ -1215,7 +1227,6 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.string "merge_jid" t.boolean "discussion_locked" t.integer "latest_merge_request_diff_id" - t.string "rebase_commit_sha" t.boolean "allow_maintainer_to_push" end @@ -1475,8 +1486,8 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_index "project_ci_cd_settings", ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true, using: :btree create_table "project_custom_attributes", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false t.integer "project_id", null: false t.string "key", null: false t.string "value", null: false @@ -1568,8 +1579,10 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.string "avatar" t.string "import_status" t.integer "star_count", default: 0, null: false + t.boolean "merge_requests_rebase_enabled", default: false, null: false t.string "import_type" t.string "import_source" + t.boolean "merge_requests_ff_only_enabled", default: false, null: false t.text "import_error" t.integer "ci_id" t.boolean "shared_runners_enabled", default: true, null: false @@ -1585,6 +1598,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.boolean "only_allow_merge_if_pipeline_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" t.string "repository_storage", default: "default", null: false + t.boolean "repository_read_only" t.boolean "request_access_enabled", default: false, null: false t.boolean "has_external_wiki" t.string "ci_config_path" @@ -1599,9 +1613,6 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.datetime "last_repository_updated_at" t.integer "storage_version", limit: 2 t.boolean "resolve_outdated_diff_discussions" - t.boolean "repository_read_only" - t.boolean "merge_requests_ff_only_enabled", default: false - t.boolean "merge_requests_rebase_enabled", default: false, null: false t.integer "jobs_cache_index" t.boolean "pages_https_only", default: true t.boolean "remote_mirror_available_overridden" @@ -1945,9 +1956,9 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.string "model_type" t.string "uploader", null: false t.datetime "created_at", null: false + t.integer "store" t.string "mount_point" t.string "secret" - t.integer "store" end add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree @@ -2179,8 +2190,9 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade add_foreign_key "clusters", "users", on_delete: :nullify add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade - add_foreign_key "clusters_applications_ingress", "clusters", name: "fk_753a7b41c1", on_delete: :cascade - add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade + add_foreign_key "clusters_applications_ingress", "clusters", on_delete: :cascade + add_foreign_key "clusters_applications_jupyters", "clusters", on_delete: :cascade + add_foreign_key "clusters_applications_prometheus", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade add_foreign_key "container_repositories", "projects" @@ -2285,8 +2297,8 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_foreign_key "u2f_registrations", "users" add_foreign_key "user_callouts", "users", on_delete: :cascade add_foreign_key "user_custom_attributes", "users", on_delete: :cascade - add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade - add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade + add_foreign_key "user_interacted_projects", "projects", on_delete: :cascade + add_foreign_key "user_interacted_projects", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "users", "application_setting_terms", column: "accepted_term_id", name: "fk_789cd90b35", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 3deca103578..4600d17abb1 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -35,5 +35,6 @@ FactoryBot.define do factory :clusters_applications_ingress, class: Clusters::Applications::Ingress factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus factory :clusters_applications_runner, class: Clusters::Applications::Runner + factory :clusters_applications_jupyter, class: Clusters::Applications::Jupyter end end diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json index d27c12e43f2..ccef17a6615 100644 --- a/spec/fixtures/api/schemas/cluster_status.json +++ b/spec/fixtures/api/schemas/cluster_status.json @@ -31,7 +31,8 @@ } }, "status_reason": { "type": ["string", "null"] }, - "external_ip": { "type": ["string", "null"] } + "external_ip": { "type": ["string", "null"] }, + "hostname": { "type": ["string", "null"] } }, "required" : [ "name", "status" ] } diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index b942554d67b..6f66515b45f 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -234,9 +234,10 @@ describe Clusters::Cluster do let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) } let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } let!(:runner) { create(:clusters_applications_runner, cluster: cluster) } + let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) } it 'returns a list of created applications' do - is_expected.to contain_exactly(helm, ingress, prometheus, runner) + is_expected.to contain_exactly(helm, ingress, prometheus, runner, jupyter) end end end diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml new file mode 100644 index 00000000000..f9455f90986 --- /dev/null +++ b/vendor/jupyter/values.yaml @@ -0,0 +1,16 @@ +rbac: + enabled: false + +hub: + extraEnv: + JUPYTER_ENABLE_LAB: 1 + extraConfig: | + c.KubeSpawner.cmd = ['jupyter-labhub'] + +singleuser: + defaultUrl: "/lab" + +ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: "nginx" -- cgit v1.2.1 From b3cf1530829755411a7057031c2e8ea36ee31b62 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 24 May 2018 18:11:30 +0300 Subject: Add oauth reference to jupyter cluster app Signed-off-by: Dmitriy Zaporozhets --- .../projects/clusters/applications_controller.rb | 21 +++++++++++++- app/models/clusters/applications/jupyter.rb | 32 ++++++++++++++++++++-- ...1131058_create_clusters_applications_jupyter.rb | 1 + db/schema.rb | 1 + vendor/jupyter/values.yaml | 3 ++ 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb index 9198a66b73d..6de7a332164 100644 --- a/app/controllers/projects/clusters/applications_controller.rb +++ b/app/controllers/projects/clusters/applications_controller.rb @@ -6,7 +6,15 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll def create application = @application_class.find_or_create_by!(cluster: @cluster) - application.update(hostname: params[:hostname]) if application.respond_to?(:hostname) + + if application.respond_to?(:hostname) + application.update(hostname: params[:hostname]) + end + + if application.respond_to?(:oauth_application) + application.oauth_application = create_oauth_application(application) + application.save + end Clusters::Applications::ScheduleInstallationService.new(project, current_user).execute(application) @@ -24,4 +32,15 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll def application_class @application_class ||= Clusters::Cluster::APPLICATIONS[params[:application]] || render_404 end + + def create_oauth_application(application) + oauth_application_params = { + name: params[:application], + redirect_uri: application.callback_url, + scopes: 'api read_user openid', + owner: current_user + } + + Applications::CreateService.new(current_user, oauth_application_params).execute + end end diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index ef62be34abd..1f9cb1aade2 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -9,6 +9,8 @@ module Clusters include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationData + belongs_to :oauth_application, class_name: 'Doorkeeper::Application' + default_value_for :version, VERSION def chart @@ -32,16 +34,40 @@ module Clusters ) end + def callback_url + "http://#{hostname}/hub/oauth_callback" + end + private def specification { - "ingress" => { "hosts" => [hostname] }, - "hub" => { "cookieSecret" => SecureRandom.hex(32) }, - "proxy" => { "secretToken" => SecureRandom.hex(32) } + "ingress" => { + "hosts" => [hostname] + }, + "hub" => { + "extraEnv" => { + "GITLAB_HOST" => gitlab_url + }, + "cookieSecret" => SecureRandom.hex(32) + }, + "proxy" => { + "secretToken" => SecureRandom.hex(32) + }, + "auth" => { + "gitlab" => { + "clientId" => oauth_application.uid, + "clientSecret" => oauth_application.secret, + "callbackUrl" => callback_url + } + } } end + def gitlab_url + Gitlab.config.gitlab.url + end + def content_values YAML.load_file(chart_values_file).deep_merge!(specification) end diff --git a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb index 5fd39f24d98..53aee1a5abf 100644 --- a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb +++ b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb @@ -9,6 +9,7 @@ class CreateClustersApplicationsJupyter < ActiveRecord::Migration def change create_table :clusters_applications_jupyters do |t| t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade } + t.references :oauth_application t.integer :status, null: false t.string :version, null: false diff --git a/db/schema.rb b/db/schema.rb index 3a57f9ecbd2..1d0b9fab758 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -637,6 +637,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do create_table "clusters_applications_jupyters", force: :cascade do |t| t.integer "cluster_id", null: false + t.integer "oauth_application_id" t.integer "status", null: false t.string "version", null: false t.string "hostname" diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml index f9455f90986..90817de0f1b 100644 --- a/vendor/jupyter/values.yaml +++ b/vendor/jupyter/values.yaml @@ -7,6 +7,9 @@ hub: extraConfig: | c.KubeSpawner.cmd = ['jupyter-labhub'] +auth: + type: gitlab + singleuser: defaultUrl: "/lab" -- cgit v1.2.1 From e1d4deb2d2bc91c0730b57f81196b290fdfa86cf Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 24 May 2018 21:00:21 +0100 Subject: Disables jupyter install button while ingress is not installed Includes juptyer hostname in the post request Adds tests --- app/assets/javascripts/clusters/clusters_bundle.js | 5 +- .../clusters/components/application_row.vue | 17 ++++- .../clusters/components/applications.vue | 55 ++++------------ .../clusters/services/clusters_service.js | 11 +--- .../javascripts/clusters/stores/clusters_store.js | 2 +- spec/javascripts/clusters/clusters_bundle_spec.js | 32 +++++++-- .../clusters/components/application_row_spec.js | 32 ++++++++- .../clusters/components/applications_spec.js | 77 ++++++++++++++++++++-- spec/javascripts/clusters/services/mock_data.js | 35 ++++++++++ .../clusters/stores/clusters_store_spec.js | 18 +++++ 10 files changed, 212 insertions(+), 72 deletions(-) diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 6bf9dca1112..e42a3632e79 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -211,11 +211,12 @@ export default class Clusters { } } - installApplication(appId) { + installApplication(data) { + const appId = data.id; this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING); this.store.updateAppProperty(appId, 'requestReason', null); - this.service.installApplication(appId) + this.service.installApplication(appId, data.params) .then(() => { this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS); }) diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index fae580c091b..ff4fd0b118c 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -52,6 +52,16 @@ type: String, required: false, }, + disableInstallButton: { + type: Boolean, + required: false, + default: false, + }, + installApplicationRequestParams: { + type: Object, + required: false, + default: () => ({}), + }, }, computed: { rowJsClass() { @@ -67,7 +77,7 @@ // Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but // we already made a request to install and are just waiting for the real-time // to sync up. - return (this.status !== APPLICATION_INSTALLABLE + return this.disableInstallButton || (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) || this.requestStatus === REQUEST_LOADING || this.requestStatus === REQUEST_SUCCESS; @@ -109,7 +119,10 @@ }, methods: { installClicked() { - eventHub.$emit('installApplication', this.id); + eventHub.$emit('installApplication', { + id: this.id, + params: this.installApplicationRequestParams, + }); }, }, }; diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index e03db7b8974..5b127c7911c 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -37,11 +37,6 @@ export default { default: '', }, }, - data() { - return { - jupyterSuggestHostnameValue: '', - }; - }, computed: { generalApplicationDescription() { return sprintf( @@ -132,14 +127,6 @@ export default { jupyterHostname() { return this.applications.jupyter.hostname; }, - jupyterSuggestHostname() { - return `jupyter.${this.applications.ingress.externalIp}.xip.io`; - }, - }, - watch: { - jupyterSuggestHostname() { - this.jupyterSuggestHostnameValue = this.jupyterSuggestHostname; - }, }, }; @@ -305,6 +292,8 @@ export default { :status-reason="applications.jupyter.statusReason" :request-status="applications.jupyter.requestStatus" :request-reason="applications.jupyter.requestReason" + :disable-install-button="!ingressInstalled" + :install-application-request-params="{ hostname: applications.jupyter.hostname }" >

@@ -314,45 +303,23 @@ export default { notebooks to a class of students, a corporate data science group, or a scientific research group.`) }}

- -