diff --git a/devops-client/lib/devops-client/handler/handler.rb b/devops-client/lib/devops-client/handler/handler.rb index 18f6d14..3d4ffed 100644 --- a/devops-client/lib/devops-client/handler/handler.rb +++ b/devops-client/lib/devops-client/handler/handler.rb @@ -12,6 +12,7 @@ require "devops-client/handler/helpers/http_utils" require "devops-client/handler/helpers/outputtable" require "devops-client/handler/helpers/resources_fetcher" require "devops-client/handler/helpers/resources_selector" +require "devops-client/providers/providers" class Handler diff --git a/devops-client/lib/devops-client/providers/providers.rb b/devops-client/lib/devops-client/providers/providers.rb new file mode 100644 index 0000000..4c7b3c2 --- /dev/null +++ b/devops-client/lib/devops-client/providers/providers.rb @@ -0,0 +1,21 @@ +module Providers + Ec2 = 'ec2' + Openstack = 'openstack' + Static = 'static' + + def self.available + [Ec2, Openstack, Static] + end + + def self.functionalities + { + images: [Ec2, Openstack], + stack_templates: [Ec2, Openstack], + stacks: [Ec2, Openstack] + } + end + + def self.has_functionality?(provider, functionality) + functionalities.fetch(functionality).include?(provider.to_s) + end +end \ No newline at end of file diff --git a/devops-service/db/mongo/models/stack/stack_base.rb b/devops-service/db/mongo/models/stack/stack_base.rb index 427a7a1..94dd3f2 100644 --- a/devops-service/db/mongo/models/stack/stack_base.rb +++ b/devops-service/db/mongo/models/stack/stack_base.rb @@ -2,14 +2,15 @@ module Devops module Model class StackBase < MongoModel - attr_accessor :id, :project, :deploy_env, :stack_template, :cloud_stack_id, :provider + attr_accessor :id, :project, :deploy_env, :stack_template, :cloud_stack_id, :provider, :parameters types id: {type: String, empty: false}, - provider: {type: String, empty: false}, - project: {type: String, empty: false}, - deploy_env: {type: String, empty: false}, - stack_template: {type: String, empty: false}, - cloud_stack_id: {type: String, empty: false} + provider: {type: String, empty: false}, + project: {type: String, empty: false}, + deploy_env: {type: String, empty: false}, + stack_template: {type: String, empty: false} + # cloud_stack_id: {type: String, empty: true} + # TODO: add parameters Hash def initialize attrs={} self.id = attrs['id'] @@ -18,19 +19,22 @@ module Devops self.deploy_env = attrs['deploy_env'] self.stack_template = attrs['stack_template'] self.cloud_stack_id = attrs['cloud_stack_id'] + self.parameters = attrs['parameters'] self end def to_hash_without_id { provider: provider, - project: self.project, - deploy_env: self.deploy_env, - stack_template: self.stack_template, - cloud_stack_id: self.cloud_stack_id + project: project, + deploy_env: deploy_env, + stack_template: stack_template, + cloud_stack_id: cloud_stack_id, + parameters: parameters } end + # attrs should include: # - id (String) # - provider (String) @@ -51,6 +55,15 @@ module Devops raise 'override me' end + def delete_stack_in_cloud! + raise 'override me' + end + + def template_body + stack_template_model = DevopsService.mongo.stack_template(stack_template) + stack_template_model.template_body + end + end end end diff --git a/devops-service/db/mongo/models/stack/stack_factory.rb b/devops-service/db/mongo/models/stack/stack_factory.rb index f704563..be0e0fa 100644 --- a/devops-service/db/mongo/models/stack/stack_factory.rb +++ b/devops-service/db/mongo/models/stack/stack_factory.rb @@ -16,12 +16,12 @@ module Devops def self.get_class(provider) case provider - when ::Provider::Openstack::PROVIDER - StackOpenstack - when ::Provider::Ec2::PROVIDER - StackEc2 - else - raise InvalidRecord.new "Invalid provider: '#{provider}'" + when ::Provider::Openstack::PROVIDER + StackOpenstack + when ::Provider::Ec2::PROVIDER + StackEc2 + else + raise InvalidRecord.new "Invalid provider: '#{provider}'" end end diff --git a/devops-service/db/mongo/models/stack/stack_openstack.rb b/devops-service/db/mongo/models/stack/stack_openstack.rb index b2db156..83151ef 100644 --- a/devops-service/db/mongo/models/stack/stack_openstack.rb +++ b/devops-service/db/mongo/models/stack/stack_openstack.rb @@ -3,10 +3,17 @@ module Devops class StackOpenstack < StackBase def create_stack_in_cloud! - provider = ::Provider::ProviderFactory.get('openstack') - provider.create_stack(self) - # # create stack in Openstack - # self.cloud_stack_id = '4c712026-dcd5-4664-90b8-0915494c1332' + begin + provider = Provider::ProviderFactory.get('openstack') + self.cloud_stack_id = provider.create_stack(self) + rescue ProviderErrors::NameConflict + raise InvalidRecord.new "Duplicate key error: stack with name '#{self.id}' already exists in cloud" + end + end + + def delete_stack_in_cloud! + provider = Provider::ProviderFactory.get('openstack') + provider.delete_stack(self) end end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_base.rb b/devops-service/db/mongo/models/stack_template/stack_template_base.rb index 1ca7edc..910d824 100644 --- a/devops-service/db/mongo/models/stack_template/stack_template_base.rb +++ b/devops-service/db/mongo/models/stack_template/stack_template_base.rb @@ -5,23 +5,15 @@ module Devops module Model class StackTemplateBase < MongoModel - attr_accessor :id, :template_url, :template_json, :provider - - # Few words about template_url: - # In Amazon Cloudformation the template file must be stored on an Amazon S3 bucket, - # but for Openstack stacks it isn't neccessary (your may use local file). - # I decided to enforce template_url strategy using in openstack to reach more common - # interface between different providers' stack templates. + attr_accessor :id, :template_body, :provider types id: {type: String, empty: false}, - provider: {type: String, empty: false}, - template_json: {type: String, empty: false}, - template_url: {type: String, empty: false} + provider: {type: String, empty: false}, + template_body: {type: String, empty: false} def initialize(attrs) self.id = attrs['id'] - self.template_json = attrs['template_json'].to_s - self.template_url = attrs['template_url'] + self.template_body = attrs['template_body'] self.provider = attrs['provider'] self end @@ -29,23 +21,15 @@ module Devops def to_hash_without_id { provider: provider, - template_json: template_json, - template_url: template_url + template_body: template_body } end - # do not forget to destroy template files on template destroying - def delete_template_file_from_storage - raise 'Override me' - end - # attrs should include: # - id (String) # - provider (String) - # - template_json (String) + # - template_body (String) def self.create(attrs) - json = attrs['template_json'] - attrs['template_url'] = generate_template_file_and_upload_to_storage(attrs['id'], json) new(attrs) end @@ -54,24 +38,6 @@ module Devops self.new(attrs) end - class << self - private - - def generate_template_file_and_upload_to_storage(id, json) - tempfile = Tempfile.new('foo') - tempfile.write(json) - secure_filename = "#{id}-#{SecureRandom.hex}.template" - upload_file_to_storage(secure_filename, tempfile.path) - ensure - tempfile.close - tempfile.unlink - end - - def upload_file_to_storage(filename, file_path) - raise 'Override me' - end - - end end end end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb b/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb index d0bc8d8..7d12269 100644 --- a/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb +++ b/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb @@ -2,16 +2,50 @@ module Devops module Model class StackTemplateEc2 < StackTemplateBase + # In Amazon Cloudformation the template file must be stored on an Amazon S3 bucket. + attr_accessor :template_url + + types template_url: {type: String, empty: false} + + def initialize(attrs) + self.template_url = attrs['template_url'] + super(attrs) + end + + def to_hash_without_id + super.merge(template_url: template_url) + end + def delete_template_file_from_storage raise 'Implement me' end class << self + + def create(attrs) + template = attrs['template_body'] + attrs['template_url'] = generate_template_file_and_upload_to_storage(attrs['id'], template) + super(attrs) + end + private - def upload_file_to_storage(filename, path) - "https://s3.amazonaws.com/#{filename}" + def generate_template_file_and_upload_to_storage(id, json) + begin + tempfile = Tempfile.new('foo') + tempfile.write(json) + tempfile.close + secure_filename = "#{id}-#{SecureRandom.hex}.template" + upload_file_to_storage(secure_filename, tempfile.path) + ensure + tempfile.unlink + end end + + def upload_file_to_storage(filename, file_path) + "https://s3.amazonaws.com/#{filename}" + end + end end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_factory.rb b/devops-service/db/mongo/models/stack_template/stack_template_factory.rb index df2e5aa..15414d8 100644 --- a/devops-service/db/mongo/models/stack_template/stack_template_factory.rb +++ b/devops-service/db/mongo/models/stack_template/stack_template_factory.rb @@ -16,12 +16,12 @@ module Devops def self.get_class(provider) case provider - when ::Provider::Openstack::PROVIDER - StackTemplateOpenstack - when ::Provider::Ec2::PROVIDER - StackTemplateEc2 - else - raise InvalidRecord.new "Invalid provider: '#{provider}'" + when ::Provider::Openstack::PROVIDER + StackTemplateOpenstack + when ::Provider::Ec2::PROVIDER + StackTemplateEc2 + else + raise InvalidRecord.new "Invalid provider: '#{provider}'" end end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb b/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb index a085636..aa62adf 100644 --- a/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb +++ b/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb @@ -2,18 +2,6 @@ module Devops module Model class StackTemplateOpenstack < StackTemplateBase - def delete_template_file_from_storage - raise 'Implement me' - end - - class << self - private - - def upload_file_to_storage(filename, path) - "https://openstack_host/v1/my_account/#{filename}" - end - end - end end end diff --git a/devops-service/providers/base_provider.rb b/devops-service/providers/base_provider.rb index 77deb86..8e0195c 100644 --- a/devops-service/providers/base_provider.rb +++ b/devops-service/providers/base_provider.rb @@ -1,4 +1,5 @@ require "fog" +Dir["providers/exceptions/*.rb"].each {|file| require file } module Provider class BaseProvider diff --git a/devops-service/providers/exceptions/name_conflict.rb b/devops-service/providers/exceptions/name_conflict.rb new file mode 100644 index 0000000..f4b7af5 --- /dev/null +++ b/devops-service/providers/exceptions/name_conflict.rb @@ -0,0 +1,5 @@ +module ProviderErrors + class NameConflict < StandardError + + end +end \ No newline at end of file diff --git a/devops-service/providers/openstack.rb b/devops-service/providers/openstack.rb index 101c559..b3f92f7 100644 --- a/devops-service/providers/openstack.rb +++ b/devops-service/providers/openstack.rb @@ -192,9 +192,22 @@ module Provider end def create_stack(stack) - byebug - result = orchestration.create_stack(stack.id, {template_url: stack.template_url}) + begin + response = orchestration.create_stack(stack.id, { + template: stack.template_body, + tenant_id: connection_options[:openstack_tenant], + parameters: stack.parameters + }) + response[:body]['stack']['id'] + rescue Excon::Errors::Conflict => e + raise ProviderErrors::NameConflict + end end + + def delete_stack(stack) + orchestration.delete_stack(stack.id, stack.cloud_stack_id) + end + private def convert_groups list res = {} @@ -217,7 +230,7 @@ module Provider end def orchestration - @connection ||= Fog::Orchestration::OpenStack.new(connection_options) + @connection ||= Fog::Orchestration.new(connection_options) end end diff --git a/devops-service/providers/openstack_stub.rb b/devops-service/providers/openstack_stub.rb index c5d3dab..bdb28eb 100644 --- a/devops-service/providers/openstack_stub.rb +++ b/devops-service/providers/openstack_stub.rb @@ -61,4 +61,12 @@ class Provider::Openstack ] end + def create_stack(stack) + '4c712026-dcd5-4664-90b8-0915494c1332' + end + + def delete_stack(stack) + true + end + end \ No newline at end of file diff --git a/devops-service/routes/v2.0.rb b/devops-service/routes/v2.0.rb index 7fdae89..84520ff 100644 --- a/devops-service/routes/v2.0.rb +++ b/devops-service/routes/v2.0.rb @@ -18,6 +18,8 @@ require "routes/v2.0/server" require "routes/v2.0/script" require "routes/v2.0/status" require "routes/v2.0/bootstrap_templates" +require "routes/v2.0/stack_template" +require "routes/v2.0/stack" require "routes/v2.0/handlers/provider" require "routes/v2.0/handlers/bootstrap_templates" @@ -34,6 +36,8 @@ require "routes/v2.0/handlers/status" require "routes/v2.0/handlers/tag" require "routes/v2.0/handlers/user" require "routes/v2.0/handlers/server" +require "routes/v2.0/handlers/stack_template" +require "routes/v2.0/handlers/stack" require "routes/routes_container" require "auth/devops_auth" @@ -61,7 +65,9 @@ module Devops Devops::Version2_0::Routes::ServerRoutes, Devops::Version2_0::Routes::StatusRoutes, Devops::Version2_0::Routes::TagRoutes, - Devops::Version2_0::Routes::DeployRoutes + Devops::Version2_0::Routes::DeployRoutes, + Devops::Version2_0::Routes::StackTemplateRoutes, + Devops::Version2_0::Routes::StackRoutes ] def init diff --git a/devops-service/routes/v2.0/handlers/stack.rb b/devops-service/routes/v2.0/handlers/stack.rb new file mode 100644 index 0000000..1e1d794 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/stack.rb @@ -0,0 +1,60 @@ +require 'db/mongo/models/stack/stack_factory' + +module Devops + module Version2_0 + module Handler + class Stack + + def self.get_stacks + lambda { + check_privileges("stack", "r") + stacks = settings.mongo.stacks + json stacks.map(&:to_hash) + } + end + + def self.get_stacks_for_provider + lambda { + check_privileges("stack", "r") + check_provider(params[:provider]) + stacks = settings.mongo.stacks(params[:provider]) + json stacks.map(&:to_hash) + } + end + + def self.create_stack + lambda { + check_privileges("stack", "w") + + object = create_object_from_json_body + stack_model = Model::StackFactory.create(object['provider'], object) + settings.mongo.stack_insert(stack_model) + + create_response "Created", stack_model.to_hash, 201 + } + end + + def self.get_stack + lambda { + check_privileges("stack", "r") + stack = settings.mongo.stack(params[:stack_id]) + json stack.to_hash + } + end + + def self.delete_stack + lambda { + check_privileges("stack", "w") + + stack = settings.mongo.stack(params[:stack_id]) + stack.delete_stack_in_cloud! + settings.mongo.stack_delete(params[:stack_id]) + + create_response("Stack '#{params[:stack_id]}' has been removed") + } + end + + end + end + end +end \ No newline at end of file diff --git a/devops-service/routes/v2.0/handlers/stack_template.rb b/devops-service/routes/v2.0/handlers/stack_template.rb new file mode 100644 index 0000000..5a690bb --- /dev/null +++ b/devops-service/routes/v2.0/handlers/stack_template.rb @@ -0,0 +1,57 @@ +require 'db/mongo/models/stack_template/stack_template_factory' + +module Devops + module Version2_0 + module Handler + class StackTemplate + + def self.get_stack_templates + lambda { + check_privileges('stack_template', 'r') + stack_templates = settings.mongo.stack_templates + json stack_templates.map(&:to_hash) + } + end + + def self.get_stack_templates_for_provider + lambda { + check_privileges('stack_template', 'r') + check_provider(params[:provider]) + stack_templates = settings.mongo.stack_templates(params[:provider]) + json stack_templates.map(&:to_hash) + } + end + + def self.create_stack_template + lambda { + check_privileges('stack_template', 'w') + + attrs = create_object_from_json_body + template_model = Model::StackTemplateFactory.create(attrs['provider'], attrs) + + settings.mongo.stack_template_insert(template_model) + create_response 'Created', template_model.to_hash, 201 + } + end + + def self.get_stack_template + lambda { + check_privileges('stack_template', 'r') + stack_template = settings.mongo.stack_template(params[:stack_template_id]) + json stack_template.to_hash + } + end + + def self.delete_stack_template + lambda { + check_privileges('stack_template', 'w') + + settings.mongo.stack_template_delete params[:stack_template_id] + create_response("Template '#{params[:stack_template_id]}' has been removed") + } + end + + end + end + end +end \ No newline at end of file diff --git a/devops-service/routes/v2.0/stack.rb b/devops-service/routes/v2.0/stack.rb new file mode 100644 index 0000000..70c3364 --- /dev/null +++ b/devops-service/routes/v2.0/stack.rb @@ -0,0 +1,31 @@ +module Devops + module Version2_0 + module Routes + module StackRoutes + + def self.registered(app) + app.after %r{\A/stack_template(/[\w]+)?\z} do + statistic + end + + app.get_with_headers '/stacks', :headers => [:accept], &Devops::Version2_0::Handler::Stack.get_stacks + + app.get_with_headers '/stacks/provider/:provider', :headers => [:accept], &Devops::Version2_0::Handler::Stack.get_stacks_for_provider + + app.post_with_headers "/stack", :headers => [:accept], &Devops::Version2_0::Handler::Stack.create_stack + + hash = {} + + hash['GET'] = Devops::Version2_0::Handler::Stack.get_stack + + hash['DELETE'] = Devops::Version2_0::Handler::Stack.delete_stack + + app.multi_routes '/stack/:stack_id', {}, hash + + puts "Stack routes initialized" + end + + end + end + end +end \ No newline at end of file diff --git a/devops-service/routes/v2.0/stack_template.rb b/devops-service/routes/v2.0/stack_template.rb new file mode 100644 index 0000000..abf5488 --- /dev/null +++ b/devops-service/routes/v2.0/stack_template.rb @@ -0,0 +1,31 @@ +module Devops + module Version2_0 + module Routes + module StackTemplateRoutes + + def self.registered(app) + app.after %r{\A/stack_template(/[\w]+)?\z} do + statistic + end + + app.get_with_headers '/stack_templates', :headers => [:accept], &Devops::Version2_0::Handler::StackTemplate.get_stack_templates + + app.get_with_headers '/stack_templates/provider/:provider', :headers => [:accept], &Devops::Version2_0::Handler::StackTemplate.get_stack_templates_for_provider + + app.post_with_headers "/stack_template", :headers => [:accept], &Devops::Version2_0::Handler::StackTemplate.create_stack_template + + hash = {} + + hash['GET'] = Devops::Version2_0::Handler::StackTemplate.get_stack_template + + hash['DELETE'] = Devops::Version2_0::Handler::StackTemplate.delete_stack_template + + app.multi_routes '/stack_template/:stack_template_id', {}, hash + + puts "Stack_template routes initialized" + end + + end + end + end +end \ No newline at end of file