Hello community,
here is the log from the commit of package velum for openSUSE:Factory checked in at 2018-02-22 15:03:31
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/velum (Old)
and /work/SRC/openSUSE:Factory/.velum.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "velum"
Thu Feb 22 15:03:31 2018 rev:7 rq:578943 version:3.0.0+dev+git_r650_4eb2d26dfbef4dd92b3b4685f1704ed8787d5732
Changes:
--------
--- /work/SRC/openSUSE:Factory/velum/velum.changes 2018-02-18 11:42:51.856523989 +0100
+++ /work/SRC/openSUSE:Factory/.velum.new/velum.changes 2018-02-22 15:03:34.463568486 +0100
@@ -1,0 +2,9 @@
+Thu Feb 22 09:58:32 UTC 2018 - containers-bugowner@suse.de
+
+- Commit 4eb2d26 by James Mason jmason@suse.com
+ Add user interface for public cloud bootstrapping
+
+ new file: app/assets/stylesheets/pages/instance_type.scss
+
+
+-------------------------------------------------------------------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ velum.spec ++++++
--- /var/tmp/diff_new_pack.RjePRA/_old 2018-02-22 15:03:35.919516113 +0100
+++ /var/tmp/diff_new_pack.RjePRA/_new 2018-02-22 15:03:35.923515970 +0100
@@ -23,7 +23,7 @@
# Version: 1.0.0
# %%define branch 1.0.0
-Version: 3.0.0+dev+git_r646_67806291a2f2903835f76b154b3e1b4811873011
+Version: 3.0.0+dev+git_r650_4eb2d26dfbef4dd92b3b4685f1704ed8787d5732
Release: 0
%define branch master
Summary: Dashboard for CaasP
@@ -96,7 +96,7 @@
%description
velum is the dashboard for CaasP to manage and deploy kubernetes clusters on top of MicroOS
-This package has been built with commit 67806291a2f2903835f76b154b3e1b4811873011 from branch master on date Thu, 15 Feb 2018 15:53:13 +0000
+This package has been built with commit 4eb2d26dfbef4dd92b3b4685f1704ed8787d5732 from branch master on date Thu, 22 Feb 2018 09:57:53 +0000
%prep
%setup -q -n velum-%{branch}
++++++ master.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/javascripts/application.js new/velum-master/app/assets/javascripts/application.js
--- old/velum-master/app/assets/javascripts/application.js 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/app/assets/javascripts/application.js 2018-02-22 10:57:04.000000000 +0100
@@ -13,6 +13,7 @@
//= require jquery
//= require jquery_ujs
//= require bootstrap.min
+//= require bootstrap-slider.min
//
//= require_tree ./dashboard
//= require_tree ./setup
@@ -21,4 +22,4 @@
$('body').on('click', '[disabled]', function(e) {
e.preventDefault();
-});
\ No newline at end of file
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/javascripts/cloud/bootstrap.js new/velum-master/app/assets/javascripts/cloud/bootstrap.js
--- old/velum-master/app/assets/javascripts/cloud/bootstrap.js 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/app/assets/javascripts/cloud/bootstrap.js 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,65 @@
+$(function() {
+ // https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-t...
+ // https://creativecommons.org/licenses/by-sa/4.0/
+ function humanFileSize(bytes, si) {
+ var thresh = si ? 1000 : 1024;
+ if(Math.abs(bytes) < thresh) {
+ return bytes + ' B';
+ }
+ var units = si
+ ? ['kB','MB','GB','TB','PB','EB','ZB','YB']
+ : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
+ var u = -1;
+ do {
+ bytes /= thresh;
+ ++u;
+ } while(Math.abs(bytes) >= thresh && u < units.length - 1);
+ return bytes.toFixed(1)+' '+units[u];
+ }
+
+ function calcClusterVcpus() {
+ vcpusPerVm = $('.instance-type-description .vcpu-count').data('vcpus');
+ vmCount = clusterSize.getValue();
+ $('#cluster-cpu-count').html(vcpusPerVm * vmCount);
+ }
+
+ function calcClusterRam() {
+ bytesPerVm = $('.instance-type-description .ram-size').data('bytes');
+ siUnits = $('.instance-type-description .ram-size').data('si');
+ vmCount = clusterSize.getValue();
+ totalBytes = bytesPerVm * vmCount;
+ $('#cluster-ram-size').attr('data-bytes', totalBytes);
+ $('#cluster-ram-size').html(humanFileSize(totalBytes, siUnits))
+ }
+
+ var updateClusterSize = function() {
+ calcClusterVcpus();
+ calcClusterRam();
+ }
+
+ var clusterSize = $('#cloud_cluster_instance_count').slider()
+ .on('slide change', updateClusterSize).data('slider');
+
+ $('input[name="cloud_cluster[instance_type]"]').click(function() {
+ definition = $(this).siblings('.definition').html();
+ $('.instance-type-description').html(definition);
+ ramSize = $('.instance-type-description .ram-size')
+ ramSize.html(
+ humanFileSize(ramSize.data('bytes'), ramSize.data('si'))
+ )
+
+ if (this.id === 'cloud_cluster_instance_type_custom') {
+ $('.cluster-cpu-count,.cluster-ram-size').hide();
+ $('input#cloud_cluster_instance_type_custom[type="text"]').
+ show().focus();
+ } else {
+ $('input#cloud_cluster_instance_type_custom[type="text"]').
+ val("").hide();
+ updateClusterSize();
+ $('.cluster-cpu-count,.cluster-ram-size').show();
+ }
+ });
+
+ // kick things off
+ $('input[name="cloud_cluster[instance_type]"][checked="checked"]').click();
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/stylesheets/application.scss new/velum-master/app/assets/stylesheets/application.scss
--- old/velum-master/app/assets/stylesheets/application.scss 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/app/assets/stylesheets/application.scss 2018-02-22 10:57:04.000000000 +0100
@@ -6,6 +6,7 @@
@import 'bootstrap-sprockets';
@import 'velum_bootstrap-variables';
@import 'bootstrap';
+@import 'bootstrap-slider.min';
@import 'components/**/*';
@import 'velum_general';
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/stylesheets/pages/instance_type.scss new/velum-master/app/assets/stylesheets/pages/instance_type.scss
--- old/velum-master/app/assets/stylesheets/pages/instance_type.scss 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/app/assets/stylesheets/pages/instance_type.scss 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,106 @@
+.instance-type-selector {
+ background-color: $panel-default-heading-bg;
+
+ .instance-types {
+ border-right: 1px solid $table-border-color;
+ }
+
+ label.instance-type {
+
+ input[type="radio"] {
+ display: none;
+
+ &:checked + .instance-type-box {
+ background-color: $brand-success;
+ color: $table-bg;
+ box-shadow: none;
+ }
+ }
+
+ .instance-type-box {
+ display: inline-block;
+ overflow: hidden;
+ width: 148px;
+ height: 148px;
+
+ border: 1px solid $table-border-color;
+ border-radius: 5px;
+ box-shadow: 1px 1px 6px 1px $table-border-color;
+
+ background: $table-bg;
+ color: $brand-success;
+
+ margin: 10px;
+ padding: 10px;
+
+ font-size: 14pt;
+ text-align: center;
+ font-weight: normal;
+ word-wrap: break-word;
+
+ &.double {
+ width: 316px;
+ }
+ }
+
+ .definition {
+ display: none;
+ }
+ }
+
+ .instance-type-description {
+ h1, h2, h3, h4, h5, h6 {
+ color: #3d4042;
+ }
+ }
+}
+
+.cluster-size-selector {
+ background-color: $panel-default-heading-bg;
+ min-height: 113px;
+
+ .slider {
+ &.slider-horizontal {
+ width: 100% !important;
+ }
+
+ .slider-handle {
+ background: $brand-success;
+ border: 1px solid $brand-success;
+ box-shadow: none;
+ }
+
+ .slider-track {
+ background: $table-bg;
+ border: 1px solid $table-border-color;
+ }
+
+ .slider-track-low, .slider-track-high {
+ background: $table-bg;
+ }
+
+ .slider-selection {
+ background: $brand-success; //#337ab7;
+ }
+
+ .slider-tick {
+ background: $table-bg;
+ border: 1px solid $table-border-color;
+ box-shadow: none;
+
+ &.in-selection {
+ background: $brand-success; //#337ab7;
+ border: 1px solid $brand-success; //#337ab7;
+ }
+ }
+ }
+
+ label {
+ display: block;
+
+ &.h3 {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/controllers/internal_api/v1/pillars_controller.rb new/velum-master/app/controllers/internal_api/v1/pillars_controller.rb
--- old/velum-master/app/controllers/internal_api/v1/pillars_controller.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/app/controllers/internal_api/v1/pillars_controller.rb 2018-02-22 10:57:04.000000000 +0100
@@ -1,14 +1,18 @@
# Serve the pillar information
class InternalApi::V1::PillarsController < InternalApiController
def show
- ok content: pillar_contents.merge(registry_contents)
+ ok content: pillar_contents.merge(
+ registry_contents
+ ).merge(
+ cloud_framework_contents
+ )
end
private
def pillar_contents
pillar_struct = {}.tap do |h|
- Pillar.all_pillars.each do |k, v|
+ Pillar.simple_pillars.each do |k, v|
h[v] = Pillar.value(pillar: k.to_sym) unless Pillar.value(pillar: k.to_sym).nil?
end
end
@@ -40,4 +44,34 @@
end
{ registries: (registries + registry_mirrors) }
end
+
+ def cloud_framework_contents
+ case Pillar.value(pillar: :cloud_framework)
+ when "ec2"
+ ec2_cloud_contents
+ else
+ {}
+ end
+ end
+
+ def ec2_cloud_contents
+ {
+ cloud: {
+ framework: "ec2",
+ profiles: {
+ cluster_node: {
+ size: Pillar.value(pillar: :cloud_worker_type),
+ network_interfaces: [
+ {
+ DeviceIndex: 0,
+ AssociatePublicIpAddress: false,
+ SubnetId: Pillar.value(pillar: :cloud_worker_subnet),
+ SecurityGroupId: Pillar.value(pillar: :cloud_worker_security_group)
+ }
+ ]
+ }
+ }
+ }
+ }
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/controllers/setup_controller.rb new/velum-master/app/controllers/setup_controller.rb
--- old/velum-master/app/controllers/setup_controller.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/app/controllers/setup_controller.rb 2018-02-22 10:57:04.000000000 +0100
@@ -1,4 +1,5 @@
require "velum/salt"
+require "velum/instance_type"
# SetupController is responsible for everything related to the bootstrapping
# process:
@@ -52,6 +53,38 @@
def worker_bootstrap
@controller_node = Pillar.value pillar: :dashboard
+
+ return unless (cloud = Pillar.value(pillar: :cloud_framework))
+
+ @instance_types = Velum::InstanceType.for(cloud)
+ @cloud_cluster = CloudCluster.new(cloud_framework: cloud)
+ case cloud
+ when "ec2"
+ @cloud_cluster.instance_type = Pillar.value(
+ pillar: :cloud_worker_type
+ ) || @instance_types.first.key
+ @cloud_cluster.subnet_id = Pillar.value(
+ pillar: :cloud_worker_subnet
+ ) || "subnet-"
+ @cloud_cluster.security_group_id = Pillar.value(
+ pillar: :cloud_worker_security_group
+ ) || "sg-"
+ end
+ render "worker_bootstrap_#{cloud}".to_sym
+ end
+
+ def build_cloud_cluster
+ @cloud_cluster = CloudCluster.new(cloud_cluster_params)
+
+ if @cloud_cluster.save
+ Velum::Salt.build_cloud_cluster(@cloud_cluster.instance_count)
+ redirect_to setup_discovery_path,
+ notice: "Starting to build #{@cloud_cluster}..."
+ else
+ flash.keep
+ redirect_to setup_worker_bootstrap_path,
+ flash: { error: @cloud_cluster.errors.full_messages.to_sentence }
+ end
end
def set_roles
@@ -135,6 +168,21 @@
[parameters]
end
+ def cloud_cluster_params
+ cloud_cluster = params.require(:cloud_cluster).permit(
+ :instance_type,
+ :instance_type_custom,
+ :instance_count,
+ :vnet_id,
+ :subnet_id,
+ :security_group_id,
+ :publishsettings,
+ :media_link
+ )
+ cloud_cluster["cloud_framework"] = Pillar.value(pillar: :cloud_framework)
+ cloud_cluster
+ end
+
def update_nodes_params
params.require(:roles)
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/models/cloud_cluster.rb new/velum-master/app/models/cloud_cluster.rb
--- old/velum-master/app/models/cloud_cluster.rb 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/app/models/cloud_cluster.rb 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+require "velum/salt"
+# CloudCluster represents user-configured attributes of a cloud deployment.
+class CloudCluster
+ include ActiveModel::Model
+ attr_accessor :cloud_framework,
+ :instance_count, :instance_type, :instance_type_custom,
+ :subnet_id, :security_group_id # EC2
+
+ def initialize(*args)
+ super
+ if @instance_type.blank? || @instance_type == "CUSTOM"
+ @instance_type = instance_type_custom
+ end
+ @instance_count = @instance_count.to_i
+ end
+
+ def to_s
+ parts = ["a cluster of #{@instance_count} #{@instance_type} instances"]
+ parts.push("in the #{@subnet_id} subnet") if @subnet_id
+ parts.push("in the #{@security_group_id} security group") if @security_group_id
+ case @cloud_framework
+ when "ec2"
+ parts.push("in EC2")
+ end
+ parts.join(" ")
+ end
+
+ def save!
+ case @cloud_framework
+ when "ec2"
+ persist_to_pillar!(:cloud_worker_type, @instance_type)
+ persist_to_pillar!(:cloud_worker_subnet, @subnet_id)
+ persist_to_pillar!(:cloud_worker_security_group, @security_group_id)
+ Velum::Salt.call(action: "saltutil.refresh_pillar")
+ end
+ end
+
+ def save
+ save!
+ return true
+ rescue ActiveRecord::ActiveRecordError,
+ Velum::SaltApi::SaltConnectionException => e
+ errors[:base] << e.message
+ return false
+ end
+
+ private
+
+ def persist_to_pillar!(pillar_key, value)
+ pillar = Pillar.find_or_initialize_by(pillar: Pillar.all_pillars[pillar_key])
+ pillar.value = value
+ pillar.save!
+ end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/models/pillar.rb new/velum-master/app/models/pillar.rb
--- old/velum-master/app/models/pillar.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/app/models/pillar.rb 2018-02-22 10:57:04.000000000 +0100
@@ -13,6 +13,10 @@
end
def all_pillars
+ simple_pillars.merge(cloud_worker_pillars)
+ end
+
+ def simple_pillars
{
dashboard: "dashboard",
dashboard_external_fqdn: "dashboard_external_fqdn",
@@ -47,6 +51,19 @@
}
end
+ # rubocop:disable Layout/AlignHash
+ def cloud_worker_pillars
+ {
+ cloud_worker_type:
+ "cloud:profiles:cluster_node:size",
+ cloud_worker_subnet:
+ "cloud:profiles:cluster_node:network_interfaces:SubnetId",
+ cloud_worker_security_group:
+ "cloud:profiles:cluster_node:network_interfaces:SecurityGroupId"
+ }
+ end
+ # rubocop:enable Layout/AlignHash
+
# Apply the given pillars into the database. It returns an array with the
# encountered errors.
def apply(pillars, required_pillars: [], unprotected_pillars: [])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/views/setup/_cluster_size_panel.html.slim new/velum-master/app/views/setup/_cluster_size_panel.html.slim
--- old/velum-master/app/views/setup/_cluster_size_panel.html.slim 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/app/views/setup/_cluster_size_panel.html.slim 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,16 @@
+.panel.panel-default
+ .panel-heading
+ h3.panel-title Cluster size
+ .panel-body.cluster-size-selector
+ .col-md-8
+ label for="cloud_cluster_instance_count" Number of instances
+ = form.text_field :instance_count, data: {slider_min: 3, slider_value: 5, slider_max: 50, slider_scale: "logarithmic", slider_tooltip: "always", slider_tooltip_position: "bottom", slider_ticks: [3, 5, 10, 20, 50], slider_ticks_snap_bounds: 0.5}, class: 'h3'
+ .col-md-2.text-right.cluster-cpu-count
+ label Total vCPUs
+ label.h3#cluster-cpu-count = 0
+ .col-md-2.text-right.cluster-ram-size
+ label Total RAM
+ label.h3#cluster-ram-size data-bytes=0 = 0
+ .panel-footer
+ h4 Tip
+ p At least three nodes are required for a reliable cluster.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/views/setup/_instance_type_panel.html.slim new/velum-master/app/views/setup/_instance_type_panel.html.slim
--- old/velum-master/app/views/setup/_instance_type_panel.html.slim 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/app/views/setup/_instance_type_panel.html.slim 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,56 @@
+.panel.panel-default
+ .panel-heading
+ h3.panel-title Instance Type
+ .panel-body.instance-type-selector
+ .row
+ .col-md-8.instance-types
+ .form-group
+ - @instance_types.each do |instance_type|
+ label.instance-type
+ = form.radio_button :instance_type, instance_type.key
+ .instance-type-box
+ = instance_type.category.name
+ br
+ small = instance_type.key
+ .definition
+ h2 = instance_type.category.name
+ h3 = instance_type.key
+ p
+ = instance_type.category.description
+ p
+ - instance_type.category.features.each do |feature|
+ '
+ span.label.label-default = feature
+
+ dl
+ dt vCPUs
+ dd.vcpu-count(data-vcpus="#{instance_type.vcpu_count}")
+ = pluralize(instance_type.vcpu_count, 'core')
+ dt RAM
+ dd.ram-size(data-bytes="#{instance_type.ram_bytes}"
+ data-si="#{instance_type.ram_si_units}")
+ - instance_type.details.each do |key, value|
+ dt
+ = key
+ dd
+ = value
+ label.instance-type
+ = form.radio_button :instance_type, 'CUSTOM'
+ .instance-type-box.double
+ ' Other types…
+ br
+ br
+ small = form.text_field :instance_type_custom, class: "form-control", style: "display: none;"
+ .definition
+ p
+ ' You may specify a preferred instance type with a minimum of
+ ' are 2 vCPUs and 8GB of RAM; 4 vCPUs are recommended.
+
+
+ .col-md-4.instance-type-description
+ .panel-footer
+ h4 Tip
+ p
+ ' Not sure which type of instance to use? Check the
+ = link_to "Instance Types", list_url
+ ' list.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim new/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim
--- old/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,38 @@
+= content_for :body_class, "worker_bootstrap"
+
+h1
+ ' Bootstrap your CaaS Platform
+ small in Amazon Web Services' Elastic Compute Cloud
+
+
+= form_for @cloud_cluster, url: setup_build_cloud_cluster_path do |form|
+ = form.hidden_field :cloud_framework
+ p
+ ' In order to complete the installation, it is necessary to bootstrap a few
+ ' additional nodes, those will be the Kubernetes Master and Workers.
+
+ = render "instance_type_panel",
+ form: form,
+ list_url: "https://aws.amazon.com/ec2/instance-types/"
+
+ = render "cluster_size_panel", form: form
+
+ .panel.panel-default
+ .panel-heading
+ h3.panel-title Networking
+ .panel-body
+ .col-md-4
+ .form-group
+ label for="cloud_cluster_subnet_id" Subnet ID
+ = form.text_field :subnet_id, class: "form-control"
+ .col-md-4
+ .form-group
+ label for="cloud_cluster_security_group_id" Security Group ID
+ = form.text_field :security_group_id, class: "form-control"
+
+ .clearfix.text-right.steps-container
+ = link_to "Back", setup_path, class: "btn btn-danger"
+ = form.submit "Next", class: "btn btn-primary"
+
+- content_for :page_javascript do
+ = javascript_include_tag 'cloud/bootstrap', defer: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/config/initializers/assets.rb new/velum-master/config/initializers/assets.rb
--- old/velum-master/config/initializers/assets.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/config/initializers/assets.rb 2018-02-22 10:57:04.000000000 +0100
@@ -9,4 +9,7 @@
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
-Rails.application.config.assets.precompile += ["authentication.css"]
+Rails.application.config.assets.precompile += [
+ "authentication.css",
+ "cloud/bootstrap.js"
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/config/routes.rb new/velum-master/config/routes.rb
--- old/velum-master/config/routes.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/config/routes.rb 2018-02-22 10:57:04.000000000 +0100
@@ -37,7 +37,8 @@
namespace :setup do
get "/", action: :welcome
match "/", action: :configure, via: [:put, :patch]
- get :"worker-bootstrap"
+ get "worker-bootstrap"
+ post :build_cloud_cluster
get :discovery
post :discovery, action: :set_roles
get :bootstrap
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/lib/velum/salt.rb new/velum-master/lib/velum/salt.rb
--- old/velum-master/lib/velum/salt.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/lib/velum/salt.rb 2018-02-22 10:57:04.000000000 +0100
@@ -1,4 +1,5 @@
require "velum/salt_api"
+require "securerandom"
module Velum
# This class allows to interact with global salt actions
@@ -34,6 +35,23 @@
[needed["return"], failed["return"]]
end
+ # Trigger salt-cloud to construct a cluster
+ def self.build_cloud_cluster(count)
+ instance_names = (1..count).collect { "caasp-node-" + SecureRandom.hex(4) }
+ instance_names.collect do |instance_name|
+ perform_request(
+ endpoint: "/",
+ method: "post",
+ data: {
+ client: "local_async",
+ tgt: "admin",
+ fun: "cloud.profile",
+ arg: ["cluster_node", instance_name]
+ }
+ )
+ end
+ end
+
# Returns the minions as discovered by salt.
def self.minions
res = perform_request(endpoint: "/minions", method: "get")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb new/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb
--- old/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb 2018-02-22 10:57:04.000000000 +0100
@@ -74,4 +74,56 @@
expect(json).to match expected_registries_response
end
end
+
+ context "when in EC2 framework" do
+ let(:custom_instance_type) { "custom-instance-type" }
+ let(:subnet_id) { "subnet-9d4a7b6c" }
+ let(:security_group_id) { "sg-903004f8" }
+
+ let(:expected_response) do
+ {
+ registries: [],
+ cloud: {
+ framework: "ec2",
+ profiles: {
+ cluster_node: {
+ size: custom_instance_type,
+ network_interfaces: [
+ {
+ DeviceIndex: 0,
+ AssociatePublicIpAddress: false,
+ SubnetId: subnet_id,
+ SecurityGroupId: security_group_id
+ }
+ ]
+ }
+ }
+ }
+ }
+ end
+
+ before do
+ create(:ec2_pillar)
+ create(
+ :pillar,
+ pillar: "cloud:profiles:cluster_node:size",
+ value: custom_instance_type
+ )
+ create(
+ :pillar,
+ pillar: "cloud:profiles:cluster_node:network_interfaces:SubnetId",
+ value: subnet_id
+ )
+ create(
+ :pillar,
+ pillar: "cloud:profiles:cluster_node:network_interfaces:SecurityGroupId",
+ value: security_group_id
+ )
+ end
+
+ it "has remote registries and respective mirrors" do
+ get :show
+ expect(json).to eq(expected_response)
+ end
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/controllers/setup_controller_spec.rb new/velum-master/spec/controllers/setup_controller_spec.rb
--- old/velum-master/spec/controllers/setup_controller_spec.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/spec/controllers/setup_controller_spec.rb 2018-02-22 10:57:04.000000000 +0100
@@ -88,6 +88,104 @@
get :worker_bootstrap
expect(assigns(:controller_node)).to eq("localhost")
end
+
+ context "when in EC2 framework" do
+ before do
+ create(:ec2_pillar)
+ get :worker_bootstrap
+ end
+
+ it "assigns @instance_sizes" do
+ expect(assigns(:instance_types)).to all(be_a(Velum::InstanceType))
+ end
+
+ it "renders EC2 view" do
+ expect(response).to render_template(:worker_bootstrap_ec2)
+ end
+ end
+ end
+
+ describe "POST /setup/worker-boostrap via HTML in EC2" do
+ let(:instance_type) { "t2.xlarge" }
+ let(:instance_count) { 5 }
+ let(:subnet_id) { "subnet-9d4a7b6c" }
+ let(:security_group_id) { "sg-903004f8" }
+ let(:cloud_cluster_params) do
+ {
+ instance_type: instance_type,
+ instance_count: instance_count,
+ subnet_id: subnet_id,
+ security_group_id: security_group_id
+ }
+ end
+
+ before do
+ sign_in user
+ create(:ec2_pillar)
+ Pillar.create pillar: "dashboard", value: "localhost"
+
+ allow(Velum::Salt).to receive(:build_cloud_cluster)
+ end
+
+ context "when saving succeeds" do
+ before do
+ ensure_pillar_refresh do
+ post :build_cloud_cluster, cloud_cluster: cloud_cluster_params
+ end
+ end
+
+ it "always uses the framework pillar" do
+ expect(assigns(:cloud_cluster).cloud_framework).to eq("ec2")
+ end
+
+ it "assigns the instance type" do
+ expect(assigns(:cloud_cluster).instance_type).to eq(instance_type)
+ end
+
+ it "assigns the quantity of workers" do
+ expect(assigns(:cloud_cluster).instance_count).to eq(instance_count)
+ end
+
+ it "assigns the EC2 subnet ID" do
+ expect(assigns(:cloud_cluster).subnet_id).to eq(subnet_id)
+ end
+
+ it "assigns the EC2 security group ID" do
+ expect(assigns(:cloud_cluster).security_group_id).to eq(security_group_id)
+ end
+
+ it "calls salt-cloud" do
+ expect(Velum::Salt).to have_received(:build_cloud_cluster).with(instance_count).once
+ end
+
+ it "uses a flash to provide confirmation" do
+ expect(flash[:notice]).to be_present
+ end
+ end
+
+ context "when saving fails" do
+ let(:error_message) { "Nope!" }
+ let(:mock_cloud_cluster) do
+ mock = CloudCluster.new(cloud_cluster_params)
+ allow(mock).to receive(:save!).and_raise(
+ ActiveRecord::ActiveRecordError.new(error_message)
+ )
+ mock
+ end
+
+ before do
+ allow(CloudCluster).to receive(:new).and_return(mock_cloud_cluster)
+ post :build_cloud_cluster, cloud_cluster: cloud_cluster_params
+ end
+
+ it "redirects back to bootstrap" do
+ expect(controller).to redirect_to(:setup_worker_bootstrap)
+ end
+
+ it "uses a flash to show error messages" do
+ expect(flash[:error]).to be_present
+ end
+ end
end
describe "POST /setup/discovery via HTML" do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/factories/pillar_factory.rb new/velum-master/spec/factories/pillar_factory.rb
--- old/velum-master/spec/factories/pillar_factory.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/spec/factories/pillar_factory.rb 2018-02-22 10:57:04.000000000 +0100
@@ -7,4 +7,8 @@
pillar { Pillar.all_pillars[:apiserver] }
value "myapiserver.example.com"
end
+ factory :ec2_pillar, parent: :pillar do
+ pillar { Pillar.all_pillars[:cloud_framework] }
+ value "ec2"
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb new/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb
--- old/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,70 @@
+require "rails_helper"
+require "velum/instance_type"
+
+describe "Feature: Bootstrap a cluster in EC2" do
+ let(:user) { create(:user) }
+ let(:instance_types) { Velum::InstanceType.for("ec2") }
+ let(:custom_instance_type) { OpenStruct.new(key: "CUSTOM") }
+
+ before do
+ login_as user, scope: :user
+ create(:ec2_pillar)
+ setup_stubbed_update_status!
+ visit setup_worker_bootstrap_path
+ end
+
+ it "refers to EC2 in the heading" do
+ expect(page).to have_css("h1", text: "Elastic Compute Cloud")
+ end
+
+ it "allows selection of an instance type" do
+ instance_types.each do |instance_type|
+ expect(page).to have_css(instance_type_radio_finder(instance_type))
+ end
+ end
+
+ it "displays the category of the selected instance type", js: true do
+ instance_types.each do |instance_type|
+ click_instance_type_radio(instance_type)
+ expect(page).to have_text(:visible, instance_type.category.name)
+ expect(page).to have_text(:visible, instance_type.category.description)
+ end
+ end
+
+ it "hides the custom instance type box by default", js: true do
+ expect(page).not_to have_css("input#cloud_cluster_instance_type_custom[type='text']")
+ end
+
+ it "shows the textbox when choosing a custom instance type", js: true do
+ click_instance_type_radio(custom_instance_type)
+ expect(page).to have_css("input#cloud_cluster_instance_type_custom[type='text']")
+ end
+
+ context "when sizing the cluster" do
+ let(:cluster_size) do
+ page.find("#cloud_cluster_instance_count", visible: :any)["data-slider-value"].to_i
+ end
+
+ it "calculates the total cluster vCPU count", js: true do
+ instance_types.each do |instance_type|
+ total = instance_type.vcpu_count * cluster_size
+ click_instance_type_radio(instance_type)
+ expect(page).to have_css("#cluster-cpu-count", text: total)
+ end
+ end
+
+ it "calculates the total cluster RAM size", js: true do
+ instance_types.each do |instance_type|
+ total = instance_type.ram_bytes * cluster_size
+ click_instance_type_radio(instance_type)
+ expect(page.find("#cluster-ram-size")["data-bytes"].to_i).to eq(total)
+ end
+ end
+
+ it "hides calculations for custom types", js: true do
+ click_instance_type_radio(custom_instance_type)
+ expect(page).not_to have_css("#cluster-cpu-count")
+ expect(page).not_to have_css("#cluster-ram-count")
+ end
+ end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/lib/velum/salt_spec.rb new/velum-master/spec/lib/velum/salt_spec.rb
--- old/velum-master/spec/lib/velum/salt_spec.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/spec/lib/velum/salt_spec.rb 2018-02-22 10:57:04.000000000 +0100
@@ -76,4 +76,16 @@
end
end
end
+
+ describe "#build_cloud_cluster" do
+ let(:count) { rand(49) + 1 } # 1..50
+
+ it "calls cloud.profile salt function the specified number of times" do
+ VCR.use_cassette("salt/cloud_profile", record: :none, allow_playback_repeats: true) do
+ responses = described_class.build_cloud_cluster(count)
+ expect(responses.length).to eq(count)
+ expect(responses).to all(be_a(Net::HTTPOK))
+ end
+ end
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/models/cloud_cluster_spec.rb new/velum-master/spec/models/cloud_cluster_spec.rb
--- old/velum-master/spec/models/cloud_cluster_spec.rb 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/spec/models/cloud_cluster_spec.rb 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,116 @@
+require "rails_helper"
+require "velum/salt_api"
+
+describe CloudCluster do
+ let(:custom_instance_type) { "custom-instance-type" }
+ let(:subnet_id) { "subnet-9d4a7b6c" }
+ let(:security_group_id) { "sg-903004f8" }
+ let(:instance_count) { 5 }
+
+ it "can implicitly represent a custom instance type" do
+ cluster = described_class.new(instance_type_custom: custom_instance_type)
+ expect(cluster.instance_type).to be(custom_instance_type)
+ end
+
+ it "can explicity represent a custom instance type" do
+ cluster = described_class.new(
+ instance_type: "CUSTOM",
+ instance_type_custom: custom_instance_type
+ )
+ expect(cluster.instance_type).to be(custom_instance_type)
+ end
+
+ context "when represented as a string" do
+ let(:cluster) do
+ described_class.new(
+ instance_type: custom_instance_type,
+ instance_count: instance_count,
+ subnet_id: subnet_id,
+ security_group_id: security_group_id
+ )
+ end
+
+ it "counts out the instances" do
+ substring = "a cluster of #{instance_count} #{custom_instance_type} instances"
+ expect(cluster.to_s).to match(substring)
+ end
+
+ it "describes the subnet" do
+ substring = "in the #{subnet_id} subnet"
+ expect(cluster.to_s).to match(substring)
+ end
+
+ it "describes the security group" do
+ substring = "in the #{security_group_id} security group"
+ expect(cluster.to_s).to match(substring)
+ end
+ end
+
+ context "when saving, behave like ActiveRecord#save" do
+ let(:cluster) { described_class.new }
+ let(:handled_exceptions) do
+ [
+ ActiveRecord::ActiveRecordError.new("Didn't work!"),
+ Velum::SaltApi::SaltConnectionException.new("You're bad at this.")
+ ]
+ end
+
+ it "returns true" do
+ allow(cluster).to receive(:save!)
+ expect(cluster.save).to be(true)
+ end
+
+ it "returns false when there is an exception" do
+ handled_exceptions.each do |exception|
+ allow(cluster).to receive(:save!).and_raise(exception)
+ expect(cluster.save).to be(false)
+ end
+ end
+
+ it "captures downstream messages to the errors collection" do
+ handled_exceptions.each do |exception|
+ allow(cluster).to receive(:save!).and_raise(exception)
+ cluster.save
+ expect(cluster.errors[:base]).to include(exception.message)
+ end
+ end
+ end
+
+ context "when framework is EC2" do
+ let(:framework) { "ec2" }
+ let(:cluster) do
+ described_class.new(
+ cloud_framework: framework,
+ instance_type: custom_instance_type,
+ subnet_id: subnet_id,
+ security_group_id: security_group_id
+ )
+ end
+
+ it "stores instance type as :cloud_worker_type Pillar and refreshes" do
+ ensure_pillar_refresh do
+ expect(cluster.save).to be(true)
+ end
+ expect(Pillar.value(pillar: :cloud_worker_type)).to eq(custom_instance_type)
+ end
+
+ it "stores subnet ID as :cloud_worker_subnet Pillar and refreshes" do
+ ensure_pillar_refresh do
+ expect(cluster.save).to be(true)
+ end
+ expect(Pillar.value(pillar: :cloud_worker_subnet)).to eq(subnet_id)
+ end
+
+ it "stores security group ID as :cloud_worker_security_group Pillar and refreshes" do
+ ensure_pillar_refresh do
+ expect(cluster.save).to be(true)
+ end
+ expect(Pillar.value(pillar: :cloud_worker_security_group)).to eq(security_group_id)
+ end
+
+ it "describes the framework in string representation" do
+ substring = "in EC2"
+ expect(cluster.to_s).to match(substring)
+ end
+ end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/support/helpers.rb new/velum-master/spec/support/helpers.rb
--- old/velum-master/spec/support/helpers.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/spec/support/helpers.rb 2018-02-22 10:57:04.000000000 +0100
@@ -23,4 +23,16 @@
end
end
+def click_instance_type_radio(instance_type)
+ page.execute_script('$("' + instance_type_radio_finder(instance_type) + '").click()')
+end
+
+def instance_type_radio_finder(instance_type)
+ [
+ "input[type='radio']",
+ "[name='cloud_cluster[instance_type]']",
+ "[value='#{instance_type.key}']"
+ ].join
+end
+
RSpec.configure { |config| config.include Helpers, type: :feature }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/support/utils.rb new/velum-master/spec/support/utils.rb
--- old/velum-master/spec/support/utils.rb 2018-02-15 16:53:11.000000000 +0100
+++ new/velum-master/spec/support/utils.rb 2018-02-22 10:57:04.000000000 +0100
@@ -19,6 +19,17 @@
def setup_undone
Pillar.delete_all
end
+
+ def ensure_pillar_refresh
+ VCR.use_cassette(
+ "salt/refresh_pillar",
+ allow_unused_http_interactions: false,
+ allow_playback_repeats: true,
+ record: :none
+ ) do
+ yield
+ end
+ end
end
RSpec.configure { |config| config.include Utils }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml new/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml
--- old/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,107 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: https://127.0.0.1:8000/login
+ body:
+ encoding: UTF-8
+ string: '{"username":"saltapi","password":"l+ZtDm9lG1DPdt/QyFfABgWtCN/IKwnmGTK8nCt++PiOVG9Y2NccIrozchvz7RtxREIZe5CshcO0","eauth":"pam"}'
+ headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ Accept:
+ - application/json; charset=utf-8
+ User-Agent:
+ - Ruby
+ Host:
+ - 127.0.0.1:8000
+ Content-Type:
+ - application/json; charset=utf-8
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Content-Length:
+ - '217'
+ Access-Control-Expose-Headers:
+ - GET, POST
+ Vary:
+ - Accept-Encoding
+ Server:
+ - CherryPy/3.6.0
+ Allow:
+ - GET, HEAD, POST
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Date:
+ - Mon, 05 Feb 2018 20:57:34 GMT
+ Access-Control-Allow-Origin:
+ - "*"
+ X-Auth-Token:
+ - 507fef45a35e7038c9a1cdb754acfc7539aac4a2
+ Content-Type:
+ - application/json
+ Set-Cookie:
+ - session_id=507fef45a35e7038c9a1cdb754acfc7539aac4a2; expires=Tue, 06 Feb 2018
+ 06:57:34 GMT; Path=/
+ body:
+ encoding: UTF-8
+ string: '{"return": [{"perms": [".*", "@wheel", "@runner", "@jobs", "@events"],
+ "start": 1517864254.835655, "token": "507fef45a35e7038c9a1cdb754acfc7539aac4a2",
+ "expire": 1517907454.835655, "user": "saltapi", "eauth": "pam"}]}'
+ http_version:
+ recorded_at: Mon, 05 Feb 2018 20:57:34 GMT
+- request:
+ method: post
+ uri: https://127.0.0.1:8000/
+ body:
+ encoding: UTF-8
+ string: '{"client":"local_async","tgt":"admin","fun":"cloud.profile","arg":["cluster_node","caasp-node-d43efb76"]}'
+ headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ Accept:
+ - application/json; charset=utf-8
+ User-Agent:
+ - Ruby
+ Host:
+ - 127.0.0.1:8000
+ Content-Type:
+ - application/json; charset=utf-8
+ X-Auth-Token:
+ - 507fef45a35e7038c9a1cdb754acfc7539aac4a2
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Content-Length:
+ - '67'
+ Access-Control-Expose-Headers:
+ - GET, POST
+ Cache-Control:
+ - private
+ Vary:
+ - Accept-Encoding
+ Server:
+ - CherryPy/3.6.0
+ Allow:
+ - GET, HEAD, POST
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Date:
+ - Mon, 05 Feb 2018 20:57:34 GMT
+ Access-Control-Allow-Origin:
+ - "*"
+ Content-Type:
+ - application/json
+ Set-Cookie:
+ - session_id=507fef45a35e7038c9a1cdb754acfc7539aac4a2; expires=Tue, 06 Feb 2018
+ 06:57:34 GMT; Path=/
+ body:
+ encoding: UTF-8
+ string: '{"return": [{"jid": "20180205205734878026", "minions": ["admin"]}]}'
+ http_version:
+ recorded_at: Mon, 05 Feb 2018 20:57:34 GMT
+recorded_with: VCR 3.0.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml new/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml
--- old/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,107 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: https://127.0.0.1:8000/login
+ body:
+ encoding: UTF-8
+ string: '{"username":"saltapi","password":"x3NTblSaFzs2+SjtkW8zkUXL65L5AWbTPScpuzYkcDjmBMgvzixpuPTxDERT7Y1JEjqkMo7KG0+r","eauth":"pam"}'
+ headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ Accept:
+ - application/json; charset=utf-8
+ User-Agent:
+ - Ruby
+ Host:
+ - 127.0.0.1:8000
+ Content-Type:
+ - application/json; charset=utf-8
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Content-Length:
+ - '216'
+ Access-Control-Expose-Headers:
+ - GET, POST
+ Vary:
+ - Accept-Encoding
+ Server:
+ - CherryPy/3.6.0
+ Allow:
+ - GET, HEAD, POST
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Date:
+ - Sat, 27 Jan 2018 00:12:03 GMT
+ Access-Control-Allow-Origin:
+ - "*"
+ X-Auth-Token:
+ - cd96f194d3bb5a73ea6f5acd427abe4ff677cb27
+ Content-Type:
+ - application/json
+ Set-Cookie:
+ - session_id=cd96f194d3bb5a73ea6f5acd427abe4ff677cb27; expires=Sat, 27 Jan 2018
+ 10:12:03 GMT; Path=/
+ body:
+ encoding: UTF-8
+ string: '{"return": [{"perms": [".*", "@wheel", "@runner", "@jobs", "@events"],
+ "start": 1517011923.448009, "token": "cd96f194d3bb5a73ea6f5acd427abe4ff677cb27",
+ "expire": 1517055123.44801, "user": "saltapi", "eauth": "pam"}]}'
+ http_version:
+ recorded_at: Sat, 27 Jan 2018 00:12:03 GMT
+- request:
+ method: post
+ uri: https://127.0.0.1:8000/
+ body:
+ encoding: UTF-8
+ string: '{"tgt":"*","fun":"saltutil.refresh_pillar","expr_form":"glob","client":"local"}'
+ headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ Accept:
+ - application/json; charset=utf-8
+ User-Agent:
+ - Ruby
+ Host:
+ - 127.0.0.1:8000
+ Content-Type:
+ - application/json; charset=utf-8
+ X-Auth-Token:
+ - cd96f194d3bb5a73ea6f5acd427abe4ff677cb27
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Content-Length:
+ - '41'
+ Access-Control-Expose-Headers:
+ - GET, POST
+ Cache-Control:
+ - private
+ Vary:
+ - Accept-Encoding
+ Server:
+ - CherryPy/3.6.0
+ Allow:
+ - GET, HEAD, POST
+ Access-Control-Allow-Credentials:
+ - 'true'
+ Date:
+ - Sat, 27 Jan 2018 00:12:03 GMT
+ Access-Control-Allow-Origin:
+ - "*"
+ Content-Type:
+ - application/json
+ Set-Cookie:
+ - session_id=cd96f194d3bb5a73ea6f5acd427abe4ff677cb27; expires=Sat, 27 Jan 2018
+ 10:12:03 GMT; Path=/
+ body:
+ encoding: UTF-8
+ string: '{"return": [{"admin": true, "ca": true}]}'
+ http_version:
+ recorded_at: Sat, 27 Jan 2018 00:12:04 GMT
+recorded_with: VCR 3.0.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/vendor/assets/javascript/bootstrap-slider.min.js new/velum-master/vendor/assets/javascript/bootstrap-slider.min.js
--- old/velum-master/vendor/assets/javascript/bootstrap-slider.min.js 1970-01-01 01:00:00.000000000 +0100
+++ new/velum-master/vendor/assets/javascript/bootstrap-slider.min.js 2018-02-22 10:57:04.000000000 +0100
@@ -0,0 +1,5 @@
+/*! =======================================================
+ VERSION 9.9.0
+========================================================= */
+"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f