diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 00000000..34d4f527 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,21 @@ +--- +name: rubocop + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.0 + - name: Install gems + run: bundle install --jobs 4 --retry 3 + - name: Run RuboCop + run: bundle exec rubocop --parallel diff --git a/.gitignore b/.gitignore index 40c1a5c6..83fb1b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,11 @@ **/src/ **.env +# Remove after completing refactor +**/spec/dummy/segments/ +one_stack_rails/ +native + .dir-locals.el # rspec failure tracking diff --git a/.rubocop.yml b/.rubocop.yml index 2c0e8961..6980af60 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,15 @@ +--- +require: + - rubocop-rspec + - rubocop-performance +inherit_from: + - .rubocop_todo.yml AllCops: NewCops: enable TargetRubyVersion: 3.0.0 +Naming/FileName: + Exclude: + - '*/lib/onestack-*.rb' Layout/LineLength: Max: 120 Layout/SpaceAroundMethodCallOperator: @@ -19,21 +28,21 @@ Metrics/CyclomaticComplexity: Exclude: - cli/app/helpers/* Metrics/BlockLength: - IgnoredMethods: - - describe - - context - - shared_examples - - it - - xit - - xdescribe + # IgnoredMethods: + # - describe + # - context + # - shared_examples + # - it + # - xit + # - xdescribe Exclude: - '*/spec/**/*.rb' - '*/*.gemspec' -Metrics/MethodLength: - Exclude: - - cli/app/helpers/* - IgnoredMethods: - - create_table +# Metrics/MethodLength: + # Exclude: + # - cli/app/helpers/* + # IgnoredMethods: + # - create_table Metrics/PerceivedComplexity: Exclude: - cli/app/helpers/* diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..88c5a36f --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,6 @@ +--- +Gemspec/RequireMFA: + Enabled: false +Style/NestedTernaryOperator: + Exclude: + - '*/spec/dummy/config/application.rb' diff --git a/aws/.rubocop.yml b/aws/.rubocop.yml new file mode 100644 index 00000000..1e3e9a39 --- /dev/null +++ b/aws/.rubocop.yml @@ -0,0 +1,4 @@ +--- +inherit_from: + - ../.rubocop.yml + - .rubocop_todo.yml diff --git a/aws/.rubocop_todo.yml b/aws/.rubocop_todo.yml new file mode 100644 index 00000000..1d50eb03 --- /dev/null +++ b/aws/.rubocop_todo.yml @@ -0,0 +1,4 @@ +--- +Style/MissingRespondToMissing: + Exclude: + - app/models/aws/resource/ec2/instance.rb diff --git a/aws/Gemfile b/aws/Gemfile new file mode 100644 index 00000000..87000333 --- /dev/null +++ b/aws/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'onestack', path: '../onestack' +gem 'solid_app', path: '../solid_app' +gem 'solid_record', path: '../solid_record' +gem 'solid_support', path: '../solid_support' + +gemspec diff --git a/aws/app/models/aws/provider.rb b/aws/app/models/aws/provider.rb index 091e79c9..ed374827 100644 --- a/aws/app/models/aws/provider.rb +++ b/aws/app/models/aws/provider.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Aws - class Provider < Provider + class Provider < OneStack::Provider # standard credentials for aws store :config, accessors: %i[account_id access_key_id secret_access_key region] @@ -15,11 +15,9 @@ def client_config(resource_type) end def regions - begin - client.describe_regions[0].map { |r| r.region_name }.sort - rescue EC2::Errors::AuthFailure => e - raise Cnfs::Error, e.message - end + client.describe_regions[0].map(&:region_name).sort + rescue EC2::Errors::AuthFailure => e + raise OneStack::Error, e.message end def client() = @client ||= Resource::EC2::Instance.client(self) @@ -29,7 +27,7 @@ def client() = @client ||= Resource::EC2::Instance.client(self) def source() = super || 'hashicorp/aws' - def as_terraform + def as_terraform # rubocop:disable Metrics/MethodLength { terraform: { required_providers: { @@ -48,7 +46,6 @@ def as_terraform end # Pulumi Provisioner - def as_pulumi - end + def as_pulumi; end end end diff --git a/aws/app/models/aws/resource.rb b/aws/app/models/aws/resource.rb index 7b95c02e..ab58772d 100644 --- a/aws/app/models/aws/resource.rb +++ b/aws/app/models/aws/resource.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -class Aws::Resource < Resource - +class Aws::Resource < OneStack::Resource def client() = @client ||= self.class.client(provider) def valid_types() = super.merge(provider: 'Aws::Provider') @@ -10,12 +9,12 @@ class << self def client(provider) require "aws-sdk-#{service_name}" klass = "Aws::#{service_class_name}::Client".safe_constantize - raise Cnfs::Error, "AWS SDK client class not found for: #{service_name}" unless klass + raise OneStack::Error, "AWS SDK client class not found for: #{service_name}" unless klass config = client_config(provider) klass.new(config) - rescue LoadError => e - raise Cnfs::Error, "AWS SDK not found for: #{service_name}" + rescue LoadError => _e + raise OneStack::Error, "AWS SDK not found for: #{service_name}" end def client_config(provider) = provider.client_config(service_name) diff --git a/aws/app/models/aws/resource/ec2.rb b/aws/app/models/aws/resource/ec2.rb index 2684182b..151fc8f0 100644 --- a/aws/app/models/aws/resource/ec2.rb +++ b/aws/app/models/aws/resource/ec2.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class Aws::Resource::EC2 < Aws::Resource - def instance_types(family) - instance_type_offerings.select do |offer| - offer.split('.').first.eql?(family.to_s) - end.map { |offer| offer.split('.').last }.sort + def instance_types(family) = instance_types_list(family).map { |offer| offer.split('.').last }.sort + + def instance_types_list(family) + instance_type_offerings.select { |offer| offer.split('.').first.eql?(family.to_s) } end def offers_by_family @@ -12,7 +12,7 @@ def offers_by_family end def instance_type_offerings - @instance_type_offerings ||= client.describe_instance_type_offerings[0].map { |offer| offer.instance_type } + @instance_type_offerings ||= client.describe_instance_type_offerings[0].map(&:instance_type) end def describe_images(owners:, filters:) diff --git a/aws/app/models/aws/resource/ec2/instance.rb b/aws/app/models/aws/resource/ec2/instance.rb index a0fbe56d..c406eff6 100644 --- a/aws/app/models/aws/resource/ec2/instance.rb +++ b/aws/app/models/aws/resource/ec2/instance.rb @@ -2,7 +2,7 @@ class Aws::Resource::EC2::Instance < Aws::Resource::EC2 # store :config, accessors: %i[family size instance_count ami key_name monitoring - # vpc_security_group_ids subnet_id subnet_ids] + # vpc_security_group_ids subnet_id subnet_ids] # store :config, accessors: %i[public_ip os_type arn] # store :envs, accessors: %i[public_ip os_type arn] @@ -10,7 +10,7 @@ class Aws::Resource::EC2::Instance < Aws::Resource::EC2 def instance_id() = config[:id] - def valid_types() = super.merge(runtime: %w[Compose::Runtime Skaffold::Runtime]) + def valid_types() = super.merge(runtime: %w[Compose::Runtime Skaffold::Runtime]) def describe() = @describe ||= describe_instances(instance_id).reservations.first.instances.first @@ -35,5 +35,5 @@ def ssh_user_map }.with_indifferent_access end - def method_missing(method, *args) = config[method] + def method_missing(method, *_args) = config[method] end diff --git a/aws/app/models/aws/resource/ec2/vpc.rb b/aws/app/models/aws/resource/ec2/vpc.rb index d2442c45..6e75bfcf 100644 --- a/aws/app/models/aws/resource/ec2/vpc.rb +++ b/aws/app/models/aws/resource/ec2/vpc.rb @@ -2,7 +2,7 @@ class Aws::Resource::EC2::Vpc < Aws::Resource::EC2 store :config, accessors: %i[azs cidr enable_nat_gateway - enable_vpn_gateway private_subnets public_subnets], coder: YAML + enable_vpn_gateway private_subnets public_subnets], coder: YAML def source super || 'terraform-aws-modules/vpc/aws' @@ -12,7 +12,5 @@ def version super || '~> 2.64.0' end - def available_azs - @azs ||= client.describe_availability_zones[0].map { |z| z.zone_name } - end + def available_azs() = @available_azs ||= client.describe_availability_zones[0].map(&:zone_name) end diff --git a/aws/app/models/aws/resource/eks/cluster.rb b/aws/app/models/aws/resource/eks/cluster.rb index aa315d2f..f649dc6a 100644 --- a/aws/app/models/aws/resource/eks/cluster.rb +++ b/aws/app/models/aws/resource/eks/cluster.rb @@ -8,14 +8,13 @@ def kubectl_context(target) "arn:aws:eks:#{region}:#{account_id}:cluster/#{target.cluster_name.cnfs_sub}" end - # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength # NOTE: This will work with a cluster on any target since the provider instance, ie credentials, is per target def init_cluster(target, options) credentials_file = "#{Dir.home}/.aws/credentials" unless File.exist?(credentials_file) || ENV['AWS_ACCESS_KEY_ID'] - STDOUT.puts "missing #{credentials_file}" + $stdout.puts "missing #{credentials_file}" return end @@ -31,10 +30,9 @@ def init_cluster(target, options) if options.long || target.role_name cmd_string = "#{cmd_string} --role-arn arn:aws:iam::#{account_id}:role/#{target.role_name}" end - binding.pry + # binding.pry cmd_string end - # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength # def client @@ -48,14 +46,14 @@ def init_cluster(target, options) # class Target::Kubernetes < Target # store :config, accessors: %i[role_name cluster_name], coder: YAML -# +# # validates :role_name, presence: true # validates :cluster_name, presence: true -# +# # def role_name # super.cnfs_sub # end -# +# # def init(options) # provider.init_cluster(self, options) # end diff --git a/aws/app/models/aws/resource/rds.rb b/aws/app/models/aws/resource/rds.rb index 1b93c96a..043eac74 100644 --- a/aws/app/models/aws/resource/rds.rb +++ b/aws/app/models/aws/resource/rds.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # See: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/RDS/Client.html class Aws::Resource::RDS < Aws::Resource @@ -9,9 +10,9 @@ def instance_types(family) def offers_by_family @offers_by_family ||= reserved_db_instances_offerings.map { |offer| offer[0...offer.rindex('.')] }.uniq.sort end - + def reserved_db_instances_offerings - @reserved_db_instances_offerings ||= client.describe_reserved_db_instances_offerings. - reserved_db_instances_offerings.map { |offer| offer.db_instance_class } + @reserved_db_instances_offerings ||= client.describe_reserved_db_instances_offerings + .reserved_db_instances_offerings.map(&:db_instance_class) end end diff --git a/aws/app/models/aws/resource/redshift.rb b/aws/app/models/aws/resource/redshift.rb index 50b96614..585c2d86 100644 --- a/aws/app/models/aws/resource/redshift.rb +++ b/aws/app/models/aws/resource/redshift.rb @@ -1,16 +1,11 @@ # frozen_string_literal: true + # See: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Redshift/Client.html class Aws::Resource::Redshift < Aws::Resource - def instance_types(family) - @types ||= offers.select { |offer| offer.start_with?(_family) }.sort - end + def instance_types(family) = @instance_types ||= offers.select { |offer| offer.start_with?(family) }.sort + + def offers_by_family() = @offers_by_family ||= offers.map { |offer| offer.split('.').shift }.uniq.sort - def offers_by_family - @offers_by_family ||= offers.map { |offer| offer.split('.').shift }.uniq.sort - end - - def offers - @offers ||= client.describe_reserved_node_offerings.reserved_node_offerings.map { |offer| offer.node_type } - end + def offers = @offers ||= client.describe_reserved_node_offerings.reserved_node_offerings.map(&:node_type) end diff --git a/aws/app/views/aws/plan_view.rb b/aws/app/views/aws/plan_view.rb index e75f34c7..a4170cef 100644 --- a/aws/app/views/aws/plan_view.rb +++ b/aws/app/views/aws/plan_view.rb @@ -1,16 +1,14 @@ # frozen_string_literal: true -class Aws::PlanView < ApplicationView - +class Aws::PlanView < OneStack::ApplicationView def create # model.name = ask('Blueprint name:', value: random_string('instance')) - binding.pry + # binding.pry p_ask(:name) - provider_name = enum_select('Provider:', Provider.where(type: 'Provider::Aws').pluck(:name)) - model.provider = Provider.find_by(name: provider_name) + provider_name = enum_select('Provider:', OneStack::Provider.where(type: 'Provider::Aws').pluck(:name)) + model.provider = OneStack::Provider.find_by(name: provider_name) # TODO: These two will become configurable - model.builder = Builder.find_by(name: 'terraform') - model.runtime = Runtime.find_by(name: 'compose') + model.builder = OneStack::Builder.find_by(name: 'terraform') + model.runtime = OneStack::Runtime.find_by(name: 'compose') end end - diff --git a/aws/app/views/aws/provider_view.rb b/aws/app/views/aws/provider_view.rb index 71a0e011..af675b0d 100644 --- a/aws/app/views/aws/provider_view.rb +++ b/aws/app/views/aws/provider_view.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Aws::ProviderView < ProviderView +class Aws::ProviderView < OneStack::ProviderView def modify super mask_attr(:access_key_id) diff --git a/aws/app/views/aws/resource/acm/certificate_view.rb b/aws/app/views/aws/resource/acm/certificate_view.rb index 516253ab..66431d53 100644 --- a/aws/app/views/aws/resource/acm/certificate_view.rb +++ b/aws/app/views/aws/resource/acm/certificate_view.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -class Aws::Resource::ACM::CertificateView < ResourceView +class Aws::Resource::ACM::CertificateView < OneStack::ResourceView end diff --git a/aws/app/views/aws/resource/ec2/instance_view.rb b/aws/app/views/aws/resource/ec2/instance_view.rb index 7f47db34..8c490756 100644 --- a/aws/app/views/aws/resource/ec2/instance_view.rb +++ b/aws/app/views/aws/resource/ec2/instance_view.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -class Aws::Resource::EC2::InstanceView < ResourceView - def edit +class Aws::Resource::EC2::InstanceView < OneStack::ResourceView + def edit # rubocop:disable Metrics/AbcSize model.family = view_select(:instance_family, model.offers_by_family, model.family) instance_types = model.instance_types(model.family) model.size = view_select(:instance_type, instance_types, model.size) @@ -9,7 +9,7 @@ def edit model.name ||= ask('Instance name:', value: random_string(blueprint.name)) model.key_name = ask('Key pair:', value: model.key_name || '') os = enum_select('OS', os_values.keys) - model.ami = os_images(os).sort_by{ |image| image.creation_date }.reverse.first.image_id + model.ami = os_images(os).max_by(&:creation_date).image_id model.instance_count = ask('Instance count:', value: model.instance_count.to_s, convert: :integer) end @@ -22,7 +22,7 @@ def os_images(os) def os_values { debian: { owner: '136693071363', name: 'debian-10-amd64-*' }, - ubuntu: { owner: '099720109477', name: 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*' }, + ubuntu: { owner: '099720109477', name: 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*' } }.with_indifferent_access end end diff --git a/aws/app/views/aws/resource/ec2/vpc_view.rb b/aws/app/views/aws/resource/ec2/vpc_view.rb index 626e46e8..dcdbdeb4 100644 --- a/aws/app/views/aws/resource/ec2/vpc_view.rb +++ b/aws/app/views/aws/resource/ec2/vpc_view.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Aws::Resource::EC2::VpcView < ResourceView +class Aws::Resource::EC2::VpcView < OneStack::ResourceView def edit model.azs = multi_select('Availabiity Zones:', model.available_azs) do |menu| # menu.default 1, 3 diff --git a/aws/app/views/aws/resource/eks/cluster_view.rb b/aws/app/views/aws/resource/eks/cluster_view.rb index a84400f7..15d67c88 100644 --- a/aws/app/views/aws/resource/eks/cluster_view.rb +++ b/aws/app/views/aws/resource/eks/cluster_view.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -class Aws::Resource::EKS::ClusterView < ResourceView +class Aws::Resource::EKS::ClusterView < OneStack::ResourceView end diff --git a/aws/app/views/aws/resource/rds/dbinstance_view.rb b/aws/app/views/aws/resource/rds/dbinstance_view.rb index 6ea60c8f..98da85ce 100644 --- a/aws/app/views/aws/resource/rds/dbinstance_view.rb +++ b/aws/app/views/aws/resource/rds/dbinstance_view.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true -class Aws::Resource::RDS::DBInstanceView < ResourceView +class Aws::Resource::RDS::DBInstanceView < OneStack::ResourceView attr_accessor :selected_family - def edit - @selected_family = select('Instance family:', per_page: per_page(model.offers_by_family), filter: true, show_help: :always) do |menu| + def edit # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity + @selected_family = select('Instance family:', per_page: per_page(model.offers_by_family), filter: true, + show_help: :always) do |menu| menu.choices model.offers_by_family - menu.default ((offers_by_family.index(model.family) || 0) + 1) if model.family + menu.default((offers_by_family.index(model.family) || 0) + 1) if model.family menu.help 'Type to filter results' end instance_types = model.instance_types(selected_family) size = select('Instance type:', per_page: per_page(instance_types), filter: true, show_help: :always) do |menu| - menu.default ((instance_types.index(model.size) || 0) + 1) if model.size + menu.default((instance_types.index(model.size) || 0) + 1) if model.size menu.choices instance_types menu.help 'Type to filter results' end @@ -26,11 +27,11 @@ def edit def configure self.db_instance_class = prompt.enum_select('Instance class:', instance_types, per_page: instance_types.size) # resp = client.describe_reserved_db_instances({ - # db_instance_class: "db.t2.micro", - # duration: "1y", - # multi_az: false, - # offering_type: "No Upfront", - # product_description: "mysql", + # db_instance_class: "db.t2.micro", + # duration: "1y", + # multi_az: false, + # offering_type: "No Upfront", + # product_description: "mysql", # }) end end diff --git a/aws/app/views/aws/resource/redshift/cluster_view.rb b/aws/app/views/aws/resource/redshift/cluster_view.rb index c140926a..58d6de01 100644 --- a/aws/app/views/aws/resource/redshift/cluster_view.rb +++ b/aws/app/views/aws/resource/redshift/cluster_view.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -class Aws::Resource::Redshift::ClusterView < ResourceView +class Aws::Resource::Redshift::ClusterView < OneStack::ResourceView attr_accessor :selected_family def edit - @selected_family ||= prompt.enum_select('Instance family:', model.offers_by_family, per_page: per_page(model.offers_by_family)) + @selected_family ||= prompt.enum_select('Instance family:', model.offers_by_family, + per_page: per_page(model.offers_by_family)) instance_types = model.instance_types(selected_family) model.node_type = enum_select('Node type:', instance_types, per_page: per_page(instance_types)) end end - diff --git a/aws/app/views/aws/resource/route53/view.rb b/aws/app/views/aws/resource/route53/view.rb index 0a9907b2..57fae0b7 100644 --- a/aws/app/views/aws/resource/route53/view.rb +++ b/aws/app/views/aws/resource/route53/view.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Aws::Resource::Route53::View < ResourceView +class Aws::Resource::Route53::View < OneStack::ResourceView attr_accessor :model, :region def render(model) @@ -9,13 +9,13 @@ def render(model) provider = blueprint.provider model.provider = provider - zones = hosted_zones.map {|zone| zone.name } + zones = hosted_zones.map(&:name) model.zone = enum_select('Zone:', zones, per_page: zones.size) - binding.pry end class Zone - attr_accessor :name, :id, :resource_record_sets, :client + attr_accessor :name, :id, :client + attr_writer :resource_record_sets def initialize(name:, id:, client:) @name = name @@ -38,5 +38,5 @@ def list_hosted_zones @list_hosted_zones ||= client.list_hosted_zones end - def client; model.client end + def client() = model.client end diff --git a/aws/app/views/aws/resource/s3/bucket_view.rb b/aws/app/views/aws/resource/s3/bucket_view.rb index a52b1c12..4fab479a 100644 --- a/aws/app/views/aws/resource/s3/bucket_view.rb +++ b/aws/app/views/aws/resource/s3/bucket_view.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -class Aws::Resource::S3::BucketView < ResourceView +class Aws::Resource::S3::BucketView < OneStack::ResourceView def edit # if yes?('Create a new bucket?') # model.name = ask('Budket name:', value: "#{blueprint.name}-#{random_string}") # else - model.name = enum_select('Bucket name:', list_buckets.map(&:name), per_page: list_buckets.size) - #end + model.name = enum_select('Bucket name:', list_buckets.map(&:name), per_page: list_buckets.size) + # end end end diff --git a/aws/bin/console b/aws/bin/console index be7c623d..aa32a757 100755 --- a/aws/bin/console +++ b/aws/bin/console @@ -2,11 +2,8 @@ # frozen_string_literal: true require 'bundler/setup' -require 'cnfs/cli/aws' -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -require 'pry' -Pry.start +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/aws/config/providers.yml b/aws/config/providers.yml deleted file mode 100644 index 5706357d..00000000 --- a/aws/config/providers.yml +++ /dev/null @@ -1,19 +0,0 @@ -# providers.yml -# Providers are defined to map to what is available as TF templates -# Generic providers, e.g. localstack are defined in the CLI -# NOTE: Important to be indented two spaces ---- - amazon: &AMAZON_DEFAULTS - name: $LABEL - type: Provider::Aws - config: - tf_version: '>= 2.38.0' - alicloud: &ALICLOUD_DEFAULTS - name: $LABEL - type: Provider::Alicloud - azure: &AZURE_DEFAULTS - name: $LABEL - type: Provider::Azure - gcp: &GCP_DEFAULTS - name: $LABEL - type: Provider::Gcp diff --git a/aws/config/runtimes.yml b/aws/config/runtimes.yml deleted file mode 100644 index 3c546f6e..00000000 --- a/aws/config/runtimes.yml +++ /dev/null @@ -1,15 +0,0 @@ -# runtimes.yml ---- -skaffold: - <<: *DEFAULTS - type: Runtime::Skaffold - config: - version: skaffold/v1 - -terraform: - <<: *DEFAULTS - type: Runtime::Terraform - config: - version: 0.12.6 - custom_providers: - restapi: https://github.com/Mastercard/terraform-provider-restapi/releases/download/v1.10.0/terraform-provider-restapi_v1.10.0-{platform}-amd64 diff --git a/aws/lib/cnfs/aws.rb b/aws/lib/cnfs/aws.rb deleted file mode 100644 index 7f3f95ea..00000000 --- a/aws/lib/cnfs/aws.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require_relative 'aws/version' -require_relative 'aws/plugin' - -module Aws - module Concerns; end -end - -module Cnfs - module Aws - def self.gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end -end diff --git a/aws/lib/cnfs/aws/plugin.rb b/aws/lib/one_stack/aws/plugin.rb similarity index 59% rename from aws/lib/cnfs/aws/plugin.rb rename to aws/lib/one_stack/aws/plugin.rb index a225674c..8bcf17b6 100644 --- a/aws/lib/cnfs/aws/plugin.rb +++ b/aws/lib/one_stack/aws/plugin.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -module Cnfs +module OneStack module Aws - class Plugin < Cnfs::Plugin - initializer 'add console shortcuts' do |app| - Cnfs.logger.info('[Aws] Initializing from', gem_root) + class Plugin < OneStack::Plugin + config.before_initialize { |config| OneStack::Aws.config.merge!(config.aws) } - Cnfs.subscribers << ActiveSupport::Notifications.subscribe('add_console_shortcuts.cnfs') do |event| + initializer 'add console shortcuts' do |_app| + # OneStack.logger.info('[Aws] Initializing from', gem_root) + + SolidSupport.subscribers << ActiveSupport::Notifications.subscribe('add_console_shortcuts.onestack') do |event| add_console_shortcuts(event.payload[:shortcuts]) end end @@ -17,7 +19,7 @@ def before_loader_setup(loader) end def add_inflectors(loader) - Cnfs.logger.info '[Aws] Configuring loader' + OneStack.logger.info '[Aws] Configuring loader' loader.inflector.inflect( 'acm' => 'ACM', @@ -25,12 +27,12 @@ def add_inflectors(loader) 'eks' => 'EKS', 'rds' => 'RDS', 'dbinstance' => 'DBInstance', - 'dbinstance_view' => 'DBInstanceView', + 'dbinstance_view' => 'DBInstanceView' ) end def add_console_shortcuts(shortcuts) - Cnfs.logger.info '[Aws] Adding console shortcuts' + OneStack.logger.info '[Aws] Adding console shortcuts' shortcuts.merge!( acm: ::Aws::Resource::ACM::Certificate, ec2: ::Aws::Resource::EC2::Instance, @@ -41,7 +43,7 @@ def add_console_shortcuts(shortcuts) ) end - def gem_root() = Cnfs::Aws.gem_root + def gem_root() = OneStack::Aws.gem_root end end end diff --git a/aws/lib/cnfs/aws/version.rb b/aws/lib/one_stack/aws/version.rb similarity index 82% rename from aws/lib/cnfs/aws/version.rb rename to aws/lib/one_stack/aws/version.rb index 37f61572..8d170980 100644 --- a/aws/lib/cnfs/aws/version.rb +++ b/aws/lib/one_stack/aws/version.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Cnfs +module OneStack module Aws VERSION = '0.1.0' end diff --git a/aws/lib/onestack-aws.rb b/aws/lib/onestack-aws.rb new file mode 100644 index 00000000..d719ca44 --- /dev/null +++ b/aws/lib/onestack-aws.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative 'one_stack/aws/version' + +require 'onestack' + +require_relative 'one_stack/aws/plugin' + +module Aws + module Concerns; end +end + +module OneStack + module Aws + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + def config() = @config ||= ActiveSupport::OrderedOptions.new + end + end +end diff --git a/aws/cnfs-aws.gemspec b/aws/onestack-aws.gemspec similarity index 69% rename from aws/cnfs-aws.gemspec rename to aws/onestack-aws.gemspec index 2c42a44d..441e1851 100644 --- a/aws/cnfs-aws.gemspec +++ b/aws/onestack-aws.gemspec @@ -1,18 +1,18 @@ # frozen_string_literal: true -require_relative 'lib/cnfs/aws/version' +require_relative 'lib/one_stack/aws/version' Gem::Specification.new do |spec| - spec.name = 'cnfs-aws' - spec.version = Cnfs::Aws::VERSION + spec.name = 'onestack-aws' + spec.version = OneStack::Aws::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] - spec.summary = 'CNFS CLI plugin for Amazon Web Services' - spec.description = 'CNFS CLI plugin to create CNFS compatible blueprints for AWS' + spec.summary = 'OneStack plugin for Amazon Web Services' + spec.description = 'OneStack plugin to create CNFS compatible blueprints for AWS' spec.homepage = 'https://cnfs.io' spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" @@ -29,7 +29,6 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'cnfs-core', '~> 0.1.0' spec.add_dependency 'aws-sdk-acm', '~> 1.38' spec.add_dependency 'aws-sdk-ec2', '~> 1.211' spec.add_dependency 'aws-sdk-eks', '~> 1.46' @@ -37,8 +36,14 @@ Gem::Specification.new do |spec| spec.add_dependency 'aws-sdk-redshift', '~> 1.51' spec.add_dependency 'aws-sdk-route53', '~> 1.44' spec.add_dependency 'aws-sdk-s3', '~> 1.86' + spec.add_dependency 'onestack', '~> 0.1.0' spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'guard-rspec', '~> 4.7' + spec.add_development_dependency 'pry-byebug', '~> 3.9' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'rubocop', '~> 1.22' + spec.add_development_dependency 'rubocop-performance', '~> 1.13' + spec.add_development_dependency 'rubocop-rspec', '~> 2.7' end diff --git a/aws/spec/dummy/config/application.rb b/aws/spec/dummy/config/application.rb new file mode 100644 index 00000000..b66e35bf --- /dev/null +++ b/aws/spec/dummy/config/application.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + config.solid_record.namespace = :one_stack + config.solid_record.load_paths = [ + { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/cnfs/spec/fixtures/config/boot.rb b/aws/spec/dummy/config/boot.rb similarity index 100% rename from cnfs/spec/fixtures/config/boot.rb rename to aws/spec/dummy/config/boot.rb diff --git a/cnfs/spec/fixtures/config/environment.rb b/aws/spec/dummy/config/environment.rb similarity index 77% rename from cnfs/spec/fixtures/config/environment.rb rename to aws/spec/dummy/config/environment.rb index 37320d4a..2ff3c59d 100644 --- a/cnfs/spec/fixtures/config/environment.rb +++ b/aws/spec/dummy/config/environment.rb @@ -4,4 +4,4 @@ require_relative 'application' # Initialize the application -Cnfs.application.initialize! +OneStack.application.initialize! diff --git a/aws/spec/dummy/config/segment.yml b/aws/spec/dummy/config/segment.yml new file mode 100644 index 00000000..f53a5592 --- /dev/null +++ b/aws/spec/dummy/config/segment.yml @@ -0,0 +1,17 @@ +--- +segments_type: stack +# default: + # segment_name: backend +# config: +# domain: cnfs.io +# host: host.${config.domain} +# targets: +# one: one.${config.host} +# two: two.${config.host} +# segments_type: target +# default: +# provisioner_name: terraform +# repository_name: cnfs-projects +# resource_name: instance1 +# runtime_name: compose +# segment_name: backend diff --git a/aws/spec/dummy/segments/providers.yml b/aws/spec/dummy/segments/providers.yml new file mode 100644 index 00000000..8f776b12 --- /dev/null +++ b/aws/spec/dummy/segments/providers.yml @@ -0,0 +1,28 @@ +--- +aws_east: + config: + region: test + access_key_id: !binary |- + bkXOJoVxNVALMcbpVydnCw1wWzWOd5+Od9i6xmjAYVprl2Yu+Ct+ + inherit: true + tags: {} + type: Aws::Provider +aws_west: + config: + region: us-west-1 + tags: {} + type: Aws::Provider +localstack: + config: + account_id: 123456789012 + region: localstack + s3: + endpoint: http://localstack:4572 + force_path_style: true + sqs: + endpoint: http://localstack:4576 + access_key_id: !binary |- + WsiMGNGoRmJNyv0TNDteRDKT0TMn0GFRwYH+AP7SFoUUccj1HR/T4pU= + inherit: true + tags: {} + type: Aws::Provider diff --git a/core/spec/fixtures/segments/node/stacks/resources.yml b/aws/spec/dummy/segments/resources.yml similarity index 51% rename from core/spec/fixtures/segments/node/stacks/resources.yml rename to aws/spec/dummy/segments/resources.yml index 1aad112a..75de65e6 100644 --- a/core/spec/fixtures/segments/node/stacks/resources.yml +++ b/aws/spec/dummy/segments/resources.yml @@ -11,3 +11,16 @@ instance: type: Aws::Resource::EC2::Instance runtime_name: compose provider_name: aws_apse_1 +res1: + config: + family: r2 + size: newton + inherit: false + runtime_name: compose + type: Aws::Resource::EC2::Instance +bucket2: + config: + family: ah23 + size: cool! + provider_name: aws_east + type: Aws::Resource::S3::Bucket diff --git a/aws/spec/spec_helper.rb b/aws/spec/spec_helper.rb index 8a4237d4..fda41792 100644 --- a/aws/spec/spec_helper.rb +++ b/aws/spec/spec_helper.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'pathname' - -SPEC_DIR = Pathname.new(__dir__) - -require_relative '../../spec/spec_helper' +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' diff --git a/cnfs/.rubocop.yml b/cnfs/.rubocop.yml deleted file mode 100644 index f22e5973..00000000 --- a/cnfs/.rubocop.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -inherit_from: - - ../.rubocop.yml -Lint/EmptyClass: - Exclude: - - 'lib/cnfs/plugins/core.rb' diff --git a/cnfs/app/controllers/cnfs/concerns/command_controller.rb b/cnfs/app/controllers/cnfs/concerns/command_controller.rb deleted file mode 100644 index 9dbe665f..00000000 --- a/cnfs/app/controllers/cnfs/concerns/command_controller.rb +++ /dev/null @@ -1,124 +0,0 @@ -# frozen_string_literal: true - -require 'active_support/concern' -require 'active_support/inflector' - -module Cnfs::Concerns::CommandController - extend ActiveSupport::Concern - - attr_accessor :calling_method - - # rubocop:disable Metrics/BlockLength - class_methods do - # Add an option and it's values to be referenced by cnfs_class_options or cnfs_options - def add_cnfs_option(name, options = {}) - @shared_options ||= {} - @shared_options[name] = options - end - - def cnfs_class_options(*option_names) - option_names.flatten.each do |option_name| - opt = @shared_options[option_name] - raise "Tried to access shared option '#{option_name}' but it was not previously defined" if opt.nil? - - class_option option_name, opt - end - end - - def cnfs_options(*option_names) - option_names.flatten.each do |option_name| - opt = @shared_options[option_name] - raise "Tried to access shared option '#{option_name}' but it was not previously defined" if opt.nil? - - option option_name, opt - end - end - - # Add an option and it's values to a specific command in a controller - # Used by plugin modules to add options to an existing command - def add_cnfs_method_option(method_name, options_name, **options) - @cnfs_method_options ||= {} - @cnfs_method_options[method_name] ||= {} - @cnfs_method_options[method_name][options_name] = options - end - - def cnfs_method_options(method_name) - return unless (command = @cnfs_method_options.try(:[], method_name)) - - command.each { |name, values| option name, values } - end - - def add_cnfs_action(lifecycle_command, method_name) - lifecycle, *command = lifecycle_command.to_s.split('_') - command = command.join('_').to_sym - @cnfs_actions ||= {} - @cnfs_actions[command] ||= [] - @cnfs_actions[command].append({ lifecycle: lifecycle, method_name: method_name }) - end - - def cnfs_actions(method_name) - return unless (actions = @cnfs_actions.try(:[], method_name)) - - actions.each { |action| send(action[:lifecycle], action[:method_name]) } - end - end - # rubocop:enable Metrics/BlockLength - - included do |_base| - add_cnfs_option :dry_run, desc: 'Do not execute commands', - aliases: '-d', type: :boolean - add_cnfs_option :force, desc: 'Do not prompt for confirmation', - aliases: '-f', type: :boolean - add_cnfs_option :quiet, desc: 'Do not output execute commands', - aliases: '-q', type: :boolean - end - - private - - # Invokes the appropriate method on the appropriate controller. Default is ExecController#base_execute - def execute(**kwargs) - namespace = kwargs.delete(:namespace) || self.class.name.deconstantize - controller = kwargs.delete(:controller) || :exec - method = kwargs.delete(:method)&.to_sym - - # The name of the method that invoked 'execute', e.g. 'console' - @calling_method = caller_locations(1).first.label.to_sym - - # Arguments and Options that will be passed to the controller - @args = Thor::CoreExt::HashWithIndifferentAccess.new(kwargs) - @options = Thor::CoreExt::HashWithIndifferentAccess.new(options) - - controller_klass = controller_class(namespace: namespace, controller: controller) - controller_obj = controller_klass.new(**controller_args) - - # If a method was specified then invoke that method - # If not, then check if the controller implements a method of the same name as the calling_method - # Otherwise invoke the default #execute method - method ||= controller_obj.respond_to?(calling_method) ? calling_method : :execute - controller_obj.base_execute(method) - end - - # Return the class to execute using the following priorities: - # 1. If a controller exists in the same or specified namespace with the name of the calling method then return it - # 2. Otherwise return the specified or default namespace and controller - def controller_class(namespace:, controller:) # , method:) - class_names = ["#{namespace}/#{calling_method}_controller", "#{namespace}/#{controller}_controller"] - class_names.each do |class_name| - next unless (klass = class_name.classify.safe_constantize) - - return klass - end - # Cnfs.logger.debug('controller classes not found:', class_names.join(' ')) - raise Cnfs::Error, set_color("Class not found: #{class_names.join(' ')} (this is a bug. please let us know)", :red) - end - - # Override to provide custom arguments and/or options to the exec controller - def controller_args() = { options: options, args: args, command: calling_method } - - # Will raise an error unless force option is provided or user confirms the action - def validate_destroy(msg = "\n#{'WARNING!!! ' * 5}\nAction cannot be reversed\nAre you sure?") - return true if options.force || yes?(msg) - - raise Cnfs::Error, 'Operation cancelled' - end -end diff --git a/cnfs/app/controllers/cnfs/concerns/exec_controller.rb b/cnfs/app/controllers/cnfs/concerns/exec_controller.rb deleted file mode 100644 index 25552072..00000000 --- a/cnfs/app/controllers/cnfs/concerns/exec_controller.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'active_model' - -module Cnfs - module Concerns - module ExecController - extend ActiveSupport::Concern - - included do - extend ActiveModel::Callbacks - include ActiveModel::AttributeAssignment - - attr_accessor :options, :args, :command - - define_model_callbacks :execute - - # Define methods for each command controller on exec controllers to invoke methods on command controllers - # e.g. execute_image(:build, *services) => Image::CommandController#build - Cnfs::MainController.all_commands.keys.each do |cmd| - define_method("execute_#{cmd}") do |*args| - Cnfs::MainController.new.send(cmd.to_sym, *args) - end - end - end - - def initialize(**kwargs) = assign_attributes(**kwargs) - - # This method is invoked from Cnfs::Concerns::CommandController execute method - # and invokes the target method wrapped in any defined callbacks - def base_execute(method) = run_callbacks(:execute) { send(method) } - - # Implement with an around_execute :timer call in the controller - def timer(&block) = Cnfs.with_timer('Command execution', &block) - end - end -end diff --git a/cnfs/app/controllers/cnfs/concerns/new_controller.rb b/cnfs/app/controllers/cnfs/concerns/new_controller.rb deleted file mode 100644 index f24cbf0a..00000000 --- a/cnfs/app/controllers/cnfs/concerns/new_controller.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'active_support/concern' - -module Cnfs::Concerns::NewController - extend ActiveSupport::Concern - - included do - include Cnfs::Concerns::CommandController - end - - class_methods do - def exit_on_failure?() = true - # option :force, desc: 'Force creation even if the project directory already exists', - # aliases: '-f', type: :boolean - # option :config, desc: 'Create project with a working configuration (instead of commented examples)', - # aliases: '-c', type: :boolean - # option :guided, desc: 'Create project with a guided configuration', - # aliases: '-g', type: :boolean - end - - private - - def check_dir(name) - if Dir.exist?(name) && !validate_destroy('Directory already exists. Destroy and recreate?') - raise Cnfs::Error, set_color('Directory exists. exiting.', :red) - end - true - end -end diff --git a/cnfs/app/controllers/cnfs/main_controller.rb b/cnfs/app/controllers/cnfs/main_controller.rb deleted file mode 100644 index ae99554f..00000000 --- a/cnfs/app/controllers/cnfs/main_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class MainController < Thor - include Cnfs::Concerns::Extendable if defined? APP_ROOT - - def self.exit_on_failure?() = true - end -end diff --git a/cnfs/app/controllers/cnfs/new/command_controller.rb b/cnfs/app/controllers/cnfs/new/command_controller.rb deleted file mode 100644 index 3c6c105b..00000000 --- a/cnfs/app/controllers/cnfs/new/command_controller.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module New - class CommandController < Thor - include Cnfs::Concerns::NewController - - register Cnfs::New::PluginController, 'plugin', 'plugin', 'Create a new CNFS plugin' - - desc 'new NAME', 'Create a new CNFS project' - long_desc <<-DESC.gsub("\n", "\x5") - - The 'cnfs new' command creates a new CNFS project with a default - directory structure and configuration at the path you specify. - - You can specify extra command-line arguments to be used every time - 'cnfs new' runs in the ~/.config/cnfs/cnfs.yml configuration file - - Note that the arguments specified in the cnfs.yml file don't affect the - defaults values shown above in this help message. - - Example: - cnfs new ~/Projects/todo - - This generates a skeletal CNFS project in ~/Projects/todo. - DESC - option :force, desc: 'Force creation even if the project directory already exists', - aliases: '-f', type: :boolean - option :config, desc: 'Create project with a working configuration (instead of commented examples)', - aliases: '-c', type: :boolean - option :guided, desc: 'Create project with a guided configuration', - aliases: '-g', type: :boolean - def new(path) = check_dir(path) && execute(path: path, type: :project) - # def new(path) - # check_dir(path) - # binding.pry - # execute(path: path, type: :project) - # end - - # Utility - desc 'version', 'Show cnfs version' - def version - require 'cnfs/version' - puts("Cnfs #{Cnfs::VERSION}") - end - - # TODO: Move to MainController that is inherited by Cnfs::Core::MainController - # def version() = Cnfs::VersionController.new([], options).execute - end - end -end diff --git a/cnfs/app/controllers/cnfs/new/exec_controller.rb b/cnfs/app/controllers/cnfs/new/exec_controller.rb deleted file mode 100644 index ba48c8fa..00000000 --- a/cnfs/app/controllers/cnfs/new/exec_controller.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'ostruct' - -module Cnfs - module New - class ExecController - include Cnfs::Concerns::ExecController - - def new - path.rmtree if path.exist? - send(args.type) - end - - def project - path.mkdir - Dir.chdir(path) { generator.invoke_all } - Dir.chdir(path) { generator(:extension).invoke(:segments) } - - Dir.chdir(path) do - Cnfs.loaders['framework'].unload - load 'cnfs/boot_loader.rb' - SegmentRoot.first.generate_key - end - - return unless context.options.guided - - # Start a view here - # TODO: This should create a node which should create a file with the yaml or a directory - # Project.new(name: context.args.name).create - end - - def plugin - generator.invoke_all - Dir.chdir(path) { generator(:extension).invoke(:segments) } - end - - def extension - path.mkdir - Dir.chdir(path) { generator.invoke_all } - end - - def generator(type = args.type) = generator_class(type).new([context, name], options) - - def generator_class(type) = "cnfs/new/#{type}_generator".classify.constantize - - def name() = @name ||= path.basename.to_s - - def path() = @path ||= Pathname.new(args.path) - - # TODO: To be compaitble with CnfsCli::Concerns::CommandController - # this should be removed or consolidated - def context() = OpenStruct.new(options: options, args: args) - end - end -end diff --git a/cnfs/app/controllers/cnfs/new/plugin_controller.rb b/cnfs/app/controllers/cnfs/new/plugin_controller.rb deleted file mode 100644 index b5ffa335..00000000 --- a/cnfs/app/controllers/cnfs/new/plugin_controller.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module New - class PluginController < Thor - include Cnfs::Concerns::NewController - - desc 'new NAME', 'Create a new CNFS plugin' - long_desc <<-DESC.gsub("\n", "\x5") - - The 'cnfs plugin new' command creates a new CNFS project with a default - directory structure and configuration at the path you specify. - - Example: - cnfs new ~/Projects/todo - - This generates a skeletal CNFS plugin in ~/Projects/todo. - DESC - option :force, desc: 'Force creation even if the project directory already exists', - aliases: '-f', type: :boolean - option :config, desc: 'Create project with a working configuration (instead of commented examples)', - aliases: '-c', type: :boolean - option :full, desc: 'Create the full', - type: :boolean - def new(path) - # By default generate an extension which is just a segments directory - # When passing the full option then create a gem - type = options.full ? :plugin : :extension - check_dir(path) && exec(path: path, type: type) - end - end - end -end diff --git a/cnfs/app/controllers/cnfs/version_controller.rb b/cnfs/app/controllers/cnfs/version_controller.rb deleted file mode 100644 index 0d1e3f2d..00000000 --- a/cnfs/app/controllers/cnfs/version_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class VersionController - include Cnfs::Concerns::ExecController - - attr_accessor :name, :options - - def initialize(name, options) - @name = name - @options = options - end - - # rubocop:disable Metrics/AbcSize - def execute - name = Cnfs.plugin_root.name.underscore - keys = Cnfs.plugin_root.plugins.keys.append(name) - pad = keys.max_by(&:length).size + 10 - puts "Component#{' ' * (pad - 9)}Version", "#{name}#{' ' * (pad - name.length)}#{Cnfs.plugin_root::VERSION}" - puts "cli_core#{' ' * (pad - 8)}#{Cnfs::VERSION}" - Cnfs.plugin_root.plugins.sort.each do |namespace, plugin_class| - print "#{namespace}#{' ' * (pad - namespace.length)}" - lib_class = plugin_class.to_s.split('::').reject { |n| n.eql?('Plugins') }.join('::').safe_constantize - puts lib_class::VERSION - end - end - # rubocop:enable Metrics/AbcSize - end -end diff --git a/cnfs/app/generators/cnfs/application_generator.rb b/cnfs/app/generators/cnfs/application_generator.rb deleted file mode 100644 index 034610c1..00000000 --- a/cnfs/app/generators/cnfs/application_generator.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module Cnfs -class ApplicationGenerator < Thor::Group - include Thor::Actions - # include Concerns::Extendable - - # Argument order: - # 1. Context is always received first - # 2. Any additional arugments declared in sub-classes - argument :context - - private - - # NOTE: These methods are available within templates as well as any sub classes - - # Utility method if template is in the standard directory and the destiation file is file_name - .erb - def cnfs_template(file_name) - generated_files << template(templates_path.join("#{file_name}.erb"), file_name) - end - - # Array of ERB templates in the views_path/templates directory - def templates() = templates_path.glob('**/*.erb') - - def templates_path() = views_path.join('templates') - - # TODO: Verify if this picks up hidden files - def files() = files_path.glob('**/*') - - def files_path() = views_path.join('files') - - # Thor serach paths is an array. The default is a one element array based on the generator's directory - def source_paths() = [views_path] - - def views_path() = @views_path ||= internal_path.join(assets_path) - - # returns 'runtime', 'provisioner', 'project', 'plugin', 'component', etc - def assets_path() = self.class.name.demodulize.delete_suffix('Generator').underscore - - # The path to the currently invoked generator subclass - def internal_path() = raise(NotImplementedError, 'Generator must implement #internal_path() = Pathname.new(__dir__)') - - # returns 'compose', 'skaffold', 'terraform', 'new', etc - def generator_type() = self.class.name.deconstantize.underscore - - # Utility methods for managing files in the target directory - - # Use Thor's remove_file to output the removed files - # After the generator has completed it may call this method to remove old files - # In order to do this it must track files as they are generated by implementing: - # - # generated_files << template('templates/env.erb', file_name, env: environment) - # - def remove_stale_files() = stale_files.each { |file| remove_file(file) } - - def stale_files() = all_files - excluded_files - generated_files - - # All files in the current directory - def all_files() = path.glob('**/*').select(&:file?).map(&:to_s) - - # Array of file names that should not be removed - # A subclass can override this method to define files that should not be considered as stale and removed - def excluded_files() = [] - - # Stores an array of files that are created during an invocation of the Generator - def generated_files() = @generated_files ||= [] - - def path() = Pathname.new('.') -end -end diff --git a/cnfs/app/generators/cnfs/new/plugin/templates/gemspec.rb.erb b/cnfs/app/generators/cnfs/new/plugin/templates/gemspec.rb.erb deleted file mode 100644 index a1b9fa10..00000000 --- a/cnfs/app/generators/cnfs/new/plugin/templates/gemspec.rb.erb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true -require_relative 'lib/cnfs/<%= name %>/version' - -Gem::Specification.new do |spec| - spec.name = 'cnfs-<%= name %>' - spec.version = CnfsCli::<%= name.classify %>::VERSION - spec.authors = ['Robert Roach'] - spec.email = ['rjayroach@gmail.com'] - spec.summary = 'CNFS CLI plugin for <%= metadata.try(:[], name).try(:[], 'summary') %>' - spec.description = 'CNFS CLI plugin to <%= metadata.try(:[], name).try(:[], 'description') %>' - spec.homepage = 'https://cnfs.io' - spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') - - # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/rails-on-services/cnfs-cli' - # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] - - # spec.add_dependency 'cnfs-cli', '~> 0.1.0' - - spec.add_development_dependency 'bundler', '~> 2.0' - spec.add_development_dependency 'pry', '~> 0.12' - spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rspec', '~> 3.0' -end diff --git a/cnfs/app/generators/cnfs/new/plugin/templates/lib/cnfs/plugin.rb.erb b/cnfs/app/generators/cnfs/new/plugin/templates/lib/cnfs/plugin.rb.erb deleted file mode 100644 index aedcac07..00000000 --- a/cnfs/app/generators/cnfs/new/plugin/templates/lib/cnfs/plugin.rb.erb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module <%= name.camelize %> - class Plugin < Cnfs::Plugin - # initializer 'logger' do |app| - # Cnfs.logger.info('[<%= name.camelize %>] Initializing from', gem_root) - # end - - class << self - def gem_root() = Cnfs::<%= name.camelize %>.gem_root - end - end - end -end diff --git a/cnfs/app/generators/cnfs/new/plugin_generator.rb b/cnfs/app/generators/cnfs/new/plugin_generator.rb deleted file mode 100644 index 6700b655..00000000 --- a/cnfs/app/generators/cnfs/new/plugin_generator.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class New::PluginGenerator < NewGenerator - def gem - if options.noop - puts cmd - else - system(cmd) - FileUtils.mv(gem_name, name) - end - end - - # All Thor methods are automatically invoked inside destination_root - def set_root() = self.destination_root = name - - def component_files() = _component_files - - def libs - if options.config - remove_dir('.git') - remove_file('.travis.yml') - remove_file('.gitignore') - end - remove_file("lib/cnfs/#{name}.rb") - remove_file('Gemfile') - template('templates/Gemfile.erb', 'Gemfile') - template('templates/lib/cnfs/module.rb.erb', "lib/cnfs/#{name}.rb") - template('templates/lib/cnfs/plugin.rb.erb', "lib/cnfs/#{name}/plugin.rb") - end - - # def gemspec - # in_root do - # old_file_name = "cnfs_cli-#{name}.gemspec" - # file_name = "cnfs-#{name}.gemspec" - # remove_file(old_file_name) - # template('templates/gemspec.rb.erb', file_name) - # end - # end - - private - - def cmd() = "bundle gem --test=rspec --ci=none --no-coc --no-rubocop --mit --changelog #{gem_name}" - - def gem_name() = "cnfs-#{name}" - - def internal_path() = Pathname.new(__dir__) - end -end diff --git a/cnfs/app/generators/cnfs/new/project/templates/Gemfile.erb b/cnfs/app/generators/cnfs/new/project/templates/Gemfile.erb deleted file mode 100644 index 32498908..00000000 --- a/cnfs/app/generators/cnfs/new/project/templates/Gemfile.erb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -<% %w[core aws docker gcp kubernetes native terraform].unshift('').each do |gem_name| -%> -<%= gemfile_gem_string(gem_name) %> -<% end -%> diff --git a/cnfs/app/generators/cnfs/new/project_generator.rb b/cnfs/app/generators/cnfs/new/project_generator.rb deleted file mode 100644 index e44aa1b6..00000000 --- a/cnfs/app/generators/cnfs/new/project_generator.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class New::ProjectGenerator < NewGenerator - # def data_files - # data_path.rmtree if data_path.exist? - # create_file(data_path.join('keys.yml'), { name => Lockbox.generate_key }.to_yaml) - # end - - def project_files() = directory('files', '.') - - def template_files - templates.each do |template| - destination = template.relative_path_from(templates_path).to_s.delete_suffix('.erb') - template(template, destination) - end - end - - def component_files() = _component_files - - private - - def internal_path() = Pathname.new(__dir__) - - # def data_path() = CnfsCli.config.data_home.join('projects', uuid) - - def uuid() = @uuid ||= SecureRandom.uuid - end -end diff --git a/cnfs/app/generators/cnfs/new_generator.rb b/cnfs/app/generators/cnfs/new_generator.rb deleted file mode 100644 index 063e1e07..00000000 --- a/cnfs/app/generators/cnfs/new_generator.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class NewGenerator < ApplicationGenerator - argument :name - - private - - def _component_files - keep("app/controllers/#{name}/concerns") - keep("app/generators/#{name}/concerns") - keep("app/models/#{name}/concerns") - keep("app/views/#{name}/concerns") - keep('config/initializers') - end - - def keep(keep_path) = create_file(path.join(keep_path, '.keep')) - - def gemfile_gem_string(name) - gem_name = name.empty? ? 'cnfs' : "cnfs-#{name}" - return "gem '#{gem_name}'" if ENV['CNFS_ENV'].eql?('production') - - name = 'cnfs' if name.empty? - "gem '#{gem_name}', path: '#{gems_path.join(name)}'" - end - - def gems_path() = internal_path.join('../../../../../') - end -end diff --git a/cnfs/app/models/cnfs/application_record.rb b/cnfs/app/models/cnfs/application_record.rb deleted file mode 100644 index 9ac3cfc7..00000000 --- a/cnfs/app/models/cnfs/application_record.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -require 'active_record' - -class Cnfs::ApplicationRecord < ActiveRecord::Base - self.abstract_class = true -end diff --git a/cnfs/app/models/cnfs/concerns/extendable.rb b/cnfs/app/models/cnfs/concerns/extendable.rb deleted file mode 100644 index 5e25f134..00000000 --- a/cnfs/app/models/cnfs/concerns/extendable.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -# Extendable should be included last as it includes plugin modules that may depend on methods -# defined in previously loaded modules -module Cnfs - module Concerns - module Extendable - extend ActiveSupport::Concern - - included do - # Plugins that have an appropriately named A/S Concern will be automatically included - # - # Example: - # The terraform plugin adds methods to the Resource model by including A/S Concern in the module - # Terraform::Concerns::Resource declared in file Terraform.gem_root/app/models/terraform/concerns/resource.rb - Cnfs.modules_for(self).each { |mod| include mod } - end - end - end -end diff --git a/cnfs/app/models/cnfs/node.rb b/cnfs/app/models/cnfs/node.rb deleted file mode 100644 index 6bd72a47..00000000 --- a/cnfs/app/models/cnfs/node.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class Node < ApplicationRecord - self.table_name = 'cnfs/nodes' - - belongs_to :parent, class_name: 'Cnfs::Node' - - store :config, coder: YAML - - def bname() = pathname.basename.to_s - - def pathname() = @pathname ||= Pathname.new(path) - - def self.create_table(schema) - schema.create_table table_name, force: true do |t| - t.references :parent - t.string :path - t.string :type - t.string :config - end - end - end - - class Directory < Node - has_many :directories, foreign_key: 'parent_id', class_name: 'Cnfs::Directory', dependent: :destroy - has_many :files, foreign_key: 'parent_id', class_name: 'Cnfs::File', dependent: :destroy - - delegate :rmtree, to: :pathname, prefix: :pathname - - store :config, accessors: %i[pattern] - - after_create :load_children - after_destroy :pathname_rmtree - - def mkpath(name) - dir = directories.create(path: pathname.join(name), pattern: pattern) - pathname.join(name).mkpath - end - - def load_children - files_to_load.each do |childpath| - child = File.new(path: childpath, parent: self) - child.type = file_type(child) - child.save - end - - dirs_to_load.each do |childpath| - directories << self.class.create(path: childpath, pattern: pattern) - end - - true - end - - def dirs_to_load() = pathname.children.select(&:directory?) - - def files_to_load() = pattern ? pathname.glob(pattern) : pathname.children.select(&:file?) - - def file_type(_child) = 'Cnfs::File' - - # TODO: Move the TTY stuff to a controller and just return the hash - def to_tree() = puts(TTY::Tree.new({ '.' => as_tree }).render) - - def as_tree - directories.each_with_object(files.map{ |f| f.bname }) do |dir, ary| - value = dir.pathname.children.size.zero? ? dir.bname : { dir.bname => dir.as_tree } - ary.append(value) - end - end - end - - class File < Node - delegate :delete, to: :pathname, prefix: :pathname - - after_destroy :pathname_delete - - def file_content() = @file_content ||= read - - def read() = send("#{parser}_read") - - def write(content) - send("#{parser}_write", content) - @file_content = read - end - - def parser() = parser_mapping[extension.to_sym] || :raw - - def parser_mapping - { - yml: :yaml, - yaml: :yaml - } - end - - def shortname() = bname.delete_suffix(".#{extension}") - - def extension() = bname.split('.').last - - def raw_read() = pathname.read - - def yaml_read() = YAML.load_file(pathname) - - def yaml_write(content) = pathname.write(content.to_yaml) - end -end diff --git a/cnfs/app/views/cnfs/application_view.rb b/cnfs/app/views/cnfs/application_view.rb deleted file mode 100644 index 0663cf5a..00000000 --- a/cnfs/app/views/cnfs/application_view.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -require 'tty-prompt' -require 'tty-tree' - -class Cnfs::ApplicationView < TTY::Prompt - def default_options() = { help_color: :cyan } - - # Set an attribute from a Prompt.select result - # - # ==== Examples - # view_select(:instance_family, model.offers_by_family, model.family) - # - # ==== Parameters - # title:: The title text to display to the user. If it is also an attribute of the object ti will be set - # data:: An array of data to be presented to the user as a select list - # current:: The default value in the Array - def select_attr(key, title: nil, default: nil, choices: [], **options) - default ||= model.send(key) - ret_val = select_val(key, title: title, default: default, choices: choices, **options) - model.send("#{key}=", ret_val) - end - - def select_val(key, title: nil, default: nil, choices: [], **options) - title ||= key.to_s.humanize - - options[:help] ||= 'Type to filter results' - options[:filter] ||= true - options[:per_page] ||= per_page(choices) - options[:show_help] ||= :always - - select("#{title}:", choices, **options) do |menu| - # menu.help 'Type to filter results' - menu.default((choices.index(default) || 0) + 1) if default - # yield menu if block_given? - end - end - - def enum_select_attr(key, title: nil, default: nil, choices: [], **options) - default ||= model.send(key) - ret_val = enum_select_val(key, title: title, default: default, choices: choices, **options) - model.send("#{key}=", ret_val) - end - - def enum_select_val(key, title: nil, default: nil, choices: [], **options) - title ||= key.to_s.humanize - - options[:per_page] ||= per_page(choices) - - enum_select("#{title}:", choices, **options) do |menu| - menu.default(default) if default - end - end - - def ask_attr(key, title: nil, **options) - options[:default] ||= model.send(key) - ret_val = ask_val(key, title: title, **options) - model.send("#{key}=", ret_val) - end - - def ask_val(key, title: nil, **options) - title ||= key.to_s.humanize - ask(title, **options) - end - - def mask_attr(key, title: nil, **options) - options[:default] ||= model.send(key) - ret_val = mask_val(key, title: title, **options) - model.send("#{key}=", ret_val) - end - - def mask_val(key, title: nil, **options) - title ||= key.to_s.humanize - mask(title, **options) - end - - def yes_attr(key, title: nil, **options) - options.fetch(:default, model.send(key)) - # binding.pry - ret_val = yes_val(key, title: title, **options) - model.send("#{key}=", ret_val) - end - - def yes_val(key, title: nil, **options) - title ||= key.to_s.humanize - # binding.pry - yes?(title, **options) - end - - - # TODO: Refactor below methods - - def ask_hash(field) - model.send("#{field}=".to_sym, hv(field)) - end - - def hv(field) - model.send(field.to_sym).each_with_object({}) do |(key, value), hash| - hash[key] = ask(key, value: value) - end - end - - def per_page(array, buffer = 3) - [TTY::Screen.rows, array.size].max - buffer - end - - def random_string(name = nil, length: 12) - rnd = (0...length).map { rand(65..90).chr }.join.downcase - [name, rnd].compact.join('-') - end -end diff --git a/cnfs/bin/console b/cnfs/bin/console deleted file mode 100755 index ce4fbfc6..00000000 --- a/cnfs/bin/console +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'bundler/setup' -require 'cnfs/cli/cnfs_core' - -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require 'irb' -IRB.start(__FILE__) diff --git a/cnfs/cnfs.gemspec b/cnfs/cnfs.gemspec deleted file mode 100644 index 03c697b1..00000000 --- a/cnfs/cnfs.gemspec +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -require_relative 'lib/cnfs/version' - -Gem::Specification.new do |spec| - spec.name = 'cnfs' - spec.version = Cnfs::VERSION - spec.authors = ['Robert Roach'] - spec.email = ['rjayroach@gmail.com'] - - spec.summary = 'CNFS CLI Core Services' - spec.description = 'CNFS CLI plugin to install service configurations into CNFS projects' - spec.homepage = 'https://cnfs.io' - spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') - - # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/rails-on-services/cnfs-cli' - # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] - - spec.add_dependency 'activerecord', '~> 6.1' - spec.add_dependency 'activesupport', '~> 6.1' - # spec.add_dependency 'json_schemer' - spec.add_dependency 'pry', '~> 0.13' - spec.add_dependency 'sqlite3', '~> 1.4' - spec.add_dependency 'thor', '~> 1.0' - - spec.add_dependency 'tty-command', '~> 0.10' - spec.add_dependency 'tty-file', '~> 0.10.0' - spec.add_dependency 'tty-logger', '~> 0.5' - spec.add_dependency 'tty-prompt', '~> 0.22' - spec.add_dependency 'tty-screen', '~> 0.8' - spec.add_dependency 'tty-spinner', '~> 0.9' - spec.add_dependency 'tty-table', '~> 0.12.0' - spec.add_dependency 'tty-tree', '~> 0.4' - spec.add_dependency 'tty-which', '~> 0.5' - - spec.add_dependency 'xdg', '~> 5' - spec.add_dependency 'zeitwerk', '~> 2.4' - - # spec.add_development_dependency 'bump', '~> 0.10' - spec.add_development_dependency 'bundler', '~> 2.0' - spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rspec', '~> 3.0' - # spec.add_development_dependency "awesome_print" -end diff --git a/cnfs/exe/cnfs b/cnfs/exe/cnfs deleted file mode 100755 index e3a8a31a..00000000 --- a/cnfs/exe/cnfs +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -git_path = File.expand_path("../../.git", __dir__) - -if File.exist?(git_path) - ENV['CNFS_CLI_ENV'] ||= 'development' - require 'pry' - plugin_path = File.expand_path("../lib", __dir__) - $:.unshift(plugin_path) -end - -ENV['CNFS_CLI_ENV'] ||= 'production' - -ROOT_FILE_ID = 'config/environment.rb' - -require 'cnfs/boot_loader' diff --git a/cnfs/lib/cnfs.rb b/cnfs/lib/cnfs.rb deleted file mode 100644 index f0ce672c..00000000 --- a/cnfs/lib/cnfs.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -# Stdlibs -require 'fileutils' -require 'yaml' - -# External dependencies -require 'active_support/concern' -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/hash' -require 'active_support/core_ext/module/introspection' -require 'active_support/inflector' -require 'active_support/notifications' -require 'active_support/time' - -# TODO: Move lockbox to core somewhere -require 'lockbox' -require 'thor' -require 'tty-logger' -# require 'xdg' -# require 'zeitwerk' - -# Cnfs libs -require_relative 'cnfs/data_store' -require_relative 'cnfs/errors' -require_relative 'cnfs/loader' -require_relative 'cnfs/timer' -require_relative 'cnfs/version' - -# Cnfs core extensions -require_relative 'ext/hash' -require_relative 'ext/open_struct' -require_relative 'ext/pathname' -require_relative 'ext/string' - - -module Cnfs - class Error < StandardError; end - - class << self - attr_writer :logger - attr_accessor :app_class, :cli_mode - - def config() = application.config - - def application() = @application ||= app_class.new - - def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') - - # TODO: Multiple loggers - def logger() = @logger ||= set_logger - - def set_logger - default_level = (config.logging || 'warn').to_sym - level = ::TTY::Logger::LOG_TYPES.keys.include?(default_level) ? default_level : :warn - ::TTY::Logger.new do |config| - Cnfs.config.logging = config.level = level - config.level = level - end - end - - # TODO: Decide what to do with subscribers - def x_reset - subscribers.each { |sub| ActiveSupport::Notifications.unsubscribe(sub.pattern) } - end - - # Record any ActiveRecord pubsub subsribers - def subscribers() = @subscribers ||= [] - - # TODO: Is this necessary? - def with_profiling - unless ENV['CNFS_PROF'] - yield - return - end - - require 'ruby-prof' - RubyProf.start - yield - results = RubyProf.stop - File.open("#{Dir.home}/cnfs-cli-prof.html", 'w+') { |file| RubyProf::GraphHtmlPrinter.new(results).print(file) } - end - end -end diff --git a/cnfs/lib/cnfs/app_loader.rb b/cnfs/lib/cnfs/app_loader.rb deleted file mode 100644 index dedf15be..00000000 --- a/cnfs/lib/cnfs/app_loader.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -# start_time = Time.now -# lib_path = File.expand_path('../lib', __dir__) -# $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) - -# ENV['CNFS_PROF'] ||= ARGV.delete('--cli-prof') -# dval = ARGV.index('--logging') || ARGV.index('-l') -# ENV['CNFS_LOGGING'] = ARGV[dval + 1] if dval - -# Require the cnfs framework and external dependencies -require 'cnfs' - -# Require application, extension and plugin base classes after the framework -require 'cnfs/application' - -# Add this gem to the plugin hash -require 'cnfs/cnfs_plugin' - -# Require the application's environment from /config/environment.rb -# The environment requires the application and then calls initialize! which return self -# The application requires boot.rb -# Boot.rb requires the gems, e.g. cnfs-core, cnfs-aws, etc as specified in the Gemfile -require APP_ROOT.join(ROOT_FILE_ID) - -# Configure all classes in each plugin's app path to be autoloaded -Cnfs.plugins.values.each do |plugin| - Cnfs.add_loader(name: :framework, path: plugin.gem_root.join('app'), notifier: plugin) -end - -# Setup the autoloader -Cnfs.loaders.values.map(&:setup) - -# Run each plugin's initializers -Cnfs.run_initializers - -# Invoke the CLI -add_args = ARGV.size.positive? && ARGV.first.start_with?('-') -ARGV.unshift('project', 'console') if ARGV.size.zero? || add_args - -# Signal.trap('INT') do -# warn("\n#{caller.join("\n")}: interrupted") -# # exit 1 -# end - -invoked_from_new_generator = ARGV[0].eql?('new') -in_test_mode = ENV['CNFS_CLI_ENV'].eql?('test') -skip_controller_start = invoked_from_new_generator || in_test_mode - -unless skip_controller_start - begin - # The MainController is empty. The controlling gem needs to add a concern to implement commands - Cnfs::MainController.start - rescue Cnfs::Error => e - puts e.message - exit 1 - end -end diff --git a/cnfs/lib/cnfs/application.rb b/cnfs/lib/cnfs/application.rb deleted file mode 100644 index eca3cfdd..00000000 --- a/cnfs/lib/cnfs/application.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'cnfs/plugin' - -module Cnfs - class Application < Plugin - require 'cnfs/application/configuration' - - def config() = self.class.config - - def name() = self.class.name.deconstantize.downcase - - class << self - def config() = @config ||= Configuration.new - - # https://github.com/rails/rails/blob/main/railties/lib/rails/application.rb#L68 - def inherited(base) - super - Cnfs.app_class = base - end - - def gem_root() = APP_ROOT - end - - # https://github.com/rails/rails/blob/main/railties/lib/rails/application.rb#L367 - def initialize! - config.after_user_config - self - end - end -end diff --git a/cnfs/lib/cnfs/application/configuration.rb b/cnfs/lib/cnfs/application/configuration.rb deleted file mode 100644 index 95996c2c..00000000 --- a/cnfs/lib/cnfs/application/configuration.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'cnfs/plugin/configuration' - -require 'active_support/inflector' - -module Cnfs - class Application - class Configuration < Cnfs::Plugin::Configuration - XDG_BASE = 'cnfs-cli' - XDG_PROJECTS_BASE = 'cnfs-cli/projects' - - def after_initialize - # Set defaults - # self.dev = false - # self.dry_run = false - self.logging = :warn - self.quiet = false - - # Default paths - paths.segments = 'segments' - # paths.data = 'data' - # paths.tmp = 'tmp' - paths.src = 'src' - end - - def after_user_config - # Transform paths from a string to an absolute path - paths.transform_values! { |path| path.is_a?(Pathname) ? path : root.join(path) } - - # Set values for component selection based on any user defined ENVs - segments.each do |key, values| - env_key = "#{env_base}#{values[:env] || key}".upcase - values[:env_value] = ENV[env_key] - end - - # Set Command Options - self.command_options = segments.dup.transform_values! do |opt| - { desc: opt[:desc], aliases: opt[:aliases], type: :string } - end - - # Disable loading of component if not in a valid project, e.g. cnfs new is being invoked - # - self.load_nodes = false unless project - end - - # The prefix of ENV vars specified in config/application.rb - def env_base() = 'CNFS_' - - # return XDG_BASE and the project_id so that each project's local files are isolated - def xdg_name() = @xdg_name ||= "#{self.class::XDG_PROJECTS_BASE}/#{project_id}" - - def cli_cache_home() = xdg.cache_home.join(self.class::XDG_BASE) - end - end -end diff --git a/cnfs/lib/cnfs/boot_loader.rb b/cnfs/lib/cnfs/boot_loader.rb deleted file mode 100644 index af850ecb..00000000 --- a/cnfs/lib/cnfs/boot_loader.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'pathname' - -# Determine if cwd is inside an app or not -path = Pathname.new(Dir.pwd).ascend { |path| break path if path.join(ROOT_FILE_ID).file? } - -# If cwd is inside an app then load the framework and run the CLI -if path - APP_CWD = Pathname.new(Dir.pwd) - APP_ROOT = path - Dir.chdir(APP_ROOT) { require 'cnfs/app_loader' } -else - require 'cnfs/new_loader' -end diff --git a/cnfs/lib/cnfs/cnfs_plugin.rb b/cnfs/lib/cnfs/cnfs_plugin.rb deleted file mode 100644 index 8c056c9c..00000000 --- a/cnfs/lib/cnfs/cnfs_plugin.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module New; end - module Concerns; end - class CnfsPlugin < ::Cnfs::Plugin - class << self - def before_loader_setup(loader) = loader.ignore(loader_ignore_files) - - def loader_ignore_files - [ - gem_root.join('app/generators/cnfs/new/extension'), - gem_root.join('app/generators/cnfs/new/plugin'), - gem_root.join('app/generators/cnfs/new/project') - ] - end - - def gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end - end -end diff --git a/cnfs/lib/cnfs/data_store.rb b/cnfs/lib/cnfs/data_store.rb deleted file mode 100644 index 282532e0..00000000 --- a/cnfs/lib/cnfs/data_store.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class << self - def data_store() = @data_store ||= DataStore.new - end - - class DataStore - class << self - # This class accessor is used in #create_database_tables - attr_accessor :model_names - - # Use this method to dump the latest version of the schema to a file - # Gems can use this to create a schema that used with NullDB to emulate models without having the actual classes - # or underlying database prsent - def schema_dump(file_name = nil) - schema = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, StringIO.new).string - return schema unless file_name - - File.open(file_name, 'w') { |f| f.puts(schema) } - end - end - - def model_names() = @model_names ||= [] - - def add_models(*model_list) = model_names.concat(model_list.flatten) - - def setup - Cnfs.with_timer('database initialization') do - require 'active_record' - # ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: Cnfs.config.data_home.join('sqlite.db')) - # Set up in-memory database - ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') - create_database_tables - end - true - end - - def reset() = create_database_tables - - private - - def create_database_tables - ActiveRecord::Migration.verbose = log_migrations? - Cnfs::DataStore.model_names = model_names - ActiveRecord::Schema.define do |s| - Cnfs::DataStore.model_names.each do |model_name| - model = model_name.classify.constantize - model.create_table(s) - model.reset_column_information - end - end - end - - def log_migrations? - Cnfs.logger.compare_levels(Cnfs.logger.instance_variable_get('@config').level, :debug).eql?(:eq) - end - end -end diff --git a/cnfs/lib/cnfs/dependencies.rb b/cnfs/lib/cnfs/dependencies.rb deleted file mode 100644 index 83b6eb02..00000000 --- a/cnfs/lib/cnfs/dependencies.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'active_record' -require 'active_support/log_subscriber' -require 'active_support/string_inquirer' - -# require 'json_schemer' -require 'open-uri' -require 'sqlite3' -require 'tty-command' -require 'tty-prompt' -require 'tty-table' -require 'tty-tree' -require 'tty-which' - -require_relative 'tty/prompt' -require_relative 'tty/prompt/answers_collector' diff --git a/cnfs/lib/cnfs/extension.rb b/cnfs/lib/cnfs/extension.rb deleted file mode 100644 index f81df43e..00000000 --- a/cnfs/lib/cnfs/extension.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true -# -# Extensions provide a search path for segments - -module Cnfs - class << self - def extensions() = @extensions ||= {} - end - - class Extension - class << self - def segment(name) - path = segments_path.join(name) - return path if path.exist? - end - - def segments() = @segments ||= segments_path.glob('**/*').select(&:directory?) - - # TODO: Extension needs to define a path as gem_root is only available in Plugins - def segments_path() = root.join('segments') - - def inherited(base) - name = name_from_base(base) - return if base.to_s.eql?('Cnfs::Plugin') - - Cnfs.extensions[name] = base - end - - # Return the module namespace from an Extension subclass - # - # Cnfs::Aws::Plugin => :aws - # Test::Application => :application - # - def name_from_base(base) = base.name.to_s.split('::')[1].downcase.to_sym - end - end -end diff --git a/cnfs/lib/cnfs/extension/configuration.rb b/cnfs/lib/cnfs/extension/configuration.rb deleted file mode 100644 index 050d91fe..00000000 --- a/cnfs/lib/cnfs/extension/configuration.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - class Extension - class Configuration - end - end -end diff --git a/cnfs/lib/cnfs/minimum_dependencies.rb b/cnfs/lib/cnfs/minimum_dependencies.rb deleted file mode 100644 index d957fc3d..00000000 --- a/cnfs/lib/cnfs/minimum_dependencies.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'active_support/concern' -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/hash' -require 'active_support/core_ext/module/introspection' -require 'active_support/inflector' -require 'active_support/notifications' -require 'active_support/time' - -require 'fileutils' -require 'lockbox' -require 'thor' -require 'thor/hollaback' -require 'zeitwerk' - -require_relative 'errors' -require_relative 'version' -require_relative 'data_store' -require_relative '../ext/hash' -require_relative '../ext/open_struct' -require_relative '../ext/string' diff --git a/cnfs/lib/cnfs/new_loader.rb b/cnfs/lib/cnfs/new_loader.rb deleted file mode 100644 index 05c16f9c..00000000 --- a/cnfs/lib/cnfs/new_loader.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# Load the classes from /app -require 'logger' -require 'cnfs/loader' -require 'thor' - -load_path = Pathname.new(__dir__).join('../../app') -Cnfs.add_loader(name: :framework, path: load_path).setup - -# Display the new command's help if no arguments provided -ARGV.append('help', 'new') if ARGV.size.zero? - -# Start the NewController -Cnfs::New::CommandController.start diff --git a/cnfs/lib/cnfs/plugin/configuration.rb b/cnfs/lib/cnfs/plugin/configuration.rb deleted file mode 100644 index 91f96245..00000000 --- a/cnfs/lib/cnfs/plugin/configuration.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require 'xdg' -require 'active_support/ordered_options' - -require 'cnfs/extension/configuration' - -module Cnfs - class Plugin - class Configuration < Cnfs::Extension::Configuration - def initialize(**options) - @configurations = options - after_initialize - end - - # Override this method to provide defaults before the user configuration is parsed - def after_initialize() = nil - - def root() = APP_ROOT - - # user-specific non-essential (cached) data - def cache_home() = @cache_home ||= xdg.cache_home.join(xdg_name) - - # user-specific configuration files - def config_home() = @config_home ||= xdg.config_home.join(xdg_name) - - # user-specific data files - def data_home() = @data_home ||= xdg.data_home.join(xdg_name) - - def xdg_name() = name - - # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - def xdg() = @xdg ||= XDG::Environment.new - - # Reused from railties/lib/rails/application/configuration.rb - def method_missing(method, *args) - return if %i[after_initialize after_user_config].include?(method) - - if method.end_with?('=') - @configurations[:"#{method[0..-2]}"] = args.first - else - @configurations.fetch(method) do - @configurations[method] = ActiveSupport::OrderedOptions.new - end - end - end - - def respond_to_missing?(_symbol, *) = true - end - end -end diff --git a/cnfs/lib/cnfs/spec_loader.rb b/cnfs/lib/cnfs/spec_loader.rb deleted file mode 100644 index 2daf5768..00000000 --- a/cnfs/lib/cnfs/spec_loader.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require 'pry' -require 'pry-byebug' - -# Setting the environment to test causes -# - app_loader to skip MainController.start -# - SegmentRoot.load to be skipped -ENV['CNFS_CLI_ENV'] ||= 'test' - -ROOT_FILE_ID = 'config/environment.rb' - -PROJECT_NAME = 'spec' -PROJECT_ID = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' -KEY_ID = '9346840c042bb4dbf7bd6a5cf49de40d420c3d1835b28044f9abcab3003c47a1' - -# Must match the value in Cnfs::Application::Configuration::XDG_PROJECTS_BASE -XDG_PROJECTS_BASE = 'cnfs-cli/projects' - -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' - - # Disable RSpec exposing methods globally on `Module` and `main` - config.disable_monkey_patching! - - config.expect_with :rspec do |c| - c.syntax = :expect - end - - config.before(:suite) { Cnfs::SpecLoader.setup_project } - # config.after(:suite) { Cnfs::SpecLoader.remove_project } -end - -module Cnfs - class SpecLoader - class << self - attr_accessor :current_spec - - def setup_project - # Ensure the temporary project path is removed - remove_project - project_path.mkpath - - # Copy the gems fixtures to the temporary project path - FileUtils.cp_r(fixtures_path, project_path) - - # Copy the common fixtures (application configuration) to the temporary project path - common_path = internal_path.join('../../spec/fixtures/.') - FileUtils.cp_r(common_path, project_path) - - # Create the key file in XDG.data_home with the preset key value - keys_path = data_path.join("#{XDG_PROJECTS_BASE}/#{PROJECT_ID}") - keys_path.mkpath - key_content = { PROJECT_NAME => KEY_ID } - File.open(keys_path.join('keys.yml'), 'w') { |f| f.write(key_content.to_yaml) } - - # CD to the project_path and load the app - Dir.chdir(project_path) do - require 'bundler/setup' - require 'cnfs/boot_loader' - end - end - - def setup_segment(spec, load_nodes: false) - @current_spec = spec - stub_local_paths - stub_segments_path - FileUtils.cp(segments_yml_path, project_path.join('config/segments.yml')) - return unless load_nodes - - Cnfs.data_store.reset - # TODO: If Node, Segment, etc move from core to cnfs then keep this, otherwise needs to be in spec itself - SegmentRoot.load - end - - # If a file exists by the same name as the current spec then use it as config/segments.yml - def segments_yml_path - path = fixtures_path.join("segments/#{segments.join('/')}.yml") - path.exist? ? path : project_path.join('config/segments.yml.bak') - end - - def stub_local_paths - { cache_home: cache_path, config_home: config_path, data_home: data_path }.each do |method, path| - current_spec.allow_any_instance_of(XDG::Environment).to( - current_spec.receive(method).and_return(path)) - end - end - - def stub_segments_path - Cnfs.config.paths.segments = project_path.join('segments', *segments) - end - - def segments() = current_spec.class.name.split('::')[2...].map { |str| str.gsub('Nested', '').downcase } - - def remove_project - [project_path, cache_path, config_path, data_path].each do |path| - path.rmtree if path.exist? - end - end - - def project_path() = tmp_path.join('project') - - def cache_path() = tmp_path.join('.cache') - - def config_path() = tmp_path.join('.config') - - def data_path() = tmp_path.join('.local/share') - - def fixtures_path() = SPEC_DIR.join('fixtures/.') - - def tmp_path() = SPEC_DIR.join('tmp') - - def internal_path() = Pathname(__dir__) - end - end -end diff --git a/cnfs/lib/ext/string.rb b/cnfs/lib/ext/string.rb deleted file mode 100644 index 266451c3..00000000 --- a/cnfs/lib/ext/string.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: false - -class String - # Custom string interpolation using the ${} pattern - # For each interpolation, pass a hash to search for the interpolated string - # - # ==== Examples - # '${project.name}'.cnfs_sub(search: hash) - # '${project.environment.name}'.cnfs_sub(search: hash) - # '${project.name} and ${project.environment.name}'.cnfs_sub(search: hash) - # - # Assuming that service is a referencable object, return service.name: - # '${name}'.cnfs_sub(default: service.to_json, parent: another_hash) - # - # references are one or more named Hashes upon which lookups are performed - # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/PerceivedComplexity - # rubocop:disable Metrics/AbcSize - def cnfs_sub(**references) - # Return the original string if no references were passed in - return self if references.empty? - - interpolations = scan(/\${(.*?)}/).flatten - - # Return the original string if no interpolations are found in the string - return self unless interpolations.any? - - references.transform_keys!(&:to_s) - - references.each do |key, param| - next if param.is_a?(Hash) - - raise ArgumentError, "argument must by of type Hash (param #{key}, type #{param.class})" - end - - # If one of the references keys is 'default' then remove the 'default' key and put its values at the root of the Hash - i_reference = if references.key?('default') - references.except('default').merge(references['default']) - else - references - end - - return_string = self - interpolations.each do |interpolation| - next unless interpolation.length.positive? - - sub_string = search_reference(i_reference, interpolation) - next unless sub_string.is_a? String - - return_string = return_string.gsub("${#{interpolation}}", sub_string) - end - - # If after interpolation the string has not changed then return itself - return self if return_string.eql?(self) - - # If the string has changed an interpolation may have been replaced with another interpolation - # So recursively invoke cnfs_sub on the return_string - return_string.cnfs_sub(**references) - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/PerceivedComplexity - # rubocop:enable Metrics/CyclomaticComplexity - - private - - def search_reference(reference, interpolation) - interpolation.split('.').each do |value| - # reference must continue to be a Hash or Object otherwise it will fail when send is called - break if reference.is_a? String - - break unless (reference = reference.is_a?(Hash) ? reference[value] : reference.send(value)) - end - reference - end -end diff --git a/cnfs/spec/fixtures/config/application.rb b/cnfs/spec/fixtures/config/application.rb deleted file mode 100644 index 28a63456..00000000 --- a/cnfs/spec/fixtures/config/application.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require_relative 'boot' - -# Require the gems listed in Gemfile, including any gems -# you've limited to :test, :development, or :production. -Bundler.require # (*Cnfs.groups) - -module Spec - class Application < Cnfs::Application - config.name = PROJECT_NAME - - # Reference id for storage of local files - config.project_id = PROJECT_ID - - # The default value is :warn - # config.logging = :warn - - # Example configurations for segments - config.segments.environment = { aliases: '-e', color: 'yellow', env: 'env' } - config.segments.namespace = { aliases: '-n', color: 'green', env: 'ns' } - config.segments.segment = {} - config.segments.stack = { aliases: '-s', color: 'green' } - config.segments.target = { aliases: '-t', color: 'blue' } - end -end diff --git a/cnfs/spec/lib/ext/string_spec.rb b/cnfs/spec/lib/ext/string_spec.rb deleted file mode 100644 index 9de56959..00000000 --- a/cnfs/spec/lib/ext/string_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../lib/ext/string' - -RSpec.describe 'string' do - describe 'cnfs_sub' do - let(:tld) { { 'tld' => 'context.com' } } - let(:domain) { { 'domain' => 'backend.${parent.tld}' } } - let(:invalid) { { 'invalid' => { 'integer' => 1 } } } - - it 'returns self if no references are provided' do - string = 'backend.${tld}' - expect(string.cnfs_sub).to eq(string) - end - - it 'returns self if no interpolations are provided' do - string = 'backend' - expect(string.cnfs_sub(parent: tld)).to eq(string) - end - - it 'raises an ArgumentError if reference is an Integer' do - expect { 'error${tld}'.cnfs_sub(1) }.to raise_error(ArgumentError) - end - - it 'raises an ArgumentError if reference is a Boolean' do - expect { 'error${tld}'.cnfs_sub(true) }.to raise_error(ArgumentError) - end - - it 'raises an ArgumentError if reference is an Array ' do - expect { 'error${tld}'.cnfs_sub(default: []) }.to raise_error(ArgumentError) - end - - it 'interpolates backend.${tld}' do - string = 'backend.${parent.tld}' - expect(string.cnfs_sub(parent: tld)).to eq('backend.context.com') - end - - it 'returns self when provided an non existing interpolation' do - string = 'host.${parent.domain.invalid}' - expect(string.cnfs_sub(parent: tld)).to eq(string) - end - - it 'returns self when provided an non existing interpolation' do - string = 'host.${parent.invalid.domain}' - expect(string.cnfs_sub(parent: tld)).to eq(string) - end - - it 'returns self when provided an empty interpolation' do - string = 'host.${}' - expect(string.cnfs_sub(parent: {})).to eq('host.${}') - end - - it 'returns self when the found reference returns an Integer' do - string = 'host.${parent.invalid.integer}' - expect(string.cnfs_sub(parent: invalid)).to eq('host.${parent.invalid.integer}') - end - - it 'returns the correct interpolation and invlald delimited interpolation when provided both' do - string = 'host.${parent.tld}.${parent.invalid}' - expect(string.cnfs_sub(parent: tld)).to eq('host.context.com.${parent.invalid}') - end - - it 'recursively interpolates strings that require multiple interpolation' do - string = 'host.${child.domain}' - expect(string.cnfs_sub(parent: tld, child: domain)).to eq('host.backend.context.com') - end - - it 'recursively interpolates strings that require multiple interpolation' do - string = 'host.${domain}.this.${domain}' - expect(string.cnfs_sub(parent: tld, default: domain)).to eq('host.backend.context.com.this.backend.context.com') - end - end -end diff --git a/core/app/controllers/concerns/command_controller.rb b/core/app/controllers/concerns/command_controller.rb deleted file mode 100644 index 38b33fe5..00000000 --- a/core/app/controllers/concerns/command_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Concerns - module CommandController - extend ActiveSupport::Concern - include Cnfs::Concerns::CommandController - - included do - extend Cnfs::Concerns::CommandController - - # Load options for configured segments - Cnfs.config.command_options.each { |name, values| add_cnfs_option(name, values) } - - add_cnfs_option :clean, desc: 'Clean component cache', - type: :boolean - add_cnfs_option :clean_all, desc: 'Clean project cache', - type: :boolean - add_cnfs_option :fail_fast, desc: 'Skip any remaining commands after a command fails', - aliases: '--ff', type: :boolean - add_cnfs_option :generate, desc: 'Force generate manifest files ', - aliases: '-g', type: :boolean - add_cnfs_option :init, desc: 'Initialize the project, e.g. download repositories and dependencies', - type: :boolean - add_cnfs_option :tags, desc: 'Filter by tags', - aliases: '--tags', type: :array - - # Load modules to add options, actions and sub-commands to existing command structure - include Concerns::Extendable - end - end -end diff --git a/core/app/controllers/concerns/exec_controller.rb b/core/app/controllers/concerns/exec_controller.rb deleted file mode 100644 index bfad65d2..00000000 --- a/core/app/controllers/concerns/exec_controller.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Concerns - module ExecController - extend ActiveSupport::Concern - include Cnfs::Concerns::ExecController - - included do - extend Cnfs::Concerns::ExecController - - # Load modules to add options, actions and sub-commands to existing command structure - include Concerns::Extendable - end - - def context() = @context ||= Component.context_from(options) - - # TODO: When moving queue to cnfs_core then move this - def queue() = @queue ||= CommandQueue.new # (halt_on_failure: true) - - # - # Invokes init method on any model class that has defined it - # - def init - return unless options.init - - Cnfs::Core.asset_names.each do |asset| - klass = asset.classify.constantize - klass.init(context) if klass.respond_to?(:init) - end - end - end -end diff --git a/core/app/controllers/core/concerns/cnfs/main_controller.rb b/core/app/controllers/core/concerns/cnfs/main_controller.rb deleted file mode 100644 index 8004ca43..00000000 --- a/core/app/controllers/core/concerns/cnfs/main_controller.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module Core - class CreateController < Thor; include ::Concerns::CrudController end - - class ListController < Thor; include ::Concerns::CrudController end - - class ShowController < Thor; include ::Concerns::CrudController end - - class EditController < Thor; include ::Concerns::CrudController end - - class DestroyController < Thor; include ::Concerns::CrudController end - - module Concerns - module Cnfs - module MainController - extend ActiveSupport::Concern - - included do - include ::Concerns::CommandController - - cnfs_class_options :dry_run - # TODO: Cnfs.config not available - # cnfs_class_options Cnfs.config.segments.keys - - # CRUD Actions - %w[create show edit destroy list].each do |action| - klass = "core/#{action}_controller".classify.constantize - register klass, action, "#{action} [ASSET] [options]", "#{action.capitalize} asset" - end - - # Builder operates on Images - register Images::CommandController, 'image', 'image [SUBCOMMAND] [options]', 'Manage images' - - # Provisioner operates on Plans - register Plans::CommandController, 'plan', 'plan SUBCOMMAND [options]', 'Manage infrastructure plans' - - register Projects::CommandController, 'project', 'project SUBCOMMAND [options]', 'Manage project' - - register Resources::CommandController, 'resource', 'resource [SUBCOMMAND]', 'Manage component resources' - - register Segments::CommandController, 'segment', 'segment [SUBCOMMAND]', 'Manage segments' - - # Runtime operates on Services - register Services::CommandController, 'service', 'service SUBCOMMAND [options]', 'Manage services' - - # register Repositories::CommandController, 'repository', 'repository SUBCOMMAND [options]', - # 'Add, create, list and remove project repositories' - - desc 'tree', 'Display a tree' - def tree - # TODO: @options are not being passed in from command line - context = Component.context_from(@options) - require 'tty-tree' - puts '', TTY::Tree.new(context.as_tree).render - end - end - end - end - end -end diff --git a/core/app/controllers/projects/command_controller.rb b/core/app/controllers/projects/command_controller.rb deleted file mode 100644 index 3fc73cf8..00000000 --- a/core/app/controllers/projects/command_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Projects - class CommandController < Thor - include Concerns::CommandController - - cnfs_class_options :dry_run, :init, :clean, :clean_all, :tags - cnfs_class_options Cnfs.config.segments.keys - - desc 'console', 'Start a CNFS project console (short-cut: c)' - def console(name = nil, *values) - hash = { method: :execute } # Specify the method :execute to avoid method_missing being invoked on 'console' - hash.merge!(name.to_sym => values) if name # filter the context asset specified in 'name' by values - execute(**hash) - end - end -end diff --git a/core/app/controllers/projects/console_controller.rb b/core/app/controllers/projects/console_controller.rb deleted file mode 100644 index 01a4db37..00000000 --- a/core/app/controllers/projects/console_controller.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -# Rename pry commands so they still work but can be reassigned to CLI specific commands -%w[ls cd help].each do |cmd| - Pry.config.commands["p#{cmd}"] = "Pry::Command::#{cmd.camelize}".constantize - Pry.config.commands[cmd] = nil -end - -Pry::Commands.block_command 'cd', 'change segment' do |path| - if path.nil? || APP_CWD.relative_path_from(Cnfs.config.paths.segments).to_s.split('/').include?('..') - path = '.' if path.nil? - Object.send(:remove_const, :APP_CWD) - APP_CWD = Cnfs.config.paths.segments - end - if (try_path = APP_CWD.join(path)).exist? - Object.send(:remove_const, :APP_CWD) - APP_CWD = try_path - Cnfs.config.console.instance_variable_set('@context', nil) - Cnfs.config.console.init_class - end -end - -Pry::Commands.block_command 'ls', 'list assets' do |*_args| - Cnfs::Core.asset_names.each do |asset| - next unless (names = Cnfs.config.console.context.send(asset.to_sym).pluck(:name)).any? - - puts names - end -end - -module Projects - class ConsoleController < Cnfs::ConsoleController - include Concerns::ExecController - - before_execute :init, :init_class, :create_help - - if ENV['CNFS_CLI_ENV'].eql?('development') - Cnfs::Core.asset_names.each do |asset| - delegate asset.to_sym, to: :context - end - end - - def init_class() = self.class.init(options) - - # rubocop:disable Metrics/MethodLength - def create_help - Pry::Commands.block_command 'help', 'Show help for commands' do - crud_cmds = %w[create edit list show destroy] - cmds = crud_cmds.map { |cmd| " #{cmd} [ASSET] [options]".ljust(35) + "# #{cmd.capitalize} asset" } - - controller_cmds = (Cnfs::MainController.all_commands.keys - %w[help project] - crud_cmds) - cmds += controller_cmds.map { |cmd| " #{cmd} [SUBCOMMAND] [options]".ljust(35) + "# Manage #{cmd.pluralize}" } - - cmds += [ - ' help [COMMAND]'.ljust(35) + '# Describe available commands or one specific command', - ' reload!'.ljust(35) + '# Reload classes', - ' reset!'.ljust(35) + '# Purge all records from the data store, reload classes and reload records' - ] - - puts 'Commands:', cmds.sort, '' - end - end - # rubocop:enable Metrics/MethodLength - - def reload! - super - @context = nil - Node.source = :asset - true - end - - # Remove all records from the data store, reload classes and load segments into the data store - def reset! - Cnfs.data_store.reset - reload! - SegmentRoot.load - true - end - - class << self - def init(options) - @options = options - @colors = nil - @segmented_prompt = nil - end - - def prompt - proc do |obj, _nest_level, _| - klass = obj.class.name.demodulize.delete_suffix('Controller').underscore - label = klass.eql?('console') ? '' : " (#{obj.class.name})" - "#{segmented_prompt}#{label}> " - end - end - - # rubocop:disable Metrics/AbcSize - def segmented_prompt - @segmented_prompt ||= Component.structs(@options).each_with_object([]) do |component, prompt| - segment_type = Cnfs.config.cli.show_segment_type ? component.segment_type : nil - segment_name = Cnfs.config.cli.show_segment_name ? component.name : nil - next if (prompt_value = [segment_type, segment_name].compact.join(':')).empty? - - prompt_value = colorize(component, prompt_value) if Cnfs.config.cli.colorize - prompt << prompt_value - end.join('/') - end - # rubocop:enable Metrics/AbcSize - - def colorize(component, title) - color = component.color - color = color.call(component.name) if color&.class.eql?(Proc) - colors.delete(color) if color - color ||= colors.shift - Pry::Helpers::Text.send(color, title) - end - - # TODO: Sniff the monitor and use black if monitor b/g is white and vice versa - def colors() = @colors ||= %i[blue green purple magenta cyan yellow red] # white black] - - def shortcuts() = model_shortcuts.merge(super) - - def model_shortcuts - { b: Builder, c: Context, co: Component, con: Configurator, d: Dependency, im: Image, n: Node, - p: Plan, pr: Provider, pro: Provisioner, r: Resource, re: Repository, reg: Registry, ru: Runtime, - s: Service, sr: SegmentRoot, u: User } - end - end - end -end diff --git a/core/app/controllers/resources/command_controller.rb b/core/app/controllers/resources/command_controller.rb deleted file mode 100644 index cf3e04df..00000000 --- a/core/app/controllers/resources/command_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Resources - class CommandController < Thor - include Concerns::CommandController - - cnfs_class_options :dry_run, :init, :quiet - cnfs_class_options Cnfs.config.segments.keys - - desc 'console RESOURCE', 'Connect to a resource in the specified segment' - def console(resource) = execute(resource: resource) - end -end diff --git a/core/app/controllers/segments/command_controller.rb b/core/app/controllers/segments/command_controller.rb deleted file mode 100644 index 65bc684e..00000000 --- a/core/app/controllers/segments/command_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Segments - class CommandController < Thor - include Concerns::CommandController - - cnfs_class_options :dry_run, :init, :clean, :clean_all - cnfs_class_options Cnfs.config.segments.keys - - desc 'console', 'Start a CNFS project console (short-cut: c)' - def console() = execute - end -end diff --git a/core/app/generators/builder_generator.rb b/core/app/generators/builder_generator.rb deleted file mode 100644 index 37e14161..00000000 --- a/core/app/generators/builder_generator.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class BuilderGenerator < Cnfs::ApplicationGenerator - argument :builder -end diff --git a/core/app/generators/provisioner_generator.rb b/core/app/generators/provisioner_generator.rb deleted file mode 100644 index e225542d..00000000 --- a/core/app/generators/provisioner_generator.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class ProvisionerGenerator < Cnfs::ApplicationGenerator - argument :provisioner -end diff --git a/core/app/generators/runtime_generator.rb b/core/app/generators/runtime_generator.rb deleted file mode 100644 index 2f085e0b..00000000 --- a/core/app/generators/runtime_generator.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -class RuntimeGenerator < Cnfs::ApplicationGenerator - argument :runtime - - # NOTE: Generate the environment files first b/c the manifest template will - # look for the existence of those files - def environments - context.services.select { |service| service.environment.any? }.each do |service| - file_name = path.join("#{service.name}.env") - binding.pry - # TODO: remove Config::Options - environment = Config::Options.new.merge!(service.environment) - binding.pry - generated_files << template('templates/env.erb', file_name, env: environment) - end - end - - def manifests - name = nil - context.services.each do |service| - name = service.name - # binding.pry - generated_files << template(template_file(service), "#{path.join(service.name)}.yml") - end - rescue StandardError => e - Cnfs.logger.warn("Error generating template for #{name}: #{e.message}") - if CnfsCli.config.dev - msg = "#{e}\n#{e.backtrace}" - # binding.pry - end - ensure - remove_stale_files - end - - private - - # All content comes from Repository and Project Components so source paths reflect that - # TODO: This probably needs to be further filtered based on the blueprint in the case of Provisoners - # and by Resource? in the case of Runtimes - # In fact this may need to be refactored from a global CnfsCli registry to a component hierarchy based - def source_paths - @source_paths ||= CnfsCli.loaders.values.map { |path| path.join('generators', generator_type) }.select(&:exist?) - end - - def template_file(service) - [service.template, service.name, service.class.name.deconstantize, 'service'].each do |template_name| - source_paths.each do |source_path| - template_path = "templates/#{template_name}.yml.erb" - return template_path if source_path.join(template_path).exist? - end - end - end - - # List of services to be configured on the proxy server (nginx for compose) - # TODO: Move this to an nginx service class in an RC - def proxy_services() = context.services.select { |service| service.profiles.key?(:server) } - - def template_types - @template_types ||= context.services.map { |service| entity_to_template(service).to_sym }.uniq - end - - def version - runtime.version - end - - # Default space_count is for compose - # Template is expected to pass in a hash for example for profile - def labels(labels: {}, space_count: 6) - context.labels.merge(labels).merge(service: service.name).map do |key, value| - "\n#{' ' * space_count}#{key}: #{value}" - end.join - end - - def env_files(space_count = 6) - @env_files ||= {} - @env_files[service] ||= set_env_files.join("\n#{' ' * space_count}- ") - end - - def set_env_files - files = [] - files.append("./#{service.name}.env") if path.join("#{service.name}.env").exist? - files - end -end diff --git a/core/app/models/application_record.rb b/core/app/models/application_record.rb deleted file mode 100644 index c41af088..00000000 --- a/core/app/models/application_record.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -class ApplicationRecord < Cnfs::ApplicationRecord - self.abstract_class = true - - # by default rails does not serialize the type field - def as_json - super.merge(type_json).except(*except_json).compact - end - - def type_json - has_attribute?(:type) ? { 'type' => type } : {} - end - - # TODO: Maybe this should move to parent concern? - def except_json() = self.class.except_json - - # Disable storing database primary and foreign keys to yaml - # All references are determined dynamically when the context builds the assets - # Storing keys in yaml would be confusing to user and will cause problems as yaml content changes and IDs change - # Any nested stored_attributes should not be serialized as the root is already serialized - def self.except_json - %w[id name owner_type] + column_names.select { |n| n.end_with?('_id') } + - (stored_attributes.keys.map(&:to_s) - column_names) - end -end diff --git a/core/app/models/asset.rb b/core/app/models/asset.rb deleted file mode 100644 index 186c1fdb..00000000 --- a/core/app/models/asset.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class Asset < ApplicationRecord - belongs_to :owner, polymorphic: true - - def self.create_table(schema) - schema.create_table :assets, force: true do |t| - t.string :name - t.string :type - t.string :path - t.string :owner_type - t.string :owner_id - t.string :tags - end - end -end diff --git a/core/app/models/asset/credential.rb b/core/app/models/asset/credential.rb deleted file mode 100644 index c75bcaf6..00000000 --- a/core/app/models/asset/credential.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class Asset::Credential < Asset - FIXED_FILE = 'application_default_credentials.json' - - # TODO: This needs to change and coordinate with following files: - # https://github.com/rails-on-services/helm-charts/blob/master/charts/cp-kafka-connect/templates/deployment.yaml - # lib/ros/be/application/services/templates/jobs/kafka-connect/connector-provision-job.yml.erb - def deploy_commands(runtime) - runtime.kubectl("create secret generic #{name} --from-literal=#{FIXED_FILE}=#{secret}") - end - - def secret - Cnfs.decrypt_file(full_path) - end - - def full_path - Cnfs.root.join(path) - end -end diff --git a/core/app/models/asset/manifest.rb b/core/app/models/asset/manifest.rb deleted file mode 100644 index 3eb1bed6..00000000 --- a/core/app/models/asset/manifest.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -class Asset::Manifest < Asset -end diff --git a/core/app/models/builder.rb b/core/app/models/builder.rb deleted file mode 100644 index add75068..00000000 --- a/core/app/models/builder.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -class Builder < ApplicationRecord - # Define target and commands before including Operator Concern - class << self - def target() = :images - - def commands() = %i[build push pull test] - end - - include Concerns::Operator - - # Assigned in Operator#execute - attr_accessor :images - - before_execute :generate -end diff --git a/core/app/models/command.rb b/core/app/models/command.rb deleted file mode 100644 index df1007a3..00000000 --- a/core/app/models/command.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -# exec: The string to pass to the shell to run -# env: Hash of ENV vars to pass to the shell to run -# opts: Hash of custom options converted into: -# 1. a Hash of command_options for a TTY::Command instance -# 2. a Hash of run_options for the instance's run method -require 'tty-command' - -class Command - include ActiveModel::AttributeAssignment - - attr_accessor :exec, :env, :opts - # attr_reader :cmd_opts, :run_opts, :result, :exit_error - attr_reader :result, :exit_error - - delegate :out, :err, to: :result, allow_nil: true - - def initialize(**attrs) - assign_attributes(**attrs) if attrs.size.positive? - @env ||= {} - @opts ||= {} - @opts.transform_keys!(&:to_sym) - @opts = opts_defaults.merge(@opts) - end - - def run!(exec: nil, env: {}, opts: {}) - run(method: :run!, exec: exec, env: env, opts: opts) - end - - # run will throw an exception if the command fails - # run! will do what? - def run(method: :run, exec: nil, env: {}, opts: {}) - return if @result || @exit_error - - @exec = exec if exec - @env.merge!(env) - @opts.merge!(opts).transform_keys!(&:to_sym) - @result = command.send(method, @env, @exec, run_opts) - self - rescue TTY::Command::ExitError => e - @exit_error = e - ensure - # binding.pry - self - end - - def command - TTY::Command.new(**cmd_opts) - end - - def to_a(term: "\n") - out ? out.split(term) : [] - end - - def run_opts - @run_opts ||= opts.slice(*run_keys) - end - - # The options keys that are attributes of the run and run! methods - def run_keys - %i[verbose pty only_output_on_error in out err] - end - - def cmd_opts - @cmd_opts ||= opts.slice(*cmd_keys) - end - - # The options keys that are attributes of the command object - def cmd_keys - %i[uuid dry_run printer] - end - - def opts_defaults - { uuid: false } - end - - private - - # TODO: It will be a complex map of an external key to a hash of TTY::Command cmd and run options - def transform_keys - { a: 1, b: 2 }.transform_keys { |key| key_map[key] || key } - end - - def key_map - { silent: :only_output_on_error } - end - - # options for the TTY command - def x_set_command_options(context) - defaults = { uuid: false } - defaults.merge!(dry_run: true) if context.options.key?(:dry_run) - defaults - end - - def x_set_run_options(context) - defaults = {} - defaults.merge!(verbose: true) if context.options.verbose - defaults.merge!(pty: true) if 1 == 2 - defaults.merge!(only_output_on_error: true) if context.options.quiet - defaults - end -end diff --git a/core/app/models/command_queue.rb b/core/app/models/command_queue.rb deleted file mode 100644 index 5bb1ff47..00000000 --- a/core/app/models/command_queue.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -class CommandQueue - include ActiveModel::AttributeAssignment - attr_reader :queue, :results, :on_failure - - delegate :each, :map, :shfit, :unshift, :first, :last, :pop, :size, to: :queue - - def initialize(**options) - assign_attributes(options) - @queue = [] - @results = [] - # @on_failure ||= :raise - @on_failure ||= ActiveSupport::StringInquirer.new('raise') - end - - def run! - run(cmd: :run!) - end - - # rubocop:disable Metrics/CyclomaticComplexity - def run(cmd: :run) - queue.each do |command| - command.send(cmd) - # binding.pry - next unless command.exit_error || command.result.failure? - - msg = command.exit_error&.to_s || command.result.err - binding.pry - raise Cnfs::Error, msg if on_failure.raise? - - return false if on_failure.halt? - end - end - # rubocop:enable Metrics/CyclomaticComplexity - - def append(*commands) - commands.each do |command| - raise ArgumentError, 'Incorrect command format' unless command.instance_of?(Command) - - queue.append(command) - end - end - - def on_failure=(value) - binding.pry - @on_failure ||= ActiveSupport::StringInquirer.new(value.to_s) - end - - # def execute - # current_command = queue.shift - # result = command.run!(*current_command) - # results.append(result) - # true - # end - # - # def failure? - # failures.any? - # end - # - # def success? - # failures.empty? - # end - # - # def failure_messages - # failures.map(&:err) - # end - # - # def failures - # results.select(&:failure?) - # end - # - # def successes - # results.select(&:success?) - # end - # - # def runtime - # results.map(&:runtime).reduce(:+) - # end -end diff --git a/core/app/models/component.rb b/core/app/models/component.rb deleted file mode 100644 index 2bb06cfe..00000000 --- a/core/app/models/component.rb +++ /dev/null @@ -1,193 +0,0 @@ -# frozen_string_literal: true - -class Component < ApplicationRecord - include Concerns::Parent - include Concerns::Extendable - - belongs_to :node, class_name: 'SegmentFile' - belongs_to :owner, class_name: 'Component' - has_one :context - - has_many :components, foreign_key: 'owner_id' - - has_many :context_components - has_many :contexts, through: :context_components - - store :default, coder: YAML, accessors: :segment_name - - # For every asset Companent has_many and an _name default stored_attribute - Cnfs::Core.asset_names.each do |asset_name| - has_many asset_name.to_sym, as: :owner - - store :default, accessors: "#{asset_name.singularize}_name".to_sym - - # Return array of names for each of the component's assets - define_method("#{asset_name.singularize}_names") { send(asset_name.to_sym).pluck(:name) } - end - - # if segments_type is not present then cannot search beyond this component - # validates :segments_type, presence: true - - class << self - def context_from(options) - c_list = list(options) - Context.find_or_create_by(component: c_list.last) do |context| - context.options = options - context.components << c_list.slice(0, c_list.size - 1) - end - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - # - # List hierarchy of components based on CLI options, cwd, ENV and default segment_name(s) - def list(options) - pwd = APP_CWD.relative_path_from(Cnfs.config.paths.segments).to_s.split('/').excluding('..') - current = SegmentRoot.first - components = [current] - while current.components.any? - next_segment = current.next_segment(options, pwd) - break if next_segment.name.nil? # No search values found so stop at the current component - - unless next_segment.component - Cnfs.logger.warn(current.segments_type&.capitalize, "'#{next_segment.name}' specified by", - "*#{next_segment.source}* not found.", - "Context set to #{current.owner&.segments_type} '#{current.name}'") - break - end - - current = next_segment.component - components << current - end - components - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - def structs(options) = list(options).each_with_object([]) { |comp, ary| ary.append(comp.struct) } - end - - def struct() = OpenStruct.new(segment_type: owner.segments_type, name: name, color: color) - - def next_segment(options, pwd = nil) - next_name, next_source = next_segment_spec(options, pwd) - OpenStruct.new(name: next_name, source: next_source, component: components.find_by(name: next_name)) - end - - # Returns an Array for the component's next segment based on priority - # If a command line option was provided (e.g. -s stack_name) then return that - # If an ENV was provided (e.g. CNFS_STACK) then return that - # If the current component has an attribute 'default' then return that - # Otherwise return an empty Array - def next_segment_spec(options, pwd) - if (name = options.fetch(segments_type, nil)) - [name, 'CLI option'] - # TODO: remove the lonely operator after dropping context - elsif pwd&.any? - [pwd.shift, 'cwd'] - elsif (name = Cnfs.config.segments[segments_type].try(:[], :env_value)) - [name, 'ENV value'] - elsif segment_name # default[:segment_name] in this component's yaml file - [segment_name, parent.node_name] - else - [] - end - end - - # Return the default dir_path of the parent's path + this component's name unless segment_path is configured - # In which case parse segment_path to search the component hierarchy for the specified repository and blueprint - def dir_path - # binding.pry - if extension.nil? - node_warn(node: parent, msg: "Extension '#{extension_name}' not found") - elsif extension.segment(extension_path).nil? - node_warn(node: parent, msg: "Extension '#{extension_name}' segment '#{extension_path}' not found") - else - return extension.segment(extension_path) - end - parent.parent.rootpath.join(parent.node_name).to_s - end - - def extension() = Cnfs.extensions[extension_name] - - def extension_name() = segment_path ? segment_path.split('/').first.to_sym : :application - - def extension_path() = segment_path ? segment_path.split('/')[1...]&.join('/') : owner_extension_path - - def owner_extension_path() = owner.extension_path.join(name) - - # Key search priorities: - # 1. ENV var - # 2. Project's data_path/keys.yml - # 3. Owner's key - def key() = @key ||= ENV[key_name_env] || local_file_read(path: keys_file).fetch(key_name, owner&.key) - - # 1_target/backend => 1_target_backend - def key_name() = @key_name ||= "#{owner.key_name}_#{name}" - - # 1_target/backend => CNFS_KEY_BACKEND - def key_name_env() = @key_name_env ||= "#{owner.key_name_env}_#{name.upcase}" - - def keys_file() = @keys_file ||= Cnfs.config.data_home.join('keys.yml') - - def generate_key - key_file_values = local_file_read(path: keys_file).merge(new_key) - local_file_write(path: keys_file, values: key_file_values) - end - - def new_key() = { key_name => Lockbox.generate_key } - - # Read/Write 'runtime' component data to this file - def cache_file() = @cache_file ||= "#{cache_path}.yml" - - # This component's local file to provide local overrides to project values, e.g. change default runtime_name - # The user must maintain these files locally - # These values are merged for runtime so they are not saved into the project's files - def data_file() = @data_file ||= "#{data_path}.yml" - - # Operators and Context's Assets can read/write their 'runtime' data into this directory - def cache_path() = @cache_path ||= owner.cache_path.join(name) - - # Child Components and Assets can read their local overrides into this directory - def data_path() = @data_path ||= owner.data_path.join(name) - - def attrs() = @attrs ||= owner.attrs.dup.append(name) - - def as_merged() = as_json - - def segment_names() = @segment_names ||= components.pluck(:name) - - def color() = owner.segments_type ? Cnfs.config.segments[owner.segments_type].try(:[], :color) : nil - - def as_tree() = { '.' => { segments_type&.pluralize || '.' => tree } } - - def tree - components.each_with_object([]) do |comp, ary| - if comp.components.size.zero? - ary.append(comp.name) - else - ary.append({ comp.name => { comp.segments_type&.pluralize => comp.tree } }) - end - end - end - - def tree_name - bp_name = component_name.nil? ? '' : " source: #{component_name.gsub('/', '-')}" - "#{name} (#{owner.segments_type})#{bp_name}" - end - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.string :name - t.string :type # The only subclass is SegmentRoot. Not to be configured by User - t.string :segment_path # Usually nil, but can be set to extension/segment_name - t.string :segments_type # corresponds to ENV and CLI options to filter the components to most specific - t.string :default - t.string :config - t.references :owner - t.references :node - end - end - end -end diff --git a/core/app/models/concerns/generic.rb b/core/app/models/concerns/generic.rb deleted file mode 100644 index 9f34d8c6..00000000 --- a/core/app/models/concerns/generic.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -# Common Functionallity for Component and Asset -module Concerns - module Generic - extend ActiveSupport::Concern - - included do - include Concerns::Asset - include Concerns::Extendable - end - end -end diff --git a/core/app/models/concerns/node_writer.rb b/core/app/models/concerns/node_writer.rb deleted file mode 100644 index 183bab12..00000000 --- a/core/app/models/concerns/node_writer.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# This only applies to Component and Asset -module Concerns - module NodeWriter - extend ActiveSupport::Concern - - included do - attr_writer :yaml_payload, :owner_class - - after_create :make_owner, if: proc { Node.source.eql?(:node) } - - # BEGIN: source asset - - after_create :write_yaml, if: proc { Node.source.eql?(:asset) } - after_update :write_yaml, if: proc { Node.source.eql?(:asset) } - after_destroy :destroy_yaml, if: proc { Node.source.eql?(:asset) } - end - - # rubocop:disable Metrics/AbcSize - def make_owner - obj = parent.nil? ? @owner_class.create(yaml_payload) : owner_association.create(yaml_payload) - return unless obj - - update(owner: obj) - owner_log('Created owner') - rescue ActiveModel::UnknownAttributeError, ActiveRecord::AssociationTypeMismatch, ActiveRecord::RecordInvalid => e - Cnfs.logger.warn('NodeWriter:', e.message, yaml_payload) - Cnfs.logger.debug('NodeWriter Error:', e.backtrace.join("\n")) - owner_log('Error creating owner') - rescue NoMethodError => e - Cnfs.logger.error('NodeWriter:', e.message, yaml_payload) - Cnfs.logger.debug('NodeWriter Error:', e.backtrace.join("\n")) - end - - # rubocop:enable Metrics/AbcSize - # Returns an A/R association, e.g. components, resources, etc - # owner_association_name is implemented in Node::Component and Node::Asset - def owner_association - owner_ref(self).send(owner_association_name.to_sym) - end - - def owner_log(title) - Cnfs.logger.debug("#{title} from: #{pathname}\n#{yaml_payload}") - end - - def yaml_payload - @yaml_payload ||= { 'name' => node_name }.merge(yaml) - rescue StandardError => e - binding.pry - end - - # BEGIN: source asset - - def write_yaml - yaml_to_write = owner.as_json_encrypted.to_yaml - Cnfs.logger.debug("Writing to #{realpath} with\n#{yaml_to_write}") - File.open(realpath, 'w') { |f| f.write(yaml_to_write) } - end - end -end diff --git a/core/app/models/concerns/parent.rb b/core/app/models/concerns/parent.rb deleted file mode 100644 index 990845a2..00000000 --- a/core/app/models/concerns/parent.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -# Common Functionallity for Component and Asset -module Concerns - module Parent - extend ActiveSupport::Concern - - included do - include Concerns::Encryption - include Concerns::Interpolation - - store :config, coder: YAML - - validates :name, presence: true - - after_create :create_node_record - after_update :update_node_record - after_destroy :destroy_node_record - end - - def to_context() = as_interpolated - - def node_content() = as_json_encrypted - - def create_node_record() = node_class.create_content(object: self) - - def node_class() = self.class.reflect_on_association(:node).klass - - # Assets whose owner is Context are ephemeral so don't create/update a node - def update_node_record() = node.update_content(object: self) - - def destroy_node_record() = node.destroy_content(object: self) - - # Log message at level warn appending the parent path to the message - def node_warn(node:, msg: []) - text = [msg].flatten.append("Source: #{node.rootpath}").join("\n#{' ' * 10}") - Cnfs.logger.warn(text) - end - - # def p_node?() = Node.source.eql?(:p_node) - # is_a?(Component) || owner.is_a?(Component) - # end - - # Convenience methods for cache_* and data_* for clarity in calling code - def cache_file_read() = local_file_read(path: cache_file) - - def data_file_read() = local_file_read(path: data_file) - - def local_file_read(path:) = path.exist? ? (YAML.load_file(path) || {}) : {} - - def cache_file_write(**values) = local_file_write(path: cache_file, values: values) - - def data_file_write(**values) = local_file_write(path: data_file, values: values) - - def local_file_write(path:, values:) - path.parent.mkpath - File.open(path, 'w') { |f| f.write(values.to_yaml) } - end - - class_methods do - def node_callbacks - [ - %i[create after create_node_record], - %i[update after update_node_record] - ] - end - end - end -end diff --git a/core/app/models/configurator.rb b/core/app/models/configurator.rb deleted file mode 100644 index f317acd2..00000000 --- a/core/app/models/configurator.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class Configurator < ApplicationRecord - include Concerns::Operator - - attr_accessor :playbooks - - before_execute :generate - - def target() = :playbooks -end diff --git a/core/app/models/context.rb b/core/app/models/context.rb deleted file mode 100644 index b847ad49..00000000 --- a/core/app/models/context.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -class Context < ApplicationRecord - belongs_to :component - - has_many :context_components - has_many :components, through: :context_components - - Cnfs::Core.asset_names.each do |asset_name| - # component_ = all from the component hierarchy - has_many "component_#{asset_name}".to_sym, through: :components, source: asset_name.to_sym - has_many asset_name.to_sym, as: :owner - end - - store :options, coder: YAML - - # When decrypting values assets ask for the key from their owner - # When context is the owner it must ask the component for the key - # TODO: The assets need to be decrypted with reference to their actual owner not the context.component - # This will not work for inherited values. This needs to be tested - delegate :attrs, :cache_path, :data_path, :key, :as_interpolated, :segments_type, :segment_names, :as_tree, - to: :component - - after_create :create_assets - after_commit :update_asset_associations - - def create_assets - Node.with_asset_callbacks_disabled do - Cnfs::Core.asset_names.each do |asset_type| - component_assets = component.send(asset_type.to_sym) - inheritable_assets = send("component_#{asset_type}".to_sym).inheritable - context_assets = send(asset_type.to_sym) - first_pass(component_assets, inheritable_assets, context_assets) - second_pass(component_assets, inheritable_assets, context_assets) - end - end - end - - def first_pass(component_assets, inheritable_assets, context_assets) - component_assets.enabled.each do |asset| - # binding.pry if asset.class.name.eql? 'Plan' - name = asset.name - json = inheritable_assets.where(name: name).each_with_object({}) do |record, hash| - hash.deep_merge!(record.to_context.compact) - end - json.deep_merge!(asset.to_context.compact) - context_assets.create(json.merge(name: name)) - end - end - - # Inheritable assets that are not defined in the component, but are enabled are added to the context - # All values of the inherited assets of the same name are deep merged - def second_pass(component_assets, inheritable_assets, context_assets) - names = component_assets.pluck(:name) - # binding.pry if inheritable_assets.any? and inheritable_assets.first.class.name.eql?('Plan') - inheritable_assets.enabled.where.not(name: names).group_by(&:name).each_with_object([]) do |(name, records), _ary| - json = records.each_with_object({}) do |record, hash| - hash.deep_merge!(record.to_context.compact) - end - context_assets.create(json.merge(name: name)) # unless json['disabled'] - end - end - - def update_asset_associations - Cnfs::Core.asset_names.each { |asset_type| asset_type.classify.constantize.update_associations(self) } - end - - # Used by runtime generator templates so the runtime can query services by label - def labels() = @labels ||= labels_hash - - def labels_hash - # TODO: Refactor; maybe to Component class - component_structs.each_with_object({ 'context' => name }) do |component, hash| - hash[component.segment_type] = component.name - end - end - - def name() = @name ||= attrs.join('_') - - def path() = @path ||= attrs.join('/') - - def options() = @options ||= Thor::CoreExt::HashWithIndifferentAccess.new(super) - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.references :component - t.string :options - end - end - end -end diff --git a/core/app/models/definition.rb b/core/app/models/definition.rb deleted file mode 100644 index 105aceda..00000000 --- a/core/app/models/definition.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -class Definition < ApplicationRecord - include Concerns::Generic - - store :config, accessors: :path - - before_validation :set_defaults - - # TODO: Definitions do not require an owner - # They do have a parent - # So need a new concern for this type of arrangement, the owner-less asset - def set_defaults - self.name = path.split('/').last - # self.owner = SegmentRoot.create(name: :test) - end - - after_create :do_it - - attr_reader :mod, :parent_class, :class_name - - def do_it - parse_attributes - create_class - end - - def parse_attributes - asset_type, *mod_types, model_type = file.to_s.delete_suffix('.yml').split('/').map(&:classify) - @mod = mod_types.join('::').safe_constantize || Object - @class_name = [model_type, asset_type].join - @parent_class = asset_type.safe_constantize - end - - # services/nginx.yml => NginxService - # plans/terraform/ec2.yml # => Terraform::Ec2Plan - def create_class - attributes = content['attributes'] - mod.const_set class_name, Class.new(parent_class) { - attr_accessor(*attributes) - # include Concerns::Extendable - } - end - - def content() = @content ||= YAML.load_file(path) - - # TODO: The DefinitionFile should be passed in and it is relative from there somehow - # so that definitions are in extensions as well - def file() = pathname.relative_path_from(Cnfs.config.paths.definitions) - - def pathname() = @pathname ||= Pathname.new(path) -end diff --git a/core/app/models/dependency.rb b/core/app/models/dependency.rb deleted file mode 100644 index 5e765667..00000000 --- a/core/app/models/dependency.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class Dependency < ApplicationRecord - include Concerns::Download - include Concerns::Generic - - store :config, accessors: %i[source linux mac] - - validate :available - - # TODO: Search cache path instead of system directories or an option to do so - # TTY::Which.which("ruby", paths: ["/usr/local/bin", "/usr/bin", "/bin"]) - def available - return unless Node.source.eql?(:asset) - - errors.add(:dependency_not_found, name) unless TTY::Which.exist?(name) - end - - def do_download(version) - dep = with_other(operator: { 'version' => version }, platform: CnfsCli.platform.to_hash) - url = Pathname.new(dep['config']['source']) - file = cache_path.join(version, url.basename) - return if file.exist? - - download(url: url, path: cache_path.join(version)) - end - - def cache_path() = CnfsCli.config.cli_cache_home.join('dependencies', name) - - # This may be about TF modules rather than binaries like tf, kubectl, etc - # TODO: Figure out how to manage these - # def dependencies - # super.map(&:with_indifferent_access) - # end - - # TODO: What is this for? - # def fetch_data_repo - # Cnfs.logger.info "Fetching data source v#{data.config.data_version}..." - # File.open('data.tar.gz', 'wb') do |fo| - # fo.write open("https://github.com/#{data.config.data_repo}/archive/#{data.config.data_version}.tar.gz", - # 'Authorization' => "token #{data.config.github_token}", - # 'Accept' => 'application/vnd.github.v4.raw').read - # end - # `tar xzf "data.tar.gz"` - # end -end diff --git a/core/app/models/image.rb b/core/app/models/image.rb deleted file mode 100644 index f294919b..00000000 --- a/core/app/models/image.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class Image < ApplicationRecord - # Define operator before inclding Concerns::Target - def self.operator() = ::Builder - - include Concerns::Target - include Concerns::Git - - belongs_to :builder - belongs_to :registry - belongs_to :repository - - # TODO: Move all of this up to delegation command to Compose::Image - # As these things will be very different for e.g. Packer images - # NOTE: dockerfile is relative to repository.git_path - store :config, accessors: %i[args dockerfile tag] - - serialize :test_commands, Array - - before_validation :set_defaults - - def set_defaults - return unless Node.source.eql?(:asset) - - self.dockerfile ||= 'Dockerfile' - end - - # Concerns::Git methods require #git_path to be defined in order to behave as expected - # NOTE: Templates can use image.git.tag or any other value from methods in Concerns::Git - delegate :git_path, to: :repository - - class << self - def add_columns(t) - t.references :builder - t.string :builder_name - t.references :registry - t.string :registry_name - t.references :repository - t.string :repository_name - t.string :test_commands - # t.string :build - # t.string :path - end - end -end diff --git a/core/app/models/manifest.rb b/core/app/models/manifest.rb deleted file mode 100644 index 76e58a35..00000000 --- a/core/app/models/manifest.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -class Manifest - include ActiveModel::AttributeAssignment - include ActiveModel::Validations - - # relative_from: Cnfs.config.root - # attr: Any attribute of Pathname, e.g. :atime, :size - # Example: Node.list(relative_path_from: Cnfs.config.root).max - # returns an Array containing one element of the most recently updated Node with its mtime and relative path - attr_accessor :source, :target, :relative_path_from, :attr - - validate :outdated, :no_target_files - - def initialize(**options) - assign_attributes(**options) - @attr ||= :mtime - end - - def reload - @source_files, @target_files, @newest_source, @oldest_target = nil - validate - self - end - - def newest_source() = @newest_source ||= list(source_files).max - - def source_files() = @source_files ||= source.call - - def oldest_target() = @oldest_target ||= list(target_files).min - - def target_files() = @target_files ||= target.call - - def rm_targets() = target_files.each { |path| path.delete } - - def list(file_set) - file_set.select(&:file?).reject(&:symlink?).map do |path| - path = path.relative_path_from(relative_path_from) if relative_path_from - [path.send(attr), path] - end - end - - private - - def outdated - return if source_files.empty? || target_files.empty? || oldest_target.first > newest_source.first - - errors.add(:stale_files, "#{newest_source} > #{oldest_target}") - end - - def no_target_files - errors.add(:no_files_found, '') if source_files.any? && target_files.empty? - end -end diff --git a/core/app/models/node.rb b/core/app/models/node.rb deleted file mode 100644 index f0a5bef5..00000000 --- a/core/app/models/node.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -class Node < ApplicationRecord - belongs_to :parent, class_name: 'Node' - belongs_to :owner, polymorphic: true - has_many :nodes, foreign_key: 'parent_id' - - before_validation :set_realpath - - def set_realpath - binding.pry unless Pathname.new(path).exist? - self.realpath ||= Pathname.new(path).realpath.to_s - end - - # Returns a Component instance by searching recursively up the current node's parent hierarchy - # until reaching a Node::Component which overrides this method and returns its owner - def owner_ref(obj) = parent.owner_ref(obj) - - def yaml(reload: false) - @yaml = nil if reload - @yaml ||= rootpath.file? ? (YAML.load_file(rootpath) || {}) : {} - end - - def node_name() = @node_name ||= base_name(pathname) - - def base_name(path_name) = path_name.basename.to_s.delete_suffix(path_name.extname) - - def pathname() = @pathname ||= Pathname.new(path) - - def rootpath() = @rootpath ||= Pathname.new(realpath) - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - - # Comment - def x_tree - nodes.group_by(&:type).each_with_object([]) do |(key, nodes), ary| - binding.pry - ary.append({ key => x_tree(nodes) }) - end - end - - # def x_tree(nodes) - def tree - nodes.each_with_object([]) do |node, ary| - case node.type - when 'Node::Asset' - ary.append(node.tree_name) - when 'Node::Component' - if node.nodes.any? - ary.append({ node.tree_name => node.nodes.first.tree }) - else - ary.append(node.tree_name) - end - else - ary.append({ node.node_name => node.tree }) - end - end - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/MethodLength - - class << self - attr_reader :source - - def source=(source) - @source = source.to_s.downcase.to_sym - end - - def with_asset_callbacks_disabled - self.source = :node - - assets_to_disable.each do |asset| - asset.node_callbacks.each { |callback| asset.skip_callback(*callback) } - end - - yield - - assets_to_disable.each do |asset| - asset.node_callbacks.each { |callback| asset.set_callback(*callback) } - end - - self.source = :asset - end - - def assets_to_disable - @assets_to_disable ||= Cnfs::Core.asset_names.dup.append('component').map { |name| name.classify.constantize } - end - - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.references :parent - t.references :owner, polymorphic: true - t.string :type - t.string :path - t.string :realpath - end - end - end -end diff --git a/core/app/models/node/asset.rb b/core/app/models/node/asset.rb deleted file mode 100644 index 7c138b2d..00000000 --- a/core/app/models/node/asset.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -class Node::Asset < Node - include Concerns::NodeWriter - - # BEGIN: source asset - - after_initialize :identify_parent, if: proc { Node.source.eql?(:asset) } - - delegate :tree_name, to: :owner - - def identify_parent - return if persisted? - - self.parent = who_parent - self.path = parent.path - end - - # rubocop:disable Metrics/AbcSize - def who_parent - if candidates.size.zero? - path = "#{node_component_dir.path}/#{owner.class.table_name}.yml" - node_component_dir.nodes.create(type: 'Node::AssetGroup', path: path) - elsif candidates.size.eql?(1) - candidates.first - elsif candidates.size.eql?(2) - candidates.select { |c| c.type.eql?('Node::AssetGroup') }.first - end - end - # rubocop:enable Metrics/AbcSize - - def candidates - @candidates ||= node_component_dir.nodes.select { |node| node.node_name.eql?(asset_type) } - end - - def node_component_dir - # owner is resource - # owner.owner is resource.owner (Project.first) - # owner.owner.parent is Project.first's Node::Component - # owner.owner.parent.dir is Node::Component's Node::ComponentDir - owner.owner.parent.component_dir - end - - def asset_type - owner.class.table_name - end - - # returns a value to Node#owner_association to call on owner - def owner_association_name - parent.node_name - end - - # If parent is an AssetGroup then delegate writing to the parent - # If parent is an AssetDir then just write the yaml to realpath - def write_yaml - if parent.type.eql?('Node::AssetGroup') - parent.write_yaml(owner) - else - super - end - end - - def destroy_yaml - parent.destroy_yaml(self) - end -end diff --git a/core/app/models/node/asset_dir.rb b/core/app/models/node/asset_dir.rb deleted file mode 100644 index 191a0667..00000000 --- a/core/app/models/node/asset_dir.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class Node::AssetDir < Node - after_create :create_asset_nodes, if: proc { Node.source.eql?(:node) } - - def create_asset_nodes - pathname.children.select(&:file?).each do |path_name| - nodes.create(type: 'Node::Asset', path: path_name.to_s) - end - end - - # BEGIN: source asset - - after_create :make_path, if: proc { Node.source.eql?(:asset) } - - # TODO: Is necessary? - def make_path - # binding.pry - rootpath.mkdir - end - - # Called from Asset - def destroy_yaml(asset) - Cnfs.logger.debug("Deleting #{realpath}") - FileUtils.rm(asset.realpath) - return if rootpath.children.size.positive? - - rootpath.rmtree - destroy - end -end diff --git a/core/app/models/node/asset_group.rb b/core/app/models/node/asset_group.rb deleted file mode 100644 index eeeb6882..00000000 --- a/core/app/models/node/asset_group.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -class Node::AssetGroup < Node - after_create :create_asset_nodes, if: proc { Node.source.eql?(:node) } - - def create_asset_nodes - yaml.each do |key, values| - new_values = { 'name' => key }.merge(values) - nodes.create(type: 'Node::Asset', path: path, yaml_payload: new_values) - end - end - - # BEGIN: source asset - - # after_create :make_path, if: proc { Node.source.eql?(:asset) } - - # Override Node#set_realpath to create the file first, otherwise super will fail due to path not existing - def set_realpath - return super if Node.source.eql?(:node) || persisted? - - FileUtils.touch(path) - super - end - - # TODO: Is necessary? - def make_path - # binding.pry - FileUtils.touch(rootpath) - end - - def write_yaml(owner) - write_file(yaml.merge(owner.name => owner.as_json_encrypted).sort.to_h) - end - - def destroy_yaml(asset) - write_file(yaml.except(asset.owner.name)) - end - - def write_file(yaml_to_write) - Cnfs.logger.debug("Writing to #{realpath} with\n#{yaml_to_write}") - File.open(realpath, 'w') { |f| f.write(yaml_to_write.to_yaml) } - - return unless yaml_to_write.empty? - - FileUtils.rm(realpath) - destroy - end - - def tree_name - { node_name => nodes.map(&:tree_name) } - end -end diff --git a/core/app/models/node/component.rb b/core/app/models/node/component.rb deleted file mode 100644 index 9fcd938c..00000000 --- a/core/app/models/node/component.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -class Node::Component < Node - include Concerns::NodeWriter - - attr_writer :dir_path - - after_create :load_dir_path, if: proc { Node.source.eql?(:node) } - - # Create a ComponentDir object to query the child nodes if the directory exists on the filesystem - def load_dir_path - component_path = Pathname.new(dir_path) - nodes.create(type: 'Node::ComponentDir', path: component_path.to_s) if component_path.exist? - end - - # The default dir_path is the same name as the component file but with '.yml' stripped - def dir_path - @dir_path ||= (owner.respond_to?(:dir_path) ? owner.dir_path : "#{parent.parent.dir_path}/#{node_name}") - end - - # Override Node's definition to return owner unless calling owner_ref on self - # Components are the only type that can return an owner - def owner_ref(obj) - obj.eql?(self) ? parent.owner_ref(obj) : owner - end - - # returns a value to Node#owner_association to call on owner - def owner_association_name() = 'components' - - # BEGIN: source asset - - # Override Node#set_realpath to create the file first, otherwise super will fail due to path not existing - def set_realpath - return super if Node.source.eql?(:node) || persisted? - - self.parent ||= owner.owner.parent.component_dir - base_path = "#{parent.path}/#{owner.name}" - self.path ||= "#{base_path}.yml" - FileUtils.touch(path) - binding.pry - nodes << Node::ComponentDir.new(path: base_path) - super - end - - # Called by Asset to get the ComponentDir in which its AssetDir or AssetGroup is located - def component_dir() = nodes.first - - def destroy_yaml - Cnfs.logger.debug("Deleting #{realpath}") - component_dir.destroy - FileUtils.rm(realpath) - end - - delegate :tree_name, to: :owner - - def to_tree() = puts("\n#{as_tree.render}") - - def as_tree() = TTY::Tree.new(owner.name => nodes.first.tree) -end diff --git a/core/app/models/node/component_dir.rb b/core/app/models/node/component_dir.rb deleted file mode 100644 index a2ee66b2..00000000 --- a/core/app/models/node/component_dir.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -class Node::ComponentDir < Node - after_create :load_path, if: proc { Node.source.eql?(:node) } - - delegate :owner, to: :parent - - # Iterate over files and directories - def load_path - # TODO: Could this be used to load app dir and blueprint.yml? - # If not remove it - owner.before_load_path(rootpath, self) if owner.respond_to?(:before_load_path) - create_assets - validate_if_segment - create_components - end - - def create_assets - asset_paths.each do |path_name| - node_type = path_name.directory? ? 'Node::AssetDir' : 'Node::AssetGroup' - nodes.create(type: node_type, path: path_name.to_s) - end - end - - # Return files and directories whose names are found in the asset_name array e.g. resources.yml, resources/ - def asset_paths - search_path.children.select { |n| Cnfs::Core.asset_names.include?(base_name(n)) }.sort - end - - # Create Node::Components for each found component - def create_components - component_file_paths.each do |path_name| - nodes.create(type: 'Node::Component', path: path_name.to_s) - end - end - - def component_file_paths - search_path.children.select(&:file?).reject { |n| Cnfs::Core.asset_names.include?(base_name(n)) } - end - - def component_dir_paths - search_path.children.select(&:directory?).reject { |n| Cnfs::Core.asset_names.include?(base_name(n)) } - end - - # If the component has the search_path method then only search in that path - def search_path - @search_path ||= pathname.join(owner.respond_to?(:search_path) ? owner.search_path : '') - end - - # If a component dir exsists but not the file then create the file and vice versa - # So that component crud can rely on the filesystem being available - def validate_if_segment - return unless owner.type.nil? - - dirs = component_dir_paths.map { |p| base_name(p) } - files = component_file_paths.map { |p| base_name(p) } - (files - dirs).each { |path| search_path.join(path).mkdir } - (dirs - files).each { |path| FileUtils.touch(search_path.join("#{path}.yml")) } - end - - # BEGIN: source asset - - after_destroy :destroy_yaml, if: proc { Node.source.eql?(:asset) } - - # Override Node#set_realpath to create the file first, otherwise super will fail due to path not existing - def set_realpath - return super if Node.source.eql?(:node) || persisted? - - # binding.pry - path_n = Pathname.new(path) - path_n.mkdir unless path_n.exist? - super - end - - def destroy_yaml - rootpath.rmtree - end -end diff --git a/core/app/models/plan.rb b/core/app/models/plan.rb deleted file mode 100644 index 749e0bab..00000000 --- a/core/app/models/plan.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -# A Plan defines one or more resources for a Provisioner to create on the target Provider -class Plan < ApplicationRecord - # Define operator before inclding Concerns::Target - def self.operator() = Provisioner - - include Concerns::Target - - belongs_to :provider - belongs_to :provisioner - - # NOTE: This is used by Provisioner to CRUD resources after a command, e.g. deploy, destroy, is run - has_many :resources - - class << self - def add_columns(t) - t.references :provider - t.string :provider_name - t.references :provisioner - t.string :provisioner_name - end - end -end diff --git a/core/app/models/platform.rb b/core/app/models/platform.rb deleted file mode 100644 index 0026f424..00000000 --- a/core/app/models/platform.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -# OS methods -class Platform - include ActiveModel::Validations - - validate :supported_platform - - def supported_platform - errors.add(:platform, 'not supported') if os.eql?('unknown') - end - - def to_hash() = @to_hash ||= JSON.parse(as_hash.to_json) - - def as_hash() = { arch: arch, os: os }.merge(gid) - - def arch - case config['host_cpu'] - when 'x86_64' - 'amd64' - else - config['host_cpu'] - end - end - - def gid - ext_info = {} - if os.eql?('linux') && Etc.getlogin - shell_info = Etc.getpwnam(Etc.getlogin) - ext_info[:puid] = shell_info.uid - ext_info[:pgid] = shell_info.gid - end - ext_info - end - - def os - case config['host_os'] - when /linux/ - 'linux' - when /darwin/ - 'darwin' - else - 'unknown' - end - end - - def config() = @config ||= RbConfig::CONFIG -end diff --git a/core/app/models/playbook.rb b/core/app/models/playbook.rb deleted file mode 100644 index 91df6187..00000000 --- a/core/app/models/playbook.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class Playbook < ApplicationRecord - include Concerns::Target - - class << self - def add_columns(t) - t.string :configurator_name - t.references :configurator - end - end -end diff --git a/core/app/models/provider.rb b/core/app/models/provider.rb deleted file mode 100644 index a7b4d600..00000000 --- a/core/app/models/provider.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class Provider < ApplicationRecord - include Concerns::Generic -end diff --git a/core/app/models/provisioner.rb b/core/app/models/provisioner.rb deleted file mode 100644 index 31b4b11e..00000000 --- a/core/app/models/provisioner.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -class Provisioner < ApplicationRecord - # Define target and commands before including Operator Concern - class << self - def target() = :plans - - def commands() = %i[deploy undeploy] - end - - include Concerns::Operator - - # Assigned in Operator#execute - attr_accessor :plans - - before_execute :generate -end diff --git a/core/app/models/provisioner_resource.rb b/core/app/models/provisioner_resource.rb deleted file mode 100644 index 633a31f5..00000000 --- a/core/app/models/provisioner_resource.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class ProvisionerResource < ApplicationRecord - belongs_to :provisioner - - # accessors match the columns returned by docker ps - # TODO: move this to a terraform concern - # TODO: do the same for compose/docker and skaffold/docker - store :terraform, coder: YAML, accessors: %i[rid image names command labels status ports] - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.references :provisioner - # t.string :terraform - # t.string :pulumi - end - end - end -end diff --git a/core/app/models/registry.rb b/core/app/models/registry.rb deleted file mode 100644 index a3de10d9..00000000 --- a/core/app/models/registry.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class Registry < ApplicationRecord - include Concerns::Generic -end diff --git a/core/app/models/repository.rb b/core/app/models/repository.rb deleted file mode 100644 index 2ed4667c..00000000 --- a/core/app/models/repository.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -class Repository < ApplicationRecord - # include Concerns::Operator - include Concerns::Generic - include Concerns::Git - - store :config, accessors: %i[url path] - - validates :url, presence: true - - after_destroy :remove_git_path - - def remove_git_path - git_path.rmtree if git_path.exist? - end - - # COMPONENT_PATH_KEY = 'components_path' - # COMPONENT_FILE = 'component.yml' - # REPOSITORY_FILE = 'repository.yml' - - # after_create :register_components - - # def register_components - # return unless repo.path.exist? && components_path_exist? - # - # components_path.children.select(&:directory?).each do |component_path| - # next unless component_path.join(COMPONENT_FILE).exist? - # - # component_name = component_path.relative_path_from(src_path).to_s - # Cnfs.logger.info("Found component #{component_name}") - # CnfsCli.register_component(name: component_name, path: component_path) - # end - # end - - # def repo_path_exist? - # return true if path.exist? - # - # log_f(:warn, "Repository #{name} path not found #{path}") - # nil - # end - - # def components_path_exist? - # return true if components_path.exist? - # - # log_f(:warn, "Invalid configuration for repository #{name}", "Path not found: #{components_path}") - # nil - # end - - # # TODO: See about formatting messages using Logger config - # def log_f(level, *messages) - # message = messages.shift - # m_messages = messages.map { |message| "\n#{' ' * 10}#{message}" } - # Cnfs.logger.send(level, message, *m_messages) - # end - - # def components_path() = path.join(components_path_name) - - # def components_path_name() = repo_config.fetch(COMPONENT_PATH_KEY, '.') - - # Return the contents of the repository's config file or an empty hash - # def repo_config - # repo_config_file.exist? ? (YAML.load_file(repo_config_file) || {}) : {} - # end - - # /repository.yml if present must be in this specific location - # def repo_config_file() = path.join(REPOSITORY_FILE) - - def git_url() = url - - def git_path() = @git_path ||= src_path.join(path || name) - - def src_path() = Cnfs.config.paths.src - - def tree_name() = name - - class << self - def init(context) = context.repositories.each { |repo| repo.git_clone } - - # def init(context) - # context.repositories.each do |repo| - # next if repo.git_path.exist? - # - # msg = "Cloning repository #{repo.url} to #{repo.src_path}" - # Cnfs.logger.info(msg) - # - # Cnfs.with_timer(msg) do - # repo.src_path.mkpath unless repo.src_path.exist? - # Dir.chdir(repo.src_path) { repo.git_clone(repo.url).run } - # end - # end - # end - - # def add(param1, param2 = nil) - # url = name = nil - # if param1.match(git_url_regex) - # url = param1 - # name = param2 - # elsif (url = url_map[param1] || url_from_name(param1)) - # # binding.pry - # name = param1.split('/').last - # end - # name ||= url.split('/').last&.delete_suffix('.git') if url - # new(name: name, url: url) - # end - - # TODO: Move to cnfs-cli.yml - # Shortcuts for CNFS repos - # This is copied in to the user's local directory so its available to all projects on the file system - # def url_map - # { - # cnfs: 'git@github.com:rails-on-services/ros.git', - # generic: 'git@github.com:rails-on-services/generic.git' - # }.with_indifferent_access - # end - - # def url_from_name(name) - # path = name.eql?('.') ? Cnfs.context.cwd.relative_path_from(Cnfs.project.root) : Pathname.new(name) - # Dir.chdir(path) { remote.fetch_url } if path.directory? - # end - - # Returns the default repository unless the the given path is another project repository - # in which case return the Repository object that represents the given path - # The default path is the directory where the command was invoked - # def from_path(path = Cnfs.context.cwd.to_s) - # src_path = Cnfs.project_root.join(Cnfs.paths.src).to_s - # return Cnfs.project.repository if path.eql?(src_path) || !path.start_with?(src_path) - # - # repo_name = path.delete_prefix(src_path).split('/')[1] - # Cnfs.logger.debug("Identified repo name #{repo_name}") - # find_by(name: repo_name) - # end - # - # def add_columns(t) - # t.string :dockerfile - # t.string :build - # end - end -end diff --git a/core/app/models/resource.rb b/core/app/models/resource.rb deleted file mode 100644 index bd2a2ad7..00000000 --- a/core/app/models/resource.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -class Resource < ApplicationRecord - include Concerns::Generic - - belongs_to :plan, optional: true - belongs_to :provider, optional: true - belongs_to :runtime, optional: true - - has_many :services - - # store :config, accessors: %i[source version], coder: YAML - - # store :envs, coder: YAML - - class << self - def add_columns(t) - t.references :plan - t.string :plan_name - t.references :provider - t.string :provider_name - t.references :runtime - t.string :runtime_name - # t.string :envs - end - end -end diff --git a/core/app/models/runtime.rb b/core/app/models/runtime.rb deleted file mode 100644 index f6f110f9..00000000 --- a/core/app/models/runtime.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -class Runtime < ApplicationRecord - # Define target and commands before including Operator Concern - class << self - def target() = :services - - def commands() = %i[attach start stop restart terminate] - end - - include Concerns::Operator - - # Assigned in Operator#execute - attr_accessor :resource, :services - - store :config, accessors: %i[version] - - before_execute :generate - - class << self - def add_columns(t) - t.references :resource - end - end -end diff --git a/core/app/models/runtime_service.rb b/core/app/models/runtime_service.rb deleted file mode 100644 index caaa2e67..00000000 --- a/core/app/models/runtime_service.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -class RuntimeService < ApplicationRecord - belongs_to :runtime - - # accessors match the columns returned by docker ps - store :docker, coder: YAML, accessors: %i[rid image names command labels status ports] - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.references :runtime - t.string :docker - t.string :kubernetes - end - end - end -end diff --git a/core/app/models/segment_directory.rb b/core/app/models/segment_directory.rb deleted file mode 100644 index 47fcaf72..00000000 --- a/core/app/models/segment_directory.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -Cnfs::Node.all - -class DefinitionDirectory < Cnfs::Directory - def file_type(_child) = 'DefinitionFile' - - def create_records - files.each { |file| Definition.create(path: file.path) } - directories.each(&:create_records) - end -end - -class DefinitionFile < Cnfs::File; end - -class SegmentDirectory < Cnfs::Directory - def file_type(child) - asset_names = Cnfs.config.asset_names - asset_names.include?(child.shortname) || asset_names.include?(child.parent&.bname) ? 'AssetFile' : 'SegmentFile' - end - - def create_records(owner:) - files.select{ |f| f.file_content }.each { |file| file.create_owner(owner) } - - directories.each do |dir| - file = files.select{ |f| f.shortname.eql?(dir.bname) }.first - file&.update(segment_dir: dir) - # TODO: If a segment dir exists but not the file then create the File object (but not the actual file) - # The Segment is also created. If the user updates the segment then the file will get written - owner = file.segment if file - - dir.create_records(owner: owner) - end - end -end - -class SegmentFile < Cnfs::File - store :config, accessors: %i[segment segment_dir] - - def create_owner(owner) - update(segment: Component.create(file_content.merge(name: shortname, owner: owner, node: self))) - end - - def self.create_content(object:) - end - - def update_content(object:) = write(object.name => owner.node_content) - - def destroy_content(object:) - # pathname.delete - destroy - segment_dir.destroy - end -end - -class AssetFile < Cnfs::File - def create_owner(owner) - asset_content.each do |name, values| - asset_type = singular? ? parent.bname : shortname - unless (klass = asset_type.to_s.classify.safe_constantize) - Cnfs.logger.warn('Error on', asset_type) - next - end - - # TODO: Log it if not valid like it is today - c = klass.create(values.merge(name: name, owner: owner, node: self)) - # binding.pry unless c.persisted? - end - end - - def self.create_content(object:) - parent_node = object.owner.node.segment_dir - file_name = parent_node.pathname.join("#{object.class.table_name}.yml").to_s - node = find_or_create_by(parent: parent_node, path: file_name) - current_content = file_content || {} - writable_content = current_content.merge(object.name => object.node_content).sort.to_h - node.write(writable_content) - end - - def update_content(object:) - if singular? - write(object.name => object.node_content) - else - write(file_content.merge(object.node_content)) - end - end - - def destroy_content(object:) - if singular? || file_content.keys.size.eql?(1) - destroy - else - write(file_content.except(object.name)) - end - end - - def asset_content() = singular? ? { shortname => file_content } : file_content - - def singular?() = asset_names.exclude?(shortname) - - # If the file is empty YAML returns false so override this to return an empty hash when file is empty - def file_content() = super || {} - - def asset_names() = Cnfs.config.asset_names -end diff --git a/core/app/models/segment_root.rb b/core/app/models/segment_root.rb deleted file mode 100644 index ff372e9e..00000000 --- a/core/app/models/segment_root.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -class SegmentRoot < Component - def owner_extension_path() = Cnfs.config.paths.segments - - # Override superclass methods as this is the root class in the hierarchy - def key() = @key ||= super || warn_key - - def key_name() = name - - def key_name_env() = 'CNFS_KEY' - - def warn_key - Cnfs.logger.error("No encryption key found. Run 'cnfs project generate_key'") - nil - end - - def cache_file() = @cache_file ||= "#{cache_path}.yml" - - def data_file() = @data_file ||= "#{data_path}.yml" - - def cache_path() = @cache_path ||= Cnfs.config.cache_home.join(name) - - def data_path() = @data_path ||= Cnfs.config.data_home.join(name) - - def attrs() = @attrs ||= [name] - - def struct() = OpenStruct.new(segment_type: 'root', name: name) - - def name() = Cnfs.application.name - - class << self - def load - Node.with_asset_callbacks_disabled do - Node.source = :p_node - DefinitionDirectory.create(path: Cnfs.config.paths.definitions).create_records - # DefinitionDirectory.create(path: Cnfs.config.paths.definitions, autoload: true).create_records - - # Create Manually b/c it is a unique class - file = SegmentFile.create(path: Cnfs.config.root.join('config/segments.yml')) - root = create(file.file_content.merge(node: file)) - - root_dir = SegmentDirectory.create(path: Cnfs.config.paths.segments) # .load_children # , autoload: true) - file.update(segment: root, segment_dir: root_dir) - root_dir.create_records(owner: root) - end - - Cnfs::Core.model_names.each do |model| - klass = model.classify.constantize - klass.after_node_load if klass.respond_to?(:after_node_load) - end - rescue ActiveRecord::SubclassNotFound => e - binding.pry - Cnfs.logger.fatal(e.message.split('.').first.to_s) - raise Cnfs::Error, '' - end - - def with_asset_callbacks_disabled - Node.source = :node - - assets_to_disable.each do |asset| - asset.node_callbacks.each { |callback| asset.skip_callback(*callback) } - end - - yield - - assets_to_disable.each do |asset| - asset.node_callbacks.each { |callback| asset.set_callback(*callback) } - end - - Node.source = :asset - end - - def assets_to_disable - @assets_to_disable ||= Cnfs.config.asset_names.dup.append('component').map { |name| name.classify.constantize } - end - - def segment_file_path() = Cnfs.config.root.join('config/segments.yml') - end -end diff --git a/core/app/models/service.rb b/core/app/models/service.rb deleted file mode 100644 index 4c07ec9b..00000000 --- a/core/app/models/service.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -class Service < ApplicationRecord - # Define operator before inclding Concerns::Target - def self.operator() = Runtime - - include Concerns::Target - - belongs_to :resource, optional: true - - store :commands, accessors: %i[console shell test], coder: YAML - store :commands, accessors: %i[after_service_starts before_service_stops before_service_terminates], coder: YAML - store :commands, accessors: %i[attach], coder: YAML - # store :config, accessors: %i[path image depends_on ports mount], coder: YAML - store :config, accessors: %i[path depends_on ports mount] # , coder: YAML - store :image, accessors: %i[build_args dockerfile repository_name tag], coder: YAML - store :profiles, coder: YAML - - serialize :volumes, Array - - store :envs, coder: YAML - - # validate :image_values - - def volumes() = super.map(&:with_indifferent_access) - - # rubocop:disable Metrics/AbcSize - def image_values - %i[source_path target_path].each do |path| - next unless build_args.try(:[], path) - - source_path = Cnfs.paths.src.join(build_args[path]) - errors.add(:source_path_does_not_exist, "'#{source_path}'") unless source_path.exist? - end - return unless dockerfile - - source_path = Cnfs.paths.src.join(dockerfile) - errors.add(:dockerfile_path_does_not_exist, "'#{source_path}'") unless source_path.exist? - end - # rubocop:enable Metrics/AbcSize - - # depends_on is used by compose to set order of container starts - after_initialize do - # self.command_queue ||= [] - self.depends_on ||= [] - self.profiles ||= {} - end - - # after_update :add_commands_to_queue, if: proc { skip_node_create } - - # after_start { add_commands_to_queue(after_service_starts) } - # before_stop { add_commands_to_queue(before_service_stops) } - # before_terminate { add_commands_to_queue(before_service_terminates) } - - # (commands_array) - def add_commands_to_queue - self.command_queue = case state - when attribute_before_last_save(:state) - [] - when 'started' - after_service_starts - end - Cnfs.logger.debug "#{name} command_queue add: #{command_queue}" # ".split("\n")}" - end - - private - - # State handling - def update_runtime(values) - runtime_config = YAML.load_file(runtime_path) || {} - Cnfs.logger.info "Current runtime state for service #{name} is #{runtime_config}" - runtime_config.merge!(name => values).deep_stringify_keys! - Cnfs.logger.info "Updated runtime state for service #{name} is #{runtime_config}" - File.open(runtime_path, 'w') { |file| file.write(runtime_config.to_yaml) } - end - - # File handling - def runtime_path - @runtime_path ||= begin - file_path = write_path(:runtime) - file_path.mkpath unless file_path.exist? - file_path = file_path.join('services.yml') - FileUtils.touch(file_path) unless file_path.exist? - file_path - end - end - - class << self - def by_profiles(profiles = project.profiles) - where('profiles LIKE ?', profiles.map { |k, v| "%#{k}: #{v}%" }.join) - end - - # rubocop:disable Metrics/MethodLength - def add_columns(t) - t.references :resource - t.string :resource_name - t.references :repository - t.string :repository_name - t.string :commands - t.string :image - t.string :path - t.string :profiles - t.string :template - t.string :volumes - t.string :state - t.string :envs - end - # rubocop:enable Metrics/MethodLength - end -end diff --git a/core/app/models/user.rb b/core/app/models/user.rb deleted file mode 100644 index 47a67775..00000000 --- a/core/app/models/user.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class User < ApplicationRecord - include Concerns::Generic - - class << self - def add_columns(t) - t.string :role - t.string :full_name - end - end -end diff --git a/core/app/views/application_view.rb b/core/app/views/application_view.rb deleted file mode 100644 index c9eab9e3..00000000 --- a/core/app/views/application_view.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -class ApplicationView < Cnfs::ApplicationView - def initialize(**options) - # super(**default_options) - super(**default_options.merge(options)) - end -end diff --git a/core/app/views/concerns/parent_view.rb b/core/app/views/concerns/parent_view.rb deleted file mode 100644 index 88d885ae..00000000 --- a/core/app/views/concerns/parent_view.rb +++ /dev/null @@ -1,109 +0,0 @@ -# frozen_string_literal: true - -# Common Functionallity for Component and Asset -module Concerns - module ParentView - extend ActiveSupport::Concern - - included do - attr_accessor :model, :models, :context, :component, :action - end - - # @model is for methods that take a single model: create, show, destroy and edit - # @models is for methods that take an array of models: list - # @context is the current context - def initialize(**options) - @model = options.delete(:model) - @models = options.delete(:models) - @context = options.delete(:context) - @component = @context.component - super # (**{}) # (**default_options.merge(options)) - end - - def create - raise Cnfs::Error, 'Need to pass a model. this is a bug' unless model - raise Cnfs::Error, 'Create can only be called on new instances' if model.persisted? - - @action = :create - - modify - self.model = model.type.safe_constantize.new(model.attributes) if model.type - modify_type - save - end - - def modify_type - return unless model.type - - view_klass_name = "#{model.type}View" - view_klass = view_klass_name.safe_constantize - raise Cnfs::Error, "#{view_klass_name} not found. This is a bug. Please report." unless view_klass - - view_klass.new(model: model, models: models, context: context).modify - end - - - def show() = puts(model&.as_json) - - def destroy - return unless ask('Are you sure?') - - model&.destroy - end - - def edit - raise Cnfs::Error, 'Need to pass a model. this is a bug' unless model - raise Cnfs::Error, 'Create can only be called on existing instances' unless model.persisted? - - @action = :edit - - modify - save - end - - def modify() = raise(NotImplementedError, "#{self.class.name} needs to implement #modify") - - def save - if model.valid? - model.save - else - puts '', 'Not saved due to errors:', model.errors.full_messages - end - end - - def list - return unless names.size.positive? - - binding.pry - ret_val = %w[show edit destroy].each do |action| - next unless options.keys.include?(action) - - name = names.size.eql?(1) ? names.first : enum_select_val("Select #{model_class_name}", choices: names) - send(action) if (@model = models.find_by(name: name)) - break nil - end - puts names unless ret_val.nil? - end - - def select_type - return unless available_types.size.positive? - - type = available_types.size.eql?(1) ? available_types.first : enum_select_val(:type, choices: available_types) - model.type = "#{type}::#{model.class.name}" - end - - def available_types() = @available_types ||= model.class.subclasses.map(&:to_s).map(&:deconstantize) - - def names() = models.map(&:name) - - def model_class_name() = self.class.name.delete_suffix('View') - - def options() = context.options - - Cnfs::Core.asset_names.each do |asset_name| - define_method("#{asset_name.singularize}_names".to_sym) do - component.send("#{asset_name.singularize}_names".to_sym) - end - end - end -end diff --git a/core/app/views/plan_view.rb b/core/app/views/plan_view.rb deleted file mode 100644 index 558fb6bf..00000000 --- a/core/app/views/plan_view.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -class PlanView < ApplicationView - def modify - binding.pry - # %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? - # select_type - # enum_select_attr(:provider_name, choices: provider_names) - # enum_select_attr(:provisioner_name, choices: provisioner_names) - # yes_attr(:abstract, default: false) - # yes_attr(:inherit, default: true) - # yes_attr(:enable, default: true) - - # return if choices.size.zero? - # return choices.first if choices.size.eql?(1) - end -end diff --git a/core/app/views/project_view.rb b/core/app/views/project_view.rb deleted file mode 100644 index 3cb474d3..00000000 --- a/core/app/views/project_view.rb +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true - -class ProjectView < ApplicationView - # rubocop:disable Metrics/AbcSize - def create - raise Cnfs::Error, 'Create can only be called on new instances' if model.persisted? - - model.name = ask('name', value: '') if model.name.nil? - load_components(model, [], 'project') if yes?('My project has components?') - model.x_components = all_selected.uniq.each_with_object([]) { |key, ary| ary.append({ name: key }) } - model.root_tree - return unless yes?("\nSave changes?") - end - - def all_selected - @all_selected ||= [] - end - - # rubocop:disable Metrics/MethodLength - def load_components(obj, selected, title) - available = components(selected) - c_name = available.size.eql?(1) ? available.pop : select('Component type', available, filter: true) - selected << c_name - all_selected << c_name - multi_select('name', send(c_name.to_sym)).each do |name| - new_obj = Component.new(name: name, c_name: c_name) - obj.components << new_obj - if available.size.positive? && yes?("Does the #{title}'s #{name} #{c_name} have components?") - load_components(new_obj, selected.dup, "#{name} #{c_name}") - end - end - selected - end - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/AbcSize - - def edit - # model.name = view_select('name', %w[data this that], 'this') - # ask_hash(:paths) - model.x_components = model.x_components.each_with_object([]) do |comp, ary| - ab = comp.each_with_object({}) do |(key, value), hash| - hash[key] = ask(key, value: value) - end - ary.append(ab) - end - # binding.pry - end - - private - - def components(selected) - %w[environment namespace stack target] - selected - # %w[environment namespace stack target].each_with_object([]) do |key, ary| - # hash = { name: key } - # hash.merge!(disabled: '(already selected)') if selected.include?(key) - # ary.append(hash) - # end - end - - def environment - %w[development test staging production] - end - - def namespace - %w[default other] - end - - def stack - %w[backend frontend pipeline warehouse] - end - - def target - %w[cluster instance local] - end -end diff --git a/core/app/views/provider_view.rb b/core/app/views/provider_view.rb deleted file mode 100644 index 9a8f8d6c..00000000 --- a/core/app/views/provider_view.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -class ProviderView < ApplicationView - include Concerns::AssetView - - def modify - %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? - # select_type - end -end diff --git a/core/app/views/provisioner_view.rb b/core/app/views/provisioner_view.rb deleted file mode 100644 index 73c1e4c6..00000000 --- a/core/app/views/provisioner_view.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -class ProvisionerView < ApplicationView - def modify - %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? - select_type - end -end diff --git a/core/app/views/repository_view.rb b/core/app/views/repository_view.rb deleted file mode 100644 index 717525a1..00000000 --- a/core/app/views/repository_view.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class RepositoryView < ApplicationView - def modify - %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? - select_type - ask_attr(:url) - end -end diff --git a/core/app/views/resource_view.rb b/core/app/views/resource_view.rb deleted file mode 100644 index ef1b78d4..00000000 --- a/core/app/views/resource_view.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class ResourceView < ApplicationView - def modify - %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? - end -end diff --git a/core/app/views/user_view.rb b/core/app/views/user_view.rb deleted file mode 100644 index 49e2f406..00000000 --- a/core/app/views/user_view.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -class UserView < ApplicationView - def modify - %i[name full_name role].each { |attr| ask_attr(attr) } - ask_hash(:tags) - end -end diff --git a/core/lib/cnfs/core.rb b/core/lib/cnfs/core.rb deleted file mode 100644 index 9662e225..00000000 --- a/core/lib/cnfs/core.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'cnfs/core/version' -require 'cnfs/core/plugin' - -module Core - module Concerns - module Cnfs; end - end -end - -module Concerns; end -module Crud; end -module Images; end -module Main; end -module Plans; end -module Projects; end -module Repositories; end -module Resources; end -module Segments; end -module Services; end - -module Blueprint; end - -module Cnfs - module Core - class << self - # The model class list for which tables will be created in the database - def model_names() = @model_names ||= (asset_names + component_names + support_names).map(&:singularize).freeze - - def asset_names() = @asset_names ||= (operator_names + target_names + generic_names).freeze - - def operator_names() = %w[builders configurators provisioners runtimes] - - def target_names() = %w[images plans playbooks services] - - def generic_names() = %w[dependencies providers resources registries repositories users] - - def component_names() = %w[component segment_root] - - def support_names() = %w[context definitions context_component node cnfs/node runtime_service provisioner_resource] - - def gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end - end -end diff --git a/core/lib/cnfs/core/plugin.rb b/core/lib/cnfs/core/plugin.rb deleted file mode 100644 index 6fde4be6..00000000 --- a/core/lib/cnfs/core/plugin.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Core - class Plugin < Cnfs::Plugin - initializer 'cnfs data_store setup' do |app| - Cnfs.data_store.add_models(Cnfs::Core.model_names) - Cnfs.data_store.setup # if data_store - end - - initializer 'node load' do |app| - Cnfs.with_timer('load nodes') { SegmentRoot.load } - end unless ENV['CNFS_CLI_ENV'].eql?('test') - - class << self - def gem_root() = Cnfs::Core.gem_root - end - end - end -end diff --git a/core/spec/fixtures/Gemfile b/core/spec/fixtures/Gemfile deleted file mode 100644 index 4109f098..00000000 --- a/core/spec/fixtures/Gemfile +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -root_path = '../../../../' - -gem 'cnfs', path: "#{root_path}/cnfs" -gem 'cnfs-core', path: "#{root_path}/core" -gem 'cnfs-aws', path: "#{root_path}/aws" -gem 'cnfs-docker', path: "#{root_path}/docker" -gem 'cnfs-gcp', path: "#{root_path}/gcp" -gem 'cnfs-kubernetes', path: "#{root_path}/kubernetes" -gem 'cnfs-native', path: "#{root_path}/native" -gem 'cnfs-packer', path: "#{root_path}/packer" -gem 'cnfs-terraform', path: "#{root_path}/terraform" - -gem 'cnfs-backend', path: '/home/admin/cnfs-components/backend' - -gem 'guard-rspec' diff --git a/core/spec/fixtures/segments/context/repositories.yml b/core/spec/fixtures/segments/context/repositories.yml deleted file mode 100644 index 67527d72..00000000 --- a/core/spec/fixtures/segments/context/repositories.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -cnfs: - name: cnfs - config: - url: git@github.com:rails-on-services/ros.git - type: Backend::Repository -kpop: - name: kpop - config: - url: git@github.com:maxcole/kpop.git - type: Backend::Repository diff --git a/core/spec/fixtures/segments/node/providers.yml b/core/spec/fixtures/segments/node/providers.yml deleted file mode 100644 index ad0ef0c3..00000000 --- a/core/spec/fixtures/segments/node/providers.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -localstack: - config: - access_key_id: !binary |- - 9rt7CXXN/Q42yYlYIt8sq+Nz5lrLNznYAaJFlO1q7gIyJGtgq97cW+wFkQCXzzrzWQ3POWSC624Q43lmwozm4FKaFg== - account_id: 123456789012 - region: ap-southeast-1 - secret_access_key: !binary |- - ZQwlz0Vuntt8u+D6MRGUUoFnXAH9/siAIP6pC7l3w+GK9pJMNPlA6xB9Oz6i2Zom2p3X5M0jI+7ToTTraEFpNBXXug== - s3: - endpoint: http://localstack:4572 - force_path_style: true - sqs: - endpoint: http://localstack:4576 - type: Aws::Provider diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/backend.yml b/core/spec/fixtures/segments/node/stack_env_target_ns/backend.yml deleted file mode 100644 index de656938..00000000 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/backend.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -config: {} diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/providers.yml b/core/spec/fixtures/segments/node/stack_env_target_ns/providers.yml deleted file mode 100644 index 3ef23dd4..00000000 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/providers.yml +++ /dev/null @@ -1,25 +0,0 @@ -# # providers.yml -# # customize values in ~/.config/cnfs/dink/config/providers.yml ---- -aws_east: - type: Provider::Aws - config: - region: us-east-1 - -aws_west: - type: Provider::Aws - config: - region: us-west-1 - -localstack: - type: Provider::Aws - config: - access_key_id: not_required_for_localstack - account_id: 123456789012 - region: localstack - secret_access_key: not_required_for_localstack - s3: - endpoint: http://localstack:4572 - force_path_style: yes - sqs: - endpoint: http://localstack:4576 diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/provisioners.yml b/core/spec/fixtures/segments/node/stack_env_target_ns/provisioners.yml deleted file mode 100644 index 0e3ceaa4..00000000 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/provisioners.yml +++ /dev/null @@ -1,27 +0,0 @@ -# builders.yml ---- -terraform: - type: Builder::Terraform - providers: - aws: - version: 3.22 - dependencies: - - name: terraform-provider-restapi_v1.10.0 - url: https://github.com/Mastercard/terraform-provider-restapi/releases/download/v1.10.0/terraform-provider-restapi_v1.10.0-${platform}-amd64 - - name: terraform-provider-kubectl - url: https://github.com/gavinbunney/terraform-provider-kubectl/releases/download/v1.0.2/terraform-provider-kubectl-${platform}-amd64 -vagrant: - type: Builder::Vagrant - config: - box: ros/generic - box_url: https://perx-ros-boxes.s3-ap-southeast-1.amazonaws.com/vagrant/json/ros/generic.json - dependencies: - - name: setup - type: repo - url: https://github.com/rails-on-services/setup -ansible: - type: Builder::Ansible - dependencies: - - name: setup - type: repo - url: https://github.com/rails-on-services/setup diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/runtimes.yml b/core/spec/fixtures/segments/node/stack_env_target_ns/runtimes.yml deleted file mode 100644 index e952194e..00000000 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/runtimes.yml +++ /dev/null @@ -1,11 +0,0 @@ -# runtimes.yml ---- -compose: - type: Runtime::Compose - config: - version: 3.2 - dependencies: - - name: docker-compose - -native: - type: Runtime::Native diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/users.yml b/core/spec/fixtures/segments/node/stack_env_target_ns/users.yml deleted file mode 100644 index 588a75c4..00000000 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/users.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -joe: - role: admin diff --git a/core/spec/fixtures/segments/node/stacks/builders.yml b/core/spec/fixtures/segments/node/stacks/builders.yml deleted file mode 100644 index 38241a0c..00000000 --- a/core/spec/fixtures/segments/node/stacks/builders.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -compose: - type: Compose::Builder diff --git a/core/spec/fixtures/segments/node/stacks/docs.yml b/core/spec/fixtures/segments/node/stacks/docs.yml deleted file mode 100644 index dd7733b6..00000000 --- a/core/spec/fixtures/segments/node/stacks/docs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -config: {} -segment_name: jamstack/scully -default: - provider_name: aws_east diff --git a/core/spec/fixtures/segments/node/users/providers.yml b/core/spec/fixtures/segments/node/users/providers.yml deleted file mode 100644 index 2ef35a8d..00000000 --- a/core/spec/fixtures/segments/node/users/providers.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -localstack: - config: - access_key_id: test - account_id: 123456789012 - region: ap-southeast-1 - secret_access_key: test - s3: - endpoint: http://localstack:4572 - force_path_style: true - sqs: - endpoint: http://localstack:4576 - type: Aws::Provider diff --git a/core/spec/models/component_spec.rb b/core/spec/models/component_spec.rb deleted file mode 100644 index 415f11a4..00000000 --- a/core/spec/models/component_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -# require "#{ENV['SPEC_DIR']}/models/concerns/encryption_spec.rb" -# require "#{ENV['SPEC_DIR']}/models/concerns/interpolation_spec.rb" - -# rubocop:disable Metrics/BlockLength -RSpec.describe 'Component', type: :model do - let(:path) { Pathname.new(ENV['SPEC_DIR']).join('fixtures/context') } - let(:project) { Project.first } - # let(:a_context) { Context.create(root: project, options: options) } - - before(:each) do - # stub_project - end - - describe 'interpolation' do - let(:options) { { stack: :backend, environment: :production, target: :lambda } } - - describe 'does the correct interpolation for production' do - let(:subject) { Component.find_by(name: :production) } - let(:result) do - { 'config' => { 'domain' => 'production-backend.cnfs.io' }, 'default' => 'lambda', - 'segment' => 'target', 'name' => 'context_spec' } - end - it_behaves_like 'interpolated' - end - - describe 'does the correct interpolation for lambda' do - let(:subject) { Component.find_by(name: :lambda) } - let(:result) do - { 'config' => { 'host' => 'lambda.production-backend.cnfs.io' }, 'segment' => 'target', - 'default' => 'lambda', 'name' => 'context_spec' } - end - it_behaves_like 'interpolated' - end - end - - describe 'encryption' do - let(:options) { { stack: :backend, environment: :production, target: :lambda } } - - describe 'does the correct encryption for project' do - let(:subject) { Project.first } - it_behaves_like 'encrypted' - end - - describe 'does the correct encryption for lambda' do - let(:subject) { Component.find_by(name: :lambda) } - it_behaves_like 'encrypted' - end - end -end -# rubocop:enable Metrics/BlockLength diff --git a/core/spec/models/concerns/encryption_spec.rb b/core/spec/models/concerns/encryption_spec.rb deleted file mode 100644 index 8b516344..00000000 --- a/core/spec/models/concerns/encryption_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples_for 'encrypted' do - it 'decrypts all attrs' do - rf = subject.class.new(subject.parent.yaml) - rf.valid? - expect(subject.as_json).to eq(rf.as_json) - - # TODO: When encrypted yaml remains unchnaged when saving back then enable this expectation - # expect(subject.as_json_encrypted).to eq(rf.as_json_encrypted) - end -end diff --git a/core/spec/models/segment_root_spec.rb b/core/spec/models/segment_root_spec.rb deleted file mode 100644 index 26774264..00000000 --- a/core/spec/models/segment_root_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe 'SegmentRoot', type: :model do - it_behaves_like 'encryption' - it_behaves_like 'interpolate' - let(:path) { Pathname.new(ENV['SPEC_DIR']).join('fixtures/context') } - let(:root) { SegmentRoot.first } - let(:a_context) { Context.create(root: root, options: options) } - - before(:each) do - # CnfsCli.run!(path: path, load_nodes: true) do - # _n = Node::Component.create(path: 'project.yml', owner_class: Project) - # end - end - - describe 'stack: :wrong' do - let(:options) { { stack: :backend, environment: :production, target: :lambda } } - # let(:options) { { stack: :wrong } } - - it 'generates the correct number of contexts and context_components' do - a_context - expect(Context.count).to eq(1) - expect(ContextComponent.count).to eq(3) - end - end -end diff --git a/core/.rspec b/docker/.rspec similarity index 100% rename from core/.rspec rename to docker/.rspec diff --git a/docker/Gemfile b/docker/Gemfile new file mode 100644 index 00000000..87000333 --- /dev/null +++ b/docker/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'onestack', path: '../onestack' +gem 'solid_app', path: '../solid_app' +gem 'solid_record', path: '../solid_record' +gem 'solid_support', path: '../solid_support' + +gemspec diff --git a/docker/app/generators/compose/builder_generator.rb b/docker/app/generators/compose/builder_generator.rb index c084ead7..7c9b7e21 100644 --- a/docker/app/generators/compose/builder_generator.rb +++ b/docker/app/generators/compose/builder_generator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Compose::BuilderGenerator < BuilderGenerator +class Compose::BuilderGenerator < OneStack::BuilderGenerator def compose_yml() = cnfs_template('docker-compose.yml') private diff --git a/docker/app/generators/compose/runtime_generator.rb b/docker/app/generators/compose/runtime_generator.rb index 3a243182..aa61ce3a 100644 --- a/docker/app/generators/compose/runtime_generator.rb +++ b/docker/app/generators/compose/runtime_generator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Compose::RuntimeGenerator < RuntimeGenerator +class Compose::RuntimeGenerator < OneStack::RuntimeGenerator # private def nginx_conf # binding.pry diff --git a/docker/app/models/compose/builder.rb b/docker/app/models/compose/builder.rb index bc94ed55..f06e6757 100644 --- a/docker/app/models/compose/builder.rb +++ b/docker/app/models/compose/builder.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Compose::Builder < Builder +class Compose::Builder < OneStack::Builder def build Dir.chdir(path) do Cnfs.logger.warn('building!', images.pluck(:name).join(', '), 'END') diff --git a/docker/app/models/compose/runtime.rb b/docker/app/models/compose/runtime.rb index 0809e4c2..7fdca9b9 100644 --- a/docker/app/models/compose/runtime.rb +++ b/docker/app/models/compose/runtime.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # rubocop:disable Metrics/ClassLength -class Compose::Runtime < Runtime +class Compose::Runtime < OneStack::Runtime # TODO: Add support for destroying volumes; https://docs.docker.com/compose/reference/down/ def destroy diff --git a/docker/app/models/docker/registry.rb b/docker/app/models/docker/registry.rb index d9a2a7cf..de36c84b 100644 --- a/docker/app/models/docker/registry.rb +++ b/docker/app/models/docker/registry.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Docker::Registry < Registry +class Docker::Registry < OneStack::Registry store :config, accessors: %i[server username password email], coder: YAML attr_encrypted :password diff --git a/docker/bin/console b/docker/bin/console index 4be8b7f6..aa32a757 100755 --- a/docker/bin/console +++ b/docker/bin/console @@ -1,15 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require "bundler/setup" -require "cnfs_cli/docker" +require 'bundler/setup' -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require "irb" -IRB.start(__FILE__) +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/docker/lib/cnfs/docker.rb b/docker/lib/cnfs/docker.rb deleted file mode 100644 index b73e1c9a..00000000 --- a/docker/lib/cnfs/docker.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require 'docker' -require 'docker/compose' - -require 'cnfs/docker/version' -require 'cnfs/docker/plugin' - -module Compose; end -module Docker - module Concerns; end -end - -module Cnfs - module Docker - def self.gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end -end diff --git a/docker/lib/cnfs/docker/plugin.rb b/docker/lib/cnfs/docker/plugin.rb deleted file mode 100644 index 4f5c07d7..00000000 --- a/docker/lib/cnfs/docker/plugin.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Docker - class Plugin < Cnfs::Plugin - class << self - def initialize_docker - require 'cnfs/docker' - Cnfs.logger.info "[Docker] Initializing from #{gem_root}" - end - - # def customize - # src = gem_root.join('app/generators/docker') - # dest = Cnfs.paths.lib.join('generators/docker') - # FileUtils.rm_rf(dest) - # FileUtils.mkdir_p(dest) - # %w[service lib].each do |node| - # FileUtils.cp_r(src.join(node), dest) - # end - # end - - def gem_root - #{CnfsCli::Docker.gem_root - Cnfs::Docker.gem_root - end - end - end - end -end diff --git a/docker/lib/cnfs/docker/version.rb b/docker/lib/cnfs/docker/version.rb deleted file mode 100644 index 3bb29d9d..00000000 --- a/docker/lib/cnfs/docker/version.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Docker - VERSION = "0.1.0" - end -end diff --git a/docker/lib/one_stack/docker/plugin.rb b/docker/lib/one_stack/docker/plugin.rb new file mode 100644 index 00000000..2bf0adcf --- /dev/null +++ b/docker/lib/one_stack/docker/plugin.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + module Docker + class Plugin < OneStack::Plugin + config.before_initialize { |config| OneStack::Docker.config.merge!(config.docker) } + + def self.gem_root() = OneStack::Docker.gem_root + end + end +end diff --git a/core/lib/cnfs/core/version.rb b/docker/lib/one_stack/docker/version.rb similarity index 66% rename from core/lib/cnfs/core/version.rb rename to docker/lib/one_stack/docker/version.rb index eacf871c..648f4c0c 100644 --- a/core/lib/cnfs/core/version.rb +++ b/docker/lib/one_stack/docker/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -module Cnfs - module Core +module OneStack + module Docker VERSION = '0.1.0' end end diff --git a/docker/lib/onestack-docker.rb b/docker/lib/onestack-docker.rb new file mode 100644 index 00000000..e4b04856 --- /dev/null +++ b/docker/lib/onestack-docker.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'docker' +require 'docker/compose' + +require_relative 'one_stack/docker/version' +require_relative 'one_stack/docker/plugin' + +module Compose; end +module Docker + module Concerns; end +end + +module OneStack + module Docker + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + def config() = @config ||= ActiveSupport::OrderedOptions.new + end + end +end diff --git a/docker/cnfs-docker.gemspec b/docker/onestack-docker.gemspec similarity index 68% rename from docker/cnfs-docker.gemspec rename to docker/onestack-docker.gemspec index 9663ddaa..a66dcd68 100644 --- a/docker/cnfs-docker.gemspec +++ b/docker/onestack-docker.gemspec @@ -1,17 +1,18 @@ # frozen_string_literal: true -require_relative 'lib/cnfs/docker/version' + +require_relative 'lib/one_stack/docker/version' Gem::Specification.new do |spec| - spec.name = 'cnfs-docker' - spec.version = Cnfs::Docker::VERSION + spec.name = 'onestack-docker' + spec.version = OneStack::Docker::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] - spec.summary = 'CNFS CLI plugin for the Docker Container Runtime' - spec.description = 'CNFS CLI plugin to install Docker support into CNFS projects' + spec.summary = 'OneStack plugin for the Docker Container Runtime' + spec.description = 'OneStack plugin to install Docker support into OneStack projects' spec.homepage = 'https://cnfs.io' spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" @@ -28,7 +29,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'cnfs-core', '~> 0.1.0' + spec.add_dependency 'onestack', '~> 0.1.0' spec.add_dependency 'docker-api', '~> 2.2.0' spec.add_dependency 'docker-compose', '~> 0.4.1' @@ -36,4 +37,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'pry', '~> 0.12' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'rubocop', '~> 1.22' + spec.add_development_dependency 'rubocop-rspec', '~> 2.7' + spec.add_development_dependency 'rubocop-performance', '~> 1.13' end diff --git a/docker/spec/dummy/config/application.rb b/docker/spec/dummy/config/application.rb new file mode 100644 index 00000000..91548f28 --- /dev/null +++ b/docker/spec/dummy/config/application.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # initializer 'based' do |app| + # binding.pry + # end + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + # TODO: This should be teh default and comment out and set to false with an instruction to user + config.solid_record.flush_cache_on_exit = true + config.solid_record.namespace = :one_stack + config.solid_record.load_paths = [ + { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/cnfs/app/generators/cnfs/new/project/templates/config/boot.rb.erb b/docker/spec/dummy/config/boot.rb similarity index 100% rename from cnfs/app/generators/cnfs/new/project/templates/config/boot.rb.erb rename to docker/spec/dummy/config/boot.rb diff --git a/cnfs/app/generators/cnfs/new/project/templates/config/environment.rb.erb b/docker/spec/dummy/config/environment.rb similarity index 77% rename from cnfs/app/generators/cnfs/new/project/templates/config/environment.rb.erb rename to docker/spec/dummy/config/environment.rb index 37320d4a..2ff3c59d 100644 --- a/cnfs/app/generators/cnfs/new/project/templates/config/environment.rb.erb +++ b/docker/spec/dummy/config/environment.rb @@ -4,4 +4,4 @@ require_relative 'application' # Initialize the application -Cnfs.application.initialize! +OneStack.application.initialize! diff --git a/core/spec/fixtures/segments/context.yml b/docker/spec/dummy/config/segment.yml similarity index 64% rename from core/spec/fixtures/segments/context.yml rename to docker/spec/dummy/config/segment.yml index 4c140e06..0ec84aca 100644 --- a/core/spec/fixtures/segments/context.yml +++ b/docker/spec/dummy/config/segment.yml @@ -1,4 +1,4 @@ --- -segment_type: stack +segments_type: stack default: segment_name: frontend diff --git a/docker/spec/models/compose/runtime_spec.rb b/docker/spec/models/compose/runtime_spec.rb new file mode 100644 index 00000000..023403e9 --- /dev/null +++ b/docker/spec/models/compose/runtime_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Compose + RSpec.describe Runtime, type: :model do + let(:segment_root) { OneStack::SegmentRoot.first } + let(:nav) { OneStack::Navigator.new } + let(:context) { nav.context } + + before { OneStack::SpecHelper.setup_segment(self) } + + context 'when stack: :wrong' do + let(:options) { { stack: :backend, environment: :production, target: :lambda } } + # let(:options) { { stack: :wrong } } + + it { binding.pry } + end + end +end diff --git a/docker/spec/spec_helper.rb b/docker/spec/spec_helper.rb new file mode 100644 index 00000000..fda41792 --- /dev/null +++ b/docker/spec/spec_helper.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' diff --git a/solid-record/.rspec b/gcp/.rspec similarity index 100% rename from solid-record/.rspec rename to gcp/.rspec diff --git a/gcp/Gemfile b/gcp/Gemfile new file mode 100644 index 00000000..87000333 --- /dev/null +++ b/gcp/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'onestack', path: '../onestack' +gem 'solid_app', path: '../solid_app' +gem 'solid_record', path: '../solid_record' +gem 'solid_support', path: '../solid_support' + +gemspec diff --git a/gcp/app/models/gcp/provider.rb b/gcp/app/models/gcp/provider.rb index af4d3fad..ce08c705 100644 --- a/gcp/app/models/gcp/provider.rb +++ b/gcp/app/models/gcp/provider.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -class Gcp::Provider < Provider - store :config, accessors: %i[service_account_key], coder: YAML +class Gcp::Provider < OneStack::Provider + store :config, accessors: %i[service_account_key] - def credentials - { service_account_key: service_account_key } - end + def credentials() = { service_account_key: service_account_key } end diff --git a/gcp/bin/console b/gcp/bin/console index 55ceea83..aa32a757 100755 --- a/gcp/bin/console +++ b/gcp/bin/console @@ -2,14 +2,8 @@ # frozen_string_literal: true require 'bundler/setup' -require 'cnfs/cli/gcp' -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require 'irb' -IRB.start(__FILE__) +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/gcp/lib/cnfs/gcp.rb b/gcp/lib/cnfs/gcp.rb deleted file mode 100644 index 1003e819..00000000 --- a/gcp/lib/cnfs/gcp.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'cnfs/gcp/version' -require 'cnfs/gcp/plugin' - -module Gcp - module Concerns; end -end - -module Cnfs - module Gcp - def self.gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end -end diff --git a/gcp/lib/cnfs/gcp/plugin.rb b/gcp/lib/cnfs/gcp/plugin.rb deleted file mode 100644 index 45744332..00000000 --- a/gcp/lib/cnfs/gcp/plugin.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Gcp - class Plugin < Cnfs::Plugin - def self.gem_root() = Cnfs::Gcp.gem_root - end - end -end diff --git a/gcp/lib/one_stack/gcp/plugin.rb b/gcp/lib/one_stack/gcp/plugin.rb new file mode 100644 index 00000000..cc437dd3 --- /dev/null +++ b/gcp/lib/one_stack/gcp/plugin.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + module Gcp + class Plugin < OneStack::Plugin + config.before_initialize { |config| OneStack::Gcp.config.merge!(config.gcp) } + + def self.gem_root() = OneStack::Gcp.gem_root + end + end +end diff --git a/gcp/lib/cnfs/gcp/version.rb b/gcp/lib/one_stack/gcp/version.rb similarity index 82% rename from gcp/lib/cnfs/gcp/version.rb rename to gcp/lib/one_stack/gcp/version.rb index cea89260..8986e4d8 100644 --- a/gcp/lib/cnfs/gcp/version.rb +++ b/gcp/lib/one_stack/gcp/version.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Cnfs +module OneStack module Gcp VERSION = '0.1.0' end diff --git a/gcp/lib/onestack-gcp.rb b/gcp/lib/onestack-gcp.rb new file mode 100644 index 00000000..31f4994e --- /dev/null +++ b/gcp/lib/onestack-gcp.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative 'one_stack/gcp/version' + +require 'onestack' + +require_relative 'one_stack/gcp/plugin' + +module Gcp + module Concerns; end +end + +module OneStack + module Gcp + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + def config() = @config ||= ActiveSupport::OrderedOptions.new + end + end +end diff --git a/gcp/cnfs-gcp.gemspec b/gcp/onestack-gcp.gemspec similarity index 76% rename from gcp/cnfs-gcp.gemspec rename to gcp/onestack-gcp.gemspec index ebbf9928..f3e9d0d5 100644 --- a/gcp/cnfs-gcp.gemspec +++ b/gcp/onestack-gcp.gemspec @@ -1,17 +1,17 @@ # frozen_string_literal: true -require_relative 'lib/cnfs/gcp/version' +require_relative 'lib/one_stack/gcp/version' Gem::Specification.new do |spec| - spec.name = 'cnfs-gcp' - spec.version = Cnfs::Gcp::VERSION + spec.name = 'onestack-gcp' + spec.version = OneStack::Gcp::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] - spec.summary = 'CNFS plugin for Google Cloud Platform' - spec.description = 'CNFS plugin to create CNFS compatible blueprints for GCP' + spec.summary = 'OneStack plugin for Google Cloud Platform' + spec.description = 'OneStack plugin to create compatible blueprints for GCP' spec.homepage = 'https://cnfs.io' spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" @@ -24,11 +24,12 @@ Gem::Specification.new do |spec| spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end + spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'cnfs-core', '~> 0.1.0' + spec.add_dependency 'onestack', '~> 0.1.0' spec.add_dependency 'google-cloud-storage', '~> 1.29' spec.add_development_dependency 'bundler', '~> 2.0' diff --git a/gcp/spec/dummy/config/application.rb b/gcp/spec/dummy/config/application.rb new file mode 100644 index 00000000..b66e35bf --- /dev/null +++ b/gcp/spec/dummy/config/application.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + config.solid_record.namespace = :one_stack + config.solid_record.load_paths = [ + { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/gcp/spec/dummy/config/boot.rb b/gcp/spec/dummy/config/boot.rb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/gcp/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/gcp/spec/dummy/config/environment.rb b/gcp/spec/dummy/config/environment.rb new file mode 100644 index 00000000..2ff3c59d --- /dev/null +++ b/gcp/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +OneStack.application.initialize! diff --git a/gcp/spec/dummy/config/segment.yml b/gcp/spec/dummy/config/segment.yml new file mode 100644 index 00000000..f53a5592 --- /dev/null +++ b/gcp/spec/dummy/config/segment.yml @@ -0,0 +1,17 @@ +--- +segments_type: stack +# default: + # segment_name: backend +# config: +# domain: cnfs.io +# host: host.${config.domain} +# targets: +# one: one.${config.host} +# two: two.${config.host} +# segments_type: target +# default: +# provisioner_name: terraform +# repository_name: cnfs-projects +# resource_name: instance1 +# runtime_name: compose +# segment_name: backend diff --git a/gcp/spec/spec_helper.rb b/gcp/spec/spec_helper.rb new file mode 100644 index 00000000..fda41792 --- /dev/null +++ b/gcp/spec/spec_helper.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' diff --git a/solid-support/.rspec b/kubernetes/.rspec similarity index 100% rename from solid-support/.rspec rename to kubernetes/.rspec diff --git a/kubernetes/Gemfile b/kubernetes/Gemfile new file mode 100644 index 00000000..87000333 --- /dev/null +++ b/kubernetes/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'onestack', path: '../onestack' +gem 'solid_app', path: '../solid_app' +gem 'solid_record', path: '../solid_record' +gem 'solid_support', path: '../solid_support' + +gemspec diff --git a/kubernetes/app/generators/skaffold/generator.rb b/kubernetes/app/generators/skaffold/generator.rb index cf12e201..60c793b3 100644 --- a/kubernetes/app/generators/skaffold/generator.rb +++ b/kubernetes/app/generators/skaffold/generator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Skaffold::Generator < RuntimeGenerator +class Skaffold::Generator < OneStack::RuntimeGenerator def files directory('files', path) end diff --git a/kubernetes/app/models/kubernetes/helm_registry.rb b/kubernetes/app/models/kubernetes/helm_registry.rb index a7c34955..7140f05c 100644 --- a/kubernetes/app/models/kubernetes/helm_registry.rb +++ b/kubernetes/app/models/kubernetes/helm_registry.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Kubernetes::HelmRegistry < Registry +class Kubernetes::HelmRegistry < OneStack::Registry store :config, accessors: %i[url], coder: YAML def add_deploy_commands(runtime) diff --git a/kubernetes/app/models/skaffold/runtime.rb b/kubernetes/app/models/skaffold/runtime.rb index f9a73921..fbc5f5dc 100644 --- a/kubernetes/app/models/skaffold/runtime.rb +++ b/kubernetes/app/models/skaffold/runtime.rb @@ -3,7 +3,7 @@ require 'base64' # rubocop:disable all -class Skaffold::Runtime < Runtime +class Skaffold::Runtime < OneStack::Runtime # TODO: is there a detach keymap like with compose? # TODO: attach is actually shell (via exec) diff --git a/kubernetes/bin/console b/kubernetes/bin/console index fda23286..aa32a757 100755 --- a/kubernetes/bin/console +++ b/kubernetes/bin/console @@ -1,15 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require "bundler/setup" -require "cnfs_cli/kubernetes" +require 'bundler/setup' -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require "irb" -IRB.start(__FILE__) +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/kubernetes/cnfs-kubernetes.gemspec b/kubernetes/cnfs-kubernetes.gemspec deleted file mode 100644 index 63045b2c..00000000 --- a/kubernetes/cnfs-kubernetes.gemspec +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true -require_relative 'lib/cnfs/kubernetes/version' - -Gem::Specification.new do |spec| - spec.name = 'cnfs-kubernetes' - spec.version = Cnfs::Kubernetes::VERSION - spec.authors = ['Robert Roach'] - spec.email = ['rjayroach@gmail.com'] - - spec.summary = 'CNFS plugin for the Kubernets Container Runtime' - spec.description = 'CNFS plugin to install Kubernetes support into CNFS projects' - spec.homepage = 'https://cnfs.io' - spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') - - # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/rails-on-services/cnfs-cli' - # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] - - spec.add_dependency 'cnfs-core', '~> 0.1.0' - spec.add_dependency 'kubeclient', '~> 4.9.2' - - spec.add_development_dependency 'bundler', '~> 2.0' - spec.add_development_dependency 'pry', '~> 0.12' - spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rspec', '~> 3.0' -end diff --git a/kubernetes/lib/cnfs/kubernetes.rb b/kubernetes/lib/cnfs/kubernetes.rb deleted file mode 100644 index 4eb34ccf..00000000 --- a/kubernetes/lib/cnfs/kubernetes.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'kubeclient' - -require_relative 'kubernetes/version' -require_relative 'kubernetes/plugin' - -module Kubernetes - module Concerns; end -end - -module Skaffold; end -module Compose; end - -module Cnfs - module Kubernetes - def self.gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end -end diff --git a/kubernetes/lib/cnfs/kubernetes/plugin.rb b/kubernetes/lib/cnfs/kubernetes/plugin.rb deleted file mode 100644 index e7e382e3..00000000 --- a/kubernetes/lib/cnfs/kubernetes/plugin.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Kubernetes - class Plugin < Cnfs::Plugin - def self.gem_root() = Cnfs::Kubernetes.gem_root - end - end -end diff --git a/kubernetes/lib/cnfs/kubernetes/version.rb b/kubernetes/lib/cnfs/kubernetes/version.rb deleted file mode 100644 index 0957bd73..00000000 --- a/kubernetes/lib/cnfs/kubernetes/version.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Kubernetes - VERSION = "0.1.0" - end -end diff --git a/kubernetes/lib/one_stack/kubernetes/plugin.rb b/kubernetes/lib/one_stack/kubernetes/plugin.rb new file mode 100644 index 00000000..89dd8c3e --- /dev/null +++ b/kubernetes/lib/one_stack/kubernetes/plugin.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + module Kubernetes + class Plugin < OneStack::Plugin + config.before_initialize { |config| OneStack::Kubernetes.config.merge!(config.kubernetes) } + + def self.gem_root() = OneStack::Kubernetes.gem_root + end + end +end diff --git a/kubernetes/lib/one_stack/kubernetes/version.rb b/kubernetes/lib/one_stack/kubernetes/version.rb new file mode 100644 index 00000000..643eb6cd --- /dev/null +++ b/kubernetes/lib/one_stack/kubernetes/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + module Kubernetes + VERSION = '0.1.0' + end +end diff --git a/kubernetes/lib/onestack-kubernetes.rb b/kubernetes/lib/onestack-kubernetes.rb new file mode 100644 index 00000000..8e530e79 --- /dev/null +++ b/kubernetes/lib/onestack-kubernetes.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'kubeclient' + +require_relative 'one_stack/kubernetes/version' +require_relative 'one_stack/kubernetes/plugin' + +module Kubernetes + module Concerns; end +end + +module Skaffold; end +module Compose; end + +module OneStack + module Kubernetes + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + def config() = @config ||= ActiveSupport::OrderedOptions.new + end + end +end diff --git a/terraform/cnfs-terraform.gemspec b/kubernetes/onestack-kubernetes.gemspec similarity index 72% rename from terraform/cnfs-terraform.gemspec rename to kubernetes/onestack-kubernetes.gemspec index 920d3bc4..f629717d 100644 --- a/terraform/cnfs-terraform.gemspec +++ b/kubernetes/onestack-kubernetes.gemspec @@ -1,16 +1,15 @@ # frozen_string_literal: true - -require_relative 'lib/cnfs/terraform/version' +require_relative 'lib/one_stack/kubernetes/version' Gem::Specification.new do |spec| - spec.name = 'cnfs-terraform' - spec.version = Cnfs::Terraform::VERSION + spec.name = 'onestack-kubernetes' + spec.version = OneStack::Kubernetes::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] - - spec.summary = 'CNFS plugin for the Terraform Provisioning Tool' - spec.description = 'CNFS plugin to create Terraform templates for resources and blueprints' - spec.homepage = 'https://cnfs.io' + + spec.summary = 'OneStack plugin for the Kubernets Container Runtime' + spec.description = 'OneStack plugin to install Kubernetes support into OneStack projects' + spec.homepage = 'https://cnfs.io/onestack' spec.license = 'MIT' spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') @@ -29,8 +28,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'cnfs-core', '~> 0.1.0' - spec.add_dependency 'ruby-terraform', '~> 1.3.1' + spec.add_dependency 'onestack', '~> 0.1.0' + spec.add_dependency 'kubeclient', '~> 4.9.2' spec.add_development_dependency 'bundler', '~> 2.0' spec.add_development_dependency 'pry', '~> 0.12' diff --git a/kubernetes/spec/dummy/config/application.rb b/kubernetes/spec/dummy/config/application.rb new file mode 100644 index 00000000..b66e35bf --- /dev/null +++ b/kubernetes/spec/dummy/config/application.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + config.solid_record.namespace = :one_stack + config.solid_record.load_paths = [ + { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/kubernetes/spec/dummy/config/boot.rb b/kubernetes/spec/dummy/config/boot.rb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/kubernetes/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/kubernetes/spec/dummy/config/environment.rb b/kubernetes/spec/dummy/config/environment.rb new file mode 100644 index 00000000..2ff3c59d --- /dev/null +++ b/kubernetes/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +OneStack.application.initialize! diff --git a/kubernetes/spec/dummy/config/segment.yml b/kubernetes/spec/dummy/config/segment.yml new file mode 100644 index 00000000..f53a5592 --- /dev/null +++ b/kubernetes/spec/dummy/config/segment.yml @@ -0,0 +1,17 @@ +--- +segments_type: stack +# default: + # segment_name: backend +# config: +# domain: cnfs.io +# host: host.${config.domain} +# targets: +# one: one.${config.host} +# two: two.${config.host} +# segments_type: target +# default: +# provisioner_name: terraform +# repository_name: cnfs-projects +# resource_name: instance1 +# runtime_name: compose +# segment_name: backend diff --git a/kubernetes/spec/spec_helper.rb b/kubernetes/spec/spec_helper.rb new file mode 100644 index 00000000..fda41792 --- /dev/null +++ b/kubernetes/spec/spec_helper.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' diff --git a/native/bin/console b/native/bin/console index 277b5411..aa32a757 100755 --- a/native/bin/console +++ b/native/bin/console @@ -1,15 +1,9 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require "bundler/setup" -require "cnfs_cli/native" +require 'bundler/setup' -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require "irb" -IRB.start(__FILE__) +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/onestack/.rspec b/onestack/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/onestack/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/core/CODE_OF_CONDUCT.md b/onestack/CODE_OF_CONDUCT.md similarity index 100% rename from core/CODE_OF_CONDUCT.md rename to onestack/CODE_OF_CONDUCT.md diff --git a/onestack/Gemfile b/onestack/Gemfile new file mode 100644 index 00000000..737feb3a --- /dev/null +++ b/onestack/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem_path = '..' + +gem 'solid_support', path: "#{gem_path}/solid_support" +gem 'solid_record', path: "#{gem_path}/solid_record" +# TODO: remove; this is for testing right now, but that testing should move to aws gem +# which will include this gem rather than other way around +gem 'onestack-aws', path: "#{gem_path}/aws" + +gemspec diff --git a/core/README.md b/onestack/README.md similarity index 100% rename from core/README.md rename to onestack/README.md diff --git a/core/Rakefile b/onestack/Rakefile similarity index 100% rename from core/Rakefile rename to onestack/Rakefile diff --git a/onestack/app/commands/one_stack/application_command.rb b/onestack/app/commands/one_stack/application_command.rb new file mode 100644 index 00000000..c1106375 --- /dev/null +++ b/onestack/app/commands/one_stack/application_command.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module OneStack + class ApplicationCommand < SolidSupport::ApplicationCommand + class << self + def shared_options + super.merge( + clean: { desc: 'Clean component cache', type: :boolean }, + clean_all: { desc: 'Clean project cache', type: :boolean }, + fail_fast: { desc: 'Skip any remaining commands after a command fails', aliases: '--ff', type: :boolean }, + generate: { desc: 'Force generate manifest files ', aliases: '-g', type: :boolean }, + init: { desc: 'Initialize the project, e.g. download repositories and dependencies', type: :boolean }, + tags: { desc: 'Filter by tags', aliases: '--tags', type: :array } + ).merge(seg_opts) + end + + def seg_opts + OneStack.config.segments.dup.transform_values! do |opt| + { desc: opt[:desc], aliases: opt[:aliases], type: :string } + end + end + end + + # Load modules to add options, actions and sub-commands to existing command structure + # TODO: If this is included in H:AppCommand then will it just work? + # For that matter since it is here will it just work in a subclass of this class? + include SolidSupport::Extendable + end +end diff --git a/core/app/controllers/images/command_controller.rb b/onestack/app/commands/one_stack/images_command.rb similarity index 83% rename from core/app/controllers/images/command_controller.rb rename to onestack/app/commands/one_stack/images_command.rb index 389b66df..12e36606 100644 --- a/core/app/controllers/images/command_controller.rb +++ b/onestack/app/commands/one_stack/images_command.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -module Images - class CommandController < Thor - include Concerns::CommandController - - cnfs_class_options :clean, :dry_run, :generate, :quiet - cnfs_class_options Cnfs.config.segments.keys +module OneStack + class ImagesCommand < ApplicationCommand + has_class_options :clean, :dry_run, :generate, :quiet + has_class_options OneStack.config.segments.keys desc 'build [IMAGES]', 'Build all or specific service images' def build(*images) = execute(images: images) diff --git a/onestack/app/commands/one_stack/main_command.rb b/onestack/app/commands/one_stack/main_command.rb new file mode 100644 index 00000000..db85ebf7 --- /dev/null +++ b/onestack/app/commands/one_stack/main_command.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module OneStack + class CreateController < Thor; include OneStack::Concerns::CrudController end + + class ListController < Thor; include OneStack::Concerns::CrudController end + + class ShowController < Thor; include OneStack::Concerns::CrudController end + + class EditController < Thor; include OneStack::Concerns::CrudController end + + class DestroyController < Thor; include OneStack::Concerns::CrudController end + + class MainCommand < ApplicationCommand + has_class_options :dry_run + has_class_options OneStack.config.segments.keys + + # CRUD Actions + %w[create show edit destroy list].each do |action| + klass = "one_stack/#{action}_controller".classify.constantize + register klass, action, "#{action} [ASSET] [options]", "#{action.capitalize} asset" + end + + # Builder operates on Images + register OneStack::ImagesCommand, 'image', 'image [SUBCOMMAND] [options]', 'Manage images' + + # Provisioner operates on Plans + register OneStack::PlansCommand, 'plan', 'plan SUBCOMMAND [options]', 'Manage infrastructure plans' + + register OneStack::ResourcesCommand, 'resource', 'resource [SUBCOMMAND]', 'Manage component resources' + + register OneStack::SegmentsCommand, 'segment', 'segment [SUBCOMMAND]', 'Manage segments' + + # Runtime operates on Services + register OneStack::ServicesCommand, 'service', 'service SUBCOMMAND [options]', 'Manage services' + + # register OneStack::RepositoriesCommand, 'repository', 'repository SUBCOMMAND [options]', + # 'Add, create, list and remove project repositories' + + desc 'tree', 'Display a tree' + def tree + binding.pry + # TODO: @options are not being passed in from command line + nav = Navigator.new(options: @options, args: @args, path: APP_CWD) + puts '', nav.tree + end + + desc 'generate', 'generate' + def generate(type, *attributes) = execute + + desc 'console', 'Start a console (short-cut: c)' + def console(name = nil, *values) + hash = { method: :execute } # Specify the method :execute to avoid method_missing being invoked on 'console' + name = name.pluralize if values.size > 1 + hash.merge!(name.to_sym => values) if name # filter the context asset specified in 'name' by values + hash.merge!(controller: :console, namespace: :one_stack) + # binding.pry + execute(**hash) + end + end +end diff --git a/core/app/controllers/plans/command_controller.rb b/onestack/app/commands/one_stack/plans_command.rb similarity index 77% rename from core/app/controllers/plans/command_controller.rb rename to onestack/app/commands/one_stack/plans_command.rb index fa80e021..f8056445 100644 --- a/core/app/controllers/plans/command_controller.rb +++ b/onestack/app/commands/one_stack/plans_command.rb @@ -1,22 +1,20 @@ # frozen_string_literal: true -module Plans - class CommandController < Thor - include Concerns::CommandController - - cnfs_class_options :dry_run, :generate # , :logging - cnfs_class_options Cnfs.config.segments.keys +module OneStack + class PlansCommand < ApplicationCommand + has_class_options :dry_run, :generate # , :logging + has_class_options OneStack.config.segments.keys # TODO: If these options stay they need to move to a Terrform::Concerns::PlanController # option :clean, desc: 'Clean local modules cache. Force to download latest modules from TF registry', # type: :boolean # option :init, desc: 'Force to download latest modules from TF registry', # type: :boolean - cnfs_options :force + has_options :force desc 'deploy', 'Deploy all resources for the specified segment' def deploy() = execute - cnfs_options :force + has_options :force desc 'undeploy', 'Destroy all resources for the specified segment' def undeploy validate_destroy("\n#{'WARNING!!! ' * 5}\nAbout to *permanently destroy* #{context.component.name} " \ diff --git a/onestack/app/commands/one_stack/project_command.rb b/onestack/app/commands/one_stack/project_command.rb new file mode 100644 index 00000000..3919abdd --- /dev/null +++ b/onestack/app/commands/one_stack/project_command.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module OneStack + # class PluginCommand < Hendrix::PluginCommand; end + + class ProjectCommand # < Hendrix::ProjectCommand + # binding.pry + # register OneStack::PluginCommand, 'plugin', 'plugin', 'Create a new Hendrix plugin' + end +end diff --git a/core/app/controllers/repositories/command_controller.rb b/onestack/app/commands/one_stack/repositories_command.rb similarity index 69% rename from core/app/controllers/repositories/command_controller.rb rename to onestack/app/commands/one_stack/repositories_command.rb index 8f2a40fb..6da82109 100644 --- a/core/app/controllers/repositories/command_controller.rb +++ b/onestack/app/commands/one_stack/repositories_command.rb @@ -1,14 +1,13 @@ # frozen_string_literal: true -module Repositories - class CommandController < Thor - include Concerns::CommandController +module OneStack + class RepositoriesCommand < ApplicationCommand # Activate common options - cnfs_class_options :dry_run, :init + has_class_options :dry_run, :init - register Repositories::CreateController, 'create', 'create TYPE NAME [options]', - 'Create a new CNFS compatible services repository' + # register Repositories::CreateController, 'create', 'create TYPE NAME [options]', + # 'Create a new CNFS compatible services repository' # NOTE: This works for adding options to an existing command # May not be necessary if create just looks for types @@ -20,12 +19,11 @@ class CommandController < Thor # end desc 'add [NAME | URL [NAME]]', 'Add a repository configuration to the project' - cnfs_method_options(:add) - option :init, desc: 'Initialize repository', - aliases: '-i', type: :boolean + # has_options(:add) + has_options :init def add(p1, p2 = nil) repo = Repository.add(p1, p2) - raise Cnfs::Error, repo.errors.full_messages.join("\n") unless repo.save + raise Error, repo.errors.full_messages.join("\n") unless repo.save init(repo.name) if options.init # If this is the first source repository added to the project then make it the default @@ -33,29 +31,29 @@ def add(p1, p2 = nil) end desc 'new_add [NAME | URL [NAME]]', 'Add a repository configuration to the project' - cnfs_method_options(:new_add) + # has_options(:new_add) def new_add(p1, p2 = nil) repo = Repository.add(p1, p2) repo.save end desc 'destroy [NAME]', 'Delete a repository configuration and its contents from the project' - cnfs_method_options(:destroy) - cnfs_options :force + # cnfs_method_options(:destroy) + has_options :force # before :validate_destroy def destroy(name) - raise Cnfs::Error, "Repository #{name} not found" unless (repo = Repository.find_by(name: name)) + raise Error, "Repository #{name} not found" unless (repo = Repository.find_by(name: name)) repo.remove_tree repo.destroy end desc 'init [NAME]', 'Initialize (clone) a configured repository' - cnfs_method_options(:init) + has_options(:init) def init(name) - raise Cnfs::Error, "Repository not found: '#{name}'" unless (repo = Repository.find_by(name: name)) + raise Error, "Repository not found: '#{name}'" unless (repo = Repository.find_by(name: name)) - raise Cnfs::Error, "Directory already exists at '#{repo.full_path}'" if repo.full_path.exist? + raise Error, "Directory already exists at '#{repo.full_path}'" if repo.full_path.exist? return if options.dry_run @@ -87,14 +85,14 @@ def list # before :validate_destroy map %w[rm] => :remove def remove(name) - raise Cnfs::Error, "Repository #{name} not found" unless (repo = Repository.find_by(name: name)) + raise Error, "Repository #{name} not found" unless (repo = Repository.find_by(name: name)) repo.destroy end desc 'show [NAME]', 'Show repository configuration details' def show(name) - raise Cnfs::Error, "Repository #{name} not found" unless (repo = Repository.find_by(name: name)) + raise Error, "Repository #{name} not found" unless (repo = Repository.find_by(name: name)) puts repo.name puts repo.config diff --git a/onestack/app/commands/one_stack/resources_command.rb b/onestack/app/commands/one_stack/resources_command.rb new file mode 100644 index 00000000..8d1e17c2 --- /dev/null +++ b/onestack/app/commands/one_stack/resources_command.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + class ResourcesCommand < ApplicationCommand + has_class_options :dry_run, :init, :quiet + has_class_options OneStack.config.segments.keys + + desc 'console RESOURCE', 'Connect to a resource in the specified segment' + def console(resource) = execute(resource: resource) + end +end diff --git a/onestack/app/commands/one_stack/segments_command.rb b/onestack/app/commands/one_stack/segments_command.rb new file mode 100644 index 00000000..98d04c35 --- /dev/null +++ b/onestack/app/commands/one_stack/segments_command.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + class SegmentsCommand < ApplicationCommand + has_class_options :dry_run, :init, :clean, :clean_all + has_class_options OneStack.config.segments.keys + + desc 'console', 'Start a OneStack project console (short-cut: c)' + def console() = execute + end +end diff --git a/core/app/controllers/services/command_controller.rb b/onestack/app/commands/one_stack/services_command.rb similarity index 80% rename from core/app/controllers/services/command_controller.rb rename to onestack/app/commands/one_stack/services_command.rb index c0c42ffd..419c7b4d 100644 --- a/core/app/controllers/services/command_controller.rb +++ b/onestack/app/commands/one_stack/services_command.rb @@ -1,25 +1,23 @@ # frozen_string_literal: true -module Services - class CommandController < Thor - include Concerns::CommandController - +module OneStack + class ServicesCommand < ApplicationCommand # Define common options for this controller - add_cnfs_option :attach, desc: "Connect to service's running process ", - aliases: '-a', type: :string - add_cnfs_option :build, desc: 'Build image before executing command', - aliases: '-b', type: :boolean - add_cnfs_option :console, desc: "Connect to service's console", - aliases: '-c', type: :string - add_cnfs_option :profiles, desc: 'Service profiles', - aliases: '-p', type: :array - add_cnfs_option :profile, desc: 'Service profile', - aliases: '-p', type: :string - add_cnfs_option :sh, desc: "Connect to service's OS shell", - aliases: '--sh', type: :string - - cnfs_class_options :dry_run, :generate, :quiet - cnfs_class_options Cnfs.config.segments.keys + class << self + def shared_options + super.merge( + attach: { desc: "Connect to service's running process ", aliases: '-a', type: :string }, + build: { desc: 'Build image before executing command', aliases: '-b', type: :boolean }, + console: { desc: "Connect to service's console", aliases: '-c', type: :string }, + profiles: { desc: 'Service profiles', aliases: '-p', type: :array }, + profile: { desc: 'Service profile', aliases: '-p', type: :string }, + sh: { desc: "Connect to service's OS shell", aliases: '--sh', type: :string } + ) + end + end + + has_class_options :dry_run, :generate, :quiet + has_class_options OneStack.config.segments.keys # Service Runtime desc 'attach SERVICE', 'Attach to the process of a running service (short-cut: a)' @@ -29,7 +27,7 @@ class CommandController < Thor ctrl-f to detach; ctrl-c to stop/kill the service DESC - cnfs_options :tags, :profile, :build + has_options :tags, :profile, :build def attach(service) = execute(service: service) # NOTE: This is a test to run a command with multiple arguments; different from exec @@ -40,7 +38,7 @@ def attach(service) = execute(service: service) # end desc 'console SERVICE', 'Start a console on a running service (short-cut: c)' - cnfs_options :profile + has_options :profile # map %w[c] => :console def console(service) = execute(service: service) @@ -53,7 +51,7 @@ def console(service) = execute(service: service) To copy to a service: cnfs service copy local/path/to/file service_name:path/to/file DESC - cnfs_options :profile + has_options :profile map %w[cp] => :copy def copy(src, dest = nil) # TODO: Refactor; User can provide an absolute or relative path @@ -62,11 +60,11 @@ def copy(src, dest = nil) end desc 'exec SERVICE COMMAND', 'Execute a command on a running service' - cnfs_options :profile + has_options :profile def exec(service, *command) = super(service: service, command: command) desc 'logs SERVICE', 'Display the logs of a running service' - cnfs_options :profile + has_options :profile option :tail, desc: 'Continuous logging', aliases: '-f', type: :boolean def logs(service) = execute(service: service) @@ -80,7 +78,7 @@ def logs(service) = execute(service: service) def ps(*args) = execute(args: args) desc 'restart [SERVICES]', 'Stop and start one or more running services' - cnfs_options :attach, :build, :console, :profiles, :sh, :tags + has_options :attach, :build, :console, :profiles, :sh, :tags # TODO: When taking array of services but attach only uses the last one # Add default: '' so if -a is passed and value is blank then it's the last one # NOTE: Seed the database before executing command @@ -101,7 +99,7 @@ def sh(service) = execute(method: :shell, service: service) def show(*services) = execute(services: services) desc 'start [SERVICES]', 'Start one or more services (short-cut: s)' - cnfs_options :attach, :build, :console, :profiles, :sh + has_options :attach, :build, :console, :profiles, :sh # option :clean, type: :boolean, aliases: '--clean', desc: 'Seed the database before executing command' option :foreground, type: :boolean, aliases: '-f', desc: 'Run in foreground (default is daemon)' # option :seed, type: :boolean, aliases: '--seed', desc: 'Seed the database before starting the service' @@ -110,11 +108,11 @@ def show(*services) = execute(services: services) def start(*services) = execute(services: services) desc 'stop [SERVICES]', 'Stop one or more running services' - cnfs_options :profiles + has_options :profiles def stop(*services) = execute(services: services) desc 'terminate [SERVICES]', 'Terminate one or more running services' - cnfs_options :tags, :profiles + has_options :tags, :profiles def terminate(*services) = execute(services: services) # TODO: refactor publish; move to cnfs-backend gem; It should be loaded into this controller diff --git a/core/app/controllers/.gitkeep b/onestack/app/controllers/one_stack/.gitkeep similarity index 100% rename from core/app/controllers/.gitkeep rename to onestack/app/controllers/one_stack/.gitkeep diff --git a/onestack/app/controllers/one_stack/application_controller.rb b/onestack/app/controllers/one_stack/application_controller.rb new file mode 100644 index 00000000..ab1a2ea1 --- /dev/null +++ b/onestack/app/controllers/one_stack/application_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module OneStack + class ApplicationController < SolidSupport::ApplicationController + def context() = nav.context + + def nav() = Navigator.current || Navigator.new(path: APP_CWD, options: options, args: args) + + # + # Invokes init method on any model class that has defined it + # + def init + # TODO: Remove this when models are fully loaded + return unless options.init + + OneStack.config.asset_names.each do |asset| + klass = asset.classify.constantize + klass.init(context) if klass.respond_to?(:init) + end + end + end +end diff --git a/core/app/controllers/concerns/crud_controller.rb b/onestack/app/controllers/one_stack/concerns/crud_controller.rb similarity index 79% rename from core/app/controllers/concerns/crud_controller.rb rename to onestack/app/controllers/one_stack/concerns/crud_controller.rb index 2f3ed2e8..8d845d92 100644 --- a/core/app/controllers/concerns/crud_controller.rb +++ b/onestack/app/controllers/one_stack/concerns/crud_controller.rb @@ -1,22 +1,21 @@ # frozen_string_literal: true -module Concerns - module CrudController +module OneStack + module Concerns::CrudController extend ActiveSupport::Concern - include Concerns::CommandController + # include Concerns::CommandController - # rubocop:disable Metrics/BlockLength - included do - extend Concerns::CommandController + included do # rubocop:disable Metrics/BlockLength + # extend Concerns::CommandController action = name.demodulize.delete_suffix('Controller') # create list show edit destroy method = action.downcase.to_sym # cnfs_class_options :dry_run, :quiet, :clean - cnfs_class_options Cnfs.config.segments.keys + # has_class_options OneStack.config.segments.keys - Cnfs::Core.asset_names.dup.append('components').each do |asset_name| - model_class_name = asset_name.classify # Plan Provisioner Service User + OneStack.config.asset_names.dup.append('components').each do |asset_name| + model_class_name = "one_stack/#{asset_name}".classify # Plan Provisioner Service User command_name = method.eql?(:list) ? asset_name : asset_name.singularize command_hint = method.eql?(:list) ? 'PATTERN' : 'NAME' @@ -30,8 +29,7 @@ module CrudController class_option :context, desc: 'From current context', type: :boolean desc "#{command_name} [#{command_hint}]", "#{action} #{command_desc}" - # rubocop:disable Metrics/MethodLength - define_method(command_name) do |name = nil| + define_method(command_name) do |name = nil| # rubocop:disable Metrics/MethodLength model = models = nil # component = Component.list(options).last context = Component.context_from(options) @@ -56,9 +54,7 @@ module CrudController view_class.new(model: model, models: models, context: context).send(method) end - # rubocop:enable Metrics/MethodLength end end - # rubocop:enable Metrics/BlockLength end end diff --git a/onestack/app/controllers/one_stack/console_controller.rb b/onestack/app/controllers/one_stack/console_controller.rb new file mode 100644 index 00000000..dcaf5efe --- /dev/null +++ b/onestack/app/controllers/one_stack/console_controller.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +# Rename pry commands so they still work but can be reassigned to CLI specific commands +%w[ls cd help].each do |cmd| + Pry.config.commands["p#{cmd}"] = "Pry::Command::#{cmd.camelize}".constantize + Pry.config.commands[cmd] = nil +end + +Pry::Commands.block_command 'cd', 'change segment' do |path| + puts "cd: invalid segment: #{path}" unless OneStack::Navigator.current.cd(path) +end + +Pry::Commands.block_command 'pwd', 'print segment' do + OneStack::Navigator.current.path.relative_path_from(OneStack.config.paths.segments).to_s +end + +Pry::Commands.block_command 'ls', 'list current context assets' do |*args| + asset_names = args.any? ? OneStack.config.asset_names.select{ |a| args.include?(a) } : OneStack.config.asset_names + ab = asset_names.each_with_object({}) do |asset, hash| + klass = "one_stack/#{asset}".classify.constantize + abr = OneStack::ConsoleController.model_shortcuts.invert[klass] + records = OneStack::Navigator.current.context.send(asset.to_sym) + hash["#{asset} [#{abr}]"] = records.map{ |r| "#{r.name}#{r.type? ? " (#{r.type})" : ''}" } + end + puts TTY::Tree.new('.' => ab).render +end + +module OneStack + class ConsoleController < ApplicationController + include SolidSupport::ConsoleController + + before_execute :init, :nav, :create_help + + if ENV['HENDRIX_CLI_ENV'].eql?('development') + # OneStack.config.asset_names.each do |asset| + # delegate asset.to_sym, to: :context + # end + end + + def create_help # rubocop:disable Metrics/MethodLength + Pry::Commands.block_command 'help', 'Show help for commands' do + crud_cmds = %w[create edit list show destroy] + cmds = crud_cmds.map { |cmd| " #{cmd} [ASSET] [options]".ljust(35) + "# #{cmd.capitalize} asset" } + + controller_cmds = (MainCommand.all_commands.keys - %w[help project] - crud_cmds) + cmds += controller_cmds.map { |cmd| " #{cmd} [SUBCOMMAND] [options]".ljust(35) + "# Manage #{cmd.pluralize}" } + + cmds += [ + ' help [COMMAND]'.ljust(35) + '# Describe available commands or one specific command', + ' reload!'.ljust(35) + '# Reload classes', + ' reset!'.ljust(35) + '# Purge all records from the data store, reload classes and reload records' + ] + + puts 'Commands:', cmds.sort, '' + end + end + + def component() = context.component + + def reload! + super + Navigator.reload! + nav + true + end + + # Remove all records from the data store, reload classes and load segments into the data store + def reset! + SolidRecord::DataStore.reset(*SolidRecord.config.load_paths) + reload! + end + + class << self + def prompt + proc do |obj, _nest_level, _| + klass = obj.class.name.demodulize.delete_suffix('Controller').underscore + label = klass.eql?('console') ? '' : " (#{obj.class.name})" + "#{Navigator.current&.prompt}#{label}> " + end + end + + def shortcuts() = model_shortcuts.merge(super) + + def model_shortcuts + { b: Builder, c: Context, co: Component, con: Configurator, d: Dependency, im: Image, + p: Plan, pr: Provider, pro: Provisioner, r: Resource, re: Repository, reg: Registry, ru: Runtime, + s: Service, sr: SegmentRoot, u: User } + end + end + end +end diff --git a/core/app/controllers/images/exec_controller.rb b/onestack/app/controllers/one_stack/images_controller.rb similarity index 77% rename from core/app/controllers/images/exec_controller.rb rename to onestack/app/controllers/one_stack/images_controller.rb index c623424e..57377caf 100644 --- a/core/app/controllers/images/exec_controller.rb +++ b/onestack/app/controllers/one_stack/images_controller.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -module Images - class ExecController - include Concerns::ExecController - - around_execute :timer +module OneStack + class ImagesController < ApplicationController + # around_execute :timer def execute(cmd = command) context.images.filter_by(args).with_tags(options.tags).group_by(&:builder).each do |builder, images| diff --git a/core/app/controllers/plans/exec_controller.rb b/onestack/app/controllers/one_stack/plans_controller.rb similarity index 74% rename from core/app/controllers/plans/exec_controller.rb rename to onestack/app/controllers/one_stack/plans_controller.rb index ab22aea0..e701c146 100644 --- a/core/app/controllers/plans/exec_controller.rb +++ b/onestack/app/controllers/one_stack/plans_controller.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -module Plans - class ExecController - include Concerns::ExecController - +module OneStack + class PlansController < ApplicationController def execute(cmd = command) context.plans.group_by(&:provisioner).each do |provisioner, plans| provisioner.execute(cmd.to_sym, plans: plans) diff --git a/onestack/app/controllers/one_stack/project_controller.rb b/onestack/app/controllers/one_stack/project_controller.rb new file mode 100644 index 00000000..166b5d34 --- /dev/null +++ b/onestack/app/controllers/one_stack/project_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module OneStack + class ProjectController < SolidSupport::ApplicationController + def new + binding.pry + super + end + + def new_application + super + + # Dir.chdir(path) { generator(:extension).invoke(:segments) } + + # Dir.chdir(path) do + # Hendrix.loaders['framework'].unload + # load 'cnfs/boot_loader.rb' + # SegmentRoot.first.generate_key + # end + + return unless options.guided + + # Start a view here + # TODO: This should create a node which should create a file with the yaml or a directory + # Project.new(name: context.args.name).create + end + end +end diff --git a/core/app/controllers/repositories/create_controller.rb b/onestack/app/controllers/one_stack/repositories_controller.rb similarity index 88% rename from core/app/controllers/repositories/create_controller.rb rename to onestack/app/controllers/one_stack/repositories_controller.rb index 3e992096..f20418d2 100644 --- a/core/app/controllers/repositories/create_controller.rb +++ b/onestack/app/controllers/one_stack/repositories_controller.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true -module Repositories - class CreateController < Thor - include Concerns::CommandController +module OneStack + class RepositoriesController < Thor + # Create + # include Concerns::CommandController - cnfs_class_options :dry_run, :force + # cnfs_class_options :dry_run, :force private diff --git a/core/app/controllers/resources/exec_controller.rb b/onestack/app/controllers/one_stack/resources_controller.rb similarity index 80% rename from core/app/controllers/resources/exec_controller.rb rename to onestack/app/controllers/one_stack/resources_controller.rb index 9bb370da..84074c6c 100644 --- a/core/app/controllers/resources/exec_controller.rb +++ b/onestack/app/controllers/one_stack/resources_controller.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true -module Resources - class ExecController - include Concerns::ExecController +module OneStack + class ResourcesController < ApplicationController # TODO: Lookup the resource which is what returns the thing to do, e.g. ssh for an EC2 def console @@ -16,7 +15,7 @@ def console else warn = "Resource #{resource} not found" end - Cnfs.logger.warn(warn) if warn + Hendrix.logger.warn(warn) if warn end end end diff --git a/core/app/controllers/segments/status_controller.rb b/onestack/app/controllers/one_stack/segments_controller.rb similarity index 95% rename from core/app/controllers/segments/status_controller.rb rename to onestack/app/controllers/one_stack/segments_controller.rb index f8db6f55..d241de85 100644 --- a/core/app/controllers/segments/status_controller.rb +++ b/onestack/app/controllers/one_stack/segments_controller.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'tty-table' - -module Segments - class StatusController +module OneStack + class SegmentsController < ApplicationController + # show status; lifted from namespace def execute(entry) super # context.each_target do diff --git a/core/app/controllers/services/exec_controller.rb b/onestack/app/controllers/one_stack/services_controller.rb similarity index 98% rename from core/app/controllers/services/exec_controller.rb rename to onestack/app/controllers/one_stack/services_controller.rb index daefded4..3ee9ed5e 100644 --- a/core/app/controllers/services/exec_controller.rb +++ b/onestack/app/controllers/one_stack/services_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -module Services - class ExecController - include Concerns::ExecController +module OneStack + class ServicesController < ApplicationController + # include Concerns::ExecController before_execute :process_before_execute_options diff --git a/onestack/app/generators/one_stack/application_generator.rb b/onestack/app/generators/one_stack/application_generator.rb new file mode 100644 index 00000000..9479c687 --- /dev/null +++ b/onestack/app/generators/one_stack/application_generator.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + class ApplicationGenerator < SolidSupport::ApplicationGenerator + + # Argument order: + # 1. Context is always received first + # 2. Any additional arugments declared in sub-classes + argument :context + end +end diff --git a/onestack/app/generators/one_stack/builder_generator.rb b/onestack/app/generators/one_stack/builder_generator.rb new file mode 100644 index 00000000..ebb0305e --- /dev/null +++ b/onestack/app/generators/one_stack/builder_generator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + class BuilderGenerator < ApplicationGenerator + argument :builder + end +end diff --git a/cnfs/app/generators/cnfs/new/extension/templates/config/segments.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/config/segments.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/config/segments.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/config/segments.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension/templates/lib/cnfs/extension.rb.erb b/onestack/app/generators/one_stack/project/extension/templates/lib/cnfs/extension.rb.erb similarity index 74% rename from cnfs/app/generators/cnfs/new/extension/templates/lib/cnfs/extension.rb.erb rename to onestack/app/generators/one_stack/project/extension/templates/lib/cnfs/extension.rb.erb index 1782395f..d87760e1 100644 --- a/cnfs/app/generators/cnfs/new/extension/templates/lib/cnfs/extension.rb.erb +++ b/onestack/app/generators/one_stack/project/extension/templates/lib/cnfs/extension.rb.erb @@ -1,8 +1,8 @@ # frozen_string_literal: true -module Cnfs +module OneStack module <%= name.camelize %> - class Extension < Cnfs::Extension + class Extension < OneStack::Extension class << self def root() = Pathname.new(__dir__).join('../..') end diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/dependencies.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/segments/dependencies.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/dependencies.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/segments/dependencies.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/providers.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/segments/providers.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/providers.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/segments/providers.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/provisioners.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/segments/provisioners.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/provisioners.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/segments/provisioners.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/repositories.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/segments/repositories.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/repositories.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/segments/repositories.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/resources.yml b/onestack/app/generators/one_stack/project/extension/templates/segments/resources.yml similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/resources.yml rename to onestack/app/generators/one_stack/project/extension/templates/segments/resources.yml diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/runtimes.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/segments/runtimes.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/runtimes.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/segments/runtimes.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension/templates/segments/users.yml.erb b/onestack/app/generators/one_stack/project/extension/templates/segments/users.yml.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/extension/templates/segments/users.yml.erb rename to onestack/app/generators/one_stack/project/extension/templates/segments/users.yml.erb diff --git a/cnfs/app/generators/cnfs/new/extension_generator.rb b/onestack/app/generators/one_stack/project/extension_generator.rb similarity index 70% rename from cnfs/app/generators/cnfs/new/extension_generator.rb rename to onestack/app/generators/one_stack/project/extension_generator.rb index 225dc29c..0e9c0250 100644 --- a/cnfs/app/generators/cnfs/new/extension_generator.rb +++ b/onestack/app/generators/one_stack/project/extension_generator.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true -module Cnfs - class New::ExtensionGenerator < NewGenerator +module OneStack + class Project::ExtensionGenerator < ApplicationGenerator + # class Project::ExtensionGenerator < Hendrix::Project::ExtensionGenerator def segments + binding.pry templates.each do |template| destination = template.relative_path_from(templates_path).to_s.delete_suffix('.erb') template(template, destination) @@ -14,6 +16,8 @@ def segments private + def source_paths() = super + def internal_path() = Pathname.new(__dir__) end end diff --git a/onestack/app/generators/one_stack/provisioner_generator.rb b/onestack/app/generators/one_stack/provisioner_generator.rb new file mode 100644 index 00000000..5d51daf7 --- /dev/null +++ b/onestack/app/generators/one_stack/provisioner_generator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + class ProvisionerGenerator < ApplicationGenerator + argument :provisioner + end +end diff --git a/onestack/app/generators/one_stack/runtime_generator.rb b/onestack/app/generators/one_stack/runtime_generator.rb new file mode 100644 index 00000000..54bcff47 --- /dev/null +++ b/onestack/app/generators/one_stack/runtime_generator.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module OneStack + class RuntimeGenerator < ApplicationGenerator + argument :runtime + + # NOTE: Generate the environment files first b/c the manifest template will + # look for the existence of those files + def environments + context.services.select { |service| service.environment.any? }.each do |service| + file_name = path.join("#{service.name}.env") + binding.pry + # TODO: remove Config::Options + environment = Config::Options.new.merge!(service.environment) + binding.pry + generated_files << template('templates/env.erb', file_name, env: environment) + end + end + + def manifests + name = nil + context.services.each do |service| + name = service.name + # binding.pry + generated_files << template(template_file(service), "#{path.join(service.name)}.yml") + end + rescue StandardError => e + Cnfs.logger.warn("Error generating template for #{name}: #{e.message}") + if CnfsCli.config.dev + msg = "#{e}\n#{e.backtrace}" + # binding.pry + end + ensure + remove_stale_files + end + + private + + # All content comes from Repository and Project Components so source paths reflect that + # TODO: This probably needs to be further filtered based on the blueprint in the case of Provisoners + # and by Resource? in the case of Runtimes + # In fact this may need to be refactored from a global CnfsCli registry to a component hierarchy based + def source_paths + @source_paths ||= CnfsCli.loaders.values.map { |path| path.join('generators', generator_type) }.select(&:exist?) + end + + def template_file(service) + [service.template, service.name, service.class.name.deconstantize, 'service'].each do |template_name| + source_paths.each do |source_path| + template_path = "templates/#{template_name}.yml.erb" + return template_path if source_path.join(template_path).exist? + end + end + end + + # List of services to be configured on the proxy server (nginx for compose) + # TODO: Move this to an nginx service class in an RC + def proxy_services() = context.services.select { |service| service.profiles.key?(:server) } + + def template_types + @template_types ||= context.services.map { |service| entity_to_template(service).to_sym }.uniq + end + + def version + runtime.version + end + + # Default space_count is for compose + # Template is expected to pass in a hash for example for profile + def labels(labels: {}, space_count: 6) + context.labels.merge(labels).merge(service: service.name).map do |key, value| + "\n#{' ' * space_count}#{key}: #{value}" + end.join + end + + def env_files(space_count = 6) + @env_files ||= {} + @env_files[service] ||= set_env_files.join("\n#{' ' * space_count}- ") + end + + def set_env_files + files = [] + files.append("./#{service.name}.env") if path.join("#{service.name}.env").exist? + files + end + end +end diff --git a/onestack/app/models/one_stack/application_record.rb b/onestack/app/models/one_stack/application_record.rb new file mode 100644 index 00000000..c7589013 --- /dev/null +++ b/onestack/app/models/one_stack/application_record.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end +end diff --git a/onestack/app/models/one_stack/asset.rb b/onestack/app/models/one_stack/asset.rb new file mode 100644 index 00000000..20abb819 --- /dev/null +++ b/onestack/app/models/one_stack/asset.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module OneStack + class Asset < ApplicationRecord + belongs_to :owner, polymorphic: true + + def self.create_table(schema) + schema.create_table :assets, force: true do |t| + t.string :name + t.string :type + t.string :path + t.string :owner_type + t.string :owner_id + t.string :tags + end + end + end +end diff --git a/onestack/app/models/one_stack/asset/credential.rb b/onestack/app/models/one_stack/asset/credential.rb new file mode 100644 index 00000000..7532d844 --- /dev/null +++ b/onestack/app/models/one_stack/asset/credential.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module OneStack + class Asset::Credential < Asset + FIXED_FILE = 'application_default_credentials.json' + + # TODO: This needs to change and coordinate with following files: + # https://github.com/rails-on-services/helm-charts/blob/master/charts/cp-kafka-connect/templates/deployment.yaml + # lib/ros/be/application/services/templates/jobs/kafka-connect/connector-provision-job.yml.erb + def deploy_commands(runtime) + runtime.kubectl("create secret generic #{name} --from-literal=#{FIXED_FILE}=#{secret}") + end + + def secret + Cnfs.decrypt_file(full_path) + end + + def full_path + Cnfs.root.join(path) + end + end +end diff --git a/onestack/app/models/one_stack/builder.rb b/onestack/app/models/one_stack/builder.rb new file mode 100644 index 00000000..2cb1def6 --- /dev/null +++ b/onestack/app/models/one_stack/builder.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module OneStack + class Builder < ApplicationRecord + # Define target and commands before including Operator Concern + class << self + def target() = :images + + def commands() = %i[build push pull test] + end + + include OneStack::Concerns::Operator + + # Assigned in Operator#execute + attr_accessor :images + + before_execute :generate + end +end diff --git a/onestack/app/models/one_stack/component.rb b/onestack/app/models/one_stack/component.rb new file mode 100644 index 00000000..900de037 --- /dev/null +++ b/onestack/app/models/one_stack/component.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +module OneStack + class Component < ApplicationRecord + include Concerns::Parent + include SolidRecord::TreeView + include SolidSupport::Extendable if defined?(SolidSupport) + + belongs_to :owner, class_name: 'Component' + has_one :context + + has_many :components, foreign_key: 'owner_id' + + has_many :context_components + has_many :contexts, through: :context_components + + store :default, coder: YAML, accessors: :segment_name + + # For every asset Companent has_many and an _name default stored_attribute + OneStack.config.asset_names.each do |asset_name| + has_many asset_name.to_sym, as: :owner + + store :default, accessors: "#{asset_name.singularize}_name".to_sym + + # Return array of names for each of the component's assets + # runtime_names => ['compose', 'skaffold'] + define_method("#{asset_name.singularize}_names") { send(asset_name.to_sym).pluck(:name) } + end + + # if segments_type is not present then cannot search beyond this component + # validates :segments_type, presence: true + + def next_segment(options, pwd = nil) + next_name, next_source = next_segment_spec(options, pwd) + OpenStruct.new(name: next_name, source: next_source, component: components.find_by(name: next_name)) + end + + # Returns an Array for the component's next segment based on priority + # If a command line option was provided (e.g. -s stack_name) then return that + # If an ENV was provided (e.g. CNFS_STACK) then return that + # If the current component has an attribute 'default' then return that + # Otherwise return an empty Array + def next_segment_spec(options, pwd) + if (name = options.fetch(segments_type, nil)) + [name, 'CLI option'] + elsif pwd + [pwd, 'cwd'] + elsif (name = OneStack.config.segments[segments_type].try(:[], :env_value)) + [name, 'ENV value'] + elsif segment_name # default[:segment_name] in this component's yaml file + [segment_name, 'parent.node_name'] + else + [] + end + end + + # Return the default dir_path of the parent's path + this component's name unless segment_path is configured + # In which case parse segment_path to search the component hierarchy for the specified repository and blueprint + # def dir_path + # # binding.pry + # if extension.nil? + # node_warn(node: parent, msg: "Extension '#{extension_name}' not found") + # elsif extension.segment(extension_path).nil? + # node_warn(node: parent, msg: "Extension '#{extension_name}' segment '#{extension_path}' not found") + # else + # return extension.segment(extension_path) + # end + # parent.parent.rootpath.join(parent.node_name).to_s + # end + + def extension() = OneStack.extensions[extension_name] + + def extension_name() = segment_path ? segment_path.split('/').first.to_sym : :application + + def extension_path() = segment_path ? segment_path.split('/')[1...]&.join('/') : owner_extension_path + + def owner_extension_path() = owner.extension_path.join(name) + + # Key search priorities: + # 1. ENV var + # 2. Project's data_path/keys.yml + # 3. Owner's key + def encryption_key() = @key ||= ENV[key_name_env] || local_file_read(path: keys_file).fetch(key_name, owner&.key) + + # 1_target/backend => 1_target_backend + def key_name() = @key_name ||= "#{owner.key_name}_#{name}" + + # 1_target/backend => OS_KEY_BACKEND + def key_name_env() = @key_name_env ||= "#{owner.key_name_env}_#{name.upcase}" + + def keys_file() = @keys_file ||= OneStack.config.data_home.join('keys.yml') + + def generate_key + key_file_values = local_file_read(path: keys_file).merge(new_key) + local_file_write(path: keys_file, values: key_file_values) + end + + def new_key() = { key_name => Lockbox.generate_key } + + # Read/Write 'runtime' component data to this file + def cache_file() = @cache_file ||= "#{cache_path}.yml" + + # This component's local file to provide local overrides to project values, e.g. change default runtime_name + # The user must maintain these files locally + # These values are merged for runtime so they are not saved into the project's files + def data_file() = @data_file ||= "#{data_path}.yml" + + # Operators and Context's Assets can read/write their 'runtime' data into this directory + def cache_path() = @cache_path ||= owner.cache_path.join(name) + + # Child Components and Assets can read their local overrides into this directory + def data_path() = @data_path ||= owner.data_path.join(name) + + def attrs() = @attrs ||= owner.attrs.dup.append(name) + + # def segment_names() = @segment_names ||= components.pluck(:name) + + def struct() = OpenStruct.new(segment_type: owner.segments_type, name: name, color: color) + + def color() = owner.segments_type ? OneStack.config.segments[owner.segments_type].try(:[], :color) : nil + + def as_merged() = as_json + + def tree_label() = "#{name} (#{owner.segments_type})" + + def tree_assn() = :components + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.string :name + t.string :type # The only subclass is SegmentRoot. Not to be configured by User + t.string :segment_path # Usually nil, but can be set to extension/segment_name + t.string :segments_type # corresponds to ENV and CLI options to filter the components to most specific + t.string :default + t.string :config + t.references :owner + end + end + end + end +end diff --git a/core/app/models/concerns/asset.rb b/onestack/app/models/one_stack/concerns/asset.rb similarity index 76% rename from core/app/models/concerns/asset.rb rename to onestack/app/models/one_stack/concerns/asset.rb index 07586dd5..4f656831 100644 --- a/core/app/models/concerns/asset.rb +++ b/onestack/app/models/one_stack/concerns/asset.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -module Concerns - module Asset +module OneStack + module Concerns::Asset extend ActiveSupport::Concern included do include Concerns::Parent - belongs_to :node, class_name: 'AssetFile' belongs_to :owner, polymorphic: true, required: true scope :inheritable, -> { where(inherit: [true, nil], abstract: [false, nil]).order(:id) } @@ -37,27 +36,23 @@ module Asset store :tags, coder: YAML - delegate :key, to: :owner + delegate :encryption_key, to: :owner - before_validation :cli_owner + before_validation :cli_owner, if: -> { SolidRecord.status.loaded? && OneStack.config.cli.mode } - validate :dynamic_association_types + validate :dynamic_association_types, if: -> { SolidRecord.status.loaded? } end - def cli_owner - return unless Node.source.eql?(:asset) && Cnfs.config.cli.mode - - self.owner ||= Cnfs.config.console.context.component - end + def cli_owner() = self.owner ||= Navigator.current.context.component def as_merged - return as_json unless from && (source = owner.send(self.class.table_name).find_by(name: from)) + return as_solid unless from && (source = owner.send(self.class.table_name).find_by(name: from)) - source.as_json.except('abstract').deep_merge(as_json) + source.as_solid.except('abstract').deep_merge(as_json) end def dynamic_association_types - return unless Node.source.eql?(:asset) + return # unless Node.source.eql?(:asset) self.class.belongs_to_names.each do |attribute| # In order to validate there must be a defined association and a value or array of values to check against @@ -71,14 +66,10 @@ def dynamic_association_types end end - def valid_types - {}.with_indifferent_access - end + def valid_types() = {}.with_indifferent_access - def tree_name - # [name, type&.deconstantize&.underscore].compact.join(': ').gsub('/', '-') - [name, type&.deconstantize].compact.join(': ').gsub('::', ' ') - end + # TODO: Include SolidSupport::TreeView and override as_tree + def tree_label() = [name, type&.deconstantize].compact.join(': ').gsub('::', ' ') # TODO: Implement when option vebose is paased in def tree_name_verbose @@ -91,22 +82,13 @@ def cache_file() = @cache_file ||= owner.cache_path.join(asset_type, "#{name}.ym def data_file() = @data_file ||= owner.data_path.join(asset_type, "#{name}.yml") - def asset_type() = self.class.name.demodulize.underscore.pluralize + # def asset_type() = self.class.name.demodulize.underscore.pluralize + def asset_type() = self.class.table_name class_methods do - def with_node_callbacks_diabled - node_callbacks.each { |callback| skip_callback(*callback) } - yield - node_callbacks.each { |callback| set_callback(*callback) } - end - def update_associations(context) - return if belongs_to_names.size.zero? - - with_node_callbacks_diabled { dynamic_update(context) } - end + return unless belongs_to_names.size.zero? - def dynamic_update(context) res_msg = "#{table_name.classify} not configured: " context.send(table_name).each do |asset| @@ -120,7 +102,7 @@ def dynamic_update(context) if (obj = assn.find_by(name: name)) hash[attribute] = obj else - # Cnfs.logger.warn("#{res_msg}#{asset.name}" \ + # Hendrix.logger.warn("#{res_msg}#{asset.name}" \ # "\n#{' ' * 10}#{attribute.capitalize} '#{name}' is not available in this segment" \ # "\n#{' ' * 10}Available #{attribute.pluralize}: #{assn.pluck(:name).join(', ')}") end @@ -139,7 +121,7 @@ def dynamic_update(context) hash[attribute] = obj hash["#{attribute}_name"] = obj.name else - # Cnfs.logger.warn("#{res_msg}#{asset.name}" \ + # Hendrix.logger.warn("#{res_msg}#{asset.name}" \ # "\n#{' ' * 10}Default #{attribute} #{name} not found" \ # "\n#{' ' * 10}Source: #{context.component.parent.rootpath}") end @@ -148,7 +130,7 @@ def dynamic_update(context) asset.update(update_hash) next unless asset.errors.any? - Cnfs.logger.warn(asset.errors.full_messages.unshift("#{res_msg}#{asset.name}").join("\n#{' ' * 10}")) + OneStack.logger.warn(asset.errors.full_messages.unshift("#{res_msg}#{asset.name}").join("\n#{' ' * 10}")) end end @@ -162,13 +144,9 @@ def reference_columns @reference_columns ||= column_names.select { |n| n.end_with?('_id') || n.end_with?('_name') } end - def table_mod(method) - table_mods.append(method) - end + def table_mod(method) = table_mods.append(method) - def table_mods - @table_mods ||= [] - end + def table_mods() = @table_mods ||= [] def create_table(schema) schema.create_table table_name, force: true do |t| @@ -179,7 +157,6 @@ def create_table(schema) t.boolean :inherit t.string :from t.references :owner, polymorphic: true - t.references :node t.string :config t.string :tags add_columns(t) if respond_to?(:add_columns) diff --git a/onestack/app/models/one_stack/concerns/generic.rb b/onestack/app/models/one_stack/concerns/generic.rb new file mode 100644 index 00000000..05ae4f35 --- /dev/null +++ b/onestack/app/models/one_stack/concerns/generic.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Functionality for Asset models that are not Operators or Targets +module OneStack + module Concerns::Generic + extend ActiveSupport::Concern + + included do + include SolidRecord::Model + include Concerns::Asset + include SolidSupport::Extendable if defined?(SolidSupport) + end + end +end diff --git a/core/app/models/concerns/operator.rb b/onestack/app/models/one_stack/concerns/operator.rb similarity index 92% rename from core/app/models/concerns/operator.rb rename to onestack/app/models/one_stack/concerns/operator.rb index 8fb1c93e..642a0c9d 100644 --- a/core/app/models/concerns/operator.rb +++ b/onestack/app/models/one_stack/concerns/operator.rb @@ -4,8 +4,8 @@ # 1. Generate content from project configuration # 2. Ensure Operator dependencies are available on the system or download them if requested and available # 3. Provide Queue mechanism for running OS commands -module Concerns - module Operator +module OneStack + module Concerns::Operator extend ActiveSupport::Concern included do @@ -18,12 +18,12 @@ module Operator table_mod(:operator_columns) - include Concerns::Extendable + include SolidSupport::Extendable if defined?(SolidSupport) end def execute(method, **kwargs) unless self.class.commands.include?(method) - Cnfs.logger.fatal("#{self.class.name} does not support the #{method} command") + OneStack.logger.fatal("#{self.class.name} does not support the #{method} command") return end @@ -52,14 +52,14 @@ def generate binding.pry manifest.rm_targets - Cnfs.logger.debug("Processing manifest in #{path}") + OneStack.logger.debug("Processing manifest in #{path}") # Rather than set generator.destination_root which requires generators to use in_root blocks # just cd into path and then invoke the generator Dir.chdir(path) { generator.invoke_all } if manifest.reload.valid? - Cnfs.logger.info('manifest validated - OK') + OneStack.logger.info('manifest validated - OK') else - Cnfs.logger.warn("Invalid manifest: #{manifest.errors.full_messages}") + OneStack.logger.warn("Invalid manifest: #{manifest.errors.full_messages}") end end diff --git a/onestack/app/models/one_stack/concerns/parent.rb b/onestack/app/models/one_stack/concerns/parent.rb new file mode 100644 index 00000000..a21291a0 --- /dev/null +++ b/onestack/app/models/one_stack/concerns/parent.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Common functionality for Component and Asset +module OneStack + module Concerns::Parent + extend ActiveSupport::Concern + + class_methods do + # Disable storing database primary and foreign keys to yaml + # All references are determined dynamically when the context builds the assets + # Storing keys in yaml would be confusing to user and will cause problems as yaml content changes and IDs change + # Any nested stored_attributes should not be serialized as the root is already serialized + def except_solid + (super + %w[owner_type] + column_names.select { |n| n.end_with?('_id') } + + (stored_attributes.keys.map(&:to_s) - column_names)).uniq + end + end + + included do + include SolidRecord::Model + # TODO: Test and refactor Interpolation + include SolidSupport::Interpolation + + # NOTE: These methods do not work when declared in class_methods block + def self.owner_association_name() = :owner + def self.key_column() = 'name' + + store :config, coder: YAML + + validates :name, presence: true + end + + # TODO: Simplify interpolated + def to_context() = as_interpolated + + # Convenience methods for cache_* and data_* for clarity in calling code + def cache_file_read() = local_file_read(path: cache_file) + + def data_file_read() = local_file_read(path: data_file) + + def local_file_read(path:) = path.exist? ? (YAML.load_file(path) || {}) : {} + + def cache_file_write(**values) = local_file_write(path: cache_file, values: values) + + def data_file_write(**values) = local_file_write(path: data_file, values: values) + + def local_file_write(path:, values:) + path.parent.mkpath + File.open(path, 'w') { |f| f.write(values.to_yaml) } + end + end +end diff --git a/core/app/models/concerns/target.rb b/onestack/app/models/one_stack/concerns/target.rb similarity index 79% rename from core/app/models/concerns/target.rb rename to onestack/app/models/one_stack/concerns/target.rb index 44230aa0..c7404964 100644 --- a/core/app/models/concerns/target.rb +++ b/onestack/app/models/one_stack/concerns/target.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -module Concerns - module Target +module OneStack + module Concerns::Target extend ActiveSupport::Concern included do @@ -13,7 +13,7 @@ module Target define_model_callbacks(*operator.target_callbacks, only: %i[before after]) if respond_to?(:operator) - include Concerns::Extendable + include SolidSupport::Extendable if defined?(SolidSupport) end end end diff --git a/onestack/app/models/one_stack/configurator.rb b/onestack/app/models/one_stack/configurator.rb new file mode 100644 index 00000000..bef3029e --- /dev/null +++ b/onestack/app/models/one_stack/configurator.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module OneStack + class Configurator < ApplicationRecord + include OneStack::Concerns::Operator + + attr_accessor :playbooks + + before_execute :generate + + def target() = :playbooks + end +end diff --git a/onestack/app/models/one_stack/context.rb b/onestack/app/models/one_stack/context.rb new file mode 100644 index 00000000..4c419851 --- /dev/null +++ b/onestack/app/models/one_stack/context.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +module OneStack + class Context < ApplicationRecord + include SolidRecord::Table + + belongs_to :component + + has_many :context_components + has_many :components, through: :context_components + + OneStack.config.asset_names.each do |asset_name| + # component_ = all from the component hierarchy + has_many "component_#{asset_name}".to_sym, through: :components, source: asset_name.to_sym + has_many asset_name.to_sym, as: :owner + end + + store :options, coder: YAML + + # When decrypting values assets ask for the key from their owner + # When context is the owner it must ask the component for the key + # TODO: The assets need to be decrypted with reference to their actual owner not the context.component + # This will not work for inherited values. This needs to be tested + delegate :attrs, :cache_path, :data_path, :key, :as_interpolated, :segments_type, :segment_names, :as_tree, + to: :component + + after_create :create_assets + after_commit :update_asset_associations + + def create_assets + SolidRecord.skip_persistence_callbacks do + OneStack.config.asset_names.each do |asset_type| + component_assets = component.send(asset_type.to_sym) + inheritable_assets = send("component_#{asset_type}".to_sym).inheritable + context_assets = send(asset_type.to_sym) + first_pass(component_assets, inheritable_assets, context_assets) + second_pass(component_assets, inheritable_assets, context_assets) + end + end + end + + def first_pass(component_assets, inheritable_assets, context_assets) + component_assets.enabled.each do |asset| + # binding.pry if asset.class.name.eql? 'Plan' + name = asset.name + json = inheritable_assets.where(name: name).each_with_object({}) do |record, hash| + hash.deep_merge!(record.to_context.compact) + end + json.deep_merge!(asset.to_context.compact) + context_assets.create(json.merge(name: name)) + end + end + + # Inheritable assets that are not defined in the component, but are enabled are added to the context + # All values of the inherited assets of the same name are deep merged + def second_pass(component_assets, inheritable_assets, context_assets) + names = component_assets.pluck(:name) + # binding.pry if inheritable_assets.any? and inheritable_assets.first.class.name.eql?('Plan') + inheritable_assets.enabled.where.not(name: names).group_by(&:name).each_with_object([]) do |(name, records), _ary| + json = records.each_with_object({}) do |record, hash| + hash.deep_merge!(record.to_context.compact) + end + context_assets.create(json.merge(name: name)) # unless json['disabled'] + end + end + + def update_asset_associations + SolidRecord.skip_persistence_callbacks do + OneStack.config.asset_names.each do |asset_type| + "one_stack/#{asset_type}".classify.constantize.update_associations(self) + end + end + end + + # Used by runtime generator templates so the runtime can query services by label + def labels() = @labels ||= labels_hash + + def labels_hash + # TODO: Refactor; maybe to Component class + component.structs.each_with_object({ 'context' => name }) do |component, hash| + hash[component.segment_type] = component.name + end + end + + def name() = @name ||= attrs.join('_') + + def path() = @path ||= attrs.join('/') + + def options() = @options ||= Thor::CoreExt::HashWithIndifferentAccess.new(super) + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :component + t.string :options + end + end + end + end +end diff --git a/core/app/models/context_component.rb b/onestack/app/models/one_stack/context_component.rb similarity index 88% rename from core/app/models/context_component.rb rename to onestack/app/models/one_stack/context_component.rb index 33a85777..1adda2cb 100644 --- a/core/app/models/context_component.rb +++ b/onestack/app/models/one_stack/context_component.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true +module OneStack class ContextComponent < ApplicationRecord + include SolidRecord::Table + belongs_to :context belongs_to :component @@ -15,3 +18,4 @@ def create_table(schema) end end end +end diff --git a/onestack/app/models/one_stack/definition.rb b/onestack/app/models/one_stack/definition.rb new file mode 100644 index 00000000..2d55a796 --- /dev/null +++ b/onestack/app/models/one_stack/definition.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module OneStack + class Definition < ApplicationRecord + include OneStack::Concerns::Generic + + store :config, accessors: :path + + before_validation :set_defaults + + # TODO: Definitions do not require an owner + # They do have a parent + # So need a new concern for this type of arrangement, the owner-less asset + def set_defaults + self.name = path.split('/').last + # self.owner = SegmentRoot.create(name: :test) + end + + after_create :do_it + + attr_reader :mod, :parent_class, :class_name + + def do_it + parse_attributes + create_class + end + + def parse_attributes + asset_type, *mod_types, model_type = file.to_s.delete_suffix('.yml').split('/').map(&:classify) + @mod = mod_types.join('::').safe_constantize || Object + @class_name = [model_type, asset_type].join + @parent_class = asset_type.safe_constantize + end + + # services/nginx.yml => NginxService + # plans/terraform/ec2.yml # => Terraform::Ec2Plan + def create_class + attributes = content['attributes'] + mod.const_set class_name, Class.new(parent_class) { + attr_accessor(*attributes) + # include Concerns::Extendable + } + end + + def content() = @content ||= YAML.load_file(path) + + # TODO: The DefinitionFile should be passed in and it is relative from there somehow + # so that definitions are in extensions as well + def file() = pathname.relative_path_from(Hendrix.config.paths.definitions) + + def pathname() = @pathname ||= Pathname.new(path) + end +end diff --git a/onestack/app/models/one_stack/dependency.rb b/onestack/app/models/one_stack/dependency.rb new file mode 100644 index 00000000..b305518f --- /dev/null +++ b/onestack/app/models/one_stack/dependency.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module OneStack + class Dependency < ApplicationRecord + include SolidSupport::Download if defined?(SolidSupport) + include Concerns::Generic + + store :config, accessors: %i[source linux mac] + + validate :available, if: -> { SolidRecord.status.loaded? } + + # TODO: Search cache path instead of system directories or an option to do so + # TTY::Which.which("ruby", paths: ["/usr/local/bin", "/usr/bin", "/bin"]) + def available + # return unless SolidRecord.status.loaded? + + errors.add(:dependency_not_found, name) unless TTY::Which.exist?(name) + end + + def do_download(version) + dep = with_other(operator: { 'version' => version }, platform: OneStack.platform.to_hash) + url = Pathname.new(dep['config']['source']) + file = cache_path.join(version, url.basename) + return if file.exist? + + download(url: url, path: cache_path.join(version)) + end + + def cache_path() = OneStack.config.cli_cache_home.join('dependencies', name) + + # This may be about TF modules rather than binaries like tf, kubectl, etc + # TODO: Figure out how to manage these + # def dependencies + # super.map(&:with_indifferent_access) + # end + + # TODO: What is this for? + # def fetch_data_repo + # Cnfs.logger.info "Fetching data source v#{data.config.data_version}..." + # File.open('data.tar.gz', 'wb') do |fo| + # fo.write open("https://github.com/#{data.config.data_repo}/archive/#{data.config.data_version}.tar.gz", + # 'Authorization' => "token #{data.config.github_token}", + # 'Accept' => 'application/vnd.github.v4.raw').read + # end + # `tar xzf "data.tar.gz"` + # end + end +end diff --git a/onestack/app/models/one_stack/image.rb b/onestack/app/models/one_stack/image.rb new file mode 100644 index 00000000..2d91ae57 --- /dev/null +++ b/onestack/app/models/one_stack/image.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module OneStack + class Image < ApplicationRecord + # Define operator before inclding Concerns::Target + def self.operator() = OneStack::Builder + + include Concerns::Target + include SolidSupport::Git if defined?(SolidSupport) + + belongs_to :builder + belongs_to :registry + belongs_to :repository + + # TODO: Move all of this up to delegation command to Compose::Image + # As these things will be very different for e.g. Packer images + # NOTE: dockerfile is relative to repository.git_path + store :config, accessors: %i[args dockerfile tag] + + serialize :test_commands, Array + + before_validation :set_defaults + + def set_defaults + return unless Node.source.eql?(:asset) + + self.dockerfile ||= 'Dockerfile' + end + + # Concerns::Git methods require #git_path to be defined in order to behave as expected + # NOTE: Templates can use image.git.tag or any other value from methods in Concerns::Git + delegate :git_path, to: :repository + + class << self + def add_columns(t) + t.references :builder + t.string :builder_name + t.references :registry + t.string :registry_name + t.references :repository + t.string :repository_name + t.string :test_commands + # t.string :build + # t.string :path + end + end + end +end diff --git a/onestack/app/models/one_stack/manifest.rb b/onestack/app/models/one_stack/manifest.rb new file mode 100644 index 00000000..a91f3b09 --- /dev/null +++ b/onestack/app/models/one_stack/manifest.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module OneStack + class Manifest + include ActiveModel::AttributeAssignment + include ActiveModel::Validations + + # relative_from: Hendrix.config.root + # attr: Any attribute of Pathname, e.g. :atime, :size + # Example: Node.list(relative_path_from: Hendrix.config.root).max + # returns an Array containing one element of the most recently updated Node with its mtime and relative path + attr_accessor :source, :target, :relative_path_from, :attr + + validate :outdated, :no_target_files + + def initialize(**options) + assign_attributes(**options) + @attr ||= :mtime + end + + def reload + @source_files, @target_files, @newest_source, @oldest_target = nil + validate + self + end + + def newest_source() = @newest_source ||= list(source_files).max + + def source_files() = @source_files ||= source.call + + def oldest_target() = @oldest_target ||= list(target_files).min + + def target_files() = @target_files ||= target.call + + def rm_targets() = target_files.each { |path| path.delete } + + def list(file_set) + file_set.select(&:file?).reject(&:symlink?).map do |path| + path = path.relative_path_from(relative_path_from) if relative_path_from + [path.send(attr), path] + end + end + + private + + def outdated + return if source_files.empty? || target_files.empty? || oldest_target.first > newest_source.first + + errors.add(:stale_files, "#{newest_source} > #{oldest_target}") + end + + def no_target_files + errors.add(:no_files_found, '') if source_files.any? && target_files.empty? + end + end +end diff --git a/onestack/app/models/one_stack/navigator.rb b/onestack/app/models/one_stack/navigator.rb new file mode 100644 index 00000000..30d42029 --- /dev/null +++ b/onestack/app/models/one_stack/navigator.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module OneStack + # Manage the user's location within the segments directory hierarchy + # Used by console to change directory and keep track of the contexts + class Navigator + attr_accessor :options, :args, :path + + def initialize(**kwargs) = kwargs.each { |k, v| send("#{k}=", v) } + + def context + @context ||= Context.find_or_create_by(component: components .last) do |context| + context.options = options + context.components << components .slice(0, components .size - 1) + end + end + + def components () = @components ||= component_list + + # List hierarchy of components based on CLI options, cwd, ENV and default segment_name(s) + def component_list # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + return [] unless (current = SegmentRoot.first) + + cwd_segments = path_segments.dup + components = [current] + while current.components.any? + next_segment = current.next_segment(options, cwd_segments.shift) + break if next_segment.name.nil? # No search values found so stop at the current component + + unless next_segment.component + OneStack.logger.warn([current.segments_type&.capitalize, "'#{next_segment.name}' specified by", + "*#{next_segment.source}* not found.", + "Context set to #{current.owner&.segments_type} '#{current.name}'"].join(' ')) + break + end + + current = next_segment.component + components << current + end + components + end + + def path_segments() = path.relative_path_from(seg_path).to_s.split('/') + + def seg_path() = self.class.seg_path + + def structs() = components .each_with_object([]) { |comp, ary| ary.append(comp.struct) } + + def prompt # rubocop:disable Metrics/AbcSize + @prompt ||= structs.each_with_object([]) do |component, prompt| + segment_type = cli_config.show_segment_type ? component.segment_type : nil + segment_name = cli_config.show_segment_name ? component.name : nil + next if (prompt_value = [segment_type, segment_name].compact.join(':')).empty? + + prompt_value = colorize(component, prompt_value) if cli_config.colorize + prompt << prompt_value + end.join('/') + end + + def cli_config() = OneStack.config.cli + + def colorize(component, title) + color = component.color + color = color.call(component.name) if color&.class.eql?(Proc) + self.class.colors.delete(color) if color + color ||= self.class.colors.shift + Pry::Helpers::Text.send(color, title) + end + + def tree() = TTY::Tree.new(path => path.children).render + + def cd(path) + self.class.cd(path) + self.class.current != self + end + + class << self + attr_reader :current + + def cd(path) = new(path: path || seg_path, options: current.options, args: current.args) + + def new(**kwargs) # rubocop:disable Metrics/AbcSize + kwargs[:path] ||= APP_CWD + kwargs[:options] ||= {} + kwargs[:args] ||= {} + # %i[path options args].each { |attr| raise ArgumentError, "#{attr} required" unless kwargs.key?(attr) } + + path = Pathname.new(kwargs[:path]) + path = current_path.join(path) if path.relative? + path = current_path unless path.exist? + path = current_path if path.relative_path_from(seg_path).to_s.split('/').include?('..') + kwargs[:path] = path + + @current = navigators[path.to_s] ||= super + end + + def current_path() = current&.path || seg_path + + def seg_path() = OneStack.config.paths.segments + + def navigators() = @navigators ||= {} + + # TODO: Sniff the monitor and use black if monitor b/g is white and vice versa # white black] + def colors() = @colors ||= OneStack.config.cli.colors&.dup || %i[blue green purple magenta cyan yellow red] + + def reload!() = @current = @navigators = @colors = nil + end + end +end diff --git a/onestack/app/models/one_stack/plan.rb b/onestack/app/models/one_stack/plan.rb new file mode 100644 index 00000000..1670e11c --- /dev/null +++ b/onestack/app/models/one_stack/plan.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# A Plan defines one or more resources for a Provisioner to create on the target Provider +module OneStack + class Plan < ApplicationRecord + # Define operator before inclding Concerns::Target + def self.operator() = Provisioner + + include OneStack::Concerns::Target + + belongs_to :provider + belongs_to :provisioner + + # NOTE: This is used by Provisioner to CRUD resources after a command, e.g. deploy, destroy, is run + has_many :resources + + class << self + def add_columns(t) + t.references :provider + t.string :provider_name + t.references :provisioner + t.string :provisioner_name + end + end + end +end diff --git a/onestack/app/models/one_stack/playbook.rb b/onestack/app/models/one_stack/playbook.rb new file mode 100644 index 00000000..91417bf3 --- /dev/null +++ b/onestack/app/models/one_stack/playbook.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module OneStack + class Playbook < ApplicationRecord + include OneStack::Concerns::Target + + class << self + def add_columns(t) + t.string :configurator_name + t.references :configurator + end + end + end +end diff --git a/onestack/app/models/one_stack/provider.rb b/onestack/app/models/one_stack/provider.rb new file mode 100644 index 00000000..3510b4f0 --- /dev/null +++ b/onestack/app/models/one_stack/provider.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + class Provider < ApplicationRecord + include Concerns::Generic + end +end diff --git a/onestack/app/models/one_stack/provisioner.rb b/onestack/app/models/one_stack/provisioner.rb new file mode 100644 index 00000000..192a6c83 --- /dev/null +++ b/onestack/app/models/one_stack/provisioner.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module OneStack + class Provisioner < ApplicationRecord + # Define target and commands before including Operator Concern + class << self + def target() = :plans + + def commands() = %i[deploy undeploy] + end + + include OneStack::Concerns::Operator + + # Assigned in Operator#execute + attr_accessor :plans + + before_execute :generate + end +end diff --git a/onestack/app/models/one_stack/provisioner_resource.rb b/onestack/app/models/one_stack/provisioner_resource.rb new file mode 100644 index 00000000..1d34b263 --- /dev/null +++ b/onestack/app/models/one_stack/provisioner_resource.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module OneStack + class ProvisionerResource < ApplicationRecord + belongs_to :provisioner + + # accessors match the columns returned by docker ps + # TODO: move this to a terraform concern + # TODO: do the same for compose/docker and skaffold/docker + store :terraform, coder: YAML, accessors: %i[rid image names command labels status ports] + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :provisioner + # t.string :terraform + # t.string :pulumi + end + end + end + end +end diff --git a/onestack/app/models/one_stack/registry.rb b/onestack/app/models/one_stack/registry.rb new file mode 100644 index 00000000..7bc024e8 --- /dev/null +++ b/onestack/app/models/one_stack/registry.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + class Registry < ApplicationRecord + include Concerns::Generic + end +end diff --git a/onestack/app/models/one_stack/repository.rb b/onestack/app/models/one_stack/repository.rb new file mode 100644 index 00000000..b830a0ec --- /dev/null +++ b/onestack/app/models/one_stack/repository.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +module OneStack + class Repository < ApplicationRecord + # include Concerns::Operator + include OneStack::Concerns::Generic + include SolidSupport::Git if defined?(SolidSupport) + + store :config, accessors: %i[url path] + + validates :url, presence: true + + after_destroy :remove_git_path + + def remove_git_path + git_path.rmtree if git_path.exist? + end + + # COMPONENT_PATH_KEY = 'components_path' + # COMPONENT_FILE = 'component.yml' + # REPOSITORY_FILE = 'repository.yml' + + # after_create :register_components + + # def register_components + # return unless repo.path.exist? && components_path_exist? + # + # components_path.children.select(&:directory?).each do |component_path| + # next unless component_path.join(COMPONENT_FILE).exist? + # + # component_name = component_path.relative_path_from(src_path).to_s + # OneStack.logger.info("Found component #{component_name}") + # end + # end + + # def repo_path_exist? + # return true if path.exist? + # + # log_f(:warn, "Repository #{name} path not found #{path}") + # nil + # end + + # def components_path_exist? + # return true if components_path.exist? + # + # log_f(:warn, "Invalid configuration for repository #{name}", "Path not found: #{components_path}") + # nil + # end + + # # TODO: See about formatting messages using Logger config + # def log_f(level, *messages) + # message = messages.shift + # m_messages = messages.map { |message| "\n#{' ' * 10}#{message}" } + # OneStack.logger.send(level, message, *m_messages) + # end + + # def components_path() = path.join(components_path_name) + + # def components_path_name() = repo_config.fetch(COMPONENT_PATH_KEY, '.') + + # Return the contents of the repository's config file or an empty hash + # def repo_config + # repo_config_file.exist? ? (YAML.load_file(repo_config_file) || {}) : {} + # end + + # /repository.yml if present must be in this specific location + # def repo_config_file() = path.join(REPOSITORY_FILE) + + def git_url() = url + + def git_path() = @git_path ||= src_path.join(path || name) + + def src_path() = OneStack.config.paths.src + + def tree_name() = name + + class << self + def init(context) = context.repositories.each { |repo| repo.git_clone } + + # def init(context) + # context.repositories.each do |repo| + # next if repo.git_path.exist? + # + # msg = "Cloning repository #{repo.url} to #{repo.src_path}" + # OneStack.logger.info(msg) + # + # OneStack.with_timer(msg) do + # repo.src_path.mkpath unless repo.src_path.exist? + # Dir.chdir(repo.src_path) { repo.git_clone(repo.url).run } + # end + # end + # end + + # def add(param1, param2 = nil) + # url = name = nil + # if param1.match(git_url_regex) + # url = param1 + # name = param2 + # elsif (url = url_map[param1] || url_from_name(param1)) + # # binding.pry + # name = param1.split('/').last + # end + # name ||= url.split('/').last&.delete_suffix('.git') if url + # new(name: name, url: url) + # end + + # TODO: Move to cnfs-cli.yml + # Shortcuts for CNFS repos + # This is copied in to the user's local directory so its available to all projects on the file system + # def url_map + # { + # cnfs: 'git@github.com:rails-on-services/ros.git', + # generic: 'git@github.com:rails-on-services/generic.git' + # }.with_indifferent_access + # end + + # def url_from_name(name) + # path = name.eql?('.') ? OneStack.context.cwd.relative_path_from(OneStack.project.root) : Pathname.new(name) + # Dir.chdir(path) { remote.fetch_url } if path.directory? + # end + + # Returns the default repository unless the the given path is another project repository + # in which case return the Repository object that represents the given path + # The default path is the directory where the command was invoked + # def from_path(path = OneStack.context.cwd.to_s) + # src_path = OneStack.project_root.join(OneStack.paths.src).to_s + # return OneStack.project.repository if path.eql?(src_path) || !path.start_with?(src_path) + # + # repo_name = path.delete_prefix(src_path).split('/')[1] + # OneStack.logger.debug("Identified repo name #{repo_name}") + # find_by(name: repo_name) + # end + # + # def add_columns(t) + # t.string :dockerfile + # t.string :build + # end + end + end +end diff --git a/onestack/app/models/one_stack/resource.rb b/onestack/app/models/one_stack/resource.rb new file mode 100644 index 00000000..a9a9b9d9 --- /dev/null +++ b/onestack/app/models/one_stack/resource.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module OneStack + class Resource < ApplicationRecord + include OneStack::Concerns::Generic + + belongs_to :plan, optional: true + belongs_to :provider, optional: true + belongs_to :runtime, optional: true + + has_many :services + + # store :config, accessors: %i[source version], coder: YAML + + # store :envs, coder: YAML + + class << self + def add_columns(t) + t.references :plan + t.string :plan_name + t.references :provider + t.string :provider_name + t.references :runtime + t.string :runtime_name + # t.string :envs + end + end + end +end diff --git a/onestack/app/models/one_stack/runtime.rb b/onestack/app/models/one_stack/runtime.rb new file mode 100644 index 00000000..ffae47d6 --- /dev/null +++ b/onestack/app/models/one_stack/runtime.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module OneStack + class Runtime < ApplicationRecord + # Define target and commands before including Operator Concern + class << self + def target() = :services + + def commands() = %i[attach start stop restart terminate] + end + + include OneStack::Concerns::Operator + + # Assigned in Operator#execute + attr_accessor :resource, :services + + store :config, accessors: %i[version] + + before_execute :generate + + class << self + def add_columns(t) + t.references :resource + end + end + end +end diff --git a/onestack/app/models/one_stack/runtime_service.rb b/onestack/app/models/one_stack/runtime_service.rb new file mode 100644 index 00000000..9a33f501 --- /dev/null +++ b/onestack/app/models/one_stack/runtime_service.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module OneStack + class RuntimeService < ApplicationRecord + belongs_to :runtime + + # accessors match the columns returned by docker ps + store :docker, coder: YAML, accessors: %i[rid image names command labels status ports] + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :runtime + t.string :docker + t.string :kubernetes + end + end + end + end +end diff --git a/onestack/app/models/one_stack/segment_root.rb b/onestack/app/models/one_stack/segment_root.rb new file mode 100644 index 00000000..b2e93287 --- /dev/null +++ b/onestack/app/models/one_stack/segment_root.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module OneStack + class SegmentRoot < Component + def owner_extension_path() = OneStack.config.paths.segments + + # Override superclass methods as this is the root class in the hierarchy + def key() = @key ||= super || warn_key + + def key_name() = name + + def key_name_env() = OneStack.config.env.key_prefix + + def warn_key + OneStack.logger.error("No encryption key found. Run 'onestack generate key'") + nil + end + + # Override Component to provide the 'root' paths and attrs + def cache_path() = @cache_path ||= OneStack.config.cache_home.join(name) + + def data_path() = @data_path ||= OneStack.config.data_home.join(name) + + def attrs() = @attrs ||= [name] + + def struct() = OpenStruct.new(segment_type: 'root', name: name) + + def name() = OneStack.application.name + + def tree_label() = name + + class << self + def unknown_document_type() = OneStack::Component # For SolidRecord to determine the owner class type + end + end +end diff --git a/onestack/app/models/one_stack/service.rb b/onestack/app/models/one_stack/service.rb new file mode 100644 index 00000000..0736908e --- /dev/null +++ b/onestack/app/models/one_stack/service.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module OneStack + class Service < ApplicationRecord + # Define operator before inclding Concerns::Target + def self.operator() = Runtime + + include OneStack::Concerns::Target + + belongs_to :resource, optional: true + + store :commands, accessors: %i[console shell test], coder: YAML + store :commands, accessors: %i[after_service_starts before_service_stops before_service_terminates], coder: YAML + store :commands, accessors: %i[attach], coder: YAML + # store :config, accessors: %i[path image depends_on ports mount], coder: YAML + store :config, accessors: %i[path depends_on ports mount] # , coder: YAML + store :image, accessors: %i[build_args dockerfile repository_name tag], coder: YAML + store :profiles, coder: YAML + + serialize :volumes, Array + + store :envs, coder: YAML + + # validate :image_values + + def volumes() = super.map(&:with_indifferent_access) + + # rubocop:disable Metrics/AbcSize + def image_values + %i[source_path target_path].each do |path| + next unless build_args.try(:[], path) + + source_path = Hendrix.paths.src.join(build_args[path]) + errors.add(:source_path_does_not_exist, "'#{source_path}'") unless source_path.exist? + end + return unless dockerfile + + source_path = Hendrix.paths.src.join(dockerfile) + errors.add(:dockerfile_path_does_not_exist, "'#{source_path}'") unless source_path.exist? + end + # rubocop:enable Metrics/AbcSize + + # depends_on is used by compose to set order of container starts + after_initialize do + # self.command_queue ||= [] + self.depends_on ||= [] + self.profiles ||= {} + end + + # after_update :add_commands_to_queue, if: proc { skip_node_create } + + # after_start { add_commands_to_queue(after_service_starts) } + # before_stop { add_commands_to_queue(before_service_stops) } + # before_terminate { add_commands_to_queue(before_service_terminates) } + + # (commands_array) + def add_commands_to_queue + self.command_queue = case state + when attribute_before_last_save(:state) + [] + when 'started' + after_service_starts + end + Hendrix.logger.debug "#{name} command_queue add: #{command_queue}" # ".split("\n")}" + end + + private + + # State handling + def update_runtime(values) + runtime_config = YAML.load_file(runtime_path) || {} + Hendrix.logger.info "Current runtime state for service #{name} is #{runtime_config}" + runtime_config.merge!(name => values).deep_stringify_keys! + Hendrix.logger.info "Updated runtime state for service #{name} is #{runtime_config}" + File.open(runtime_path, 'w') { |file| file.write(runtime_config.to_yaml) } + end + + # File handling + def runtime_path + @runtime_path ||= begin + file_path = write_path(:runtime) + file_path.mkpath unless file_path.exist? + file_path = file_path.join('services.yml') + FileUtils.touch(file_path) unless file_path.exist? + file_path + end + end + + class << self + def by_profiles(profiles = project.profiles) + where('profiles LIKE ?', profiles.map { |k, v| "%#{k}: #{v}%" }.join) + end + + # rubocop:disable Metrics/MethodLength + def add_columns(t) + t.references :resource + t.string :resource_name + t.references :repository + t.string :repository_name + t.string :commands + t.string :image + t.string :path + t.string :profiles + t.string :template + t.string :volumes + t.string :state + t.string :envs + end + # rubocop:enable Metrics/MethodLength + end + end + end diff --git a/onestack/app/models/one_stack/user.rb b/onestack/app/models/one_stack/user.rb new file mode 100644 index 00000000..1509e1f3 --- /dev/null +++ b/onestack/app/models/one_stack/user.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module OneStack + class User < ApplicationRecord + include Concerns::Generic + + attr_encrypted :test + + class << self + def add_columns(t) + t.string :role + t.string :test + t.string :full_name + end + end + end +end diff --git a/onestack/app/views/one_stack/application_view.rb b/onestack/app/views/one_stack/application_view.rb new file mode 100644 index 00000000..afbc6b3d --- /dev/null +++ b/onestack/app/views/one_stack/application_view.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module OneStack + class ApplicationView < SolidSupport::ModelView + include Concerns::ParentView + end +end diff --git a/core/app/views/concerns/asset_view.rb b/onestack/app/views/one_stack/concerns/asset_view.rb similarity index 75% rename from core/app/views/concerns/asset_view.rb rename to onestack/app/views/one_stack/concerns/asset_view.rb index 6a063149..19e7391b 100644 --- a/core/app/views/concerns/asset_view.rb +++ b/onestack/app/views/one_stack/concerns/asset_view.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -module Concerns +module OneStack::Concerns module AssetView extend ActiveSupport::Concern included do - include Concerns::ParentView + include ParentView # include Concerns::Extendable end end diff --git a/onestack/app/views/one_stack/concerns/parent_view.rb b/onestack/app/views/one_stack/concerns/parent_view.rb new file mode 100644 index 00000000..c285b2d9 --- /dev/null +++ b/onestack/app/views/one_stack/concerns/parent_view.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Common Functionallity for Component and Asset +module OneStack + module Concerns::ParentView + extend ActiveSupport::Concern + + included do + attr_accessor :context, :component + end + + # @context is the current context + def initialize(**options) + @context = options.delete(:context) + @component = @context&.component + super + end + + # Override base class + def view_class_options() = super.merge(context: context) + + # Override base class + def options() = context&.options || {} + + # Cnfs::Core.asset_names.each do |asset_name| + # define_method("#{asset_name.singularize}_names".to_sym) do + # component.send("#{asset_name.singularize}_names".to_sym) + # end + # end + end +end diff --git a/onestack/app/views/one_stack/plan_view.rb b/onestack/app/views/one_stack/plan_view.rb new file mode 100644 index 00000000..db273ddc --- /dev/null +++ b/onestack/app/views/one_stack/plan_view.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module OneStack + class PlanView < ApplicationView + def modify + binding.pry + # %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? + # select_type + # enum_select_attr(:provider_name, choices: provider_names) + # enum_select_attr(:provisioner_name, choices: provisioner_names) + # yes_attr(:abstract, default: false) + # yes_attr(:inherit, default: true) + # yes_attr(:enable, default: true) + + # return if choices.size.zero? + # return choices.first if choices.size.eql?(1) + end + end +end diff --git a/onestack/app/views/one_stack/project_view.rb b/onestack/app/views/one_stack/project_view.rb new file mode 100644 index 00000000..11e66c9b --- /dev/null +++ b/onestack/app/views/one_stack/project_view.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module OneStack + class ProjectView < ApplicationView + # rubocop:disable Metrics/AbcSize + def create + raise Cnfs::Error, 'Create can only be called on new instances' if model.persisted? + + model.name = ask('name', value: '') if model.name.nil? + load_components(model, [], 'project') if yes?('My project has components?') + model.x_components = all_selected.uniq.each_with_object([]) { |key, ary| ary.append({ name: key }) } + model.root_tree + return unless yes?("\nSave changes?") + end + + def all_selected + @all_selected ||= [] + end + + # rubocop:disable Metrics/MethodLength + def load_components(obj, selected, title) + available = components(selected) + c_name = available.size.eql?(1) ? available.pop : select('Component type', available, filter: true) + selected << c_name + all_selected << c_name + multi_select('name', send(c_name.to_sym)).each do |name| + new_obj = Component.new(name: name, c_name: c_name) + obj.components << new_obj + if available.size.positive? && yes?("Does the #{title}'s #{name} #{c_name} have components?") + load_components(new_obj, selected.dup, "#{name} #{c_name}") + end + end + selected + end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + + def edit + # model.name = view_select('name', %w[data this that], 'this') + # ask_hash(:paths) + model.x_components = model.x_components.each_with_object([]) do |comp, ary| + ab = comp.each_with_object({}) do |(key, value), hash| + hash[key] = ask(key, value: value) + end + ary.append(ab) + end + # binding.pry + end + + private + + def components(selected) + %w[environment namespace stack target] - selected + # %w[environment namespace stack target].each_with_object([]) do |key, ary| + # hash = { name: key } + # hash.merge!(disabled: '(already selected)') if selected.include?(key) + # ary.append(hash) + # end + end + + def environment + %w[development test staging production] + end + + def namespace + %w[default other] + end + + def stack + %w[backend frontend pipeline warehouse] + end + + def target + %w[cluster instance local] + end + end +end diff --git a/onestack/app/views/one_stack/provider_view.rb b/onestack/app/views/one_stack/provider_view.rb new file mode 100644 index 00000000..26180359 --- /dev/null +++ b/onestack/app/views/one_stack/provider_view.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module OneStack + class ProviderView < ApplicationView + include Concerns::AssetView + + def modify + %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? + # select_type + end + end +end diff --git a/onestack/app/views/one_stack/provisioner_view.rb b/onestack/app/views/one_stack/provisioner_view.rb new file mode 100644 index 00000000..ae0f30e9 --- /dev/null +++ b/onestack/app/views/one_stack/provisioner_view.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module OneStack + class ProvisionerView < ApplicationView + def modify + %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? + select_type + end + end +end diff --git a/onestack/app/views/one_stack/repository_view.rb b/onestack/app/views/one_stack/repository_view.rb new file mode 100644 index 00000000..691db5ec --- /dev/null +++ b/onestack/app/views/one_stack/repository_view.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + class RepositoryView < ApplicationView + def modify + %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? + select_type + ask_attr(:url) + end + end +end diff --git a/onestack/app/views/one_stack/resource_view.rb b/onestack/app/views/one_stack/resource_view.rb new file mode 100644 index 00000000..6d79dff7 --- /dev/null +++ b/onestack/app/views/one_stack/resource_view.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module OneStack + class ResourceView < ApplicationView + def modify + %i[name].each { |attr| ask_attr(attr) } if action.eql?(:edit) || model.name.nil? + end + end +end diff --git a/onestack/app/views/one_stack/user_view.rb b/onestack/app/views/one_stack/user_view.rb new file mode 100644 index 00000000..8e3acf63 --- /dev/null +++ b/onestack/app/views/one_stack/user_view.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module OneStack + class UserView < ApplicationView + def modify + %i[name full_name role].each { |attr| ask_attr(attr) } + ask_hash(:tags) + end + end +end diff --git a/onestack/bin/console b/onestack/bin/console new file mode 100755 index 00000000..2a60705c --- /dev/null +++ b/onestack/bin/console @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' +Bundler.require + +ARGV.unshift 'console' +ENV['ONE_STACK_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_support/boot_loader' } diff --git a/cnfs/bin/setup b/onestack/bin/setup similarity index 100% rename from cnfs/bin/setup rename to onestack/bin/setup diff --git a/onestack/exe/onestack b/onestack/exe/onestack new file mode 100755 index 00000000..7f8dde2f --- /dev/null +++ b/onestack/exe/onestack @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +git_path = File.expand_path('../../.git', __dir__) + +if File.exist?(git_path) + plugin_path = File.expand_path('../../solid_support/lib', __dir__) + $:.unshift(plugin_path) + plugin_path = File.expand_path('../../solid_record/lib', __dir__) + $:.unshift(plugin_path) + plugin_path = File.expand_path('../lib', __dir__) + $:.unshift(plugin_path) +end + +require 'onestack' + +# This isn't necessary for main_loader, but project_loader needs it +load_path = Pathname.new(__dir__).join('../app') +SolidSupport.add_loader(name: :framework, path: load_path) + +BOOT_MODULE = OneStack + +require 'solid_support/boot_loader' diff --git a/onestack/lib/one_stack.rb b/onestack/lib/one_stack.rb new file mode 100644 index 00000000..b965cff2 --- /dev/null +++ b/onestack/lib/one_stack.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# require 'hendrix' +# module Hendrix +# class Extension +# ABSTRACT_EXTENSIONS = %w[OneStack::Extension OneStack::Plugin OneStack::Application Hendrix::Extension Hendrix::Plugin Hendrix::Application].freeze +# end +# end +require 'tty-which' + +require 'solid_support' +require 'solid_record' + +require 'one_stack/version' +require 'one_stack/application' +require 'one_stack/plugin' +require 'one_stack/extension' + +module OneStack + def self.logger() = SolidSupport.logger + # OneStack.logger.formatter = SolidRecord::SimpleFormatter +end diff --git a/onestack/lib/one_stack/application.rb b/onestack/lib/one_stack/application.rb new file mode 100644 index 00000000..1295eb04 --- /dev/null +++ b/onestack/lib/one_stack/application.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module OneStack + class << self + def application() = SolidSupport.application + + def config() = SolidSupport.config + end + + class Application < SolidSupport::Application + config.before_initialize do |config| + + config.operator_names = %w[builders configurators provisioners runtimes] + + config.target_names = %w[images plans playbooks services] + + config.generic_names = %w[dependencies providers resources registries repositories users] + + config.component_names = %w[component segment_root] + + config.support_names = %w[context definitions context_component runtime_service provisioner_resource] + + config.asset_names = (config.operator_names + config.target_names + config.generic_names).freeze + + # The model class list for which tables will be created in the database + config.model_names = (config.asset_names + config.component_names + config.support_names).map(&:singularize).freeze + end + + config.after_initialize do |config| + config.env.key_prefix ||= 'OS_KEY' + config.env.key_prefix = config.env.key_prefix.upcase + config.paths.segments ||= 'segments' + config.paths.src ||= 'src' + config.paths.transform_values! { |path| path.is_a?(Pathname) ? path : root.join(path) } + + # Set values for component selection based on any user defined ENVs + config.segments.each do |key, values| + env_key = "#{config.env_base}#{values[:env] || key}".upcase + values[:env_value] = ENV[env_key] + end + + # Set Command Options + config.command_options = config.segments.dup.transform_values! do |opt| + { desc: opt[:desc], aliases: opt[:aliases], type: :string } + end + end + + class Configuration < SolidSupport::Application::Configuration + def xb() = 'tuff' + end + end +end diff --git a/onestack/lib/one_stack/extension.rb b/onestack/lib/one_stack/extension.rb new file mode 100644 index 00000000..3541798a --- /dev/null +++ b/onestack/lib/one_stack/extension.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + class << self + def extensions() = SolidSupport.extensions + end + + class Extension < SolidSupport::Extension + def self.gem_root() = Plugin.gem_root + end +end diff --git a/onestack/lib/one_stack/plugin.rb b/onestack/lib/one_stack/plugin.rb new file mode 100644 index 00000000..887c1681 --- /dev/null +++ b/onestack/lib/one_stack/plugin.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module OneStack + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') + + def plugins() = SolidSupport.plugins + + # TODO: Finish refactor + def segment(name) + path = segments_path.join(name) + return path if path.exist? + end + + def segments() = @segments ||= segments_path.glob('**/*').select(&:directory?) + + def segments_path() = gem_root.join('segments') + end + + class Plugin < SolidSupport::Plugin + config.after_initialize do |config| + SolidRecord.setup + SolidRecord.toggle_callbacks do + # srp = Pathname.new(__dir__).join('../../../solid_record/spec/dummy/one_stack') + root = SolidRecord::File.create(source: 'config/segment.yml', content_format: :singular, + model_class_name: 'OneStack::SegmentRoot') + # root = SolidRecord::File.create(source: srp.join('config/segment.yml').to_s, content_format: :singular, + owner = root.segments.first.segments.first.model + SolidRecord::DirGeneric.create(source: 'segments', model_class_name: 'component', owner: owner, + namespace: 'one_stack') + # SolidRecord::DirGeneric.create(source: srp.join('segments').to_s, model_class_name: 'component', owner: owner, + end + SolidRecord.tables.select { |t| t.respond_to?(:after_load) }.each(&:after_load) + end + + class << self + def gem_root() = OneStack.gem_root + end + end +end diff --git a/onestack/lib/one_stack/spec_helper.rb b/onestack/lib/one_stack/spec_helper.rb new file mode 100644 index 00000000..5b0c2c1f --- /dev/null +++ b/onestack/lib/one_stack/spec_helper.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'pry' +# require 'pry-byebug' + +# Setting the environment to test causes +# - main_loader to skip MainCommand.start +# - SegmentRoot.load to be skipped +ENV['HENDRIX_CLI_ENV'] ||= 'test' +Dir.chdir(SPEC_PATH.join('dummy')) { require 'hendrix/boot_loader' } + +RSpec.configure do |config| + config.after(:suite) { OneStack::SpecHelper.teardown_project } + SolidRecord.logger.level = :warn +end + +module OneStack + class SpecHelper + class << self + # Ensure the temporary paths are removed + def teardown_project() = tmp_path.rmtree + + def setup_segment(spec) + stub_local_paths(spec) + create_keys_file unless keys_file.exist? + end + + def stub_local_paths(current_spec) + { cache_home: '.cache', config_home: '.config', data_home: data_path }.each do |method, path| + current_spec.allow_any_instance_of(XDG::Environment).to( + current_spec.receive(method).and_return(tmp_path.join(path)) + ) + end + end + + # Create the key file in XDG.data_home with the preset key value + def create_keys_file + keys_file.parent.mkpath + File.write(keys_file, key_content.to_yaml) + end + + def keys_file() = @keys_file ||= tmp_path.join(data_path, OneStack.config.xdg_name, 'keys.yml') + + def key_content() = { OneStack.application.name => Lockbox.generate_key } #KEY_ID } + + def data_path() = '.local/share' + + def tmp_path() = @tmp_path ||= Pathname.new(Dir.mktmpdir) + end + end +end diff --git a/cnfs/lib/cnfs/version.rb b/onestack/lib/one_stack/version.rb similarity index 77% rename from cnfs/lib/cnfs/version.rb rename to onestack/lib/one_stack/version.rb index b21f9295..fd8077bd 100644 --- a/cnfs/lib/cnfs/version.rb +++ b/onestack/lib/one_stack/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -module Cnfs +module OneStack VERSION = '0.1.0' end diff --git a/onestack/lib/onestack.rb b/onestack/lib/onestack.rb new file mode 100644 index 00000000..6c4bd401 --- /dev/null +++ b/onestack/lib/onestack.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'one_stack' diff --git a/onestack/onestack.gemspec b/onestack/onestack.gemspec new file mode 100644 index 00000000..16d53e57 --- /dev/null +++ b/onestack/onestack.gemspec @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require_relative 'lib/one_stack/version' + +Gem::Specification.new do |spec| + spec.name = 'onestack' + spec.version = OneStack::VERSION + spec.authors = ['Robert Roach'] + spec.email = ['rjayroach@gmail.com'] + + spec.summary = 'OneStack Infrasturcture and Applicaiton Framework' + spec.description = 'OneStack is a pluggable framework to configure and manage projects' + spec.homepage = 'https://cnfs.io' + spec.license = 'MIT' + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') + + # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/rails-on-services/cnfs-cli' + # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_dependency 'solid_record', '~> 0.1.0' + spec.add_dependency 'solid_support', '~> 0.1.0' + spec.add_dependency 'tty-which', '~> 0.5.0' + + spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'guard-rspec', '~> 4.7.3' + spec.add_development_dependency 'guard-rubocop' + spec.add_development_dependency 'pry-byebug', '~> 3.9' + # spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rspec', '~> 3.10' + spec.add_development_dependency 'rubocop', '~> 1.41' + spec.add_development_dependency 'rubocop-performance' + spec.add_development_dependency 'rubocop-rspec', '~> 2.18' +end diff --git a/onestack/set_path b/onestack/set_path new file mode 100755 index 00000000..fb040f78 --- /dev/null +++ b/onestack/set_path @@ -0,0 +1,8 @@ +# Use to access cnfs cli in development mode + +RUBYOPT='-W:no-deprecated' + +SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +echo $SCRIPT_PATH +export PATH=$PATH:$SCRIPT_PATH/exe +unset SCRIPT_PATH diff --git a/onestack/spec/dummy/Gemfile b/onestack/spec/dummy/Gemfile new file mode 100644 index 00000000..6cfea72b --- /dev/null +++ b/onestack/spec/dummy/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'pry' +gem 'pry-byebug' +gem 'onestack', path: '../..' +gem 'solid_support', path: '../../../solid_support' +gem 'solid_record', path: '../../../solid_record' diff --git a/onestack/spec/dummy/config/application.rb b/onestack/spec/dummy/config/application.rb new file mode 100644 index 00000000..057d5fc2 --- /dev/null +++ b/onestack/spec/dummy/config/application.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # initializer 'based' do |app| + # binding.pry + # end + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + config.solid_record.namespace = :one_stack + # config.solid_record.load_paths = [ + # { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + # { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + # ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/onestack/spec/dummy/config/boot.rb b/onestack/spec/dummy/config/boot.rb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/onestack/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/onestack/spec/dummy/config/environment.rb b/onestack/spec/dummy/config/environment.rb new file mode 100644 index 00000000..2ff3c59d --- /dev/null +++ b/onestack/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +OneStack.application.initialize! diff --git a/core/spec/fixtures/segments/node/stacks.yml b/onestack/spec/dummy/config/segment.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks.yml rename to onestack/spec/dummy/config/segment.yml diff --git a/core/spec/fixtures/segments/context/backend.yml b/onestack/spec/dummy/segments.orig/context/backend.yml similarity index 77% rename from core/spec/fixtures/segments/context/backend.yml rename to onestack/spec/dummy/segments.orig/context/backend.yml index 7f4c58a9..4d70a51a 100644 --- a/core/spec/fixtures/segments/context/backend.yml +++ b/onestack/spec/dummy/segments.orig/context/backend.yml @@ -2,6 +2,6 @@ config: tld: cnfs.io domain: backend.${config.tld} -segment_type: environment +segments_type: environment default: segment_name: production diff --git a/core/spec/fixtures/segments/context/backend/production.yml b/onestack/spec/dummy/segments.orig/context/backend/production.yml similarity index 76% rename from core/spec/fixtures/segments/context/backend/production.yml rename to onestack/spec/dummy/segments.orig/context/backend/production.yml index c45132c8..f23b7b77 100644 --- a/core/spec/fixtures/segments/context/backend/production.yml +++ b/onestack/spec/dummy/segments.orig/context/backend/production.yml @@ -1,6 +1,6 @@ --- config: domain: production-${tld} -segment_type: target +segments_type: target default: segment_name: lambda diff --git a/core/spec/fixtures/segments/context/backend/production/cap2.yml b/onestack/spec/dummy/segments.orig/context/backend/production/cap2.yml similarity index 100% rename from core/spec/fixtures/segments/context/backend/production/cap2.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/cap2.yml diff --git a/core/spec/fixtures/segments/context/backend/production/lambda.yml b/onestack/spec/dummy/segments.orig/context/backend/production/lambda.yml similarity index 100% rename from core/spec/fixtures/segments/context/backend/production/lambda.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/lambda.yml diff --git a/core/spec/fixtures/segments/context/backend/production/lambda/providers.yml b/onestack/spec/dummy/segments.orig/context/backend/production/lambda/providers.yml similarity index 100% rename from core/spec/fixtures/segments/context/backend/production/lambda/providers.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/lambda/providers.yml diff --git a/core/spec/fixtures/segments/context/backend/production/lambda/resources.yml b/onestack/spec/dummy/segments.orig/context/backend/production/lambda/resources.yml similarity index 70% rename from core/spec/fixtures/segments/context/backend/production/lambda/resources.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/lambda/resources.yml index 6d0ec54a..ed67079f 100644 --- a/core/spec/fixtures/segments/context/backend/production/lambda/resources.yml +++ b/onestack/spec/dummy/segments.orig/context/backend/production/lambda/resources.yml @@ -5,12 +5,12 @@ res1: size: awesome provider_name: localstack runtime_name: compose - envs: {} + # envs: {} tags: - type: Aws::Resource::EC2::Instance + # type: Aws::Resource::EC2::Instance res2: runtime_name: composer - type: Resource + type: OneStack::Resource config: family: ah23 size: cool! diff --git a/core/spec/fixtures/segments/context/backend/production/lambda/services.yml b/onestack/spec/dummy/segments.orig/context/backend/production/lambda/services.yml similarity index 100% rename from core/spec/fixtures/segments/context/backend/production/lambda/services.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/lambda/services.yml diff --git a/core/spec/fixtures/segments/context/backend/production/lambda/users.yml b/onestack/spec/dummy/segments.orig/context/backend/production/lambda/users.yml similarity index 100% rename from core/spec/fixtures/segments/context/backend/production/lambda/users.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/lambda/users.yml diff --git a/core/spec/fixtures/segments/context/backend/production/providers.yml b/onestack/spec/dummy/segments.orig/context/backend/production/providers.yml similarity index 100% rename from core/spec/fixtures/segments/context/backend/production/providers.yml rename to onestack/spec/dummy/segments.orig/context/backend/production/providers.yml diff --git a/core/spec/fixtures/segments/context/dependencies.yml b/onestack/spec/dummy/segments.orig/context/dependencies.yml similarity index 100% rename from core/spec/fixtures/segments/context/dependencies.yml rename to onestack/spec/dummy/segments.orig/context/dependencies.yml diff --git a/core/spec/fixtures/segments/context/frontend.yml b/onestack/spec/dummy/segments.orig/context/frontend.yml similarity index 75% rename from core/spec/fixtures/segments/context/frontend.yml rename to onestack/spec/dummy/segments.orig/context/frontend.yml index 6fa0aa68..619c4c95 100644 --- a/core/spec/fixtures/segments/context/frontend.yml +++ b/onestack/spec/dummy/segments.orig/context/frontend.yml @@ -1,6 +1,6 @@ --- config: {} -segment_type: target +segments_type: target default: runtime_name: compose segment_name: s3 diff --git a/core/spec/fixtures/segments/context/frontend/s3.yml b/onestack/spec/dummy/segments.orig/context/frontend/s3.yml similarity index 100% rename from core/spec/fixtures/segments/context/frontend/s3.yml rename to onestack/spec/dummy/segments.orig/context/frontend/s3.yml diff --git a/core/spec/fixtures/segments/context/providers.yml b/onestack/spec/dummy/segments.orig/context/providers.yml similarity index 87% rename from core/spec/fixtures/segments/context/providers.yml rename to onestack/spec/dummy/segments.orig/context/providers.yml index e5d1043c..d089e1eb 100644 --- a/core/spec/fixtures/segments/context/providers.yml +++ b/onestack/spec/dummy/segments.orig/context/providers.yml @@ -2,18 +2,18 @@ # # customize values in ~/.config/cnfs/dink/config/providers.yml --- aws_east: - type: Aws::Provider + # type: Aws::Provider inherit: true config: region: us-east-1 aws_west: - type: Aws::Provider + # type: Aws::Provider config: region: us-west-1 localstack: - type: Aws::Provider + # type: Aws::Provider inherit: true config: access_key_id: not_required_for_localstack diff --git a/core/spec/fixtures/segments/context/provisioners.yml b/onestack/spec/dummy/segments.orig/context/provisioners.yml similarity index 91% rename from core/spec/fixtures/segments/context/provisioners.yml rename to onestack/spec/dummy/segments.orig/context/provisioners.yml index 23a40ea5..ff8fa5b3 100644 --- a/core/spec/fixtures/segments/context/provisioners.yml +++ b/onestack/spec/dummy/segments.orig/context/provisioners.yml @@ -1,14 +1,14 @@ # builders.yml --- terraform: - type: Terraform::Provisioner + # type: Terraform::Provisioner dependencies: - name: terraform-provider-restapi_v1.10.0 url: https://github.com/Mastercard/terraform-provider-restapi/releases/download/v1.10.0/terraform-provider-restapi_v1.10.0-${platform}-amd64 - name: terraform-provider-kubectl url: https://github.com/gavinbunney/terraform-provider-kubectl/releases/download/v1.0.2/terraform-provider-kubectl-${platform}-amd64 vagrant: - type: Vagrant::Provisioner + # type: Vagrant::Provisioner config: box: ros/generic box_url: https://perx-ros-boxes.s3-ap-southeast-1.amazonaws.com/vagrant/json/ros/generic.json diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/repositories.yml b/onestack/spec/dummy/segments.orig/context/repositories.yml similarity index 71% rename from core/spec/fixtures/segments/node/stack_env_target_ns/repositories.yml rename to onestack/spec/dummy/segments.orig/context/repositories.yml index 65c6102e..2493f96d 100644 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/repositories.yml +++ b/onestack/spec/dummy/segments.orig/context/repositories.yml @@ -3,9 +3,9 @@ cnfs: name: cnfs config: url: git@github.com:rails-on-services/ros.git - type: Rails::Repository + # type: Backend::Repository kpop: name: kpop config: url: git@github.com:maxcole/kpop.git - type: Rails::Repository + # type: Backend::Repository diff --git a/core/spec/fixtures/segments/context/resources.yml b/onestack/spec/dummy/segments.orig/context/resources.yml similarity index 69% rename from core/spec/fixtures/segments/context/resources.yml rename to onestack/spec/dummy/segments.orig/context/resources.yml index 4fd500f9..4b57534a 100644 --- a/core/spec/fixtures/segments/context/resources.yml +++ b/onestack/spec/dummy/segments.orig/context/resources.yml @@ -5,10 +5,10 @@ res1: size: newton inherit: false runtime_name: compose - type: Aws::Resource::EC2::Instance + # type: Aws::Resource::EC2::Instance res2: config: family: ah23 size: cool! provider_name: aws_east - type: Aws::Resource::S3::Bucket + # type: Aws::Resource::S3::Bucket diff --git a/core/spec/fixtures/segments/context/runtimes.yml b/onestack/spec/dummy/segments.orig/context/runtimes.yml similarity index 64% rename from core/spec/fixtures/segments/context/runtimes.yml rename to onestack/spec/dummy/segments.orig/context/runtimes.yml index 985d373b..b073073c 100644 --- a/core/spec/fixtures/segments/context/runtimes.yml +++ b/onestack/spec/dummy/segments.orig/context/runtimes.yml @@ -1,12 +1,12 @@ # runtimes.yml --- compose: - type: Compose::Runtime + # type: Compose::Runtime inherit: true config: version: 3.2 dependencies: - name: docker-compose -native: - type: Native::Runtime +# native: + # type: Native::Runtime diff --git a/core/spec/fixtures/segments/context/users.yml b/onestack/spec/dummy/segments.orig/context/users.yml similarity index 100% rename from core/spec/fixtures/segments/context/users.yml rename to onestack/spec/dummy/segments.orig/context/users.yml diff --git a/core/spec/fixtures/segments/node/stacks/backend.yml b/onestack/spec/dummy/segments/backend.yml similarity index 81% rename from core/spec/fixtures/segments/node/stacks/backend.yml rename to onestack/spec/dummy/segments/backend.yml index d16b686a..6d4bccd1 100644 --- a/core/spec/fixtures/segments/node/stacks/backend.yml +++ b/onestack/spec/dummy/segments/backend.yml @@ -3,3 +3,4 @@ config: domain: backend.${parent.config.domain} host: host.${config.domain} segment_name: cnfs-components/backend +segments_type: environment diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/cluster/default.yml b/onestack/spec/dummy/segments/backend/development/cluster/default.yml similarity index 100% rename from core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/cluster/default.yml rename to onestack/spec/dummy/segments/backend/development/cluster/default.yml diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/resources/cap4.yml b/onestack/spec/dummy/segments/backend/development/resources/cap4.yml similarity index 63% rename from core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/resources/cap4.yml rename to onestack/spec/dummy/segments/backend/development/resources/cap4.yml index 8a5e2bea..0bd5c758 100644 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/resources/cap4.yml +++ b/onestack/spec/dummy/segments/backend/development/resources/cap4.yml @@ -1,5 +1,5 @@ -type: Resource::Aws::EC2::Instance +type: Aws::Resource::EC2::Instance config: family: asdfaksjdfasjkdfasldfj size: fasdfjasdf diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/resources/cluster.yml b/onestack/spec/dummy/segments/backend/development/resources/cluster.yml similarity index 52% rename from core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/resources/cluster.yml rename to onestack/spec/dummy/segments/backend/development/resources/cluster.yml index b50af378..403939ed 100644 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/development/resources/cluster.yml +++ b/onestack/spec/dummy/segments/backend/development/resources/cluster.yml @@ -1,5 +1,5 @@ --- -type: Resource::Aws::EC2::Instance +type: Aws::Resource::EC2::Instance config: family: m2 size: micro diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/resources.yml b/onestack/spec/dummy/segments/backend/resources.yml similarity index 56% rename from core/spec/fixtures/segments/node/stack_env_target_ns/backend/resources.yml rename to onestack/spec/dummy/segments/backend/resources.yml index 2e169069..896300bd 100644 --- a/core/spec/fixtures/segments/node/stack_env_target_ns/backend/resources.yml +++ b/onestack/spec/dummy/segments/backend/resources.yml @@ -1,11 +1,11 @@ --- cap2: - type: Resource::Aws::EC2::Instance + # type: Resource::Aws::EC2::Instance config: family: r2 size: awesome cap4: - type: Resource::Aws::EC2::Instance + # type: Resource::Aws::EC2::Instance config: family: ah23 size: cool! diff --git a/onestack/spec/dummy/segments/builders.yml b/onestack/spec/dummy/segments/builders.yml new file mode 100644 index 00000000..5b421fb1 --- /dev/null +++ b/onestack/spec/dummy/segments/builders.yml @@ -0,0 +1,4 @@ +--- +compose: + config: {} + # type: Compose::Builder diff --git a/core/spec/fixtures/segments/node/stacks/dependencies.yml b/onestack/spec/dummy/segments/dependencies.yml similarity index 96% rename from core/spec/fixtures/segments/node/stacks/dependencies.yml rename to onestack/spec/dummy/segments/dependencies.yml index 51c32a27..60997735 100644 --- a/core/spec/fixtures/segments/node/stacks/dependencies.yml +++ b/onestack/spec/dummy/segments/dependencies.yml @@ -17,8 +17,8 @@ docker: docker-compose: config: {} -skaffold: - config: {} +# skaffold: + # config: {} git: config: {} diff --git a/onestack/spec/dummy/segments/doc.yml b/onestack/spec/dummy/segments/doc.yml new file mode 100644 index 00000000..fab48bde --- /dev/null +++ b/onestack/spec/dummy/segments/doc.yml @@ -0,0 +1,5 @@ +--- +config: {} +# segment_name: jamstack/scully +# default: +# provider_name: aws_east diff --git a/core/spec/fixtures/segments/node/stacks/frontend.yml b/onestack/spec/dummy/segments/frontend.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks/frontend.yml rename to onestack/spec/dummy/segments/frontend.yml diff --git a/core/spec/fixtures/segments/node/stacks/frontend/resources.yml b/onestack/spec/dummy/segments/frontend/resources.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks/frontend/resources.yml rename to onestack/spec/dummy/segments/frontend/resources.yml diff --git a/core/spec/fixtures/segments/node/stacks/frontend/services.yml b/onestack/spec/dummy/segments/frontend/services.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks/frontend/services.yml rename to onestack/spec/dummy/segments/frontend/services.yml diff --git a/core/spec/fixtures/segments/node/stacks/providers.yml b/onestack/spec/dummy/segments/providers.yml similarity index 91% rename from core/spec/fixtures/segments/node/stacks/providers.yml rename to onestack/spec/dummy/segments/providers.yml index 729a4d97..8682f759 100644 --- a/core/spec/fixtures/segments/node/stacks/providers.yml +++ b/onestack/spec/dummy/segments/providers.yml @@ -2,11 +2,11 @@ aws_apse_1: config: region: ap-southeast-1 - type: Aws::Provider + # type: Aws::Provider aws_east_1: config: region: us-east-1 - type: Aws::Provider + # type: Aws::Provider kubectl: config: name: terraform-provider-kubectl @@ -24,7 +24,7 @@ localstack: force_path_style: true sqs: endpoint: http://localstack:4576 - type: Aws::Provider + # type: Aws::Provider rest: config: platform: linux @@ -34,4 +34,4 @@ rest: url: https://github.com/Mastercard/terraform-provider-restapi/releases/download/v${config.version}/ terraform-provider-restapi_v${config.version}-${config.platform}-${config.arch}.zip tags: {} - type: Rest::Provider + # type: Rest::Provider diff --git a/core/spec/fixtures/segments/node/stacks/provisioners.yml b/onestack/spec/dummy/segments/provisioners.yml similarity index 89% rename from core/spec/fixtures/segments/node/stacks/provisioners.yml rename to onestack/spec/dummy/segments/provisioners.yml index e4ac99b5..ebd6195f 100644 --- a/core/spec/fixtures/segments/node/stacks/provisioners.yml +++ b/onestack/spec/dummy/segments/provisioners.yml @@ -6,7 +6,7 @@ terraform: version: 1.0.11 - name: terraform-provider-kubectl url: https://github.com/gavinbunney/terraform-provider-kubectl/releases/download/v1.0.2/terraform-provider-kubectl-${platform}-amd64 - type: Terraform::Provisioner + # type: Terraform::Provisioner # cache_path: tmp # cache_path_suffix: plans @@ -18,4 +18,4 @@ vagrant: - name: setup type: repo url: https://github.com/rails-on-services/setup - type: Vagrant::Provisioner + # type: Vagrant::Provisioner diff --git a/core/spec/fixtures/segments/node/stacks/repositories.yml b/onestack/spec/dummy/segments/repositories.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks/repositories.yml rename to onestack/spec/dummy/segments/repositories.yml diff --git a/core/spec/fixtures/segments/node/stacks/runtimes.yml b/onestack/spec/dummy/segments/runtimes.yml similarity index 83% rename from core/spec/fixtures/segments/node/stacks/runtimes.yml rename to onestack/spec/dummy/segments/runtimes.yml index 18e87ada..9daf8830 100644 --- a/core/spec/fixtures/segments/node/stacks/runtimes.yml +++ b/onestack/spec/dummy/segments/runtimes.yml @@ -3,7 +3,7 @@ compose: config: version: 3.2 dependencies: '[{"name"=>"docker-compose"}]' - type: Compose::Runtime + # type: Compose::Runtime # native: # config: {} # type: Native::Runtime diff --git a/core/spec/fixtures/segments/node/stacks/users.yml b/onestack/spec/dummy/segments/users.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks/users.yml rename to onestack/spec/dummy/segments/users.yml diff --git a/core/spec/integration/.gitkeep b/onestack/spec/integration/.gitkeep similarity index 100% rename from core/spec/integration/.gitkeep rename to onestack/spec/integration/.gitkeep diff --git a/core/spec/integration/application_spec.rb b/onestack/spec/integration/application_spec.rb similarity index 100% rename from core/spec/integration/application_spec.rb rename to onestack/spec/integration/application_spec.rb diff --git a/core/spec/integration/console_spec.rb b/onestack/spec/integration/console_spec.rb similarity index 100% rename from core/spec/integration/console_spec.rb rename to onestack/spec/integration/console_spec.rb diff --git a/onestack/spec/models/one_stack/component_spec.rb b/onestack/spec/models/one_stack/component_spec.rb new file mode 100644 index 00000000..2adea1f2 --- /dev/null +++ b/onestack/spec/models/one_stack/component_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# require "#{ENV['SPEC_DIR']}/models/concerns/encryption_spec.rb" +# require "#{ENV['SPEC_DIR']}/models/concerns/interpolation_spec.rb" + +# rubocop:disable Metrics/BlockLength +module OneStack + RSpec.describe Component, type: :model do + let(:segments_yml) { SPEC_PATH.join('dummy/config/segments/component.yml') } + let(:segments_path) { SPEC_PATH.join('dummy/segments/component') } + let(:segment_root) { OneStack::SegmentRoot.first } + # let(:a_context) { Context.create(root: project, options: options) } + + before(:each) do + OneStack::SpecHelper.setup_segment(self) + SolidRecord::DataStore.reset(*[ + { path: segments_yml.to_s, model_type: 'OneStack::SegmentRoot' }, + { path: segments_path.to_s, owner: -> { segment_root } } + ]) + end + + describe 'has_many associations count' do + context 'components' do it { expect(segment_root.components.count).to eq(3) } end + context 'dependencies' do it { expect(segment_root.dependencies.count).to eq(6) } end + context 'providers' do it { expect(segment_root.providers.count).to eq(5) } end + context 'resources' do it { expect(segment_root.resources.count).to eq(0) } end + context 'runtimes' do it { expect(segment_root.runtimes.count).to eq(1) } end + context 'users' do it { expect(segment_root.users.count).to eq(1) } end + end + + xdescribe '#encryption_key' do + it { expect(segment_root.encryption_key).to eq(OneStack.config.project_id) } + end + + describe '#segments_names' do + it { expect(segment_root.segment_names).to eq(%w[backend doc frontend])} + end + + describe '#struct' do + it { binding.pry } + end + end +end +# rubocop:enable Metrics/BlockLength diff --git a/core/spec/models/concerns/asset_spec.rb b/onestack/spec/models/one_stack/concerns/asset_spec.rb similarity index 100% rename from core/spec/models/concerns/asset_spec.rb rename to onestack/spec/models/one_stack/concerns/asset_spec.rb diff --git a/core/spec/models/concerns/interpolation_spec.rb b/onestack/spec/models/one_stack/concerns/interpolation_spec.rb similarity index 100% rename from core/spec/models/concerns/interpolation_spec.rb rename to onestack/spec/models/one_stack/concerns/interpolation_spec.rb diff --git a/core/spec/models/context_spec.rb b/onestack/spec/models/one_stack/context_spec.rb similarity index 58% rename from core/spec/models/context_spec.rb rename to onestack/spec/models/one_stack/context_spec.rb index 30d20bb8..5f4b183c 100644 --- a/core/spec/models/context_spec.rb +++ b/onestack/spec/models/one_stack/context_spec.rb @@ -1,28 +1,40 @@ # frozen_string_literal: true +module OneStack + RSpec.describe 'Context' do - let(:source_path) { SPEC_DIR.join('fixtures/segments') } - let(:root) { SegmentRoot.first } - let(:subject) { Context.create(root: root, options: options) } + # let(:source_path) { SPEC_DIR.join('fixtures/segments') } + # let(:root) { SegmentRoot.first } + # let(:subject) { Context.create(root: root, options: options) } - before do - setup_project(segment: :context) + before(:each) do + OneStack::SpecLoader.setup_segment(self, load_nodes: true) end after do - remove_project + # remove_project end - describe 'stack: :frontend' do + describe 'frontend' do let(:options) { { stack: :frontend } } + let(:sr) { SolidRecord::DataPath.create(namespace: 'OneStack', path_map: 'segment_root', + path: APP_ROOT.join('config')) } + let(:segments) { SolidRecord::DataPath.create(namespace: 'OneStack', path_map: 'segments', + path: OneStack.config.paths.segments, recurse: true )} - it 'creates the correct number of Nodes' do - # binding.pry - expect(Node.count).to eq(57) + it 'creates one SegmentRoot' do + # NOTE: This stuff will move to SolidRecord after it is working so don't get too caught up about how it looks! + sr.load + expect(SegmentRoot.count).to eq(1) + end + + it 'does anotheer' do + sr.load + binding.pry end end - describe 'stack: :wrong' do + describe 'wrong' do let(:options) { { stack: :wrong } } it 'generates the correct number of contexts and context_components' do @@ -66,3 +78,4 @@ end end end +end diff --git a/core/spec/models/node_spec.rb b/onestack/spec/models/one_stack/node_spec.rb similarity index 100% rename from core/spec/models/node_spec.rb rename to onestack/spec/models/one_stack/node_spec.rb diff --git a/onestack/spec/models/one_stack/segment_root_spec.rb b/onestack/spec/models/one_stack/segment_root_spec.rb new file mode 100644 index 00000000..3bbbb600 --- /dev/null +++ b/onestack/spec/models/one_stack/segment_root_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module OneStack + RSpec.describe SegmentRoot, type: :model do + # it_behaves_like 'encryption' + # it_behaves_like 'interpolate' + let(:segments_yml) { SPEC_PATH.join('dummy/config/segments/context.yml') } + let(:segments_path) { SPEC_PATH.join('dummy/segments/context') } + let(:segment_root) { SegmentRoot.first } + # let(:a_context) { Context.create(root: root, options: options) } + + before(:each) do + SpecHelper.setup_segment(self) + SolidRecord::DataStore.reset(*[ + { path: segments_yml.to_s, model_type: 'OneStack::SegmentRoot' }, + { path: segments_path.to_s, owner: -> { OneStack::SegmentRoot.first } } + ]) + end + + context 'when stack: :wrong' do + let(:options) { { stack: :backend, environment: :production, target: :lambda } } + # let(:options) { { stack: :wrong } } + + it { binding.pry } + # it 'generates the correct number of contexts and context_components' do + it { expect(Context.count).to eq(1) } + it { expect(ContextComponent.count).to eq(3) } + end + end +end diff --git a/onestack/spec/spec_helper.rb b/onestack/spec/spec_helper.rb new file mode 100644 index 00000000..8a680d77 --- /dev/null +++ b/onestack/spec/spec_helper.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' + +# Common, well known key which yields predictable results when decrypting values +# TODO: When encryption and keys move to SolidRecord this could go away as we don't need +# to test encryption of records +# KEY_ID = '9346840c042bb4dbf7bd6a5cf49de40d420c3d1835b28044f9abcab3003c47a1' + +# For OneStack specs SPEC_PATH will be nil +# If another gem requires this helper then it should have already defined SPEC_PATH + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_status' + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end + + # config.before(:suite) { OneStack::SpecHelper.setup_project } + # config.after(:suite) { OneStack::SpecHelper.teardown_project } +end diff --git a/core/spec/unit/.gitkeep b/onestack/spec/unit/.gitkeep similarity index 100% rename from core/spec/unit/.gitkeep rename to onestack/spec/unit/.gitkeep diff --git a/core/spec/unit/console_spec.rb b/onestack/spec/unit/console_spec.rb similarity index 100% rename from core/spec/unit/console_spec.rb rename to onestack/spec/unit/console_spec.rb diff --git a/packer/.rspec b/packer/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/packer/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/packer/Gemfile b/packer/Gemfile new file mode 100644 index 00000000..87000333 --- /dev/null +++ b/packer/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'onestack', path: '../onestack' +gem 'solid_app', path: '../solid_app' +gem 'solid_record', path: '../solid_record' +gem 'solid_support', path: '../solid_support' + +gemspec diff --git a/packer/bin/console b/packer/bin/console new file mode 100755 index 00000000..aa32a757 --- /dev/null +++ b/packer/bin/console @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' + +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/packer/lib/cnfs/packer.rb b/packer/lib/cnfs/packer.rb deleted file mode 100644 index 71e59d46..00000000 --- a/packer/lib/cnfs/packer.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require_relative 'packer/version' -require_relative 'packer/plugin' - -module Packer - module Concerns; end -end - -module Cnfs - module Packer - def self.gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end -end diff --git a/packer/lib/cnfs/packer/plugin.rb b/packer/lib/cnfs/packer/plugin.rb deleted file mode 100644 index 4feef216..00000000 --- a/packer/lib/cnfs/packer/plugin.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Packer - class Plugin < Cnfs::Plugin - def self.gem_root() = Cnfs::Packer.gem_root - end - end -end diff --git a/packer/lib/one_stack/packer/plugin.rb b/packer/lib/one_stack/packer/plugin.rb new file mode 100644 index 00000000..9982f12b --- /dev/null +++ b/packer/lib/one_stack/packer/plugin.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + module Packer + class Plugin < OneStack::Plugin + config.before_initialize { |config| OneStack::Packer.config.merge!(config.packer) } + + def self.gem_root() = OneStack::Packer.gem_root + end + end +end diff --git a/packer/lib/cnfs/packer/version.rb b/packer/lib/one_stack/packer/version.rb similarity index 83% rename from packer/lib/cnfs/packer/version.rb rename to packer/lib/one_stack/packer/version.rb index 1dc7ef70..6606cb96 100644 --- a/packer/lib/cnfs/packer/version.rb +++ b/packer/lib/one_stack/packer/version.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Cnfs +module OneStack module Packer VERSION = '0.1.0' end diff --git a/packer/lib/onestack-packer.rb b/packer/lib/onestack-packer.rb new file mode 100644 index 00000000..5fcc1c3d --- /dev/null +++ b/packer/lib/onestack-packer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative 'one_stack/packer/version' + +require 'onestack' + +require_relative 'one_stack/packer/plugin' + +module Packer + module Concerns; end +end + +module OneStack + module Packer + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + def config() = @config ||= ActiveSupport::OrderedOptions.new + end + end +end diff --git a/packer/cnfs-packer.gemspec b/packer/onestack-packer.gemspec similarity index 74% rename from packer/cnfs-packer.gemspec rename to packer/onestack-packer.gemspec index c5ecfea7..bdcd60ba 100644 --- a/packer/cnfs-packer.gemspec +++ b/packer/onestack-packer.gemspec @@ -1,19 +1,19 @@ # frozen_string_literal: true -require_relative 'lib/cnfs/packer/version' +require_relative 'lib/one_stack/packer/version' Gem::Specification.new do |spec| - spec.name = 'cnfs-packer' - spec.version = Cnfs::Packer::VERSION + spec.name = 'onestack-packer' + spec.version = OneStack::Packer::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] - spec.summary = 'CNFS CLI framework' - spec.description = 'CNFS CLI is a pluggable framework to configure and manage CNFS projects' + spec.summary = 'OneStack CLI framework' + spec.description = 'OneStack CLI is a pluggable framework to configure and manage OneStack projects' spec.homepage = 'https://cnfs.io' spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'cnfs-core', '~> 0.1.0' + spec.add_dependency 'onestack', '~> 0.1.0' spec.add_dependency 'bump', '~> 0.1' spec.add_development_dependency 'bundler', '~> 2.0' diff --git a/packer/spec/dummy/config/application.rb b/packer/spec/dummy/config/application.rb new file mode 100644 index 00000000..b66e35bf --- /dev/null +++ b/packer/spec/dummy/config/application.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + config.solid_record.namespace = :one_stack + config.solid_record.load_paths = [ + { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/packer/spec/dummy/config/boot.rb b/packer/spec/dummy/config/boot.rb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/packer/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/packer/spec/dummy/config/environment.rb b/packer/spec/dummy/config/environment.rb new file mode 100644 index 00000000..2ff3c59d --- /dev/null +++ b/packer/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +OneStack.application.initialize! diff --git a/packer/spec/dummy/config/segment.yml b/packer/spec/dummy/config/segment.yml new file mode 100644 index 00000000..f53a5592 --- /dev/null +++ b/packer/spec/dummy/config/segment.yml @@ -0,0 +1,17 @@ +--- +segments_type: stack +# default: + # segment_name: backend +# config: +# domain: cnfs.io +# host: host.${config.domain} +# targets: +# one: one.${config.host} +# two: two.${config.host} +# segments_type: target +# default: +# provisioner_name: terraform +# repository_name: cnfs-projects +# resource_name: instance1 +# runtime_name: compose +# segment_name: backend diff --git a/packer/spec/spec_helper.rb b/packer/spec/spec_helper.rb new file mode 100644 index 00000000..fda41792 --- /dev/null +++ b/packer/spec/spec_helper.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' diff --git a/set_path b/set_path deleted file mode 100644 index 8dd1fcfe..00000000 --- a/set_path +++ /dev/null @@ -1,9 +0,0 @@ -# Use to access cnfs cli in development mode - -RUBYOPT='-W:no-deprecated' - -CNFS_CLI_PATH=`dirname "$0"` -echo $CNFS_CLI_PATH -PATH=$CNFS_CLI_PATH/cnfs/exe:$PATH -alias cli="cd $CNFS_CLI_PATH/cli" -unset CNFS_CLI_PATH diff --git a/solid-record/.pryrc b/solid-record/.pryrc deleted file mode 100644 index a00376b7..00000000 --- a/solid-record/.pryrc +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -# .pryrc - -# SolidRecord.glob_pattern = '*.yml' -# SolidRecord.path_column = 'sr_path' -# SolidRecord.key_column = 'sr_key' -# SolidRecord.reference_suffix = 'other' - -def path_map(what) - { - blog: 'users/blogs/posts', - infra: '.', - stack: 'segments' - }[what] -end - -%i[blog stack infra].each do |what| - %i[file monolith hybrid].each do |type| - define_method "#{what}_#{type}" do |_path_map = '.', recurse = false| - Pathname.new('.').glob("spec/dummy/#{what}/app/models/*.rb").each { |path| require_relative(path) } - # Pathname.new('.').glob('../core/app/models/*.rb').each { |path| require_relative path } - SolidRecord::DataStore.load - SolidRecord::DataPath.create(path: "spec/dummy/#{what}/data/#{type}", path_map: path_map(what), recurse: recurse) - end - end -end - -def srd() = SolidRecord::Document - -def sre() = SolidRecord::Element - -def srp() = SolidRecord::DataPath diff --git a/solid-record/.rubocop.yml b/solid-record/.rubocop.yml deleted file mode 100644 index 77ef872e..00000000 --- a/solid-record/.rubocop.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -inherit_from: ../.rubocop.yml -AllCops: - Exclude: - - 'lib/solid_record/associations.rb' -Lint/EmptyClass: - Exclude: - - 'spec/**/*.rb' diff --git a/solid-record/bin/console b/solid-record/bin/console deleted file mode 100755 index 0c846614..00000000 --- a/solid-record/bin/console +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'bundler/setup' -require 'pry' -require 'solid_record' - -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -Pry.start diff --git a/solid-record/lib/solid_record.rb b/solid-record/lib/solid_record.rb deleted file mode 100644 index 2d1cbb7f..00000000 --- a/solid-record/lib/solid_record.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require 'active_record' -require 'sqlite3' - -require 'solid_support' - -require_relative 'solid_record/version' -require_relative 'solid_record/data_store' - -require_relative 'solid_record/table' -require_relative 'solid_record/data_path' -require_relative 'solid_record/document' -require_relative 'solid_record/element' - -require_relative 'solid_record/yaml_document' - -require_relative 'solid_record/persistence' -require_relative 'solid_record/model' - -# Usage: -# SolidRecord.load -module SolidRecord - class << self - def load(**options) - [DataStore, DataPath.create(**options)].each(&:load) - true - end - end - - class Error < StandardError; end -end diff --git a/solid-record/lib/solid_record/associations.rb b/solid-record/lib/solid_record/associations.rb deleted file mode 100644 index 6d27ac66..00000000 --- a/solid-record/lib/solid_record/associations.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - module Associations - extend ActiveSupport::Concern - - class_methods do - # values is the hash of attributes that will be passed to create method - def formatted_attributes(path, values) - # puts name, path - h = assn_names.each_with_object(values) do |assn_name, hash| - # next unless (assn_value = hash["#{assn_name}_name"]) - if (assn_value = hash["#{assn_name}_name"]) - else - if path.parent.directory? - assn_value = path.parent.name - # hash["#{assn_name}_name"] = assn_value - else - puts "assn_value not found for #{assn_name}" - next - end - end - - p_path = parent(path) - assn_id = identify(p_path, assn_value) - puts p_path, assn_value, assn_id - # binding.pry - - # assn_id = SolidRecord.identify(parent(path), assn_value) - hash.merge!("#{assn_name}_id" => assn_id) - end - # puts '====' - h - end - - def parent(path) - return path.parent.parent if path.singular? - - return path.parent if path.parent.classify.safe_constantize - path.parent.parent - end - - def assn_names() = reflect_on_all_associations(:belongs_to).map(&:name) - end - - def except_solid() = super + self.class.assn_names.map { |name| "#{name}_id" } - end - - # NOTE: The old Persistence Concern code - module Persistence - MAX_ID = (2**30) - 1 - - extend ActiveSupport::Concern - - class_methods do - def load_content(path) - disable_callbacks do - formatted_assets(path).each do |key, values| - sa = if path.singular? - solid_attributes(path.parent.realpath, key) - else - solid_attributes(path.realpath, key) - end - # puts name, path.realpath, sa - # hash = formatted_attributes(path, values).merge(solid_attributes(path.realpath, key)) - hash = formatted_attributes(path, values).merge(sa) - # puts '===' - begin - if (class_name = hash[inheritance_column]) && (klass = class_name.safe_constantize) - ar = klass._create_callbacks # .select { |cb| cb.kind.eql?(:after) } - # klass._create_callbacks - # binding.pry if ar.any? # hash['kind'].eql?('Vpn') - end - res = create(hash) - puts(res.errors) unless res.persisted? - rescue ActiveRecord::SubclassNotFound => e - puts "Error on #{path}" - end - end - end - end - - # def formatted_assets(path) = path.singular? ? { SolidRecord.key_column => path.read_asset } : path.read_asset - def formatted_assets(path) = path.singular? ? { path.name => path.read_asset } : path.read_asset - - def formatted_attributes(_path, values) = values - - def solid_attributes(path, key) - { 'id' => identify(path, key), SolidRecord.path_column => path, SolidRecord.key_column => key } - end - - # Returns an deterministic integer ID for a record based on path and key value representing an object - def identify(path, key) = Zlib.crc32("#{path.keyname}/#{key}") % MAX_ID - - def disable_callbacks - skip_callback(*solid_record_callback) - yield - set_callback(*solid_record_callback) - end - - def solid_record_callback() = %i[create after _create_asset_] - end - - included do - after_create :_create_asset_ - after_update :_update_asset_ - after_destroy :_destroy_asset_ - end - - def _create_asset_() = nil - - def to_solid() = pathname.singular? ? as_solid : { _key_ => as_solid } - - def as_solid() = as_json.except(*except_solid) - - def except_solid() = ['id', SolidRecord.path_column, SolidRecord.key_column] - - def pathname() = @pathname ||= Pathname.new(_path_) - - # TODO: rename method - def gid() = identify(pathname, _key_) - - def _path_() = send(SolidRecord.path_column) - - def _key_() = send(SolidRecord.key_column) - - def _update_asset_() = pathname.write_asset(content_to_write) - - def content_to_write() = pathname.singular? ? to_solid : pathname.read_asset.merge(to_solid) - - def _destroy_asset_ - if pathname.singular? || pathname.read_asset.keys.size.eql?(1) - pathname.delete - else - pathname.write_asset(pathname.read_asset.except(_key_)) - end - end - end -end diff --git a/solid-record/lib/solid_record/data_path.rb b/solid-record/lib/solid_record/data_path.rb deleted file mode 100644 index d8ff31c3..00000000 --- a/solid-record/lib/solid_record/data_path.rb +++ /dev/null @@ -1,115 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - class << self - attr_writer :path_maps, :glob_pattern - - # array of PathMap classes - def path_maps() = @path_maps ||= [] - - def glob_pattern() = @glob_pattern ||= '*.yml' - - def load_path_maps() = path_maps.each(&:load) - - def document_map - { - yml: :yaml, - yaml: :yaml - } - end - end - - class DataPath < ActiveRecord::Base - include SolidRecord::Table - self.table_name_prefix = 'solid_record_' - - has_many :documents - - before_validation :set_defaults - - def set_defaults - self.glob ||= SolidRecord.glob_pattern - self.path_map ||= '.' - end - - validates :path, presence: true - - validate :path_exists, if: -> { path } - - def path_exists() = Pathname.new(path).exist? - - def load - return unless valid? - - SolidRecord.skip_model_callbacks do - in_path { load_path(Pathname.new('.')) } - end - end - - def in_path(&block) = Dir.chdir(path, &block) - - def load_path(loadpath) - # loadpath.glob(glob).each { |childpath| create_document(childpath) } - loadpath.glob(glob).each do |childpath| - documents.create(path: childpath, klass_type: model_class_type(childpath), type: doc_class(childpath)) - end - loadpath.children.select(&:directory?).each { |childpath| load_path(childpath) } - end - - # Return a subclass of Document based on the file's extension, e.g. .yml or .yaml returns a YamlDocument - # based on the vlaues of the SolidRecord.document_map hash - def doc_class(childpath) - doc_type = document_map[childpath.extension] - klass = "solid_record/#{doc_type}_document".classify.safe_constantize - raise ArgumentError, "Document type #{doc_type} not found" unless doc_type && klass - - klass - end - - # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/AbcSize - - # Return a class for a given Pathname based on a set of rules - def model_class_type(childpath) - klass = childpath.safe_constantize - klass ||= childpath.parent.safe_constantize - klass ||= childpath.matchpath(pathname_map)&.safe_constantize - klass ||= pathname_map.safe_constantize if recurse - debug("#{childpath} resoloved to #{klass.name}") if klass - raise ArgumentError, "Failed to resolve #{childpath} with path_map: #{path_map}" unless klass - - klass.name - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - - def pathname() = @pathname ||= Pathname.new(path) - - def pathname_map() = @pathname_map ||= Pathname.new(path_map) - - def debug(msg) = msg - - def document_map() = @document_map ||= SolidRecord.document_map.with_indifferent_access - - # TODO: Move the TTY stuff to a controller and just return the hash - # def to_tree() = puts(TTY::Tree.new({ '.' => as_tree }).render) - - # def as_tree - # directories.each_with_object(files.map(&:name)) do |dir, ary| - # value = dir.pathname.children.size.zero? ? dir.name : { dir.name => dir.as_tree } - # ary.append(value) - # end - # end - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.string :path - t.string :path_map - t.string :glob - t.boolean :recurse - end - end - end - end -end diff --git a/solid-record/lib/solid_record/data_store.rb b/solid-record/lib/solid_record/data_store.rb deleted file mode 100644 index 65d89971..00000000 --- a/solid-record/lib/solid_record/data_store.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - class << self - # Path to a file that defines an ActiveRecord::Schema - attr_accessor :schema_file - end - - class DataStore - class << self - def load - ActiveRecord::Migration.verbose = false - ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') - SolidRecord.schema_file ? require(schema_file) : load_schema_paths - end - - def load_schema_paths - ActiveRecord::Schema.define do |schema| - require_relative '../ext/table_definition' - SolidRecord.models.each do |model| - next unless model.respond_to? :create_table - - model.create_table(schema) - model.reset_column_information - end - end - true - end - - def reset() = load_schema_paths - - # Dump the latest version of the schema to a file - # Example use case: Create a schema which can be used with NullDB to emulate models without having - # the actual classes or underlying database prsent - def schema_dump(file_name = nil) - schema = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, StringIO.new).string - return schema unless file_name - - File.open(file_name, 'w') { |f| f.puts(schema) } - end - end - end -end diff --git a/solid-record/lib/solid_record/document.rb b/solid-record/lib/solid_record/document.rb deleted file mode 100644 index c309f545..00000000 --- a/solid-record/lib/solid_record/document.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - class Document < ActiveRecord::Base - include SolidRecord::Table - self.table_name_prefix = 'solid_record_' - - # belongs_to :parent, class_name: 'Document' - belongs_to :data_path - - has_many :elements - # has_many :models, through: :elements, source_type: 'Element' - def models() = elements.map(&:model) - - after_create :load_document - - def load_document - raise ArgumentError, "Content must be in Key/Value format #{path}" unless formatted_content.instance_of?(Hash) - - formatted_content.each do |key, values| - # binding.pry if klass_type.eql?('Blog') - elements.create(klass_type: klass_type, key: key, values: values, type: 'SolidRecord::RootElement') - end - end - - def formatted_content = @formatted_content ||= pathname.singular? ? { pathname.name => content } : content - - def pathname() = @pathname ||= Pathname.new(path) - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.string :path - t.string :klass_type - t.string :type - t.references :data_path - # t.references :owner, polymorphic: true - end - end - end - end -end diff --git a/solid-record/lib/solid_record/element.rb b/solid-record/lib/solid_record/element.rb deleted file mode 100644 index 1f31fea7..00000000 --- a/solid-record/lib/solid_record/element.rb +++ /dev/null @@ -1,141 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - class << self - def reference_suffix() = 'name' - end - - class Element < ActiveRecord::Base - include SolidRecord::Table - self.table_name_prefix = 'solid_record_' - - # klass is the model's class - # key is the ID used to reference the model - # values is the payload hash passed to klass.create - # owner is the optional owner of the model, e.g. the model's belongs_to object - attr_accessor :klass_type, :key, :values, :owner - - # Elements are self referencing; They belong to a parent element and have_many child elements - belongs_to :parent, class_name: 'SolidRecord::Element' - has_many :elements, foreign_key: 'parent_id' - - # Ascend the element tree until hitting the RootElement - delegate :root, to: :parent - - # The Persistence Concern adds these methods to the model's callbacks - # When a model's lifecycle event is triggred the root element will handle updating the underlying document - delegate :__create_asset, :__update_asset, :__destroy_asset, to: :root - - # The model instance of klass that is created from this element - belongs_to :model, polymorphic: true - - before_create :create_model - after_create :create_child_elements - - # Attempt to create an instance of klass and assign it to model - def create_model - # binding.pry # if model_values.nil? - self.model = klass.create(model_values) - rescue ActiveModel::UnknownAttributeError => e - puts "Err: #{e.message}" - end - - # The hash used to create the model - # TODO: If owner is only coming from #create_child_elements then values.merge shoudl happen in that method - def model_values - vals = owner ? values.merge(owner.class.base_class.name.downcase => owner) : values - vals.merge(associations).merge(solid_attributes).except(*has_many_names) - end - - # Store the object's key in the table's user defined 'key_column' - # And generate an ID based on the document path, object type and value of 'key_column' - def solid_attributes() = { klass.key_column => key, 'id' => root.identify(key: key) } - - # Return a Hash of model_foreign_key_column => identified foreign key value - # for each belongs_to association on the klass - def associations - belongs_to_assns.each_with_object({}) do |assn, hash| - assn_name = assn.name.to_s # user - hash_fk = "#{assn_name}_#{SolidRecord.reference_suffix}" # user_name - # In a monolithic document the foreign key value is not required as it is part of the document hierarchy - next unless (hash_fk_value = values[hash_fk]) - - model_fk = assn.options[:foreign_key] || "#{assn_name}_id" - # TODO: Maybe check and raise if not klass.column_names.include?(model_fk) - assn_id = root.identify(key: hash_fk_value, type: assn_name.pluralize) - hash[model_fk] = assn_id - end - end - - # If this element's payload includes associations then create elements for those - def create_child_elements - has_many_names.each do |assn_name| - next unless (assn_hash = values[assn_name]) - - assn_hash.each do |child_key, child_values| - child_klass = assn_name.classify - elements.create(klass_type: child_klass, key: child_key, values: child_values, owner: model) - end - end - end - - # rubocop:disable Naming/PredicateName - - def has_many_names() = klass.reflect_on_all_associations(:has_many).map(&:name).map(&:to_s) - # rubocop:enable Naming/PredicateName - - def belongs_to_assns() = klass.reflect_on_all_associations(:belongs_to) - - def klass() = @klass ||= klass_type.constantize - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.string :type - t.references :parent - t.references :document - t.references :model, polymorphic: true - end - end - end - end - - # Specific Element type which represents a Document - class RootElement < Element - MAX_ID = (2**30) - 1 - - belongs_to :document - delegate :pathname, to: :document - - def root() = self - - # Returns an deterministic integer ID for a record based on path and key value representing an object - def identify(key:, type: nil) - type ||= pathname.name - key_name = pathname.realpath.parent.join(type, key).to_s - Zlib.crc32(key_name) % MAX_ID - # debug("#{type}: #{key}", "from: #{pathname.realpath}", key_name, ret_val) - end - - def __create_element - # TODO: Determine where to write the element - # binding.pry - end - - def __update_element - # TODO: call document.write - # binding.pry - end - - # def content_to_write() = pathname.singular? ? to_solid : pathname.read_asset.merge(to_solid) - - def __destroy_element - # TODO: call document.write - # if pathname.singular? || pathname.read_asset.keys.size.eql?(1) - # pathname.delete - # else - # pathname.write_asset(pathname.read_asset.except(_key_)) - # end - end - end -end diff --git a/solid-record/lib/solid_record/persistence.rb b/solid-record/lib/solid_record/persistence.rb deleted file mode 100644 index 6ad826f5..00000000 --- a/solid-record/lib/solid_record/persistence.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - class << self - def element_key() = 'element' - # attr_writer :path_column, :key_column, :reference_suffix - - # Default values for SolidRecord that are used in the Persistence Concern - # def path_column() = @path_column ||= 'path' - - # def reference_suffix() = @reference_suffix ||= 'name' - - def persisted_models() = @persisted_models ||= [] - - def skip_model_callbacks - persisted_models.each { |model| model.skip_callback(*model_callbacks) } - yield - persisted_models.each { |model| model.set_callback(*model_callbacks) } - end - - def model_callbacks() = %i[create after __create_element] - end - - module Persistence - extend ActiveSupport::Concern - - class_methods do - def key_column() = 'key' - end - - included do - has_one SolidRecord.element_key.to_sym, class_name: 'SolidRecord::Element', as: :model - - # Model lifecyle methods are delegated to RootElement - delegate :__create_element, :__update_element, :__destroy_element, to: SolidRecord.element_key.to_sym - - after_create :__create_element - after_update :__update_element - after_destroy :__destroy_element - - SolidRecord.persisted_models << self - end - - def to_solid() = { send(self.class.key_column) => as_solid } - - def as_solid() = as_json.except(*except_solid) - - def except_solid() = ['id', self.class.key_column] - end -end diff --git a/solid-record/lib/solid_record/yaml_document.rb b/solid-record/lib/solid_record/yaml_document.rb deleted file mode 100644 index c1dce79c..00000000 --- a/solid-record/lib/solid_record/yaml_document.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - class YamlDocument < Document - def content() = YAML.load_file(path) || {} - - def write(content = {}) = pathname.write(content.to_yaml) - end -end diff --git a/solid-record/spec/dummy/blog/app/models/user.rb b/solid-record/spec/dummy/blog/app/models/user.rb deleted file mode 100644 index 410a8690..00000000 --- a/solid-record/spec/dummy/blog/app/models/user.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true -end - -class User < ApplicationRecord - include SolidRecord::Model - def self.key_column() = 'first' - - has_many :blogs, foreign_key: 'kid' - has_many :posts, through: :blogs - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.string :first - t.string :last - end - end - end -end - -class Blog < ApplicationRecord - include SolidRecord::Model - def self.key_column() = 'name' - - belongs_to :user, foreign_key: 'kid' - - has_many :posts - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - # t.references :user, solid: true - t.integer :kid - t.string :user_name - t.string :name - end - end - end -end - -class Post < ApplicationRecord - include SolidRecord::Model - def self.key_column() = 'title' - - belongs_to :blog - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - t.references :blog, solid: true - t.string :title - t.text :content - end - end - end -end diff --git a/solid-record/spec/dummy/stack/app/models/stack.rb b/solid-record/spec/dummy/stack/app/models/stack.rb deleted file mode 100644 index fea05d30..00000000 --- a/solid-record/spec/dummy/stack/app/models/stack.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true - self.inheritance_column = 'kind' -end - -class Segment < ApplicationRecord - include SolidRecord::Model - - store :config, accessors: %i[segments_type], coder: YAML - - class << self - def create_table(schema) - schema.create_table table_name, force: true do |t| - # t.solid - t.string :name - end - end - end -end - -class Dependency < Segment; end - -class Plan < Segment; end - -class Provider < Segment; end - -class Provisioner < Segment; end - -class Repository < Segment; end - -class Environment < Segment; end - -class Service < Segment; end diff --git a/solid-record/spec/lib/solid_record/data_path_spec.rb b/solid-record/spec/lib/solid_record/data_path_spec.rb deleted file mode 100644 index c29cad8d..00000000 --- a/solid-record/spec/lib/solid_record/data_path_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -# path: 'spec/dummay/data' -# layout: -# backend/development -# backend/production/cluster -# frontend/cluster -# -# 1. Single class at all levels of hierarchy (i.e. self referencing) -# Required models: Segment -# map: { '.' => 'segments' } -# -# 2. Consistent map of hierarchy paths to classes -# Required models Stack, Environment, Target -# map: { '.' => 'stacks', 'stacks' => 'environments', 'stacks/environments' => 'targets' } -# map: 'stacks/environments/targets' -# -# map: 'stacks/backend' => environments' -# map: 'stacks/frontend' => targets' -# -# 3. Each path within the hierarchy has it's own class hierarchy -# Required models Stack, Environment, Target -# map: { '.' => 'stacks', 'frontend' => 'target', 'backend' => 'environments', backend/production' => 'target' }) -# -# 4. Default -# Required models: path.basename.to_s.classify -# map: {} -# -class Stack; end - -class Environment; end - -module SolidRecord - RSpec.describe 'DataPath' do - let(:subject) { DataPath } - - before(:all) do - DataStore.load - end - - describe 'valid?' do - it 'returns false if path is not set' do - expect(subject.new).not_to be_valid - end - - it 'returns true if path is set' do - expect(subject.new(path: '.')).to be_valid - end - end - - describe 'load' do - before do - # allow_any_instance_of(DataPath).to receive(:create_document).and_return(nil) - allow_any_instance_of(DataPath).to receive(:load_path).and_return([]) - end - - it 'returns nil when not valid' do - expect(subject.new.load).to be_nil - end - - it 'returns not nil when valid' do - expect(subject.new(path: '.').load).not_to be_nil - end - end - - describe 'model_class_type' do - let(:subject) { DataPath.new(path: '.', path_map: 'stacks/environments') } - - it 'returns the correct class' do - expect(subject.model_class_type(Pathname.new('backend'))).to eq('Stack') - end - - it 'returns the correct class' do - expect(subject.model_class_type(Pathname.new('backend/production'))).to eq('Environment') - end - - it 'raises an error if pathmap is invalid' do - expect { subject.model_class_type(Pathname.new('backend/production/cluster')) }.to raise_error(ArgumentError) - end - - it 'returns the correct class when recurse is true' do - subject.recurse = 'true' - expect(subject.model_class_type(Pathname.new('backend/production/cluster'))).to eq('Environment') - end - - it 'returns the correct class when recurse is true' do - subject.recurse = 'true' - subject.path_map = 'stacks' - expect(subject.model_class_type(Pathname.new('backend/production/cluster'))).to eq('Stack') - end - end - end -end diff --git a/solid-record/spec/lib/solid_record/document_spec.rb b/solid-record/spec/lib/solid_record/document_spec.rb deleted file mode 100644 index 4601c62d..00000000 --- a/solid-record/spec/lib/solid_record/document_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - RSpec.describe 'Document' do - before(:all) do - Pathname.new('.').glob(ROOT.join('spec/dummy/infra/app/models/*.rb')).each { |path| require_relative path } - DataStore.load - end - - before(:each) do - DataStore.reset - # Pathname.new('.').glob(ROOT.join('spec/dummy/stack/app/models/*.rb')).each { |path| require_relative path } - # Pathname.new('.').glob('../core/app/models/*.rb').each { |path| require_relative path } - # Dir.chdir(ROOT) do - # SolidRecord::DataPath.new(path: 'spec/dummy/stack/data', path_map: 'segments', recurse: true) - # end - end - - describe 'monolithic yaml' do - let(:file) { Pathname.new(ROOT.join('spec/dummy/infra/data/monolith/groups.yml')) } - - let(:doc) { SolidRecord.skip_model_callbacks { YamlDocument.create(klass_type: '::Group', path: file) } } - - xit 'creates the correct number of models' do - expect(::Group.count).to eq(4) - expect(::Host.count).to eq(11) - expect(::Service.count).to eq(1) - end - - xit 'creates the correct model associations' do - # expect(::Group.find_by(oauth_domain: 'ASC').hosts.count).to eq(6) - end - - xit 'creates the correct number of Documents and Elements' do - expect(Document.first.models.size).to eq(4) - expect(::Host.last.element.root.document).to eq(Document.first) - end - end - - # describe 'hierarchial yaml' do - # end - end -end diff --git a/solid-record/spec/lib/solid_record/element_spec.rb b/solid-record/spec/lib/solid_record/element_spec.rb deleted file mode 100644 index 5bc943df..00000000 --- a/solid-record/spec/lib/solid_record/element_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module SolidRecord - RSpec.describe 'Element' do - before(:each) do - # Pathname.new('.').glob(ROOT.join('spec/dummy/stack/app/models/*.rb')).each { |path| require_relative path } - Pathname.new('.').glob(ROOT.join('spec/dummy/infra/app/models/*.rb')).each { |path| require_relative path } - # Pathname.new('.').glob('../core/app/models/*.rb').each { |path| require_relative path } - # SolidRecord::DataStore.load - DataStore.load - # Dir.chdir(ROOT) do - # SolidRecord::PathLoader.new(path: 'spec/dummy/stack/data', path_map: 'segments', recurse: true) - # end - end - - describe 'count' do - it 'creates the correct number of Files' do - file = Pathname.new(ROOT.join('spec/dummy/infra/data/groups.yml')) - _doc = YamlDocument.new(klass_type: '::Group', path: file) - # binding.pry - # file_count = Pathname.new('spec/fixtures').glob('**/*').select(&:file?).size - # expect(SolidRecord::File.count).to eq(file_count) - end - end - end -end diff --git a/solid-record/spec/spec_helper.rb b/solid-record/spec/spec_helper.rb deleted file mode 100644 index 132b9a0c..00000000 --- a/solid-record/spec/spec_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'bundler/setup' -require 'pry' -require 'solid_record' - -ROOT = Pathname.new(__dir__).join('..') diff --git a/solid-support/.pryrc b/solid-support/.pryrc deleted file mode 100644 index 527990e5..00000000 --- a/solid-support/.pryrc +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -# .pryrc - -# SolidRecord.glob_pattern = '*.yml' -# SolidRecord.path_column = 'sr_path' -# SolidRecord.key_column = 'sr_key' -# SolidRecord.reference_suffix = 'baby' -# SolidRecord.parser = :yaml - -def blog - Pathname.new('.').glob('spec/dummy/blog/app/models/*.rb').each { |path| require_relative path } - SolidRecord.path_maps = [{ path: 'spec/dummy/blog/data' }] - SolidRecord.load -end - -def infra - Pathname.new('.').glob('spec/dummy/infra/app/models/*.rb').each { |path| require_relative path } - SolidRecord.path_maps = [{ path: 'spec/dummy/infra/data' }] - SolidRecord.path_map = SolidRecord::MyPathMap - SolidRecord.load -end - -def stack - Pathname.new('.').glob('spec/dummy/stack/app/models/*.rb').each { |path| require_relative path } - SolidRecord.path_maps = [{ path: 'spec/dummy/stack/data', map: './segments', recursive: true }] - SolidRecord.load -end diff --git a/solid-support/Gemfile b/solid-support/Gemfile deleted file mode 100644 index e0a009ab..00000000 --- a/solid-support/Gemfile +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -# Specify your gem's dependencies in solid-record.gemspec -gemspec - -gem 'pry' -# gem "rake", "~> 13.0" diff --git a/solid-support/README.md b/solid-support/README.md deleted file mode 100644 index 7da038ad..00000000 --- a/solid-support/README.md +++ /dev/null @@ -1 +0,0 @@ -# Solid Support diff --git a/solid-support/lib/ext/pathname.rb b/solid-support/lib/ext/pathname.rb deleted file mode 100644 index 4a54a1a9..00000000 --- a/solid-support/lib/ext/pathname.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'active_support/inflector' - -class Pathname - # Argument is a string or Pathname - # Returns a Pathname when matched or nil - def matchpath(path_map) - return unless (str = last_element_match(path_map)) - - Pathname.new(str) - end - - # returns argument's path element in position one less than the size of the path elements - # path_map, self.to_s, result - # 'stacks/environments/targets', 'foo/bar/baz', 'targets' - # 'stacks/environments/targets', 'foo/bar', 'environments' - # 'stacks/environments/targets', 'foo', 'stacks' - def last_element_match(path_map) = path_map.to_s.split('/')[last_element_index] - - # 'foo/bar/baz' => 2, 'foo/bar' => 1, 'foo' => 0 - def last_element_index() = to_s.split('/').size - 1 - - # returns true if rootname is users or users.yml - def plural?() = name.eql?(name.pluralize) - - # returns true if rootname is user or user.yml - def singular?() = name.eql?(name.singularize) - - # users.yml => User - def safe_constantize() = classify.safe_constantize - - # Root name functionality - # users.yml => 'User' - def classify() = name.classify - - # users.yml => users - def name() = @name ||= rootname.delete_suffix(".#{extension}") - - # users.yml => yml - def extension() = @extension ||= rootname.split('.').last - - # Pathname.new('path/users.yml').rootname => 'users.yml' - def rootname() = @rootname ||= basename.to_s -end diff --git a/solid-support/lib/solid_support.rb b/solid-support/lib/solid_support.rb deleted file mode 100644 index 5613757d..00000000 --- a/solid-support/lib/solid_support.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require_relative 'ext/pathname' - -module SolidSupport - class Error < StandardError; end -end diff --git a/solid-support/solid-support.gemspec b/solid-support/solid-support.gemspec deleted file mode 100644 index 66f7d322..00000000 --- a/solid-support/solid-support.gemspec +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require_relative 'lib/solid_support/version' - -Gem::Specification.new do |spec| - spec.name = 'solid-support' - spec.version = SolidSupport::VERSION - spec.authors = ['Robert Roach'] - spec.email = ['rjayroach@gmail.com'] - - spec.summary = 'Support Classes' - spec.description = 'A set of Support Classes' - spec.homepage = 'https://cnfs.io' - spec.license = 'MIT' - spec.required_ruby_version = '>= 3.0.0' - - # spec.metadata['allowed_push_host'] = "TODO: Set to 'https://mygemserver.com'" - - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/cnfs.io/solid-support' - # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject do |f| - (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) - end - end - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] - - # Uncomment to register a new dependency of your gem - spec.add_dependency 'activesupport', '~> 6.1' - - spec.add_development_dependency 'guard-rspec', '~> 4.7' - spec.add_development_dependency 'pry-byebug', '~> 3.9' - spec.add_development_dependency 'rspec', '~> 3.10' - spec.add_development_dependency 'rubocop', '~> 1.22' - - # For more information and examples about making a new gem, checkout our - # guide at: https://bundler.io/guides/creating_gem.html -end diff --git a/solid-support/spec/lib/solid_support/pathname_spec.rb b/solid-support/spec/lib/solid_support/pathname_spec.rb deleted file mode 100644 index d9193753..00000000 --- a/solid-support/spec/lib/solid_support/pathname_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable Lint/EmptyClass -class Stack -end -# rubocop:enable Lint/EmptyClass - -module SolidSupport - RSpec.describe 'Pathname' do - let(:subject) { Pathname } - - describe 'last_element_match' do - let(:path_map) { 'stacks/environments/targets' } - - it 'returns the correct element' do - expect(subject.new('backend').last_element_match(path_map)).to eq('stacks') - end - - it 'returns the correct element' do - expect(subject.new('backend/production').last_element_match(path_map)).to eq('environments') - end - - it 'returns the correct element' do - expect(subject.new('backend/production/cluster').last_element_match(path_map)).to eq('targets') - end - - it 'returns nil when no element matches' do - expect(subject.new('backend/production/cluster/default').last_element_match(path_map)).to be_nil - end - end - - describe 'inflector' do - it 'inflectors return the proper response when file name is singular' do - expect(subject.new('test.yml').singular?).to be_truthy - expect(subject.new('test.yml').plural?).to be_falsey - end - - it 'inflectors return the proper response when file name is plural' do - expect(subject.new('tests.yml').singular?).to be_falsey - expect(subject.new('tests.yml').plural?).to be_truthy - end - end - - describe 'safe_constantize' do - let(:subject) { Pathname.new('stacks') } - - it 'returns the correct class' do - expect(subject.name).to eq('stacks') - expect(subject.classify).to eq('Stack') - expect(subject.safe_constantize).to eq(::Stack) - end - end - end -end diff --git a/solid-support/spec/spec_helper.rb b/solid-support/spec/spec_helper.rb deleted file mode 100644 index a0509f98..00000000 --- a/solid-support/spec/spec_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -require 'bundler/setup' -require 'pry-byebug' -require 'solid_support' diff --git a/cnfs/.gitignore b/solid_record/.gitignore similarity index 100% rename from cnfs/.gitignore rename to solid_record/.gitignore diff --git a/solid_record/.rspec b/solid_record/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/solid_record/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/solid_record/.rubocop.yml b/solid_record/.rubocop.yml new file mode 100644 index 00000000..d4c64419 --- /dev/null +++ b/solid_record/.rubocop.yml @@ -0,0 +1,17 @@ +--- +inherit_from: ../.rubocop.yml +AllCops: + Exclude: + - 'lib/solid_record/associations.rb' + - 'spec/dummy/one_stack/models/**/*.rb' +Lint/EmptyClass: + Exclude: + - 'spec/**/*.rb' +RSpec/BeforeAfterAll: + Enabled: false +RSpec/NestedGroups: + Max: 5 +Style/CommentedKeyword: + Enabled: false +RSpec/MultipleMemoizedHelpers: + Max: 9 diff --git a/solid-record/CHANGELOG.md b/solid_record/CHANGELOG.md similarity index 100% rename from solid-record/CHANGELOG.md rename to solid_record/CHANGELOG.md diff --git a/solid_record/Gemfile b/solid_record/Gemfile new file mode 100644 index 00000000..7f4f5e95 --- /dev/null +++ b/solid_record/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec diff --git a/solid_record/Guardfile b/solid_record/Guardfile new file mode 100644 index 00000000..dedb2f52 --- /dev/null +++ b/solid_record/Guardfile @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +group :rgf, halt_on_fail: true do # red_green_refactor + guard :rspec, cmd: 'rspec' do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } + end + + guard :rubocop, all_on_start: false, notification: true do + watch(%r{^spec/models/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) + watch(%r{^app/(.+)\.rb$}) + end +end + +notification :tmux, + display_message: true, + change_color: true + +# notification :tmux, +# display_message: true, +# timeout: 5, # in seconds +# default_message_format: '%s >> %s', +# # the first %s will show the title, the second the message +# # Alternately you can also configure *success_message_format*, +# # *pending_message_format*, *failed_message_format* +# line_separator: ' > ', # since we are single line we need a separator +# color_location: 'status-left-bg', # to customize which tmux element will change color +# +# # Other options: +# default_message_color: 'black', +# # success: 'colour150', +# # failure: 'colour174', +# # pending: 'colour179', +# success: 'green', +# failure: 'red', +# pending: 'yellow', +# +# # Notify on all tmux clients +# display_on_all_clients: false, +# color_location: %w[status-left-bg pane-active-border-fg pane-border-fg] + +# notification(:tmux, { +# display_title: true, +# display_on_all_clients: true, +# success: 'colour150', +# failure: 'colour174', +# pending: 'colour179', +# color_location: %w[status-left-bg pane-active-border-fg pane-border-fg], +# }) if ENV['TMUX'] diff --git a/solid-record/LICENSE.txt b/solid_record/LICENSE.txt similarity index 100% rename from solid-record/LICENSE.txt rename to solid_record/LICENSE.txt diff --git a/solid-record/README.md b/solid_record/README.md similarity index 100% rename from solid-record/README.md rename to solid_record/README.md diff --git a/solid-record/Rakefile b/solid_record/Rakefile similarity index 100% rename from solid-record/Rakefile rename to solid_record/Rakefile diff --git a/solid-support/bin/console b/solid_record/bin/console similarity index 80% rename from solid-support/bin/console rename to solid_record/bin/console index 4cc0dbda..4a5d024b 100755 --- a/solid-support/bin/console +++ b/solid_record/bin/console @@ -3,6 +3,6 @@ require 'bundler/setup' require 'pry' -require 'solid_support' +require 'solid_record' Pry.start diff --git a/solid-record/bin/setup b/solid_record/bin/setup similarity index 100% rename from solid-record/bin/setup rename to solid_record/bin/setup diff --git a/solid_record/lib/ext/pathname.rb b/solid_record/lib/ext/pathname.rb new file mode 100644 index 00000000..fd916818 --- /dev/null +++ b/solid_record/lib/ext/pathname.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class Pathname + # @return [Boolean] true if rootname is plural, e.g. users + def plural? = name.eql?(name.pluralize) + + # @example + # Pathname.new('user.yml').singular? # => true + # Pathname.new('users.yml').singular? # => false + # @return [Boolean] true if rootname is singular, e.g. user + def singular? = name.eql?(name.singularize) + + # @example + # Pathname.new('users.yml').name # => 'users' + # @return [String] #rootname without the extension + def name = @name ||= rootname.end_with?('.') ? rootname.chop : rootname.delete_suffix(".#{extension}") + + # @example + # Pathname.new('users.yml').extension # => 'yml' + # @return [String] + def extension + @extension ||= begin + ret_val = rootname.split('.') + ret_val.size < 2 ? '' : ret_val.last + end + end + + # @example + # Pathname.new('path/users').rootname # => 'users' + # Pathname.new('path/users.yml').rootname # => 'users.yml' + # @return [String] basename.to_s + def rootname = @rootname ||= basename.to_s + + # Simplify call to formatted output of file contents + def puts = Kernel.puts(read) + + # Copy to another file + def cp(dest) = FileUtils.cp(self, dest) +end diff --git a/solid-record/lib/ext/table_definition.rb b/solid_record/lib/ext/table_definition.rb similarity index 69% rename from solid-record/lib/ext/table_definition.rb rename to solid_record/lib/ext/table_definition.rb index fd46c08e..aa213dc9 100644 --- a/solid-record/lib/ext/table_definition.rb +++ b/solid_record/lib/ext/table_definition.rb @@ -7,7 +7,8 @@ def solid(*_args, **options) end def references(*args, **options) - string("#{args.first}_#{SolidRecord.reference_suffix}".to_sym, type: :string, **options) if options.delete(:solid) + attr_name = "#{args.first}_#{SolidRecord.config.reference_suffix}".to_sym + string(attr_name, type: :string, **options) if options.delete(:solid) super(*args, type: :integer, **options) end end diff --git a/solid_record/lib/solid_record.rb b/solid_record/lib/solid_record.rb new file mode 100644 index 00000000..9811ecf6 --- /dev/null +++ b/solid_record/lib/solid_record.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'active_record' +require 'active_record/fixtures' +require 'sqlite3' + +ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') + +require 'lockbox' +require 'tty-tree' +require 'tty-prompt' +require 'tty-screen' + +require_relative 'ext/pathname' +require_relative 'solid_record/concerns/tree_view' +require_relative 'solid_record/version' + +# +# | First Header | Second Header | +# | ------------- | ------------- | +# | Content Cell | Content Cell | +# | Content Cell | Content Cell | +#
+ +module SolidRecord + class Error < StandardError; end + + class << self + attr_writer :logger + + def config = @config ||= config_set + + def config_set + config = ActiveSupport::OrderedOptions.new + config.class_map = {} + config.document_map = { yml: :yaml, yaml: :yaml } + # config.encryption_key = Lockbox.generate_key + config.flush_cache_on_exit = true + config.glob = '*.yml' + config.load_paths = [] + config.raise_on_error = false + config.reference_suffix = :name + # config.sandbox = true + config.schema_file = nil # Path to a file that defines an ActiveRecord::Schema + config.view_options = ActiveSupport::OrderedOptions.new + config.view_options.help_color = :cyan + config + end + end +end + +require_relative 'solid_record/concerns/extension' if defined? SolidSupport + +require_relative 'solid_record/data_store' + +require_relative 'solid_record/concerns/table' +require_relative 'solid_record/segment' + +require_relative 'solid_record/path' + +require_relative 'solid_record/dir' +require_relative 'solid_record/dir_generic' +require_relative 'solid_record/dir_instance' +require_relative 'solid_record/dir_association' +require_relative 'solid_record/dir_has_many' + +require_relative 'solid_record/file' +require_relative 'solid_record/concerns/a_e' +require_relative 'solid_record/association' +require_relative 'solid_record/element' + +require_relative 'solid_record/concerns/encryption' +require_relative 'solid_record/concerns/persistence' +require_relative 'solid_record/concerns/model' +require_relative 'solid_record/base' + +require_relative 'solid_record/model_view' + +module SolidRecord + class ColorFormatter < Logger::Formatter + class << self + def call(severity, timestamp, _progname, msg) + ActiveSupport::LogSubscriber.new.send(:color, "#{timestamp}, #{severity}: #{msg}\n", color(severity)) + end + + def color(severity) = { 'FATAL' => :red, 'WARN' => :yellow, 'INFO' => :green }[severity] + end + end + + # Simple formatter which only displays the message. + class SimpleFormatter < ::Logger::Formatter + # This method is invoked when a log event occurs + def self.call(_severity, _timestamp, _progname, msg) + "#{msg.is_a?(String) ? msg : msg.inspect}\n" + end + end + + class << self + def logger = @logger ||= ActiveSupport::Logger.new($stdout) + + def raise_or_warn(err, warn = nil) + raise err if SolidRecord.config.raise_on_error + + SolidRecord.logger.warn { warn || err.message } + SolidRecord.logger.debug { err.backtrace.join("\n") } if err.backtrace + nil + end + end + + SolidRecord.logger.formatter = SimpleFormatter +end + +Kernel.at_exit { SolidRecord.at_exit } diff --git a/solid_record/lib/solid_record/association.rb b/solid_record/lib/solid_record/association.rb new file mode 100644 index 00000000..72db9b62 --- /dev/null +++ b/solid_record/lib/solid_record/association.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SolidRecord + # An Association represents as `has_many` association on a model + # The values attribute may contain an Array of model values or Hash of model values + # In either case the values represent a result set and not a single instance + class Association < Segment + include AE + + store :config, accessors: %i[name serializer] + + before_create :identify_serializer + + after_create :create_elements + + def identify_serializer = self.serializer ||= values.class.to_s.underscore + + def create_elements = send("create_from_#{serializer}".to_sym) + + def create_from_array + values.each { |model_values| create_element(model_values[model_class.key_column], model_values) } + end + + def create_from_hash = values.each { |model_key, model_values| create_element(model_key, model_values) } + + def create_element(model_key, model_values) + create_segment(Element, model_class_name: model_class_name, key: model_key, values: model_values) + end + + def to_solid(flag = nil) = send("to_solid_#{serializer}", flag) + + def to_solid_array(flag = nil) = segments.flagged_for(flag).map(&:to_solid) + + def to_solid_hash(flag = nil) + segments.flagged_for(flag).each_with_object({}) { |e, hash| hash.merge!(e.to_solid_hash) } + end + + def tree_label = "#{name} (#{type.demodulize} - #{serializer})" + end +end diff --git a/solid_record/lib/solid_record/base.rb b/solid_record/lib/solid_record/base.rb new file mode 100644 index 00000000..59b9d043 --- /dev/null +++ b/solid_record/lib/solid_record/base.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidRecord + class Base < ActiveRecord::Base + self.abstract_class = true + include SolidRecord::Model + end +end diff --git a/solid_record/lib/solid_record/concerns/a_e.rb b/solid_record/lib/solid_record/concerns/a_e.rb new file mode 100644 index 00000000..77ae78b1 --- /dev/null +++ b/solid_record/lib/solid_record/concerns/a_e.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module SolidRecord + module AE + extend ActiveSupport::Concern + + included do + # Values are passed in from File and Association to Elements and passed on to model_class.create + attr_accessor :values + + validates :model_class_name, presence: true + + delegate :pathname, :namespace, to: :root + end + + # The class of the model managed (created, updated, destroyed) by an instance of Element + def model_class(class_name = model_class_name) + @model_class ||= retc(class_name) || retc(namespace, class_name) || + SolidRecord.raise_or_warn(StandardError.new(to_json)) + # binding.pry if @model_class.nil? + # @model_class + end + + def retc(*ary) = ary.compact.join('/').classify.safe_constantize + end +end diff --git a/core/app/models/concerns/encryption.rb b/solid_record/lib/solid_record/concerns/encryption.rb similarity index 57% rename from core/app/models/concerns/encryption.rb rename to solid_record/lib/solid_record/concerns/encryption.rb index b2d56e8d..91369bbc 100644 --- a/core/app/models/concerns/encryption.rb +++ b/solid_record/lib/solid_record/concerns/encryption.rb @@ -1,24 +1,30 @@ # frozen_string_literal: true -module Concerns +module SolidRecord module Encryption extend ActiveSupport::Concern class_methods do - def attr_encrypted(*attrs) - encrypted_attrs.append(*attrs) - end + def attr_encrypted(*attrs) = encrypted_attrs.append(*attrs) - def encrypted_attrs - @encrypted_attrs ||= [] - end + def encrypted_attrs = @encrypted_attrs ||= [] end - included do - before_validation :decrypt_attrs + included { before_validation :decrypt_attrs } + + # Classes including this module can override this method to provide alternative keys + def encryption_key = SolidRecord.config.encryption_key + + def encrypt_attrs + self.class.encrypted_attrs.each do |attr| + next unless (value = send(attr)) + + send("#{attr}=", encrypt(value)) + end + self end - # When a Node creates a new record it passes in raw yaml which may include encrypted values + # When a new record is created it passes in raw yaml which may include encrypted values # In the model all encrypted values are decrypted befoer saving so that: # # 1. The attribute has the decrypted value for processing, and @@ -37,44 +43,16 @@ def decrypt_attrs end end - # Return as_json with all encrypted_attrs fields encrypted - # Used for saving the object to the filesystem - def as_json_encrypted - with_encrypted_attrs { as_json } - end - - # Encrypt each encrypted_attr, yield to the caller then decrypt each encrypted_attr - def with_encrypted_attrs - self.class.encrypted_attrs.each do |attr| - next unless (value = send(attr)) - - send("#{attr}=", encrypt(value)) - end - - ret_val = yield if block_given? - - self.class.encrypted_attrs.each do |attr| - next unless (value = send(attr)) - - send("#{attr}=", decrypt(value)) - end - - ret_val - end - - # Returns an encrypted string - # - # ==== Parameters - # plaintext:: the string to be encrypted - def encrypt(plaintext) - box.encrypt(plaintext) - end + # @param plaintext [String] text to be encrypted + # @return [String] encrypted plaintext + def encrypt(plaintext) = box.encrypt(plaintext) + # @param ciphertext [String] encrypted text + # @return [String] decrypted plaintext def decrypt(ciphertext) box.decrypt(ciphertext) rescue Lockbox::DecryptionError => e - Cnfs.logger.warn(e.message) - nil + SolidRecord.raise_or_warn(e) end # def encrypt_file(file_name, plaintext = nil) @@ -89,8 +67,6 @@ def decrypt(ciphertext) private - def box - @box ||= Lockbox.new(key: key) - end + def box = @box ||= Lockbox.new(key: encryption_key) end end diff --git a/solid_record/lib/solid_record/concerns/extension.rb b/solid_record/lib/solid_record/concerns/extension.rb new file mode 100644 index 00000000..d7162019 --- /dev/null +++ b/solid_record/lib/solid_record/concerns/extension.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidRecord + def self.gem_root = @gem_root ||= Pathname.new(__dir__).join('../..') + + class Extension < SolidSupport::Extension + # The config object belongs to the application so it is 'shared' with the app and other Extensions + # config.before_configuration do + # binding.pry + # puts 'SolidRecord before_configuration' + # end + + # config.before_initialize { |config| SolidRecord.config.merge!(config.solid_record || {}) } + config.before_initialize do |config| + binding.pry + SolidRecord.config.merge!(config.solid_record || {}) + end + + # config.before_eager_load do |config| + # puts 'SolidRecord before_eager_load' + # end + + # After all extensions have been required + config.after_initialize do |config| + # SolidRecord::DataStore.load(*config.solid_record.load_paths) + # SolidRecord.tables.select { |t| t.respond_to?(:after_load) }.each(&:after_load) + end + + def self.gem_root = SolidRecord.gem_root + end +end diff --git a/solid-record/lib/solid_record/model.rb b/solid_record/lib/solid_record/concerns/model.rb similarity index 86% rename from solid-record/lib/solid_record/model.rb rename to solid_record/lib/solid_record/concerns/model.rb index 66cad39c..36a2de4a 100644 --- a/solid-record/lib/solid_record/model.rb +++ b/solid_record/lib/solid_record/concerns/model.rb @@ -7,7 +7,7 @@ module Model included do include Table include Persistence - # include Associations + include Encryption end end end diff --git a/solid_record/lib/solid_record/concerns/persistence.rb b/solid_record/lib/solid_record/concerns/persistence.rb new file mode 100644 index 00000000..885da152 --- /dev/null +++ b/solid_record/lib/solid_record/concerns/persistence.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module SolidRecord + class << self + # The name of including model's has_one reference to the SolidRecord::Element instance + # that manges the model's persistence data + def element_attribute = 'element' + + # Array of models that have included the SolidRecord::Persistence concern + def persisted_models = @persisted_models ||= [] + + def skip_persistence_callbacks # rubocop:disable Metrics/MethodLength + persisted_models.each do |model| + persistence_callbacks.each { |cb| model.skip_callback(*cb) } + end + ret_val = yield if block_given? + rescue StandardError => e + SolidRecord.raise_or_warn(e) + ensure + persisted_models.each do |model| + persistence_callbacks.each { |cb| model.set_callback(*cb) } + end + ret_val + end + + def persistence_callbacks + [%i[create after __create_element], %i[update after __update_element], %i[destroy after __destroy_element]] + end + end + + module Persistence + extend ActiveSupport::Concern + + class_methods do + # The name of the table column that the yaml key is written to in the model + def key_column = 'key' + + def owner_association_name = nil + + def except_solid + reflect_on_all_associations(:belongs_to).each_with_object([primary_key, key_column]) do |a, ary| + ary.append(a.options[:foreign_key] || "#{a.name}_id") + end + end + + def belongs_to_names = reflect_on_all_associations(:belongs_to).map(&:name) + end + + included do + has_one SolidRecord.element_attribute.to_sym, class_name: 'SolidRecord::Element', as: :model + + delegate :__update_element, :__destroy_element, to: SolidRecord.element_attribute.to_sym + delegate :belongs_to_names, to: :class + + after_create :__create_element + after_update :__update_element + after_destroy :__destroy_element + + SolidRecord.persisted_models << self + end + + def __create_element + if belongs_to_names.size.eql?(1) # rubocop:disable Style/GuardClause + owner = send(belongs_to_names.first) + owner.element.add_model(self) + end + end + + def as_solid = attributes.except(*self.class.except_solid) + end +end diff --git a/solid-record/lib/solid_record/table.rb b/solid_record/lib/solid_record/concerns/table.rb similarity index 53% rename from solid-record/lib/solid_record/table.rb rename to solid_record/lib/solid_record/concerns/table.rb index 3b01ab48..d98d1202 100644 --- a/solid-record/lib/solid_record/table.rb +++ b/solid_record/lib/solid_record/concerns/table.rb @@ -2,15 +2,15 @@ module SolidRecord class << self - # Registry of models that have included the Persistence module - def models() = @models ||= [] + # Registry of models that have included this module + def tables = @tables ||= [] end module Table extend ActiveSupport::Concern included do - SolidRecord.models << self + SolidRecord.tables << self end end end diff --git a/solid_record/lib/solid_record/concerns/tree_view.rb b/solid_record/lib/solid_record/concerns/tree_view.rb new file mode 100644 index 00000000..1a359c37 --- /dev/null +++ b/solid_record/lib/solid_record/concerns/tree_view.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SolidRecord + module TreeView + extend ActiveSupport::Concern + + def to_tree = puts(TTY::Tree.new(tree_label => as_tree).render) + + def as_tree + send(tree_assn).each_with_object([]) do |item, ary| + ary.append(item.send(tree_assn).empty? ? item.tree_label : { item.tree_label => item.as_tree }) + end + end + end +end diff --git a/solid_record/lib/solid_record/data_store.rb b/solid_record/lib/solid_record/data_store.rb new file mode 100644 index 00000000..b8653a1e --- /dev/null +++ b/solid_record/lib/solid_record/data_store.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + + require 'pry' +module SolidRecord + class << self + def tmp_path = @tmp_path ||= Pathname.new(::Dir.mktmpdir) + # def tmp_path + # @tmp_path ||= Pathname.new(::Dir.mktmpdir) + # end + + def status = @status || (@status = ActiveSupport::StringInquirer.new('')) + + def status=(value) + @status = ActiveSupport::StringInquirer.new(value.to_s) + end + + def setup + ActiveRecord::Migration.verbose = defined?(SPEC_ROOT) ? false : logger.level.eql?(0) + config.schema_file ? load(config.schema_file) : DataStore.create_schema_from_tables + self.status = :unloaded + end + + def load + clean unless status.unloaded? + setup + self.status = :loading + # toggle_callbacks do + config.load_paths.each do |path| + lp = Path.add(source: path) + raise_or_warn(StandardError.new(lp.errors.full_messages.join("\n"))) unless lp.persisted? + end + # end + self.status = :loaded + true + end + + def toggle_callbacks(&block) + with_model_element_callbacks do + skip_persistence_callbacks(&block) + end + end + + def clean(make: config.sandbox) + tmp_path.rmtree if tmp_path.exist? && tmp_path.children.size.positive? + tmp_path.mkpath if make + end + + def at_exit + # This is called even when using Kernel.exit + flush_cache if status.loaded? && config.flush_cache_on_exit + clean(make: false) + end + + def flush_cache + Element.flagged_for(:destroy).each(&:destroy) + File.flagged.each(&:write) + Segment.update_all(flags: nil) + end + end + + class DataStore + class << self + def create_schema_from_tables + ActiveRecord::Schema.define do |schema| + require_relative '../ext/table_definition' + SolidRecord.tables.select { |table| table.respond_to?(:create_table) }.each do |table| + table.create_table(schema) + table.reset_column_information + end + end + end + + # Dump the latest version of the schema to a file + # + # Create a schema which can be used with NullDB to emulate models without having + # the actual classes or underlying database prsent + def schema_dump(file_name = nil) + schema = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, StringIO.new).string + return schema unless file_name + + File.open(file_name, 'w') { |f| f.puts(schema) } + end + end + end +end diff --git a/solid_record/lib/solid_record/dir.rb b/solid_record/lib/solid_record/dir.rb new file mode 100644 index 00000000..9b237985 --- /dev/null +++ b/solid_record/lib/solid_record/dir.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidRecord + class Dir < Path + delegate :children, :rmdir, to: :pathname + + after_create :process_contents + + after_commit :rmdir, on: :destroy + + # def create_hash = super.merge(path: path.to_s) + + def write = segments.count.zero? ? destroy : nil + + def tree_label = "#{name} (#{type.demodulize})" + + def invalid_path(path) = path.to_s.delete_prefix("#{root.pathname.parent}/") + + def msg(assn) = "is not a valid #{assn} association on #{owner.class.name}" + end +end diff --git a/solid_record/lib/solid_record/dir_association.rb b/solid_record/lib/solid_record/dir_association.rb new file mode 100644 index 00000000..ca6501ff --- /dev/null +++ b/solid_record/lib/solid_record/dir_association.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# NOTE: This is an internal class and should not be called by the user +module SolidRecord + class DirAssociation < Dir + validates :owner, presence: true + validates :model, presence: false + + def process_contents + process_has_many_files + # pit(pathname.glob(glob).select(&:plural?), File, content_format: :plural) + process_belongs_to_files + process_has_many_dirs + # pit(children.select(&:directory?).select(&:plural?), DirHasMany) + process_belongs_to_dirs + end + + def process_has_many_files # e.g. favorites/questions.yml + pathname.glob(glob).select(&:plural?).each do |path| + if owner_assn(:has_many).include?(path.name) + create_segment(File, path: path.to_s, model_class_name: path.name, content_format: :plural) + else + SolidRecord.raise_or_warn(StandardError.new("#{invalid_path(path)} #{msg(:has_many)}")) + end + end + end + + def pit(entries, klass, **hash) + entries.each do |path| + if owner_assn(:has_many).include?(path.name) + create_segment(klass, hash.merge(path: path.to_s, model_class_name: path.name)) + else + SolidRecord.raise_or_warn(StandardError.new("#{invalid_path(path)} #{msg(:has_many)}")) + end + end + end + + def process_belongs_to_files + pathname.glob(glob).select(&:singular?).each do |path| + # TODO: check if the path.name exists as a has_one on the owner + # TODO: If not then raise an error + end + end + + def process_has_many_dirs + children.select(&:directory?).select(&:plural?).each do |path| + if owner_assn(:has_many).include?(path.name) + create_segment(DirHasMany, path: path.to_s, model_class_name: path.name) + else + SolidRecord.raise_or_warn(StandardError.new("#{invalid_path(path)} #{msg(:has_many)}")) + end + end + end + + def process_belongs_to_dirs + children.select(&:directory?).select(&:singular?).each do |path| + SolidRecord.raise_or_warn(StandardError.new("#{invalid_path(path)} belongs_to dir is not allowed")) + end + end + end +end diff --git a/solid_record/lib/solid_record/dir_generic.rb b/solid_record/lib/solid_record/dir_generic.rb new file mode 100644 index 00000000..ddecc9af --- /dev/null +++ b/solid_record/lib/solid_record/dir_generic.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# Map names of Files and Dirs to recognized classes in the order of priority: +# 1. config.class_map, 2. path.name and 3. model_class_name (default) +# Files with recognized mappings will have instances of that class created +# For Dirs +# +# 1. model_class_name is the default model_type if (class_map and pathname.name).safe_constantize fail +# 2. process the documents: providers.yml, development.yml -> Component +module SolidRecord + class DirGeneric < Dir + def process_contents + process(:file?) do |class_name| + # Files that are of a recognized class, eg providers.yml -> Provider are has_many + # Files that are not recognized are a has_one + [File, { content_format: (class_name ? :plural : :singular) }] + end + process(:directory?) do |class_name| + [class_name ? DirHasMany : DirGeneric, {}] + end + end + + def process(filter) + children.select(&filter).each do |path| + class_name = classic(class_map[path.name] || path.name) + + klass, hash = yield(class_name) + create_segment(klass, hash.merge(path: path.to_s, model_class_name: class_name || model_class_name)) + + # hash.merge!(path: path.to_s, model_class_name: class_name || model_class_name) + # create_segment(klass, hash) + # TODO: If DirInstance then check if there is a File of the same name + # In fact, this should have already been processed by RootElement, but that needs to be checked + end + end + + def classic(name) = [namespace, name].compact.join('/').classify.safe_constantize&.to_s + end +end diff --git a/solid_record/lib/solid_record/dir_has_many.rb b/solid_record/lib/solid_record/dir_has_many.rb new file mode 100644 index 00000000..b199803d --- /dev/null +++ b/solid_record/lib/solid_record/dir_has_many.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SolidRecord + class DirHasMany < Dir + def process_contents + children.select(&:file?).each do |path| + create_segment(File, path: path.to_s, model_class_name: model_class_name, content_format: :singular) + end + + children.select(&:directory?).each do |path| + SolidRecord.raise_or_warn(StandardError.new("#{invalid_path(path)} has_many dir is not allowed")) + end + end + end +end diff --git a/solid_record/lib/solid_record/dir_instance.rb b/solid_record/lib/solid_record/dir_instance.rb new file mode 100644 index 00000000..721d6aa0 --- /dev/null +++ b/solid_record/lib/solid_record/dir_instance.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module SolidRecord + class DirInstance < Dir + def process_contents + self.model_class_name ||= pathname.name.classify + # binding.pry + pathname.glob(glob).each do |path| + create_segment(File, path: path.to_s, model_class_name: model_class_name, content_format: :singular) + end + + # TODO: Implement + # children.select(&:directory?).each do |path| + # end + end + end +end diff --git a/solid_record/lib/solid_record/element.rb b/solid_record/lib/solid_record/element.rb new file mode 100644 index 00000000..dfbc2c1d --- /dev/null +++ b/solid_record/lib/solid_record/element.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +module SolidRecord + class << self + def with_model_element_callbacks + model_element_callbacks.each { |cb| Element.set_callback(*cb) } + ret_val = yield + rescue StandardError => e + SolidRecord.raise_or_warn(e) + ensure + model_element_callbacks.each { |cb| Element.skip_callback(*cb) } + ret_val + end + + def model_element_callbacks = [%i[create before create_model], %i[create after create_associations]] + end + + class ModelSaveError < Error; end + + class Element < Segment + include AE + + # TODO: validate that model_class_name and model_type are identical + + # From RootElement + after_create :create_elements_in_path, if: -> { model_path.exist? } + + def create_elements_in_path = create_segment(DirAssociation, path: model_path.to_s, owner: model) + + def model_path = @model_path ||= pathname.parent.join(model_key) # bling/groups.yml->asc => bling/asc + # END: From RootElement + + # TODO: Note where each of these attributes originate from + # The ID used to reference the model + attr_accessor :key + + # The model instance of model_class that is created from this element + belongs_to :model, polymorphic: true + + # TODO: FIX: model_class is wrong now. it needs to be model.class + # Or in this class overrid model_class to point to model.class + delegate :key_column, to: :model_class + + # NOTE: These callbacks are enabled/disabled by SolidRecord.with_model_element_callbacks block + # They are listed here only for reference + # before_create :create_model + # after_create :create_associations + + # The created model's string key in the table's user defined 'key_column' + def model_key = model.send(key_column) + + # Create an instance of model_class and assign it to the model attribute + def create_model # rubocop:disable Metrics/AbcSize + self.model = model_class.create(model_values) + # binding.pry + raise ModelSaveError, "Err #{model_class}: #{model.errors.full_messages.join('. ')}" unless model.persisted? + rescue ModelSaveError, ActiveModel::UnknownAttributeError, ActiveRecord::SubclassNotFound => e + binding.pry + SolidRecord.raise_or_warn(e, "#{model_class} #{e.message} Check values for:\n#{config_values}") + end + + def config_values + "SolidRecord.config.namespace: #{SolidRecord.config.namespace}\n" \ + "#{model_class}.owner_association_name: #{model_class.owner_association_name}\n" \ + "#{model_class}.key_column: #{model_class.key_column}" \ + end + + # @return [Hash] Merge the original values with owner and any belongs_to calculated IDs + def model_values + # binding.pry if model_class_name.eql? 'favorites' + values.except(*model_assn_names).merge(belongs_to_attributes).merge(owner_attribute).merge(key_column => key) + end + + def owner_attribute + # def owner_attribute() = owner ? { owner.class.base_class.name.downcase => owner } : {} + return {} if owner.nil? + return { model_class.owner_association_name => owner } if model_class.owner_association_name + if (bt = model_class.reflect_on_all_associations(:belongs_to)).size.eql?(1) + return { bt.first.name => owner } + end + + SolidRecord.raise_or_warn(StandardError.new('no owner found')) + {} + end + + # def owner_attribute + # # abstract_class returns true if the class is abstract and nil if not + # is_sti = owner.nil? ? false : owner.class.superclass.abstract_class.nil? + # owner ? { 'owner' => owner } : {} + # ret_val = owner ? { model_class.owner_association_name => owner } : {} + # binding.pry if owner + # ret_val + # end + + # For each belongs_to association on the model_class + # @return [Hash] of model_foreign_key_column => identified foreign key value + def belongs_to_attributes + model_class.reflect_on_all_associations(:belongs_to).each_with_object({}) do |assn, _hash| + fk_attribute = "#{assn.name}_#{SolidRecord.config.reference_suffix}" # user_name + # In a monolithic document the foreign key value is not part of the document hierarchy so it will be nil + next unless (fk_value = values[fk_attribute]) # rubocop:disable Lint/UselessAssignment + + # model_fk = assn.options[:foreign_key] || "#{assn.name}_id" + # TODO: Maybe check and raise if not model_class.column_names.include?(model_fk) + # binding.pry + # raise 'TODO: This will be a bug' + # assn_id = root.identify(key: fk_value, type: assn.name.to_s.pluralize) + # hash[model_fk] = assn_id + end + end + + # If this element's payload includes associations then create elements for those + def create_associations + model_assn_names.each do |assn_name| + next unless (assn_values = values[assn_name]) + + create_segment(Association, name: assn_name, model_class_name: assn_name.classify, + values: assn_values, owner: model) + end + end + + # def assn_names = model_assn.map(&:name).map(&:to_s) + + def model_assn_names(type = :has_many) = association_names(model_class, type) + + # The Persistence Concern adds lifecycle methods to the model's callback chains + # When a model's lifecycle event is triggred it invokes its element's method of the same name + def __update_element = root.flag(:update) + + def __destroy_element + update(flags: flags << :destroy) + root.flag(:update) + end + + def to_solid_hash = { model_key => to_solid.except(key_column) } + + def to_solid = solid_elements.each_with_object(as_solid) { |e, hash| hash[e.name] = e.to_solid } + + def as_solid + model.encrypt_attrs if model.respond_to?(:encrypt_attrs) + model.as_solid.compact.sort.to_h + end + + # NOTE: This type replacement of Dir for Path is wrong b/c + # it will return other types of Dirs + def solid_elements = segments.where.not(type: 'SolidRecord::Dir') + + def tree_label = "#{model_key} (#{type.demodulize})" + + # Called from persistence#__after_create + def add_model(new_model) + assn = segments.find_by(model_class_name: new_model.class.name) + # TODO: This should use create_segment and be moved to association class + binding.pry + assn.segments.create(type: 'SolidRecord::Element', model: new_model, owner: model) + # create_segment(DirAssociation, path: model_path.to_s) + root.flag(:update) + end + end +end diff --git a/solid_record/lib/solid_record/file.rb b/solid_record/lib/solid_record/file.rb new file mode 100644 index 00000000..5bf9164e --- /dev/null +++ b/solid_record/lib/solid_record/file.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module SolidRecord + class File < Path + store :config, accessors: %i[content_format] + + delegate :delete, to: :pathname, prefix: true + + before_validation :set_defaults + + after_create :create_association + + after_commit :pathname_delete, on: :destroy, if: -> { pathname.exist? } + + def set_defaults + self.content_format ||= :plural + self.model_class_name ||= name + end + + def create_association = create_segment(Association, root: self, values: values) + + def flag(flag_type) = update(flags: flags << flag_type) # Called by Element + + # as_json strips Ruby object annotations + # NOTE: #to_solid comes from segments.first + def write = segments.count.zero? ? destroy : send("write_#{doc_type}".to_sym, segments.first.to_solid.as_json) + + def values = @values ||= content_format.eql?(:singular) ? { pathname.name => read } : read + + def read = send("read_#{doc_type}".to_sym) + + # Return a type based on the file's extension, e.g. .yml or .yaml returns :yaml + def doc_type = document_map || raise(StandardError, "File type for #{pathname.extension} not found") + + def document_map = @document_map ||= SolidRecord.document_map[pathname.extension] + + def read_yaml = YAML.load_file(path) || {} + + def write_yaml(content) = pathname.write(content.to_yaml) + + def tree_label = "#{pathname.basename} (#{type.demodulize})" + end + + def self.document_map = @document_map ||= config.document_map.transform_keys(&:to_s) +end diff --git a/solid_record/lib/solid_record/model_view.rb b/solid_record/lib/solid_record/model_view.rb new file mode 100644 index 00000000..c2d710e6 --- /dev/null +++ b/solid_record/lib/solid_record/model_view.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +module SolidRecord + class ModelView < TTY::Prompt + attr_accessor :model, :models, :action + + # @model is for methods that take a single model: create, show, destroy and edit + # @models is for methods that take an array of models: list + def initialize(**options) + @model = options.delete(:model) + @models = options.delete(:models) + super(**SolidRecord.config.view_options.merge(options)) + end + + def create + raise Error, 'Need to pass a model. this is a bug' unless model + raise Error, 'Create can only be called on new instances' if model.persisted? + + @action = :create + + modify + if model_has_sti? + self.model = model_sti_class.new(model.attributes) + modify_type + end + save + end + + def model_has_sti?() = model_class.has_attribute?(model_class.inheritance_column) + + def model_sti_class() = model.send(model_class.inheritance_column).safe_constantize + + def modify_type + view_class_name = "#{model_class.inheritance_column}View" + view_class = view_class_name.safe_constantize + raise Error, "#{view_class_name} not found. This is a bug. Please report." unless view_class + + view_class.new(view_class_options).modify + end + + def view_class_options() = { model: model, models: models } + + def show() = puts(model&.as_json) + + def destroy + model&.destroy if ask('Are you sure?') + end + + def edit + raise Error, 'Need to pass a model. this is a bug' unless model + raise Error, 'Edit can only be called on existing instances' unless model.persisted? + + @action = :edit + + modify + save + end + + def modify() = raise(NotImplementedError, "#{self.class.name} needs to implement #modify") + + def save + if model.valid? + model.save + else + puts '', 'Not saved due to errors:', model.errors.full_messages + end + end + + def list(action = nil) + return if list_items.empty? + + if action.nil? + puts list_items + return + end + item = list_items.first if list_items.size.eql?(1) + item ||= enum_select_val("Select #{model_class_name.demodulize}", choices: list_items) + # TODO: There is a bug that if two or more models have the same value for 'attribute' it will only + # return the first one. Use a where in and present a secondary list or another attribute to filter by + send(action) if (@model = models.find_by(list_attribute => item)) + end + + def select_type + return if available_types.empty? + + type = available_types.size.eql?(1) ? available_types.first : enum_select_val(:type, choices: available_types) + model.type = "#{type}::#{model.class.name}" + end + + def available_types() = @available_types ||= model_class.subclasses.map(&:to_s).map(&:deconstantize) + + def list_items() = @list_items ||= models&.map{ |m| m.send(list_attribute) } || [] + + # Override to display a different model attribute + def list_attribute = :name + + def model_class() = model_class_name.constantize + + def model_class_name() = self.class.name.delete_suffix('View') + + def options() = {} # 'edit' => true } # context.options + + + # Set an attribute from a Prompt.select result + # + # ==== Examples + # view_select(:instance_family, model.offers_by_family, model.family) + # + # ==== Parameters + # title:: The title text to display to the user. If it is also an attribute of the object ti will be set + # data:: An array of data to be presented to the user as a select list + # current:: The default value in the Array + def select_attr(key, title: nil, default: nil, choices: [], **options) + default ||= model.send(key) + ret_val = select_val(key, title: title, default: default, choices: choices, **options) + model.send("#{key}=", ret_val) + end + + def select_val(key, title: nil, default: nil, choices: [], **options) + title ||= key.to_s.humanize + + options[:help] ||= 'Type to filter results' + options[:filter] ||= true + options[:per_page] ||= per_page(choices) + options[:show_help] ||= :always + + select("#{title}:", choices, **options) do |menu| + # menu.help 'Type to filter results' + menu.default((choices.index(default) || 0) + 1) if default + # yield menu if block_given? + end + end + + def enum_select_attr(key, title: nil, default: nil, choices: [], **options) + default ||= model.send(key) + ret_val = enum_select_val(key, title: title, default: default, choices: choices, **options) + model.send("#{key}=", ret_val) + end + + def enum_select_val(key, title: nil, default: nil, choices: [], **options) + title ||= key.to_s.humanize + + options[:per_page] ||= per_page(choices) + + enum_select("#{title}:", choices, **options) do |menu| + menu.default(default) if default + end + end + + def ask_attr(key, title: nil, **options) + options[:default] ||= model.send(key) + ret_val = ask_val(key, title: title, **options) + model.send("#{key}=", ret_val) + end + + def ask_val(key, title: nil, **options) + title ||= key.to_s.humanize + ask(title, **options) + end + + def mask_attr(key, title: nil, **options) + options[:default] ||= model.send(key) + ret_val = mask_val(key, title: title, **options) + model.send("#{key}=", ret_val) + end + + def mask_val(key, title: nil, **options) + title ||= key.to_s.humanize + mask(title, **options) + end + + def yes_attr(key, title: nil, **options) + options.fetch(:default, model.send(key)) + # binding.pry + ret_val = yes_val(key, title: title, **options) + model.send("#{key}=", ret_val) + end + + def yes_val(key, title: nil, **options) + title ||= key.to_s.humanize + # binding.pry + yes?(title, **options) + end + + + # TODO: Refactor below methods + + def ask_hash(field) + model.send("#{field}=".to_sym, hv(field)) + end + + def hv(field) + model.send(field.to_sym).each_with_object({}) do |(key, value), hash| + hash[key] = ask(key, value: value) + end + end + + def per_page(array, buffer = 3) + [TTY::Screen.rows, array.size].max - buffer + end + + def random_string(name = nil, length: 12) + rnd = (0...length).map { rand(65..90).chr }.join.downcase + [name, rnd].compact.join('-') + end + end +end diff --git a/solid_record/lib/solid_record/path.rb b/solid_record/lib/solid_record/path.rb new file mode 100644 index 00000000..c20b1062 --- /dev/null +++ b/solid_record/lib/solid_record/path.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module SolidRecord + class Path < Segment + class << self + # If invoked from a subclass then just wrap in toggle_callbacks block + # If invoked on the Path class then first figure out which subclass it should be + def add(**attributes) + element_class = name.demodulize.eql?('Path') ? klass(attributes[:source]) : self + SolidRecord.toggle_callbacks { element_class.create(attributes) } + end + + def klass(source) + type = Pathname.new(source.to_s).directory? ? 'DirInstance' : 'File' + "SolidRecord::#{type}".safe_constantize + end + end + + def create_hash = super.merge(root: (source_path? ? self : root), model_class_name: model_class_name) + + # namespace and glob provided by user when creating a Path; defaults to SolidRecord.config values + store :config, accessors: %i[source status path namespace glob] + + delegate :realpath, :exist?, to: :source_path + + # From FileSystemElement + delegate :name, to: :pathname + + delegate :write, to: :parent, prefix: true, allow_nil: true + after_commit :parent_write, on: :destroy, if: -> { parent&.type&.eql?('SolidRecord::Dir') } + # END: From FileSystemElement + + before_validation :set_path, if: :source_path? + + validate :source_exists, if: :source_path?, on: :create + validate :source_uniqueness, if: %i[source_path? exist?], on: :create + + before_create :make_sandbox, if: %i[source_path? sandbox?] + + # if this is not the root record then pass to root + # if it is the root record then get the value from the :config hash + # if that value is nil then default to config.glob + def namespace = root&.namespace || super || SolidRecord.config.namespace + + def glob = root&.glob || super || SolidRecord.config.glob + + def set_path + self.path = (sandbox? ? SolidRecord.tmp_path.join(realpath.to_s.delete_prefix('/')) : realpath).to_s + end + + def source_exists + errors.add(:source, 'does not exist') unless exist? + end + + def source_uniqueness + self.class.all.each do |lp| + errors.add(:source, 'already exists') if lp.realpath.to_s.eql?(realpath.to_s) + end + end + + def source_path? = !source.nil? + + def sandbox? = SolidRecord.config.sandbox + + def make_sandbox + pathname.parent.mkpath unless pathname.parent.exist? + FileUtils.cp_r(realpath, pathname.parent) + end + + def set_defaults + self.model_class_name ||= class_map[name.singularize] || pathname.name + end + + def class_map = SolidRecord.config.class_map + + # def element_attributes + # { path: workpath.to_s, owner: (owner.is_a?(Proc) ? owner.call : owner) } + # end + + def pathname = @pathname ||= Pathname.new(path || '') + + def source_path = @source_path ||= Pathname.new(source&.to_s || '') + end +end diff --git a/solid_record/lib/solid_record/segment.rb b/solid_record/lib/solid_record/segment.rb new file mode 100644 index 00000000..fd8a0c76 --- /dev/null +++ b/solid_record/lib/solid_record/segment.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module SolidRecord + class Segment < ActiveRecord::Base + self.table_name_prefix = 'solid_record_' + include Table + include TreeView + + # Segments are self referencing; They belong to a parent segment and have_many child segments + belongs_to :parent, class_name: 'SolidRecord::Segment' + has_many :segments, foreign_key: 'parent_id' + + # root is the first element created by the user; It is at the top of the hierarchy and stores user supplied values + belongs_to :root, class_name: 'SolidRecord::Segment' + + # Owner of the model (optional), e.g. the model's belongs_to object + belongs_to :owner, polymorphic: true + + store :config, accessors: %i[model_class_name] + + serialize :flags, Set + + scope :flagged, -> { where.not(flags: nil) } + + scope :flagged_for, lambda { |flag = nil| + flag ? where('flags LIKE ?', "%#{flag}%") : where({}) + } + + # TODO: Wrap in begin/rescue + def create_segment(klass, hash) + segment = klass.create(create_hash.merge(hash)) + if segment.valid? + segments << segment + else + error_msg = segment.errors.full_messages.append(segment.to_json).join("\n").to_s + SolidRecord.raise_or_warn(StandardError.new(error_msg)) + end + end + + # The default values assigned to a new segment + def create_hash = { parent: self, root: root, owner: owner } + + def owner_assn(type = :has_many) = association_names(owner.class, type) + + def association_names(klass, type) = klass.reflect_on_all_associations(type).map(&:name).map(&:to_s) + + # NOTE: Implement TreeView by defining #tree_assn and tree_label + def tree_assn = :segments + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.string :type + t.references :parent + t.references :root + t.references :owner, polymorphic: true + t.references :model, polymorphic: true + t.string :flags + t.string :config + end + end + end + end +end diff --git a/solid-record/lib/solid_record/version.rb b/solid_record/lib/solid_record/version.rb similarity index 100% rename from solid-record/lib/solid_record/version.rb rename to solid_record/lib/solid_record/version.rb diff --git a/solid-record/solid-record.gemspec b/solid_record/solid_record.gemspec similarity index 79% rename from solid-record/solid-record.gemspec rename to solid_record/solid_record.gemspec index 0fa87db0..efafe968 100644 --- a/solid-record/solid-record.gemspec +++ b/solid_record/solid_record.gemspec @@ -3,7 +3,7 @@ require_relative 'lib/solid_record/version' Gem::Specification.new do |spec| - spec.name = 'solid-record' + spec.name = 'solid_record' spec.version = SolidRecord::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] @@ -34,12 +34,20 @@ Gem::Specification.new do |spec| # Uncomment to register a new dependency of your gem spec.add_dependency 'activerecord', '~> 6.1' + spec.add_dependency 'lockbox', '~> 0.6' spec.add_dependency 'sqlite3', '~> 1.4' + spec.add_dependency 'tty-prompt', '~> 0.22' + spec.add_dependency 'tty-screen', '~> 0.8' + spec.add_dependency 'tty-tree', '~> 0.4' + spec.add_development_dependency 'bundler', '~> 2.0' spec.add_development_dependency 'guard-rspec', '~> 4.7' + spec.add_development_dependency 'guard-rubocop' spec.add_development_dependency 'pry-byebug', '~> 3.9' spec.add_development_dependency 'rspec', '~> 3.10' - spec.add_development_dependency 'rubocop', '~> 1.22' + spec.add_development_dependency 'rubocop', '~> 1.41' + spec.add_development_dependency 'rubocop-performance' + spec.add_development_dependency 'rubocop-rspec', '~> 2.18' # For more information and examples about making a new gem, checkout our # guide at: https://bundler.io/guides/creating_gem.html diff --git a/solid_record/spec/dummy/.pryrc b/solid_record/spec/dummy/.pryrc new file mode 100644 index 00000000..76bf3c84 --- /dev/null +++ b/solid_record/spec/dummy/.pryrc @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidRecord + class << self + def a = Association + + def di = Dir + + def ds = DataStore + + def f = File + + def p = Path + + def se = Segment + + def tree(id = 1) = se.find(id).to_tree + end +end diff --git a/solid-record/Gemfile b/solid_record/spec/dummy/Gemfile similarity index 56% rename from solid-record/Gemfile rename to solid_record/spec/dummy/Gemfile index c78649a4..69ec0546 100644 --- a/solid-record/Gemfile +++ b/solid_record/spec/dummy/Gemfile @@ -2,7 +2,6 @@ source 'https://rubygems.org' -gemspec - gem 'pry' -gem 'solid-support', path: '../solid-support' +gem 'pry-byebug' +gem 'solid_record', path: '../..' diff --git a/solid_record/spec/dummy/bin/console b/solid_record/spec/dummy/bin/console new file mode 100755 index 00000000..8494b4b2 --- /dev/null +++ b/solid_record/spec/dummy/bin/console @@ -0,0 +1,30 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler' +Bundler.require + +module SolidRecord + logger.formatter = SolidRecord::ColorFormatter + logger.level ||= :info + config.sandbox = true + config.encryption_key = 'c0adafa60b624f300fe976a835e78bed7dcc15261c6250d021a5c3af86469213' + # config.load_paths.append('.') + def self.paths = @paths ||= Pathname.new(__dir__).parent.children.select(&:directory?) + + def self.h + paths.each do |p| + p.children.select(&:directory?).reject { |c| ary.include?(c.basename.to_s) }.each do |c| + puts c.to_s.split('/').last(2).join('.') + end + end + nil + end + + def self.ary = %w[models config] +end + +SolidRecord.paths.map { |p| p.join('console.rb') }.select(&:exist?).each { |p| require p } +SolidRecord.h + +SolidRecord.pry diff --git a/solid_record/spec/dummy/blog/console.rb b/solid_record/spec/dummy/blog/console.rb new file mode 100644 index 00000000..f27b4901 --- /dev/null +++ b/solid_record/spec/dummy/blog/console.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Blogger + class << self + def b = @b ||= Blog.last + def p = @p ||= Post.last + def u = @u ||= User.last + + def dir_instance + require_relative 'models' + SolidRecord.setup + SolidRecord.toggle_callbacks do + SolidRecord::DirInstance.create(source: 'blog/dir_instance', model_class_name: :user, namespace: :blogger) + end + end + + def dir_generic + require_relative 'models' + SolidRecord.setup + SolidRecord.toggle_callbacks { SolidRecord::DirGeneric.create(source: 'blog/dir_generic', namespace: :blogger) } + end + end +end + +module SolidRecord + class << self + def b = Blogger + end +end diff --git a/solid-record/spec/dummy/blog/data/monolith/blogs.yml b/solid_record/spec/dummy/blog/dir_generic/blogs.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/monolith/blogs.yml rename to solid_record/spec/dummy/blog/dir_generic/blogs.yml diff --git a/solid-record/spec/dummy/blog/data/monolith/posts.yml b/solid_record/spec/dummy/blog/dir_generic/posts.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/monolith/posts.yml rename to solid_record/spec/dummy/blog/dir_generic/posts.yml diff --git a/solid-record/spec/dummy/blog/data/monolith/users.yml b/solid_record/spec/dummy/blog/dir_generic/users.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/monolith/users.yml rename to solid_record/spec/dummy/blog/dir_generic/users.yml diff --git a/solid-record/spec/dummy/blog/data/file/happy.yml b/solid_record/spec/dummy/blog/dir_instance/happy.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/file/happy.yml rename to solid_record/spec/dummy/blog/dir_instance/happy.yml diff --git a/solid-record/spec/dummy/blog/data/file/old.yml b/solid_record/spec/dummy/blog/dir_instance/old.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/file/old.yml rename to solid_record/spec/dummy/blog/dir_instance/old.yml diff --git a/solid-record/spec/dummy/blog/data/file/quiet.yml b/solid_record/spec/dummy/blog/dir_instance/quiet.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/file/quiet.yml rename to solid_record/spec/dummy/blog/dir_instance/quiet.yml diff --git a/solid-record/spec/dummy/blog/data/file/sad.yml b/solid_record/spec/dummy/blog/dir_instance/sad.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/file/sad.yml rename to solid_record/spec/dummy/blog/dir_instance/sad.yml diff --git a/solid-record/spec/dummy/blog/data/file/sad/My Blog.yml b/solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/file/sad/My Blog.yml rename to solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog.yml diff --git a/solid-record/spec/dummy/blog/data/file/sad/My Blog/first.yml b/solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog/posts/first.yml similarity index 100% rename from solid-record/spec/dummy/blog/data/file/sad/My Blog/first.yml rename to solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog/posts/first.yml diff --git a/solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog/posts/first/comments.yml b/solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog/posts/first/comments.yml new file mode 100644 index 00000000..95bd2105 --- /dev/null +++ b/solid_record/spec/dummy/blog/dir_instance/sad/blogs/My Blog/posts/first/comments.yml @@ -0,0 +1,4 @@ +--- +first: + title: WTF? + content: What about it? diff --git a/solid_record/spec/dummy/blog/models.rb b/solid_record/spec/dummy/blog/models.rb new file mode 100644 index 00000000..2c4c9dca --- /dev/null +++ b/solid_record/spec/dummy/blog/models.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Blogger + def self.table_name_prefix = 'blogger_' + + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end + + class User < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'first' + + has_many :blogs + has_many :posts, through: :blogs + has_many :comments + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.string :first + t.string :last + end + end + end + end + + class Blog < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'name' + + belongs_to :user + + has_many :posts + has_many :comments, through: :posts + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :user + t.string :name + end + end + end + end + + class Post < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'title' + + belongs_to :blog + + has_many :comments + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :blog + t.string :title + t.text :content + end + end + end + end + + class Comment < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'title' + + belongs_to :post + belongs_to :user + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :post + t.references :user + t.string :title + t.text :content + end + end + end + end +end diff --git a/solid_record/spec/dummy/infra/console.rb b/solid_record/spec/dummy/infra/console.rb new file mode 100644 index 00000000..0f2c6de5 --- /dev/null +++ b/solid_record/spec/dummy/infra/console.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidRecord + def self.infra = Infra +end + +module Infra + class << self + def g = @g ||= Group.last + def h = @h ||= Host.last + def s = @s ||= Service.last + + def su = s.update(port: (s.port || 0) + 1) + def hu = h.update(port: (h.port || 0) + 1) + + def plural_hash + require_relative 'models' + SolidRecord.setup + SolidRecord.toggle_callbacks do + SolidRecord::File.create(source: 'infra/plural_hash/groups.yml', namespace: 'infra') + end + end + end +end diff --git a/solid_record/spec/dummy/infra/models.rb b/solid_record/spec/dummy/infra/models.rb new file mode 100644 index 00000000..b69e6bdc --- /dev/null +++ b/solid_record/spec/dummy/infra/models.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +module Infra + def self.table_name_prefix = 'infra_' + + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + self.inheritance_column = 'kind' + end + + class Group < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'name' + + attr_encrypted :hi + + store :defaults, accessors: %i[host_version], coder: YAML + + has_many :hosts + + class << self + def after_load = puts('after_load') + + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.string :name + t.string :hi + t.string :auth_domain + t.string :network + t.string :domain + t.string :defaults + end + end + end + end + + class Host < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'host' + + belongs_to :group + + serialize :shares, Array + # serialize :services, Array + has_many :services + + class << self + def create_table(schema) # rubocop:disable Metrics/MethodLength + schema.create_table table_name, force: true do |t| + # t.solid + t.references :group # , solid: true + t.string :host + t.string :kind + t.string :ip + t.integer :port + t.string :vpn_type + t.string :version + t.string :shares + t.string :services + end + end + end + end + + class Vpn < Host + def connect = puts('connect') + # after_create :bind_it + + def bind_it + puts 'bind it' + # binding.pry + end + end + + class Dc < Host; end + + class AFile < Host; end + + class Hv < Host; end + + class Imm < Host; end + + class Firewall < Host; end + + class Service < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'name' + + belongs_to :host + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.string :name + t.references :host + t.string :port + end + end + end + end + + class Repo < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'local' + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + # t.solid + t.string :local + t.string :remote + end + end + end + end + + class Home < ApplicationRecord + include SolidRecord::Model + def self.key_column = 'string' + + class << self + def create_table(schema) + schema.create_table table_name, force: true do |t| + # t.solid + t.string :string + end + end + end + end +end diff --git a/solid_record/spec/dummy/infra/plural_array/crack/hosts.yml b/solid_record/spec/dummy/infra/plural_array/crack/hosts.yml new file mode 100644 index 00000000..df9bcda5 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_array/crack/hosts.yml @@ -0,0 +1,4 @@ +--- +s-test-1: + kind: Dc + ip: 9.9.9.9 diff --git a/solid_record/spec/dummy/infra/plural_array/crack/hosts/s-test-2.yml b/solid_record/spec/dummy/infra/plural_array/crack/hosts/s-test-2.yml new file mode 100644 index 00000000..0c9b3189 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_array/crack/hosts/s-test-2.yml @@ -0,0 +1,5 @@ +--- +ip: 119.119.119.199 +kind: Dc +port: 42 +shares: [] diff --git a/solid_record/spec/dummy/infra/plural_array/groups.yml b/solid_record/spec/dummy/infra/plural_array/groups.yml new file mode 100644 index 00000000..b0e7f526 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_array/groups.yml @@ -0,0 +1,76 @@ +--- +- name: acme + domain: acme.local + defaults: {} + hosts: + - host: 192.168.1.1 + kind: Infra::Vpn + vpn_type: openvpn + - host: sng-file-1 + kind: Infra::AFile + version: 2012r2 +- name: hackme + domain: hackme.com + defaults: {} + hosts: + - host: fw + kind: Infra::Vpn + ip: fwv.hackme.com + port: 444 +- name: slackme + domain: slackme.local + hosts: + - host: fw2 + kind: Infra::Vpn + ip: 10.100.17.234 + port: 4443 + vpn_type: fortivpn + - host: cap23 + kind: Infra::Dc + ip: 192.168.9.3 +- name: crack + auth_domain: crc + defaults: + hosts: + version: 2012r2 + domain: internal.crack.org + network: 10.10.1.0/24 + hosts: + - host: s-file-1 + ip: 10.1.1.72 + kind: Infra::Dc + shares: [] + - host: s-file-2 + kind: Infra::AFile + shares: + - drive: p + path: ClientApps + purpose: apps + - drive: w + path: WPKG + purpose: wpkg + version: 2008r2 + - host: s-hyperv-1 + ip: 10.1.1.55 + kind: Infra::Hv + shares: [] + - host: s-hyperv-2 + ip: 10.1.1.59 + kind: Infra::Imm + shares: [] + - host: s-file-3 + kind: Infra::AFile + shares: + - drive: p + path: ClientApps + purpose: apps + - drive: w + path: WPKG + purpose: wpkg + version: '2016' + - host: s-fw-1 + kind: Infra::Firewall + shares: [] + services: + - name: ssh + port: 7430 diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/acme.yml b/solid_record/spec/dummy/infra/plural_dir/groups/acme.yml new file mode 100644 index 00000000..3be202f6 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/acme.yml @@ -0,0 +1,3 @@ +--- +domain: acme.local +defaults: {} diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/acme/hosts.yml b/solid_record/spec/dummy/infra/plural_dir/groups/acme/hosts.yml new file mode 100644 index 00000000..b3540ca4 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/acme/hosts.yml @@ -0,0 +1,8 @@ +--- +192.168.1.1: + kind: Vpn + vpn_type: openvpn + +sng-file-1: + kind: AFile + version: 2012r2 diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/crack.yml b/solid_record/spec/dummy/infra/plural_dir/groups/crack.yml new file mode 100644 index 00000000..d18753dd --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/crack.yml @@ -0,0 +1,6 @@ +--- +auth_domain: crc +network: 10.10.1.0/24 +domain: internal.crack.org +defaults: + host_version: 2012r2 diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/crack/hosts.yml b/solid_record/spec/dummy/infra/plural_dir/groups/crack/hosts.yml new file mode 100644 index 00000000..3bf3c02e --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/crack/hosts.yml @@ -0,0 +1,39 @@ +--- +s-file-1: + ip: 10.1.1.72 + kind: Dc + shares: [] +s-file-2: + kind: AFile + shares: + - drive: p + path: ClientApps + purpose: apps + - drive: w + path: WPKG + purpose: wpkg + version: 2008r2 +s-hyperv-1: + ip: 10.1.1.55 + kind: Hv + shares: [] +s-hyperv-2: + ip: 10.1.1.59 + kind: Imm + shares: [] +s-file-3: + kind: AFile + shares: + - drive: p + path: ClientApps + purpose: apps + - drive: w + path: WPKG + purpose: wpkg + version: '2016' +s-fw-1: + kind: Firewall + shares: [] + services: + - name: ssh + port: 7430 diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/hackme.yml b/solid_record/spec/dummy/infra/plural_dir/groups/hackme.yml new file mode 100644 index 00000000..a564f080 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/hackme.yml @@ -0,0 +1,3 @@ +--- +domain: hackme.com +defaults: {} diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/hackme/hosts.yml b/solid_record/spec/dummy/infra/plural_dir/groups/hackme/hosts.yml new file mode 100644 index 00000000..ce20b1de --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/hackme/hosts.yml @@ -0,0 +1,5 @@ +--- +fw: + kind: Vpn + ip: fwv.hackme.com + port: 444 diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/slackme.yml b/solid_record/spec/dummy/infra/plural_dir/groups/slackme.yml new file mode 100644 index 00000000..79946440 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/slackme.yml @@ -0,0 +1,2 @@ +--- +domain: vilmaoil.local diff --git a/solid_record/spec/dummy/infra/plural_dir/groups/slackme/hosts.yml b/solid_record/spec/dummy/infra/plural_dir/groups/slackme/hosts.yml new file mode 100644 index 00000000..d13a730e --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_dir/groups/slackme/hosts.yml @@ -0,0 +1,10 @@ +--- +fw2: + kind: Vpn + ip: 101.100.17.234 + port: 4443 + vpn_type: fortivpn + +cap23: + kind: Dc + ip: 192.168.9.3 diff --git a/solid_record/spec/dummy/infra/plural_hash/groups.yml b/solid_record/spec/dummy/infra/plural_hash/groups.yml new file mode 100644 index 00000000..da0a4d38 --- /dev/null +++ b/solid_record/spec/dummy/infra/plural_hash/groups.yml @@ -0,0 +1,76 @@ +--- +acme: + domain: acme.local + defaults: {} + hosts: + - host: 192.168.1.1 + kind: Infra::Vpn + vpn_type: openvpn + - host: sng-file-1 + kind: Infra::AFile + version: 2012r2 +hackme: + domain: hackme.com + defaults: {} + hosts: + - host: fw + kind: Infra::Vpn + ip: fwv.hackme.com + port: 444 +slackme: + domain: slackme.local + hosts: + - host: fw2 + kind: Infra::Vpn + ip: 10.100.17.234 + port: 4443 + vpn_type: fortivpn + - host: cap23 + kind: Infra::Dc + ip: 192.168.9.3 +crack: + auth_domain: crc + defaults: + hosts: + version: 2012r2 + domain: internal.crack.org + network: 10.10.1.0/24 + hosts: + - host: s-file-1 + ip: 10.1.1.72 + kind: Infra::Dc + shares: [] + - host: s-file-2 + kind: Infra::AFile + shares: + - drive: p + path: ClientApps + purpose: apps + - drive: w + path: WPKG + purpose: wpkg + version: 2008r2 + - host: s-hyperv-1 + ip: 10.1.1.55 + kind: Infra::Hv + shares: [] + - host: s-hyperv-2 + ip: 10.1.1.59 + kind: Infra::Imm + shares: [] + - host: s-file-3 + kind: Infra::AFile + shares: + - drive: p + path: ClientApps + purpose: apps + - drive: w + path: WPKG + purpose: wpkg + version: '2016' + - host: s-fw-1 + kind: Infra::Firewall + shares: [] + services: + - name: ssh + port: 7430 diff --git a/core/spec/fixtures/segments/node/stack_env_target_ns.yml b/solid_record/spec/dummy/one_stack/config/segment.yml similarity index 67% rename from core/spec/fixtures/segments/node/stack_env_target_ns.yml rename to solid_record/spec/dummy/one_stack/config/segment.yml index 2cf715b7..2f1032d0 100644 --- a/core/spec/fixtures/segments/node/stack_env_target_ns.yml +++ b/solid_record/spec/dummy/one_stack/config/segment.yml @@ -8,5 +8,9 @@ config: carry: ${name}.${config.targets.one} segments_type: stack default: + provisioner_name: terraform + # repository_name: jamstack + # repository_name: cnfs-backend resource_name: instance1 + runtime_name: compose segment_name: backend diff --git a/solid_record/spec/dummy/one_stack/console.rb b/solid_record/spec/dummy/one_stack/console.rb new file mode 100644 index 00000000..10c9625e --- /dev/null +++ b/solid_record/spec/dummy/one_stack/console.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module SolidRecord + def self.one_stack = OneStack +end + +module OneStack + class << self + def one_stack = segments(Pathname.new(__dir__).join('../../../../onestack/spec/dummy')) + def segments(srp = Pathname.new(__dir__)) + require_relative 'models' + SolidRecord.setup + SolidRecord.toggle_callbacks do + root = SolidRecord::File.create(source: srp.join('config/segment.yml'), content_format: :singular, + model_class_name: 'OneStack::SegmentRoot') + owner = root.segments.first.segments.first.model + SolidRecord::DirGeneric.create(source: srp.join('segments'), model_class_name: 'component', owner: owner, + namespace: 'one_stack') + end + end + end +end diff --git a/solid_record/spec/dummy/one_stack/models.rb b/solid_record/spec/dummy/one_stack/models.rb new file mode 100644 index 00000000..8177553e --- /dev/null +++ b/solid_record/spec/dummy/one_stack/models.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module OneStack + module Concerns; end + class << self + def table_name_prefix = 'one_stack_' + + def config = @config ||= set_config + def application = @application ||= set_app + + def set_app + app = ActiveSupport::OrderedOptions.new + app.name = 'solid_record_spec' + app + end + + def set_config + config = ActiveSupport::OrderedOptions.new + config.asset_names = %w[operators providers provisioners resources repositories] + config + end + + def c = Component + def co = Context + end +end + +path = Pathname.new(__dir__) + +os = path.join('../../../../onestack/app/models/one_stack') +os.join('concerns').glob('*.rb').each { |p| require p } +%w[application_record provisioner].each do |f| + require(os.join("#{f}.rb")) +end +os.glob('*.rb').each { |p| require p } + +aws = path.join('../../../../aws/app/models/aws') +aws.glob('*.rb').each { |p| require p } +aws.join('resource').glob('*.rb').each { |p| require p } +aws.join('resource').glob('**/*.rb').each { |p| require p } diff --git a/solid_record/spec/dummy/one_stack/segments/backend.yml b/solid_record/spec/dummy/one_stack/segments/backend.yml new file mode 100644 index 00000000..6d4bccd1 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/backend.yml @@ -0,0 +1,6 @@ +--- +config: + domain: backend.${parent.config.domain} + host: host.${config.domain} +segment_name: cnfs-components/backend +segments_type: environment diff --git a/solid-record/spec/dummy/stack/data/file/backend/production.yml b/solid_record/spec/dummy/one_stack/segments/backend/development/cluster/default.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/backend/production.yml rename to solid_record/spec/dummy/one_stack/segments/backend/development/cluster/default.yml diff --git a/solid_record/spec/dummy/one_stack/segments/backend/development/resources/cap4.yml b/solid_record/spec/dummy/one_stack/segments/backend/development/resources/cap4.yml new file mode 100644 index 00000000..0bd5c758 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/backend/development/resources/cap4.yml @@ -0,0 +1,5 @@ + +type: Aws::Resource::EC2::Instance +config: + family: asdfaksjdfasjkdfasldfj + size: fasdfjasdf diff --git a/solid_record/spec/dummy/one_stack/segments/backend/development/resources/cluster.yml b/solid_record/spec/dummy/one_stack/segments/backend/development/resources/cluster.yml new file mode 100644 index 00000000..403939ed --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/backend/development/resources/cluster.yml @@ -0,0 +1,5 @@ +--- +type: Aws::Resource::EC2::Instance +config: + family: m2 + size: micro diff --git a/solid_record/spec/dummy/one_stack/segments/backend/resources.yml b/solid_record/spec/dummy/one_stack/segments/backend/resources.yml new file mode 100644 index 00000000..896300bd --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/backend/resources.yml @@ -0,0 +1,11 @@ +--- +cap2: + # type: Resource::Aws::EC2::Instance + config: + family: r2 + size: awesome +cap4: + # type: Resource::Aws::EC2::Instance + config: + family: ah23 + size: cool! diff --git a/solid_record/spec/dummy/one_stack/segments/builders.yml b/solid_record/spec/dummy/one_stack/segments/builders.yml new file mode 100644 index 00000000..5b421fb1 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/builders.yml @@ -0,0 +1,4 @@ +--- +compose: + config: {} + # type: Compose::Builder diff --git a/solid_record/spec/dummy/one_stack/segments/dependencies.yml b/solid_record/spec/dummy/one_stack/segments/dependencies.yml new file mode 100644 index 00000000..60997735 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/dependencies.yml @@ -0,0 +1,24 @@ +# dependencies.yml +--- +terraform: + config: + source: https://releases.hashicorp.com/terraform/${operator.version}/terraform_${operator.version}_${platform.os}_${platform.arch}.zip + # TODO: Test on Mac + # mac: https://releases.hashicorp.com/terraform/${config.version}/terraform_${config.version}_darwin_amd64.zip + +kubectl: + config: + # linux: https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl + linux: https://storage.googleapis.com/kubernetes-release/release/v${operator.version}/bin/${platform.os}/${platform.arch}/kubectl + +docker: + config: {} + +docker-compose: + config: {} + +# skaffold: + # config: {} + +git: + config: {} diff --git a/solid_record/spec/dummy/one_stack/segments/doc.yml b/solid_record/spec/dummy/one_stack/segments/doc.yml new file mode 100644 index 00000000..fab48bde --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/doc.yml @@ -0,0 +1,5 @@ +--- +config: {} +# segment_name: jamstack/scully +# default: +# provider_name: aws_east diff --git a/solid_record/spec/dummy/one_stack/segments/frontend.yml b/solid_record/spec/dummy/one_stack/segments/frontend.yml new file mode 100644 index 00000000..f6cd8097 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/frontend.yml @@ -0,0 +1,8 @@ +--- +config: + domain: backend.${parent.config.domain} + this_host: host.${config.domain} + fart: ${name}.${config.this_host} +default: + resource_name: instance + provisioner_name: terraform diff --git a/solid_record/spec/dummy/one_stack/segments/frontend/resources.yml b/solid_record/spec/dummy/one_stack/segments/frontend/resources.yml new file mode 100644 index 00000000..df2550ae --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/frontend/resources.yml @@ -0,0 +1,5 @@ +--- +test: + enable: false + config: {} + tags: {} diff --git a/solid_record/spec/dummy/one_stack/segments/frontend/services.yml b/solid_record/spec/dummy/one_stack/segments/frontend/services.yml new file mode 100644 index 00000000..8b5b472a --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/frontend/services.yml @@ -0,0 +1,4 @@ +--- +iam: + config: {} + # type: CnfsBackend::Service diff --git a/solid_record/spec/dummy/one_stack/segments/providers.yml b/solid_record/spec/dummy/one_stack/segments/providers.yml new file mode 100644 index 00000000..8682f759 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/providers.yml @@ -0,0 +1,37 @@ +--- +aws_apse_1: + config: + region: ap-southeast-1 + # type: Aws::Provider +aws_east_1: + config: + region: us-east-1 + # type: Aws::Provider +kubectl: + config: + name: terraform-provider-kubectl + url: https://github.com/gavinbunney/terraform-provider-kubectl/releases/download/v1.0.2/terraform-provider-kubectl-${platform}-amd64 +localstack: + config: + access_key_id: !binary |- + WuGl/2ajtdGVYDMKNjLrOhvtDsDMIqhpZiFfB7RX6vwOzQkkgIAniVVULjW4UzbsnhutkoxiQA== + account_id: 123456789012 + region: localstack + secret_access_key: !binary |- + dVO3c4SGH7qt2g5zukR2zCowQ5Mt+aieD7rhH8aEXf13LuX9fDZprf2uXVmPfWGiB/Bj3bto7g== + s3: + endpoint: http://localstack:4572 + force_path_style: true + sqs: + endpoint: http://localstack:4576 + # type: Aws::Provider +rest: + config: + platform: linux + version: 1.16.0 + arch: amd64 + name: terraform-provider-restapi_v${config.version} + url: https://github.com/Mastercard/terraform-provider-restapi/releases/download/v${config.version}/ + terraform-provider-restapi_v${config.version}-${config.platform}-${config.arch}.zip + tags: {} + # type: Rest::Provider diff --git a/solid_record/spec/dummy/one_stack/segments/provisioners.yml b/solid_record/spec/dummy/one_stack/segments/provisioners.yml new file mode 100644 index 00000000..ebd6195f --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/provisioners.yml @@ -0,0 +1,21 @@ +--- +terraform: + config: + dependencies: + - name: terraform + version: 1.0.11 + - name: terraform-provider-kubectl + url: https://github.com/gavinbunney/terraform-provider-kubectl/releases/download/v1.0.2/terraform-provider-kubectl-${platform}-amd64 + # type: Terraform::Provisioner + # cache_path: tmp + # cache_path_suffix: plans + +vagrant: + config: + box: ros/generic + box_url: https://perx-ros-boxes.s3-ap-southeast-1.amazonaws.com/vagrant/json/ros/generic.json + dependencies: + - name: setup + type: repo + url: https://github.com/rails-on-services/setup + # type: Vagrant::Provisioner diff --git a/solid_record/spec/dummy/one_stack/segments/repositories.yml b/solid_record/spec/dummy/one_stack/segments/repositories.yml new file mode 100644 index 00000000..ef3ba0cb --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/repositories.yml @@ -0,0 +1,10 @@ +--- +# jamstack: + # config: + # url: git@github.com:maxcole/kpop.git +cnfs-components: + config: + url: git@github.com:rails-on-services/cnfs-components.git +ros: + config: + url: git@github.com:rails-on-services/ros.git diff --git a/solid_record/spec/dummy/one_stack/segments/runtimes.yml b/solid_record/spec/dummy/one_stack/segments/runtimes.yml new file mode 100644 index 00000000..9daf8830 --- /dev/null +++ b/solid_record/spec/dummy/one_stack/segments/runtimes.yml @@ -0,0 +1,9 @@ +--- +compose: + config: + version: 3.2 + dependencies: '[{"name"=>"docker-compose"}]' + # type: Compose::Runtime +# native: +# config: {} +# type: Native::Runtime diff --git a/core/spec/fixtures/segments/node/users/users.yml b/solid_record/spec/dummy/one_stack/segments/users.yml similarity index 52% rename from core/spec/fixtures/segments/node/users/users.yml rename to solid_record/spec/dummy/one_stack/segments/users.yml index 49fcbd6b..446225f9 100644 --- a/core/spec/fixtures/segments/node/users/users.yml +++ b/solid_record/spec/dummy/one_stack/segments/users.yml @@ -3,7 +3,3 @@ joe: config: {} role: admin tags: {} -nancy: - config: {} - role: dev - tags: {} diff --git a/solid-record/spec/dummy/stack/data/file/backend.yml b/solid_record/spec/dummy/stack/data/file/backend.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/backend.yml rename to solid_record/spec/dummy/stack/data/file/backend.yml diff --git a/solid-record/spec/dummy/stack/data/file/backend/dev.yml b/solid_record/spec/dummy/stack/data/file/backend/dev.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/backend/dev.yml rename to solid_record/spec/dummy/stack/data/file/backend/dev.yml diff --git a/solid_record/spec/dummy/stack/data/file/backend/production.yml b/solid_record/spec/dummy/stack/data/file/backend/production.yml new file mode 100644 index 00000000..56107bc2 --- /dev/null +++ b/solid_record/spec/dummy/stack/data/file/backend/production.yml @@ -0,0 +1,3 @@ +--- +config: + this: that diff --git a/solid-record/spec/dummy/stack/data/file/backend/production/services/hey.yml b/solid_record/spec/dummy/stack/data/file/backend/production/services/hey.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/backend/production/services/hey.yml rename to solid_record/spec/dummy/stack/data/file/backend/production/services/hey.yml diff --git a/solid-record/spec/dummy/stack/data/file/backend/staging.yml b/solid_record/spec/dummy/stack/data/file/backend/staging.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/backend/staging.yml rename to solid_record/spec/dummy/stack/data/file/backend/staging.yml diff --git a/solid-record/spec/dummy/stack/data/file/dependencies.yml b/solid_record/spec/dummy/stack/data/file/dependencies.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/dependencies.yml rename to solid_record/spec/dummy/stack/data/file/dependencies.yml diff --git a/solid-record/spec/dummy/stack/data/file/plans.yml b/solid_record/spec/dummy/stack/data/file/plans.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/plans.yml rename to solid_record/spec/dummy/stack/data/file/plans.yml diff --git a/solid-record/spec/dummy/stack/data/file/providers.yml b/solid_record/spec/dummy/stack/data/file/providers.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/providers.yml rename to solid_record/spec/dummy/stack/data/file/providers.yml diff --git a/solid-record/spec/dummy/stack/data/file/provisioners.yml b/solid_record/spec/dummy/stack/data/file/provisioners.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/provisioners.yml rename to solid_record/spec/dummy/stack/data/file/provisioners.yml diff --git a/solid-record/spec/dummy/stack/data/file/repositories.yml b/solid_record/spec/dummy/stack/data/file/repositories.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/repositories.yml rename to solid_record/spec/dummy/stack/data/file/repositories.yml diff --git a/solid-record/spec/dummy/stack/data/file/resources.yml b/solid_record/spec/dummy/stack/data/file/resources.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/resources.yml rename to solid_record/spec/dummy/stack/data/file/resources.yml diff --git a/solid-record/spec/dummy/stack/data/file/runtimes.yml b/solid_record/spec/dummy/stack/data/file/runtimes.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/runtimes.yml rename to solid_record/spec/dummy/stack/data/file/runtimes.yml diff --git a/solid-record/spec/dummy/stack/data/file/users.yml b/solid_record/spec/dummy/stack/data/file/users.yml similarity index 100% rename from solid-record/spec/dummy/stack/data/file/users.yml rename to solid_record/spec/dummy/stack/data/file/users.yml diff --git a/solid_record/spec/dummy/stack/data/monolith/stacks.yml b/solid_record/spec/dummy/stack/data/monolith/stacks.yml new file mode 100644 index 00000000..810a0241 --- /dev/null +++ b/solid_record/spec/dummy/stack/data/monolith/stacks.yml @@ -0,0 +1,3 @@ +# stacks.yml +--- + diff --git a/solid_record/spec/dummy/survey/console.rb b/solid_record/spec/dummy/survey/console.rb new file mode 100644 index 00000000..8fefd01a --- /dev/null +++ b/solid_record/spec/dummy/survey/console.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidRecord + def self.survey = Survey +end + +class Survey < ActiveRecord::Base + class << self + def plural_doc + require_relative 'models' + SolidRecord.setup + SolidRecord.toggle_callbacks { SolidRecord::File.create(source: 'survey/plural_doc/surveys.yml') } + end + + def plural_path + require_relative 'models' + SolidRecord.setup + SolidRecord.toggle_callbacks { SolidRecord::DirInstance.create(source: 'survey/plural_path/surveys') } + end + end +end diff --git a/solid_record/spec/dummy/survey/models.rb b/solid_record/spec/dummy/survey/models.rb new file mode 100644 index 00000000..32ef3243 --- /dev/null +++ b/solid_record/spec/dummy/survey/models.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +class Survey < ActiveRecord::Base + include SolidRecord::Model + def self.table_name_prefix = 'surveys_' + def self.key_column = 'name' + + has_many :questions + has_many :answers + + attr_encrypted :hi + + store :defaults, accessors: %i[this that], coder: YAML + # has_many :hosts + + class << self + def after_load = puts('after_load') + + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.string :name + t.string :namo + t.string :description + t.string :defaults + t.string :hi + end + end + end +end + +class Question < ActiveRecord::Base + include SolidRecord::Model + def self.table_name_prefix = 'surveys_' + def self.key_column = 'name' + + belongs_to :survey + + class << self + def after_load = puts('after_load') + + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :survey + t.string :name + t.string :description + end + end + end +end + +class Answer < ActiveRecord::Base + include SolidRecord::Model + def self.table_name_prefix = 'surveys_' + def self.key_column = 'name' + + belongs_to :survey + + class << self + def after_load = puts('after_load') + + def create_table(schema) + schema.create_table table_name, force: true do |t| + t.references :survey + t.string :name + t.string :description + end + end + end +end +# frozen_string_literal: true + +class SurveyView < SolidRecord::ModelView + def modify + # super + # mask_attr(:access_key_id) + # mask_attr(:secret_access_key) + ask_attr(:name) + ask_attr(:description) + # enum_select_attr(:region, choices: model.regions) + end +end diff --git a/solid_record/spec/dummy/survey/plural_doc/surveys.yml b/solid_record/spec/dummy/survey/plural_doc/surveys.yml new file mode 100644 index 00000000..404996cf --- /dev/null +++ b/solid_record/spec/dummy/survey/plural_doc/surveys.yml @@ -0,0 +1,3 @@ +--- +favorites: + description: My Favorite Attributes diff --git a/solid_record/spec/dummy/survey/plural_path/surveys/favorites.yml b/solid_record/spec/dummy/survey/plural_path/surveys/favorites.yml new file mode 100644 index 00000000..8590bfd2 --- /dev/null +++ b/solid_record/spec/dummy/survey/plural_path/surveys/favorites.yml @@ -0,0 +1,2 @@ +--- +description: My Favorite Attributes diff --git a/solid_record/spec/dummy/survey/plural_path/surveys/favorites/answers.yml b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/answers.yml new file mode 100644 index 00000000..b675e1e6 --- /dev/null +++ b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/answers.yml @@ -0,0 +1,3 @@ +--- +two: + description: The Second True Answer diff --git a/solid_record/spec/dummy/survey/plural_path/surveys/favorites/answers/one.yml b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/answers/one.yml new file mode 100644 index 00000000..279f0569 --- /dev/null +++ b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/answers/one.yml @@ -0,0 +1,2 @@ +--- +description: The One True Answer diff --git a/cnfs/spec/fixtures/config/initializers/.keep b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/invalids.yml similarity index 100% rename from cnfs/spec/fixtures/config/initializers/.keep rename to solid_record/spec/dummy/survey/plural_path/surveys/favorites/invalids.yml diff --git a/solid_record/spec/dummy/survey/plural_path/surveys/favorites/questions.yml b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/questions.yml new file mode 100644 index 00000000..1df578b4 --- /dev/null +++ b/solid_record/spec/dummy/survey/plural_path/surveys/favorites/questions.yml @@ -0,0 +1,7 @@ +--- +color: + description: Favorite color? +car: + description: Favorite car? +food: + description: Favorite food? diff --git a/solid_record/spec/dummy/survey/plural_path/surveys/medical.yml b/solid_record/spec/dummy/survey/plural_path/surveys/medical.yml new file mode 100644 index 00000000..baceca57 --- /dev/null +++ b/solid_record/spec/dummy/survey/plural_path/surveys/medical.yml @@ -0,0 +1,7 @@ +--- +description: Medical Survey +questions: + blood_pressure: + description: What is your blood pressure? + weight: + description: What is your weight in kilograms? diff --git a/solid_record/spec/lib/ext/pathname_spec.rb b/solid_record/spec/lib/ext/pathname_spec.rb new file mode 100644 index 00000000..06d97a41 --- /dev/null +++ b/solid_record/spec/lib/ext/pathname_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +class Stack; end +module OneStack; class Stack; end; end + +RSpec.describe Pathname do + subject(:pathname) { described_class } + + describe '#singular?' do + context 'when test.yml' do + it { expect(pathname.new('test.yml').singular?).to be_truthy } + end + + context 'when tests.yml' do + it { expect(pathname.new('tests.yml').singular?).to be_falsey } + end + end + + describe '#plural?' do + context 'when tests.yml' do + it { expect(pathname.new('tests.yml').plural?).to be_truthy } + end + + context 'when test.yml' do + it { expect(pathname.new('test.yml').plural?).to be_falsey } + end + end + + describe '#extension' do + context "when users.yml" do + it { expect(pathname.new('users.yml').extension).to eq('yml') } + end + + context "when users" do + it { expect(pathname.new('users').extension).to eq('') } + end + + context "when ." do + it { expect(pathname.new('.').extension).to eq('') } + end + end + + describe '#name' do + context "when users.yml" do + it { expect(pathname.new('users.yml').name).to eq('users') } + end + + context "when users.yml." do + it { expect(pathname.new('users.yml.').name).to eq('users.yml') } + end + + context "when users" do + it { expect(pathname.new('users').name).to eq('users') } + end + + context "when users." do + it { expect(pathname.new('users.').name).to eq('users') } + end + + context "when ." do + it { expect(pathname.new('.').name).to eq('') } + end + end +end diff --git a/solid_record/spec/lib/solid_record/association_spec.rb b/solid_record/spec/lib/solid_record/association_spec.rb new file mode 100644 index 00000000..8c872c5b --- /dev/null +++ b/solid_record/spec/lib/solid_record/association_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.describe Association do + before { SolidRecord.setup } + + context 'with infra' do + let(:doc) { SolidRecord.toggle_callbacks { File.create(source: file_path, namespace: :infra) } } + + context 'with monolithic yaml' do + let(:file_path) { DUMMY_ROOT.join('infra/plural_hash/groups.yml') } + + let(:association) { described_class.last } + let(:segments) { association.segments } + let(:model) { segments.last.model } + let(:model_update) { model.update(port: 422) } + let(:model_destroy) do + model.destroy + Element.flagged_for(:destroy).each(&:destroy) + end + + before { doc } + + describe '#to_solid' do + it { expect(doc.to_solid.count).to be(4) } + + context 'when model updated' do + it { expect { model_update }.to change { doc.reload.flags.size }.from(0).to(1) } + it { expect { model_update }.to change { doc.reload.flags.to_a.include?(:update) }.from(false).to(true) } + end + + context 'when model destroyed' do + it { expect { model_destroy }.to change { doc.reload.flags.size }.from(0).to(1) } + it { expect { model_destroy }.to change { doc.reload.flags.to_a.include?(:update) }.from(false).to(true) } + it { expect { model_destroy }.to change(Element, :count).by(-1) } + end + end + + describe '#to_solid_array' do + it { expect(doc.to_solid_array.class).to eql(Array) } + end + + describe '#to_solid_hash' do + it { expect(doc.to_solid_hash.class).to eql(Hash) } + end + + it 'creates the correct number of Models' do # rubocop:disable RSpec/MultipleExpectations + expect(Infra::Group.count).to eq(4) + expect(Infra::Host.count).to eq(11) + expect(Infra::Service.count).to eq(1) + end + + context 'when Group has_many Hosts' do + it { expect(Infra::Group.find_by(name: 'crack').hosts.count).to eq(6) } + end + + context 'with Element count' do + it { expect(Element.count).to eq(16) } + end + + context 'with Association Element type' do + it { expect(Infra::Service.first.element.parent).to be_an_instance_of(described_class) } + end + end + + context 'with monolithic yaml array' do + let(:file_path) { DUMMY_ROOT.join('infra/plural_array/groups.yml') } + + before { doc } + + it 'creates the correct number of Models' do # rubocop:disable RSpec/MultipleExpectations + expect(Infra::Group.count).to eq(4) + expect(Infra::Host.count).to eq(11) + expect(Infra::Service.count).to eq(1) + end + end + + xcontext 'with hierarchial yaml' do + let(:group_file) { DUMMY_ROOT.join('infra/plural_dir/groups/crack.yml') } + let(:hosts_file) { DUMMY_ROOT.join('infra/plural_dir/groups/crack/hosts.yml') } + + let(:group_doc) { SolidRecord.toggle_callbacks { File.create(source: group_file, model_class_name: 'Group', namespace: :infra) } } + let(:hosts_doc) { SolidRecord.toggle_callbacks { File.create(source: hosts_file, owner: Group.first, namespace: :infra) } } + + context 'with Group and Host documents' do + before do + group_doc + hosts_doc + end + + describe '#model' do + it { expect(group_doc.model_type).to eq('Group') } + it { expect(hosts_doc.model_type).to eq('Host') } + it { expect(Infra::Group.first).not_to be_nil } + it { expect(Infra::Service.last.host.group).to eq(Infra::Group.first) } + end + end + end + end + + xcontext 'when stack' do + # before(:context) { SpecHelper.before_context('stack') } + + # after(:context) { SpecHelper.after_context } + + xit { expect(true).to be_truthy } + end + end +end diff --git a/solid_record/spec/lib/solid_record/concerns/a_e_spec.rb b/solid_record/spec/lib/solid_record/concerns/a_e_spec.rb new file mode 100644 index 00000000..8628c085 --- /dev/null +++ b/solid_record/spec/lib/solid_record/concerns/a_e_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.shared_examples_for 'AE' do + let(:valid_path) { described_class.new(model_type: 'test', path: '.') } + let(:invalid_path) { described_class.new(model_type: 'test', path: 'invalid_path') } + let(:no_path) { described_class.new(model_type: 'test') } + + it { expect(valid_path).to be_valid } + it { expect(invalid_path).not_to be_valid } + it { expect(no_path).to be_invalid } + end +end diff --git a/solid_record/spec/lib/solid_record/concerns/encryption_spec.rb b/solid_record/spec/lib/solid_record/concerns/encryption_spec.rb new file mode 100644 index 00000000..c43bb3be --- /dev/null +++ b/solid_record/spec/lib/solid_record/concerns/encryption_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.shared_examples_for 'encrypted' do + it 'decrypts all attrs' do + rf = subject.class.new(subject.parent.yaml) + rf.valid? + expect(subject.as_json).to eq(rf.as_json) + + # TODO: When encrypted yaml remains unchnaged when saving back then enable this expectation + # expect(subject.as_json_encrypted).to eq(rf.as_json_encrypted) + + describe 'encryption' do + let(:options) { { stack: :backend, environment: :production, target: :lambda } } + + describe 'does the correct encryption for project' do + let(:subject) { Project.first } + # it_behaves_like 'encrypted' + end + + describe 'does the correct encryption for lambda' do + let(:subject) { Component.find_by(name: :lambda) } + # it_behaves_like 'encrypted' + end + end + end + end +end diff --git a/solid_record/spec/lib/solid_record/concerns/persistence_spec.rb b/solid_record/spec/lib/solid_record/concerns/persistence_spec.rb new file mode 100644 index 00000000..1982b0b5 --- /dev/null +++ b/solid_record/spec/lib/solid_record/concerns/persistence_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.shared_examples_for 'Persistence' do + it { expect(subject).to respond_to(:key_column) } + end +end diff --git a/solid_record/spec/lib/solid_record/dir_association_spec.rb b/solid_record/spec/lib/solid_record/dir_association_spec.rb new file mode 100644 index 00000000..f7b39b50 --- /dev/null +++ b/solid_record/spec/lib/solid_record/dir_association_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# require_relative 'file_system_element_spec' + +module SolidRecord + RSpec.describe DirAssociation do + before { SolidRecord.setup } + end +end diff --git a/solid_record/spec/lib/solid_record/dir_spec.rb b/solid_record/spec/lib/solid_record/dir_spec.rb new file mode 100644 index 00000000..272d6047 --- /dev/null +++ b/solid_record/spec/lib/solid_record/dir_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# require_relative 'file_system_element_spec' + +module SolidRecord + RSpec.describe Dir do + before { SolidRecord.setup } + end +end diff --git a/solid_record/spec/lib/solid_record/element_spec.rb b/solid_record/spec/lib/solid_record/element_spec.rb new file mode 100644 index 00000000..b6bc4335 --- /dev/null +++ b/solid_record/spec/lib/solid_record/element_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.describe Element do + before { SolidRecord.setup } + + context 'with infra' do + let(:doc) { SolidRecord.toggle_callbacks { File.create(source: file_path, namespace: :infra) } } + + context 'with monolithic yaml' do + let(:file_path) { DUMMY_ROOT.join('infra/plural_hash/groups.yml') } + + before { doc } + + it { expect(Infra::Host.last.element.root).not_to be_nil } + describe '#root' do + it { expect(Infra::Host.last.element.root).to eq(File.first) } + end + end + end + end +end diff --git a/solid_record/spec/lib/solid_record/file_spec.rb b/solid_record/spec/lib/solid_record/file_spec.rb new file mode 100644 index 00000000..878c0f40 --- /dev/null +++ b/solid_record/spec/lib/solid_record/file_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.describe File do + before { SolidRecord.setup } + + context 'when infra' do + # let(:doc) { SolidRecord.toggle_callbacks { File.create(source: file_path, namespace: :infra) } } + let(:doc) { xdoc(File, source: file_path, namespace: :infra) } + + let(:model) { Element.first.model } + + let(:association) { doc.segments.first } + + let(:model_destroy) do + # binding.pry + model.destroy + Element.flagged_for(:destroy).each(&:destroy) + end + + context 'with monolithic yaml' do + let(:file_path) { DUMMY_ROOT.join('infra/plural_hash/groups.yml') } + + before { doc } + + describe '#flag' do + it { expect { model_destroy }.to change { doc.reload.flags.size }.by(1) } + end + + describe '#write' do + it { + expect do + model_destroy + doc.write + end .to change { doc.read.size }.by(-1) + } + end + end + end + end +end diff --git a/solid_record/spec/lib/solid_record/path_spec.rb b/solid_record/spec/lib/solid_record/path_spec.rb new file mode 100644 index 00000000..b6a1aefb --- /dev/null +++ b/solid_record/spec/lib/solid_record/path_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.describe Path do + before { SolidRecord.setup } + + context 'when infra' do + #a after(:context) { SpecHelper.after_context } + let(:doc) { SolidRecord.toggle_callbacks { File.create(source: file_path, namespace: 'infra') } } + + context 'with monolithic yaml' do + let(:file_path) { DUMMY_ROOT.join('infra/plural_hash/groups.yml') } + + let(:host1) { Infra::Host.find_by(host: 's-file-1') } + let(:host2) { Infra::Host.find_by(host: 's-file-2') } + let(:group1) { Infra::Group.find_by(name: 'crack') } + + let(:host1_update) do + host1.update(port: 422) + SolidRecord.flush_cache + end + let(:host2_update) do + host1.update(port: 522) + SolidRecord.flush_cache + end + let(:group1_update) do + group1.update(auth_domain: 'test') + SolidRecord.flush_cache + end + + before { doc } + + # it_behaves_like 'FileSystemElement' + + it 'creates the correct number of Models' do # rubocop:disable RSpec/MultipleExpectations + binding.pry + expect(Infra::Group.count).to eq(4) + expect(Infra::Host.count).to eq(11) + expect(Infra::Service.count).to eq(1) + end + + context 'when destroy' do + it { + expect do + host1.destroy + SolidRecord.flush_cache + end .to change(File, :count).by(-1) + } + + it { + expect do + host2.destroy + SolidRecord.flush_cache + end .to change(described_class, :count).by(-1) + } + end + + context 'when update' do + it { expect { host1_update }.to change(Element, :count).by(0) } + it { expect { group1_update }.to change(Element, :count).by(0) } + end + end + end + end +end diff --git a/solid_record/spec/lib/solid_record/segment_spec.rb b/solid_record/spec/lib/solid_record/segment_spec.rb new file mode 100644 index 00000000..106c5b2a --- /dev/null +++ b/solid_record/spec/lib/solid_record/segment_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidRecord + RSpec.describe Segment do + before { SolidRecord.setup } + + describe '#flagged_for' do + context 'when segment is updated' do + let(:element) { described_class.create(flags: Set.new << :update) } + + before { element } + + it { expect(described_class.flagged.count).to eq(1) } + + it { expect(described_class.flagged_for(:update).count).to eq(1) } + + it { expect(described_class.flagged_for(:destroy).count).to eq(0) } + end + end + end +end diff --git a/solid_record/spec/spec_helper.rb b/solid_record/spec/spec_helper.rb new file mode 100644 index 00000000..3de69460 --- /dev/null +++ b/solid_record/spec/spec_helper.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'pry' +require 'bundler/setup' +require 'solid_record' +# require 'solid_support' + +SPEC_ROOT = Pathname.new(__dir__).join('..') +DUMMY_ROOT = SPEC_ROOT.join('spec/dummy') + +module Helpers + def xdoc(klass, hash) = SolidRecord.toggle_callbacks { klass.create(hash) } +end + +RSpec.configure do |config| + # config.before(:suite) { SolidRecord::DataStore.load } # Setup the A/R database connection + SolidRecord.logger.level = :warn # debug + SolidRecord.config.sandbox = true + config.before(:suite) do + DUMMY_ROOT.children.select(&:directory?).each do |path| + require path.join('models') if path.join('models.rb').exist? + end + end + config.include Helpers +end + +class SpecHelper + class << self + # def before_context(type) + # Use load rather than require_relative as the models are required per context + # NOTE: If the context needs to load multiple models then it handles that itself + # load DUMMY_ROOT.join(type.to_s, 'models.rb') # .glob('*.rb').each { |path| load path } + # end + + # def after_context # rubocop:disable Metrics/AbcSize + # SolidRecord.tables.reject { |table| table.name.start_with?('SolidRecord') }.each do |klass| + # # remove the class from the tables array + # SolidRecord.tables.delete(klass) + # # remove any STI classes + # klass.descendants.each { |child| child.module_parent.send(:remove_const, child.name.demodulize.to_sym) } + # # remove the class + # klass.module_parent.send(:remove_const, klass.name.demodulize.to_sym) + # end + # ActiveSupport::Dependencies::Reference.clear! # Remove any A/R Cached Classes (e.g. STI classes) + # end + end +end diff --git a/solid-support/.gitignore b/solid_support/.gitignore similarity index 100% rename from solid-support/.gitignore rename to solid_support/.gitignore diff --git a/solid_support/.pryrc b/solid_support/.pryrc new file mode 100644 index 00000000..b7994d11 --- /dev/null +++ b/solid_support/.pryrc @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +# .pryrc diff --git a/solid_support/.rspec b/solid_support/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/solid_support/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/solid_support/.rubocop.yml b/solid_support/.rubocop.yml new file mode 100644 index 00000000..6a185511 --- /dev/null +++ b/solid_support/.rubocop.yml @@ -0,0 +1,16 @@ +--- +inherit_from: ../.rubocop.yml +AllCops: + Exclude: + - 'lib/solid_record/associations.rb' +Lint/EmptyClass: + Exclude: + - 'spec/**/*.rb' +RSpec/BeforeAfterAll: + Enabled: false +RSpec/NestedGroups: + Max: 5 +Style/CommentedKeyword: + Enabled: false +RSpec/MultipleMemoizedHelpers: + Max: 9 diff --git a/solid-support/CHANGELOG.md b/solid_support/CHANGELOG.md similarity index 100% rename from solid-support/CHANGELOG.md rename to solid_support/CHANGELOG.md diff --git a/solid_support/Gemfile b/solid_support/Gemfile new file mode 100644 index 00000000..7f4f5e95 --- /dev/null +++ b/solid_support/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec diff --git a/solid-support/LICENSE.txt b/solid_support/LICENSE.txt similarity index 100% rename from solid-support/LICENSE.txt rename to solid_support/LICENSE.txt diff --git a/solid_support/README.md b/solid_support/README.md new file mode 100644 index 00000000..5dc37209 --- /dev/null +++ b/solid_support/README.md @@ -0,0 +1,2 @@ +# Solid View + diff --git a/solid-support/Rakefile b/solid_support/Rakefile similarity index 100% rename from solid-support/Rakefile rename to solid_support/Rakefile diff --git a/solid_support/app/commands/solid_support/main_command.rb b/solid_support/app/commands/solid_support/main_command.rb new file mode 100644 index 00000000..13509d0d --- /dev/null +++ b/solid_support/app/commands/solid_support/main_command.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidSupport + class MainCommand < ApplicationCommand + desc 'generate', 'generate' + def generate(_type, *_attributes) = execute + + desc 'console', 'console' + def console() = execute + end +end diff --git a/solid_support/app/commands/solid_support/plugin_command.rb b/solid_support/app/commands/solid_support/plugin_command.rb new file mode 100644 index 00000000..ac687342 --- /dev/null +++ b/solid_support/app/commands/solid_support/plugin_command.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module SolidSupport + class PluginCommand < ApplicationCommand + desc 'new NAME', "Create a new #{module_parent} plugin" + long_desc <<-DESC.tr("\n", "\x5") + + The '#{$0.split('/').last} plugin new' command creates a new Hendrix project with a default + directory structure and configuration at the path you specify. + + Example: + hendrix new ~/Projects/todo + + This generates a skeletal Hendrix plugin in ~/Projects/todo. + DESC + option :force, desc: 'Force creation even if the project directory already exists', + aliases: '-f', type: :boolean + option :config, desc: 'Create project with a working configuration (instead of commented examples)', + aliases: '-c', type: :boolean + option :full, desc: 'Create the full', + type: :boolean + def new(path) + # By default generate an extension which is just a segments directory + # When passing the full option then create a gem + type = options.full ? :plugin : :extension + check_dir(path) && execute(controller: :project, path: path, type: type) + end + end +end diff --git a/solid_support/app/commands/solid_support/project_command.rb b/solid_support/app/commands/solid_support/project_command.rb new file mode 100644 index 00000000..59f9a6c9 --- /dev/null +++ b/solid_support/app/commands/solid_support/project_command.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module SolidSupport + class ProjectCommand < ApplicationCommand + # binding.pry + # register Hendrix::PluginCommand, 'plugin', 'plugin', 'Create a new Hendrix plugin' + register module_parent::PluginCommand, 'plugin', 'plugin', "Create a new #{module_parent} plugin" + + desc 'new NAME', "Create a new #{module_parent} project" + long_desc <<-DESC.tr("\n", "\x5") + + The '#{script} new' command creates a new #{module_parent} project with a default + directory structure and configuration at the path you specify. + + You can specify extra command-line arguments to be used every time + '#{script} new' runs in the ~/.config/#{script}/#{script}.yml configuration file + + Note that the arguments specified in the #{script}.yml file don't affect the + defaults values shown above in this help message. + + Example: + #{script} new ~/Projects/todo + + This generates a skeletal #{module_parent} project in ~/Projects/todo. + DESC + option :force, desc: 'Force creation even if the project directory already exists', + aliases: '-f', type: :boolean + option :config, desc: 'Create project with a working configuration (instead of commented examples)', + aliases: '-c', type: :boolean + option :guided, desc: 'Create project with a guided configuration', + aliases: '-g', type: :boolean + def new(path) = check_dir(path) && execute(path: path, type: :application) + + desc 'version', "Show #{module_parent} version" + option :all, desc: 'Show version information for all extensions', + aliases: '-a', type: :boolean + def version() = execute(controller: :project) + end +end diff --git a/solid_support/app/controllers/solid_support/console_controller.rb b/solid_support/app/controllers/solid_support/console_controller.rb new file mode 100644 index 00000000..77815001 --- /dev/null +++ b/solid_support/app/controllers/solid_support/console_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module SolidSupport + class ConsoleController < ApplicationController + include SolidSupport::ConsoleControllerMethods + +# Rename pry commands so they still work but can be reassigned to CLI specific commands +%w[ls cd help].each do |cmd| + Pry.config.commands["p#{cmd}"] = "Pry::Command::#{cmd.camelize}".constantize + Pry.config.commands[cmd] = nil +end + +Pry::Commands.block_command 'cd', 'change segment' do |path| + puts "cd: invalid segment: #{path}" unless OneStack::Navigator.current.cd(path) +end + +Pry::Commands.block_command 'pwd', 'print segment' do + OneStack::Navigator.current.path.relative_path_from(OneStack.config.paths.segments).to_s +end + + end +end diff --git a/solid_support/app/controllers/solid_support/project_controller.rb b/solid_support/app/controllers/solid_support/project_controller.rb new file mode 100644 index 00000000..e8499f1d --- /dev/null +++ b/solid_support/app/controllers/solid_support/project_controller.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'ostruct' + +module SolidSupport + class ProjectController < ApplicationController + def version + # binding.pry + unless options.all + puts "#{parent} #{parent::VERSION}" + return + end + extensions = parent.extensions.values.map(&:module_parent).append(parent).map(&:to_s) + pad = extensions.max_by(&:length).size + 4 + extensions.sort.each do |extension| + puts [extension, ' ' * (pad - extension.length), extension.constantize::VERSION].join + end + end + + # Entrypoint for the new command for both Project and Plugin Commands + def new + # binding.pry + path.rmtree if path.exist? + send("new_#{args.type}".to_sym) + end + + # An Extension is a gem with a lib file that inherits from Hendrix::Extension/Tune + # 1. create gem is a specific method (Shared with plugin) + # 2. create extension.rb is a specific method + def new_extension() = generator.invoke_all + + # A Plugin is an extension but replaces extension.rb with plugin.rb and has an app structure + # 1. invoke extension#gem + # 2. create plugin.rb is a specific method + # 3. create an app structure is a specific method (share with application) + # 4. create test harness at spec/dummy/app to create app structure is a specific method + # 5. create app config at spec/dummy/config is a specific method (share with application) + def new_plugin + generator(:extension).invoke(:gem) + generator(:extension).invoke(:gem_libs) + generator(:extension).invoke(:gem_root_files) + generator(:extension).invoke(:app_structure) + generator.invoke_all + end + + # An application: + # 1. invokes plugin#app_structure + # 2. invoke plugin#app_config but create at the root of the project + # NOTE: An application is just a plugin with some extra stuff + def new_application + binding.pry + generator(:plugin).invoke(:app_dir_structure) + generator(:plugin).invoke(:app_structure) + generator.invoke_all + end + + def generator(type = args.type) + @generators ||= {} + return @generators[type] if @generators[type] + + # TODO: The string 'hendrix' can be passed in as an argument from the ProjectController + @generators[type] ||= generator_class(type).new([name, parent_name], options) + # All Thor methods are automatically invoked inside destination_root + @generators[type].destination_root = name + @generators[type] + end + + # def generator_class(type) = "hendrix/project/#{type}_generator".classify.constantize + def generator_class(type) = "#{parent_name}/project/#{type}_generator".classify.constantize + + def name = @name ||= path.to_s.split('/').last + + def path = @path ||= Pathname.new(args.path) + end +end diff --git a/solid_support/app/generators/solid_support/project.rb b/solid_support/app/generators/solid_support/project.rb new file mode 100644 index 00000000..36bbfc92 --- /dev/null +++ b/solid_support/app/generators/solid_support/project.rb @@ -0,0 +1,6 @@ + +module SolidSupport + module Project + + end +end diff --git a/cnfs/app/generators/cnfs/new/project/files/.gitignore b/solid_support/app/generators/solid_support/project/application/files/.gitignore similarity index 100% rename from cnfs/app/generators/cnfs/new/project/files/.gitignore rename to solid_support/app/generators/solid_support/project/application/files/.gitignore diff --git a/solid_support/app/generators/solid_support/project/application/files/config/initializers/logging.rb b/solid_support/app/generators/solid_support/project/application/files/config/initializers/logging.rb new file mode 100644 index 00000000..0a90df28 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/application/files/config/initializers/logging.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Documentation: https://github.com/piotrmurach/tty-logger + +# Custom logger +# Hendrix.logger = TTY::Logger.new do |config| +# config.level = :warn +# # config.handlers = [[:stream, formatter: :json]] +# # config.output = File.open('/tmp/errors.log', 'w') +# # https://github.com/piotrmurach/tty-logger#241-metadata +# # config.metadata = [:file] +# config.metadata = [:date, :time] +# end + +# require 'tty-logger-raven' + +# TTY::Logger.new do |c| +# c.handlers = [[:raven, {}]] +# end diff --git a/solid_support/app/generators/solid_support/project/application/templates/Gemfile.erb b/solid_support/app/generators/solid_support/project/application/templates/Gemfile.erb new file mode 100644 index 00000000..0dabcc00 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/application/templates/Gemfile.erb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +<% binding.pry %> + +<%# %w[core aws docker gcp kubernetes native terraform].unshift('').each do |gem_name| -%> +<% gems.each do |gem_name| -%> +<%= gemfile_gem_string(gem_name) %> +<% end -%> diff --git a/cnfs/app/generators/cnfs/new/project/templates/README.md.erb b/solid_support/app/generators/solid_support/project/application/templates/README.md.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/project/templates/README.md.erb rename to solid_support/app/generators/solid_support/project/application/templates/README.md.erb diff --git a/solid_support/app/generators/solid_support/project/application_generator.rb b/solid_support/app/generators/solid_support/project/application_generator.rb new file mode 100644 index 00000000..137604b7 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/application_generator.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidSupport + class Project::ApplicationGenerator < ProjectGenerator + # def data_files + # data_path.rmtree if data_path.exist? + # create_file(data_path.join('keys.yml'), { name => Lockbox.generate_key }.to_yaml) + # end + + def project_files() = directory('files', '.') + + def app_structure() = super + + private + + def gemfile_gem_string(name) + gem_name = name.empty? ? gem_name_root : "#{gem_name_root}-#{name}" + return "gem '#{gem_name}'" if ENV['CNFS_ENV'].eql?('production') + + name = gem_name_root if name.empty? + "gem '#{gem_name}', path: '#{gems_path.join(name)}'" + end + + def gems_path() = internal_path.join('../../../../../') + + def internal_path() = Pathname.new(__dir__) + + def gems = [''] + # def data_path() = CnfsCli.config.data_home.join('projects', uuid) + end +end diff --git a/solid_support/app/generators/solid_support/project/extension/templates/.rubocop.yml.erb b/solid_support/app/generators/solid_support/project/extension/templates/.rubocop.yml.erb new file mode 100644 index 00000000..e8d8fbcf --- /dev/null +++ b/solid_support/app/generators/solid_support/project/extension/templates/.rubocop.yml.erb @@ -0,0 +1,4 @@ +--- +inherit_from: + - ../.rubocop.yml + # - .rubocop_todo.yml diff --git a/solid_support/app/generators/solid_support/project/extension/templates/Gemfile.erb b/solid_support/app/generators/solid_support/project/extension/templates/Gemfile.erb new file mode 100644 index 00000000..10e44875 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/extension/templates/Gemfile.erb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem_path = '<%= internal_path.join('../../../../..') %>' + +gem 'solid-support', path: "#{gem_path}/solid-support" + +gemspec diff --git a/solid_support/app/generators/solid_support/project/extension/templates/bin/console.erb b/solid_support/app/generators/solid_support/project/extension/templates/bin/console.erb new file mode 100644 index 00000000..97c22ab5 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/extension/templates/bin/console.erb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' + +ARGV.unshift 'console' +DUMMY_PATH = Pathname.new(__dir__).join('../spec/dummy') + +Dir.chdir(DUMMY_PATH) { require 'hendrix/boot_loader' } diff --git a/cnfs/app/generators/cnfs/new/plugin/templates/lib/cnfs/module.rb.erb b/solid_support/app/generators/solid_support/project/extension/templates/lib/module.rb.erb similarity index 53% rename from cnfs/app/generators/cnfs/new/plugin/templates/lib/cnfs/module.rb.erb rename to solid_support/app/generators/solid_support/project/extension/templates/lib/module.rb.erb index ed3c3b15..56560c1c 100644 --- a/cnfs/app/generators/cnfs/new/plugin/templates/lib/cnfs/module.rb.erb +++ b/solid_support/app/generators/solid_support/project/extension/templates/lib/module.rb.erb @@ -1,13 +1,17 @@ # frozen_string_literal: true -require_relative '<%= name %>/version' -require_relative '<%= name %>/plugin' +require_relative '<%= "#{gem_name_root}/#{name}/version.rb" %>' + +require '<%= gem_name_root %>' + +require_relative '<%= "#{gem_name_root}/#{name}/plugin.rb" %>' + module <%= name.camelize %> module Concerns; end end -module Cnfs +module <%= gem_name_root.camelize %> module <%= name.camelize %> class << self def gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') diff --git a/solid_support/app/generators/solid_support/project/extension/templates/lib/plugin.rb.erb b/solid_support/app/generators/solid_support/project/extension/templates/lib/plugin.rb.erb new file mode 100644 index 00000000..b175c0a5 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/extension/templates/lib/plugin.rb.erb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module <%= gem_name_root.camelize %> + module <%= name.camelize %> + class Plugin < <%= gem_name_root.camelize %>::Plugin + # initializer 'logger' do |app| + # Hendrix.logger.info('[<%= name.camelize %>] Initializing from', gem_root) + # end + + class << self + def gem_root() = <%= gem_name_root.camelize %>::<%= name.camelize %>.gem_root + end + end + end +end diff --git a/solid_support/app/generators/solid_support/project/extension_generator.rb b/solid_support/app/generators/solid_support/project/extension_generator.rb new file mode 100644 index 00000000..b802b763 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/extension_generator.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module SolidSupport + class Project::ExtensionGenerator < ProjectGenerator + def gem + if options.noop + puts cmd + return + end + + # These methods are not Thor aware and so execute in Dir.pwd + # rather than #destination_root + Pathname.new(name).rmtree if Pathname.new(name).exist? + system(cmd) + FileUtils.mv(gem_name, name) + end + + def gem_libs + remove_file("lib/#{gem_name_root}/#{name}.rb") + template('templates/lib/module.rb.erb', "lib/#{gem_name_root}-#{name}.rb") + template('templates/lib/plugin.rb.erb', "lib/#{gem_name_root}/#{name}/plugin.rb") + inject_into_file "#{gem_name_root}-#{name}.gemspec", + " spec.add_dependency '#{gem_name_root}', '~> 0.1.0'\n", before: /^end/ + end + + def gem_root_files + remove_file('Gemfile') + end + + # renders templates + def app_structure() = super + + def gem_cleanup + return if options.noop + + if options.config + remove_dir('.git') + remove_file('.travis.yml') + remove_file('.gitignore') + end + end + + private + def manual_templates() = %w[lib/module.rb.erb lib/plugin.rb.erb] + + def cmd() = "bundle gem --test=rspec --ci=none --no-coc --no-rubocop --mit --changelog #{gem_name}" + + def gem_name() = "#{gem_name_root}-#{name}" + + def internal_path() = Pathname.new(__dir__) + end +end diff --git a/solid_support/app/generators/solid_support/project/plugin/m_templates/application.rb.erb b/solid_support/app/generators/solid_support/project/plugin/m_templates/application.rb.erb new file mode 100644 index 00000000..a56e45e3 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/plugin/m_templates/application.rb.erb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Application<%= type.classify %> < <%= gem_name_root.camelize %>::Application<%= type.classify %> +end diff --git a/solid_support/app/generators/solid_support/project/plugin/templates/config/application.rb.erb b/solid_support/app/generators/solid_support/project/plugin/templates/config/application.rb.erb new file mode 100644 index 00000000..54e73c09 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/plugin/templates/config/application.rb.erb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module <%= name.camelize %> + module Concerns; end + # class Application < <%= gem_name_root.camelize %>::Application + class Application < <%= BOOT_MODULE.to_s.camelize %>::Application + # Reference id for encryption keys and local file access + config.project_id = '<%= uuid %>' + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + # config.cli.show_segment_name = true + # config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/solid_support/app/generators/solid_support/project/plugin/templates/config/boot.rb.erb b/solid_support/app/generators/solid_support/project/plugin/templates/config/boot.rb.erb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/plugin/templates/config/boot.rb.erb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/solid_support/app/generators/solid_support/project/plugin/templates/config/environment.rb.erb b/solid_support/app/generators/solid_support/project/plugin/templates/config/environment.rb.erb new file mode 100644 index 00000000..1f198813 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/plugin/templates/config/environment.rb.erb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +<%= gem_name_root.camelize %>.application.initialize! diff --git a/solid_support/app/generators/solid_support/project/plugin_generator.rb b/solid_support/app/generators/solid_support/project/plugin_generator.rb new file mode 100644 index 00000000..c035a442 --- /dev/null +++ b/solid_support/app/generators/solid_support/project/plugin_generator.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module SolidSupport + class Project::PluginGenerator < ProjectGenerator + attr_accessor :type + + def app_dir_structure + # %w[commands controllers generators records views].each do |type| + %w[commands controllers generators].each do |type| + @type = type + app_file(type) + end + end + + # Set this to spec/dummy so that app_structure templates files in this directory + def set_dest_root() = self.destination_root = "#{name}/spec/dummy" + + def app_structure() = super + + private + + def app_file(app_path) + ap = app_path.eql?('records') ? 'models' : app_path + template("m_templates/application.rb.erb", path.join('app', ap, "application_#{app_path.singularize}.rb")) + end + + def uuid() = @uuid ||= SecureRandom.uuid + + def internal_path() = Pathname.new(__dir__) + end +end + diff --git a/solid_support/app/generators/solid_support/project_generator.rb b/solid_support/app/generators/solid_support/project_generator.rb new file mode 100644 index 00000000..183f6229 --- /dev/null +++ b/solid_support/app/generators/solid_support/project_generator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module SolidSupport + class ProjectGenerator < ApplicationGenerator + argument :name + argument :gem_name_root + + private + + # Used by Plugin and Application + def app_structure + to_render.each do |template| + destination = template.relative_path_from(templates_path).to_s.delete_suffix('.erb') + template(template, destination) + end + end + + def to_render() = (templates - manual_templates.map{ |path| templates_path.join(path) }) + + def manual_templates() = [] + end +end +=begin + # def component_files() = _component_files + # keep('config/initializers') + + def _component_files + binding.pry + # keep("app/controllers/#{name}/concerns") + # keep("app/controllers/#{name}/concerns") + # keep("app/generators/#{name}/concerns") + # keep("app/models/#{name}/concerns") + # keep("app/views/#{name}/concerns") + keep('config/initializers') + end + + # def keep(keep_path) = create_file(path.join(keep_path, '.keep')) +=end diff --git a/solid_support/bin/console b/solid_support/bin/console new file mode 100755 index 00000000..6e0e5ad3 --- /dev/null +++ b/solid_support/bin/console @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler' +Bundler.require + +module SolidSupport + def self.boot(dir = Dir.pwd) = Dir.chdir(dir) { require('solid_support/boot_loader') } +end + +SolidSupport.pry diff --git a/solid-support/bin/setup b/solid_support/bin/setup similarity index 100% rename from solid-support/bin/setup rename to solid_support/bin/setup diff --git a/solid_support/exe/solid b/solid_support/exe/solid new file mode 100755 index 00000000..ef88a8a9 --- /dev/null +++ b/solid_support/exe/solid @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +git_path = File.expand_path('../../.git', __dir__) + +if File.exist?(git_path) + plugin_path = File.expand_path('../lib', __dir__) + $:.unshift(plugin_path) +end + +require 'solid_support' +require 'solid_support/boot_loader' diff --git a/cnfs/lib/ext/pathname.rb b/solid_support/lib/ext/array.rb similarity index 55% rename from cnfs/lib/ext/pathname.rb rename to solid_support/lib/ext/array.rb index ebcb1c8b..214c6219 100644 --- a/cnfs/lib/ext/pathname.rb +++ b/solid_support/lib/ext/array.rb @@ -1,17 +1,11 @@ # frozen_string_literal: false -class Pathname - def puts() = Kernel.puts(read) - - def cp(dest) = FileUtils.cp(self, dest) -end - class Array # Support multiple options to filter an array of Hashes def where(**options) ret_val = self options.each do |key, value| - ret_val = ret_val.select{ |item| item[key].eql?(value.to_s) } + ret_val = ret_val.select { |item| item[key].eql?(value.to_s) } end ret_val end diff --git a/cnfs/lib/ext/hash.rb b/solid_support/lib/ext/hash.rb similarity index 60% rename from cnfs/lib/ext/hash.rb rename to solid_support/lib/ext/hash.rb index dfef4340..3c90ae5c 100644 --- a/cnfs/lib/ext/hash.rb +++ b/solid_support/lib/ext/hash.rb @@ -1,5 +1,7 @@ # frozen_string_literal: false +# rubocop:disable Style/OpenStructUse class Hash def deep_to_o() = JSON.parse(to_json, object_class: OpenStruct) end +# rubocop:enable Style/OpenStructUse diff --git a/cnfs/lib/ext/open_struct.rb b/solid_support/lib/ext/open_struct.rb similarity index 68% rename from cnfs/lib/ext/open_struct.rb rename to solid_support/lib/ext/open_struct.rb index 9d964788..b815a6b0 100644 --- a/cnfs/lib/ext/open_struct.rb +++ b/solid_support/lib/ext/open_struct.rb @@ -1,5 +1,7 @@ # frozen_string_literal: false +# rubocop:disable Style/OpenStructUse class OpenStruct def deep_to_h() = to_h.transform_values { |v| v.is_a?(OpenStruct) ? v.deep_to_h : v }.deep_stringify_keys end +# rubocop:enable Style/OpenStructUse diff --git a/solid_support/lib/ext/string.rb b/solid_support/lib/ext/string.rb new file mode 100644 index 00000000..965a3b09 --- /dev/null +++ b/solid_support/lib/ext/string.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: false + +# String interpolation using a dot reference notation with ${} delimter pattern +# +# For each interpolation, pass a reference hash to search for the interpolated string +# +# @example one top level key +# +# hash = { project: { domain: 'example.com' } } +# 'host.${project.domain}'.interpolate(**hash) # => 'host.example.com' +# +# hash = { project: { host: 'api', domain: 'test.com' } } +# '${project.host}.${project.domain}'.interpolate(**hash) # => 'api.test.com' +# +# @example multiple top level keys +# +# hash = { project: { name: 'test' }, admin: { tld: 'io' } } +# 'host.${project.name}.${admin.tld}'.interpolate(**hash) # => 'host.test.io' +# +# +# @example default and additional top level keys +# +# # If a top level key is named 'default' the 'default' predicate is not necessary in the interpolated string +# +# hash = { default: { host: 'test', domain: 'example' }, parent: { tld: 'io' } } +# '${host}.${domain}.${parent.tld}'.interpolate(**hash) # => 'test.example.io' +class String + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize + + # Interpolate a string with values from a Hash + def interpolate(**references) + # Return the original string if no references were passed in + return self if references.empty? + + interpolations = scan(/\${(.*?)}/).flatten + + # Return the original string if no interpolations are found in the string + return self unless interpolations.any? + + references.deep_transform_keys!(&:to_s) + + references.each do |key, param| + next if param.is_a?(Hash) + + raise ArgumentError, "argument must by of type Hash (param #{key}, type #{param.class})" + end + + # If one of the references keys is default then remove the default key and put its values at the root of the Hash + i_reference = if references.key?('default') + references.except('default').merge(references['default']) + else + references + end + + return_string = self + interpolations.each do |interpolation| + next unless interpolation.length.positive? + + sub_string = search_reference(i_reference, interpolation) + next unless sub_string.is_a? String + + return_string = return_string.gsub("${#{interpolation}}", sub_string) + end + + # If after interpolation the string has not changed then return itself + return self if return_string.eql?(self) + + # If the string has changed, an interpolation may have been replaced with another interpolation + # So recursively invoke interpolate on the return_string + return_string.interpolate(**references) + end + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize + + private + + def search_reference(reference, interpolation) + interpolation.split('.').each do |value| + # reference must continue to be a Hash or Object otherwise it will fail when send is called + break if reference.is_a? String + + break unless (reference = reference.is_a?(Hash) ? reference[value] : reference.send(value)) + end + reference + end +end diff --git a/solid_support/lib/solid_support.rb b/solid_support/lib/solid_support.rb new file mode 100644 index 00000000..481157d4 --- /dev/null +++ b/solid_support/lib/solid_support.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +# TODO: Isn't this already required by the Application? or what about when new? +# Test and make a note here if needed for new command +# require 'bundler/setup' + +# require 'active_support' +require 'active_model' + +require 'thor' +# require 'tty-logger' +require 'tty-prompt' +require 'tty-screen' +require 'tty-spinner' +require 'tty-table' +require 'tty-tree' +require 'xdg' +# require 'zeitwerk' + +# External dependencies +require 'active_support/concern' +require 'active_support/core_ext/enumerable' +require 'active_support/core_ext/hash' +require 'active_support/core_ext/module/introspection' +require 'active_support/inflector' +require 'active_support/notifications' +require 'active_support/time' + +# Stdlibs +require 'fileutils' +require 'yaml' + +require_relative 'ext/hash' +require_relative 'ext/open_struct' +require_relative 'ext/string' + +require_relative 'solid_support/interpolation' + +# require 'solid_support' + +require_relative 'solid_support/version' + +# application libs +require_relative 'solid_support/application' + +# require 'solid_record' + +require_relative 'solid_support/application_command' +require_relative 'solid_support/application_controller' +require_relative 'solid_support/application_generator' +require_relative 'solid_support/console_controller' +require_relative 'solid_support/errors' +require_relative 'solid_support/loader' +require_relative 'solid_support/timer' +require_relative 'solid_support/version' +# binding.pry + +require_relative 'solid_support/models/command' +require_relative 'solid_support/models/command_queue' +require_relative 'solid_support/models/download' +require_relative 'solid_support/models/extendable' +require_relative 'solid_support/models/git' + +require_relative 'solid_support/platform' + +module SolidSupport + class Error < StandardError; end + + class << self + attr_writer :logger + + def config() = @config ||= config_set + + def config_set + config = ActiveSupport::OrderedOptions.new + config + end + end + + + # From solid_support.orig + class << self + # TODO: Decide what to do with subscribers + def x_reset + subscribers.each { |sub| ActiveSupport::Notifications.unsubscribe(sub.pattern) } + end + + # Record any ActiveRecord pubsub subsribers + def subscribers() = @subscribers ||= [] + + # TODO: Is this necessary? + def with_profiling + unless ENV['HENDRIX_PROF'] + yield + return + end + + require 'ruby-prof' + RubyProf.start + yield + results = RubyProf.stop + File.open("#{Dir.home}/hendrix-prof.html", 'w+') { |file| RubyProf::GraphHtmlPrinter.new(results).print(file) } + end + end +end diff --git a/solid_support/lib/solid_support/app_loader.rb b/solid_support/lib/solid_support/app_loader.rb new file mode 100644 index 00000000..6744382c --- /dev/null +++ b/solid_support/lib/solid_support/app_loader.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# start_time = Time.now +# lib_path = File.expand_path('../lib', __dir__) +# $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) + +# ENV['HENDRIX_PROF'] ||= ARGV.delete('--cli-prof') +# dval = ARGV.index('--logging') || ARGV.index('-l') +# ENV['HENDRIX_LOGGING'] = ARGV[dval + 1] if dval + +# Require the solid_support framework and external dependencies +# require 'solid_support' + +# Require application, extension and plugin base classes after the framework +require_relative 'application' + +# Require the application's environment from /config/environment.rb +# The environment then requires the application at ./application.rb +# application.rb requires boot.rb +# boot.rb requires gems as specified in the Gemfile +# After this each gem, e.g. plugins and extensions, will have been required +# Finally environment.rb calls initialize! which return self +require APP_ROOT.join(ROOT_FILE_ID) + +# Signal.trap('INT') do +# warn("\n#{caller.join("\n")}: interrupted") +# # exit 1 +# end + +# Invoke the CLI +boot_module = BOOT_MODULE.to_s.underscore.upcase +invoked_from_new_generator = ARGV[0].eql?('new') +in_test_mode = ENV["#{boot_module}_ENV"].eql?('test') +skip_controller_start = invoked_from_new_generator || in_test_mode + +unless skip_controller_start + add_args = ARGV.size.positive? && ARGV.first.start_with?('-') + ARGV.unshift('help') if ARGV.size.zero? || add_args + begin + # Order of precendence for the MainCommand class: + # 1. defined in the application, 2. defined by a plugin exe or 3. the default + # BOOT_MODULE = OneStack unless defined?(BOOT_MODULE) + # BOOT_MODULE ||= SolidSupport unless defined?(BOOT_MODULE) + cmd_object = defined?(MainCommand) ? MainCommand : BOOT_MODULE::MainCommand + cmd_object.start + rescue SolidSupport::Error => e + puts e.message + exit 1 + end +end diff --git a/solid_support/lib/solid_support/application.rb b/solid_support/lib/solid_support/application.rb new file mode 100644 index 00000000..8ba826f1 --- /dev/null +++ b/solid_support/lib/solid_support/application.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require_relative 'plugin' +require_relative 'application/configuration' + +module SolidSupport + class << self + attr_writer :logger + attr_accessor :app_class, :cli_mode + + def config() = application.config + + def application() = @application ||= app_class.instance + + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + # TODO: Multiple loggers + def logger() = @logger ||= set_logger + + def set_logger + default_level = (config.logging || 'warn').to_sym + ActiveSupport::Logger.new($stdout) + # level = ::TTY::Logger::LOG_TYPES.key?(default_level) ? default_level : :warn + # ::TTY::Logger.new do |config| + # SolidSuport.config.logging = config.level = level + # config.level = level + # end + end + end + + class Application < Plugin + class << self + # https://github.com/rails/rails/blob/main/railties/lib/rails/application.rb#L68 + def inherited(base) + super + set_config(base) + module_parent.app_class = base + end + + # Extensions that have a default config are merged into the application's config + # so the user can modify those values + def set_config(base) + module_parent.extensions.values.each do |extension| + next unless extension.module_parent.respond_to?(:config) + + extension_name = extension.module_parent.name.underscore.to_sym + extension_config = extension.module_parent.config + base.config.send(extension_name).merge!(extension_config) + end + end + + def gem_root() = APP_ROOT + end + + def config() = @config ||= self.class.superclass::Configuration.new + + def name() = self.class.name.deconstantize.downcase + + # TODO: Test this wrt to the boot process and loading of config/application.rb + # The prefix of ENV vars specified in config/application.rb + config.before_initialize do |config| + # config.view_options ||= { help_color: :brown } + # config.env_base = 'CNFS_' if config.env_base.empty? + end + + # https://github.com/rails/rails/blob/main/railties/lib/rails/application.rb#L367 + def initialize! + Extension.initialize! { Plugin.initialize! } + self + end + end +end diff --git a/solid_support/lib/solid_support/application/configuration.rb b/solid_support/lib/solid_support/application/configuration.rb new file mode 100644 index 00000000..e4afb8ee --- /dev/null +++ b/solid_support/lib/solid_support/application/configuration.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'xdg' +require 'active_support/inflector' + +require_relative '../plugin/configuration' + +module SolidSupport + class Application < Plugin + class Configuration < SolidSupport::Plugin::Configuration + def root() = APP_ROOT + + # user-specific non-essential (cached) data + def cache_home() = @cache_home ||= xdg.cache_home.join(xdg_name) + + # user-specific configuration files + def config_home() = @config_home ||= xdg.config_home.join(xdg_name) + + # user-specific data files + def data_home() = @data_home ||= xdg.data_home.join(xdg_name) + + # return a unique path using the project_id so that each project's local files are isolated + def xdg_name() = @xdg_name ||= "#{xdg_base}/#{xdg_projects_base}/#{project_id}" + + # Overridde in a subclass to set a different value + def xdg_projects_base() = 'projects' + + def xdg_base() = self.class.module_parent.module_parent.to_s.underscore + + def cli_cache_home() = xdg.cache_home.join(xdg_base) + + # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + def xdg() = @xdg ||= XDG::Environment.new + end + end +end diff --git a/solid_support/lib/solid_support/application_command.rb b/solid_support/lib/solid_support/application_command.rb new file mode 100644 index 00000000..6e3b4c83 --- /dev/null +++ b/solid_support/lib/solid_support/application_command.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +module SolidSupport + class ApplicationCommand < Thor + # include Extendable if defined? APP_ROOT + attr_accessor :calling_method + + class << self + def script = $0.split('/').last + + def has_class_options(*names) = define_options(names: names, type: :class_) + + def has_options(*names) = define_options(names: names) + + def define_options(names:, type: '') + names.flatten.each do |name| + raise "Tried to access undefined shared option '#{name}'" unless (opts = shared_options[name]) + + send("#{type}option", name, opts) + end + end + + def shared_options + { + dry_run: { desc: 'Do not execute commands', aliases: '-d', type: :boolean }, + force: { desc: 'Do not prompt for confirmation', aliases: '-f', type: :boolean }, + quiet: { desc: 'Do not output execute commands', aliases: '-q', type: :boolean } + } + end + + def exit_on_failure?() = true + end + + # TODO: Refactor or Remove + class << self + # Add an option and it's values to a specific command in a controller + # Used by plugin modules to add options to an existing command + def add_cnfs_method_option(method_name, options_name, **options) + @cnfs_method_options ||= {} + @cnfs_method_options[method_name] ||= {} + @cnfs_method_options[method_name][options_name] = options + end + + def cnfs_method_options(method_name) + return unless (command = @cnfs_method_options.try(:[], method_name)) + + command.each { |name, values| option name, values } + end + + def add_cnfs_action(lifecycle_command, method_name) + lifecycle, *command = lifecycle_command.to_s.split('_') + command = command.join('_').to_sym + @cnfs_actions ||= {} + @cnfs_actions[command] ||= [] + @cnfs_actions[command].append({ lifecycle: lifecycle, method_name: method_name }) + end + + def cnfs_actions(method_name) + return unless (actions = @cnfs_actions.try(:[], method_name)) + + actions.each { |action| send(action[:lifecycle], action[:method_name]) } + end + end + + private + + # Invokes the appropriate method on the appropriate controller + def execute(**kwargs) + namespace = kwargs.delete(:namespace)&.to_s&.classify || self.class.name.deconstantize + controller = kwargs.delete(:controller)&.to_s&.classify || self.class.name.demodulize.delete_suffix('Command') + method = kwargs.delete(:method) + + # The name of the method that invoked 'execute', e.g. 'console' + @calling_method = caller_locations(1..1).first.label + + # Arguments and Options that will be passed to the controller + @args = Thor::CoreExt::HashWithIndifferentAccess.new(kwargs) + @options = Thor::CoreExt::HashWithIndifferentAccess.new(options) + + controller_klass = controller_class(namespace: namespace, controller: controller) + controller_obj = controller_klass.new(**controller_args) + + # If a method was specified then invoke that method + # If not, then check if the controller implements a method of the same name as the calling_method + # Otherwise invoke the default method #execute + method ||= controller_obj.respond_to?(calling_method) ? calling_method : :execute + controller_obj.base_execute(method.to_sym) + end + + # Return the class to execute using the following priorities: + # 1. If a controller exists in the same or specified namespace with the name of the calling method then return it + # 2. Otherwise return the specified or default namespace and controller + def controller_class(namespace:, controller:) + class_names = [calling_method.classify, controller].map { |name| "#{name}Controller" } + class_names.map! { |name| "#{namespace}::#{name}" } unless namespace.blank? + class_names.each do |name| + next unless (klass = name.safe_constantize) + + return klass + end + # binding.pry + # SolidSupport.logger.debug('controller classes not found:', class_names.join(' ')) + raise Error, set_color("Controller not found: #{class_names.join(' ')} (this is a bug)", :red) + end + + # Override to provide custom arguments and/or options to the exec controller + def controller_args() = { options: options, args: args, command: calling_method.to_sym } + + # Will raise an error unless force option is provided or user confirms the action + def validate_destroy(msg = "\n#{'WARNING!!! ' * 5}\nAction cannot be reversed\nAre you sure?") + return true if options.force || yes?(msg) + + raise Error, 'Operation cancelled' + end + + # Used by Hendrix::New and Hendrix::Plugin + def check_dir(name) + if Dir.exist?(name) && !validate_destroy('Directory already exists. Destroy and recreate?') + raise Error, set_color('Directory exists. exiting.', :red) + end + + true + end + end +end diff --git a/solid_support/lib/solid_support/application_controller.rb b/solid_support/lib/solid_support/application_controller.rb new file mode 100644 index 00000000..b3239b60 --- /dev/null +++ b/solid_support/lib/solid_support/application_controller.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module SolidSupport + class ApplicationController + extend ActiveModel::Callbacks + include ActiveModel::AttributeAssignment + + # Load modules to add options, actions and sub-commands to existing command structure + # include SolidSupport::Extendable + # include Extendable + + attr_accessor :options, :args, :command + + define_model_callbacks :execute + + # Define methods for each Command on Controllers so they can invoke methods in the command classes + # e.g. execute_image(:build, *services) => Image::CommandController#build + # module_parent::MainCommand.all_commands.keys.each do |cmd| + # define_method("execute_#{cmd}") do |*args| + # module_parent::MainCommand.new.send(cmd.to_sym, *args) + # end + # end + + def initialize(**kwargs) + assign_attributes(**kwargs) unless kwargs.empty? + end + + # This method is invoked from SolidSupport::Concerns::CommandController execute method + # and invokes the target method wrapped in any defined callbacks + def base_execute(method) = run_callbacks(:execute) { send(method) } + + # Implement with an around_execute :timer call in the controller + def timer(&block) = SolidSupport.with_timer('Command execution', &block) + + def parent_name = parent.to_s.underscore + + def parent = self.class.module_parent + + def queue() = @queue ||= CommandQueue.new # (halt_on_failure: true) + end +end diff --git a/solid_support/lib/solid_support/application_generator.rb b/solid_support/lib/solid_support/application_generator.rb new file mode 100644 index 00000000..0e5ce086 --- /dev/null +++ b/solid_support/lib/solid_support/application_generator.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module SolidSupport + class ApplicationGenerator < Thor::Group + include Thor::Actions + # include Extendable + + private + + # NOTE: These methods are available within templates as well as any sub classes + + # Utility method if template is in the standard directory and the destiation file is file_name - .erb + def cnfs_template(file_name) + generated_files << template(templates_path.join("#{file_name}.erb"), file_name) + end + + # Array of ERB templates in the views_path/templates directory + def templates() = templates_path.glob('**/*.erb', File::FNM_DOTMATCH) + + def templates_path() = views_path.join('templates') + + def files() = files_path.glob('**/*', File::FNM_DOTMATCH) + + def files_path() = views_path.join('files') + + # Thor serach paths is an array. The default is a one element array based on the generator's directory + def source_paths() = [views_path] + + def views_path() = @views_path ||= internal_path.join(assets_path) + + # returns 'runtime', 'provisioner', 'project', 'plugin', 'component', etc + def assets_path() = self.class.name.demodulize.delete_suffix('Generator').underscore + + # The path to the currently invoked generator subclass + def internal_path() = raise(NotImplementedError, 'Generator must implement #internal_path() = Pathname.new(__dir__)') + + # returns 'compose', 'skaffold', 'terraform', 'new', etc + def generator_type() = self.class.name.deconstantize.underscore + + # Utility methods for managing files in the target directory + + # Use Thor's remove_file to output the removed files + # After the generator has completed it may call this method to remove old files + # In order to do this it must track files as they are generated by implementing: + # + # generated_files << template('templates/env.erb', file_name, env: environment) + # + def remove_stale_files() = stale_files.each { |file| remove_file(file) } + + def stale_files() = all_files - excluded_files - generated_files + + # All files in the current directory + def all_files() = path.glob('**/*', File::FNM_DOTMATCH).select(&:file?).map(&:to_s) + + # Array of file names that should not be removed + # A subclass can override this method to define files that should not be considered as stale and removed + def excluded_files() = [] + + # Stores an array of files that are created during an invocation of the Generator + def generated_files() = @generated_files ||= [] + + def path() = Pathname.new('.') + end +end diff --git a/solid_support/lib/solid_support/boot_loader.rb b/solid_support/lib/solid_support/boot_loader.rb new file mode 100644 index 00000000..6cd72c67 --- /dev/null +++ b/solid_support/lib/solid_support/boot_loader.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'pathname' +require 'solid_support' + +# End: this should move to somewhere else +load_path = Pathname.new(__dir__).join('../../app') +SolidSupport.add_loader(name: :solid_support, path: load_path) +SolidSupport.load_all +# End: this should move to somewhere else + +BOOT_MODULE = SolidSupport unless defined?(BOOT_MODULE) +boot_module = BOOT_MODULE.to_s.underscore.upcase + +git_path = Pathname.new(__dir__).join('../../../.git') +ENV["#{boot_module}_ENV"] ||= git_path.exist? ? 'development' : 'production' + +ROOT_FILE_ID = 'config/environment.rb' + +# Determine if cwd is inside an app or not +app_path = Pathname.new(Dir.pwd).ascend { |path| break path if path.join(ROOT_FILE_ID).file? } + +# If cwd is inside an app then load the app and run the CLI +# Otherwise load the framework +# binding.pry +if app_path + APP_CWD = Pathname.new(Dir.pwd) + APP_ROOT = app_path + Dir.chdir(APP_ROOT) { require 'solid_support/app_loader' } +else + require 'solid_support/framework_loader' +end diff --git a/cnfs/app/controllers/cnfs/console_controller.rb b/solid_support/lib/solid_support/console_controller.rb similarity index 56% rename from cnfs/app/controllers/cnfs/console_controller.rb rename to solid_support/lib/solid_support/console_controller.rb index a3dd7e0b..491d9e51 100644 --- a/cnfs/app/controllers/cnfs/console_controller.rb +++ b/solid_support/lib/solid_support/console_controller.rb @@ -2,27 +2,25 @@ require 'pry' -# Get the commands defined on MainController and create a CLI command for each -Cnfs::MainController.all_commands.keys.excluding(%w[help]).each do |cmd| - Pry::Commands.block_command cmd, "Run #{cmd}" do |*args| - Cnfs::MainController.new.send(cmd, *args) - end -end +module SolidSupport + module ConsoleControllerMethods + extend ActiveSupport::Concern -module Cnfs - class ConsoleController - def execute - Cnfs.config.cli.mode = true - Cnfs.config.console = self + def execute # rubocop:disable Metrics/AbcSize + self.class.hex + # SolidSupport.config.cli.mode = true + # SolidSupport.config.console = self Pry.config.prompt = Pry::Prompt.new('cnfs', 'cnfs prompt', [self.class.prompt]) - self.class.define_shortcuts if defined?(ActiveRecord) && ENV['CNFS_CLI_ENV'].eql?('development') + # TODO: remove the next line + puts parent + self.class.define_shortcuts if defined?(ActiveRecord) && ENV['HENDRIX_CLI_ENV'].eql?('development') Pry.start(self) + # TODO: self.pry ? end # Reload all classes, remove and cached objects and re-define the shortcuts as classes have reloaded def reload! reset_cache - Cnfs.reload self.class.define_shortcuts true end @@ -35,14 +33,24 @@ def method_missing(method) = puts("Invalid command '#{method}'") # https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/MissingRespondToMissing def respond_to_missing?(method_name, *args) - puts method_name + puts "missing: #{method_name}" method_name == :bark || super end - class << self - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def define_shortcuts + class_methods do + def hex + # Get the MainCommand commands and create a console command for each + module_parent::MainCommand.all_commands.keys.excluding(%w[help]).each do |cmd| + main_command_class = module_parent::MainCommand + Pry::Commands.block_command cmd, "Run #{cmd}" do |*args| + # TODO: There is a bug here with passing args + main_command_class.new.send(cmd, *args) + end + end + end + + # TODO: move to solid_record + def define_shortcuts # rubocop:disable Metrics/AbcSize, Metrics/MethodLength shortcuts.each_pair do |key, klass| define_method("#{key}a") { klass.all } define_method("#{key}c") { |**attributes| klass.create(attributes) } @@ -55,14 +63,20 @@ def define_shortcuts define_method("#{key}w") { |**attributes| klass.where(attributes) } end end - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/AbcSize def shortcuts these_shortcuts = {} ActiveSupport::Notifications.instrument 'add_console_shortcuts.cnfs', { shortcuts: these_shortcuts } these_shortcuts end + + def prompt + proc do |obj, _nest_level, _| + klass = obj.class.name.demodulize.delete_suffix('Controller').underscore + label = klass.eql?('console') ? '' : " (#{obj.class.name})" + "#{label}> " + end + end end end end diff --git a/cnfs/lib/cnfs/errors.rb b/solid_support/lib/solid_support/errors.rb similarity index 100% rename from cnfs/lib/cnfs/errors.rb rename to solid_support/lib/solid_support/errors.rb diff --git a/solid_support/lib/solid_support/extension.rb b/solid_support/lib/solid_support/extension.rb new file mode 100644 index 00000000..226fdc5a --- /dev/null +++ b/solid_support/lib/solid_support/extension.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# Extensions provide the core of the Framework +module SolidSupport + class << self + def extensions() = @extensions ||= {} + + # Record initializers to be run after the application has loaded + # def initializers() = @initializers ||= [] + end + + class Extension + ABSTRACT_EXTENSIONS = %w[SolidSupport::Extension SolidSupport::Plugin SolidSupport::Application].freeze + + class << self + delegate :config, to: :instance + + def abstract_extension?(name) = ABSTRACT_EXTENSIONS.include?(name.to_s) + + def instance() = @instance ||= new + + def initializer_files() = initializers_path.exist? ? initializers_path.glob('**/*.rb') : [] + + def initializers_path() = config_path.join('initializers') + + def config_path() = gem_root.join('config') + + def app_path() = gem_root.join('app') + + def root() = gem_root + + # Called one or more times by subclasses to execute a block of code after application initialization + def initializer(init_name, &block) + yield config + config.initializers.append({ init_name: init_name, block: block }) + end + + def inherited(base) + return if abstract_extension?(base) + + SolidSupport.extensions[base.to_s] = base + super + end + end + + def config() = @config ||= Extension::Configuration.new + end +end diff --git a/solid_support/lib/solid_support/extension/configuration.rb b/solid_support/lib/solid_support/extension/configuration.rb new file mode 100644 index 00000000..2ec9708e --- /dev/null +++ b/solid_support/lib/solid_support/extension/configuration.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module SolidSupport + class Extension + class << self + def before_configuration() = @before_configuration ||= [] + def before_initialize() = @before_initialize ||= [] + def before_eager_load() = @before_eager_load ||= [] + def after_initialize() = @after_initialize ||= [] + + def initialize! # rubocop:disable Metrics/AbcSize + # First configurable block to run. Called before any initializers are run. + before_configuration.each { |blk| blk.call(SolidSupport.application.config) } + + # Second configurable block to run. Called before frameworks initialize. + before_initialize.each { |blk| blk.call(SolidSupport.application.config) } + + # Run each extension's initializers; Initialziers do not have access to plugin classes + SolidSupport.extensions.values.each do |extension| + extension.config.initializers.each { |init| init[:block].call(SolidSupport.application.config) } + extension.initializer_files.each { |file| require file } + end + + # Third configurable block to run; Still don't have access to classes in app + before_eager_load.each { |blk| blk.call(SolidSupport.application.config) } + + yield + + # Last configurable block to run. Called after frameworks initialize. + # Here have access to all classes in app + after_initialize.each { |blk| blk.call(SolidSupport.application.config) } + end + end + + class Configuration + # Record initializers to be run after the application has loaded + def initializers() = @initializers ||= [] + + # First configurable block to run. Called before any initializers are run. + def before_configuration(&block) + SolidSupport::Extension.before_configuration << block if block + end + + # Second configurable block to run. Called before frameworks initialize. + def before_initialize(&block) + SolidSupport::Extension.before_initialize << block if block + end + + # Third configurable block to run + def before_eager_load(&block) + SolidSupport::Extension.before_eager_load << block if block + end + + # Last configurable block to run. Called after frameworks initialize. + def after_initialize(&block) + SolidSupport::Extension.after_initialize << block if block + end + + def initialize(**options) = @configurations = options + + # Reused from railties/lib/rails/application/configuration.rb + def method_missing(method, *args) + return if %i[after_initialize after_user_config].include?(method) + + if method.end_with?('=') + @configurations[:"#{method[0..-2]}"] = args.first + else + @configurations.fetch(method) do + @configurations[method] = ActiveSupport::OrderedOptions.new + end + end + end + + def respond_to_missing?(_symbol, *) = true + end + end +end diff --git a/solid_support/lib/solid_support/framework_loader.rb b/solid_support/lib/solid_support/framework_loader.rb new file mode 100644 index 00000000..8690134e --- /dev/null +++ b/solid_support/lib/solid_support/framework_loader.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Load the classes from /app +require 'logger' + +# load_path = Pathname.new(__dir__).join('../../app') +# SolidSupport.add_loader(name: :framework, path: load_path) if load_path.exist? +# SolidSupport.load_all + +# Display the new command's help if no arguments provided +ARGV.append('help', 'new') if ARGV.size.zero? + +# The project enclosing this gem must define the ProjectCommand class +cmd_object = defined?(ProjectCommand) ? ProjectCommand : BOOT_MODULE::ProjectCommand +# binding.pry +cmd_object.start diff --git a/core/app/models/concerns/interpolation.rb b/solid_support/lib/solid_support/interpolation.rb similarity index 81% rename from core/app/models/concerns/interpolation.rb rename to solid_support/lib/solid_support/interpolation.rb index f8a347c9..5c994832 100644 --- a/core/app/models/concerns/interpolation.rb +++ b/solid_support/lib/solid_support/interpolation.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Concerns +module SolidSupport module Interpolation extend ActiveSupport::Concern @@ -12,7 +12,7 @@ def as_interpolated(method: :as_merged) self_hash.deep_transform_values do |value| next value unless value.is_a? String - value.cnfs_sub(default: self_hash, parent: parent_hash) + value.interpolate(default: self_hash, parent: parent_hash) end end @@ -20,7 +20,7 @@ def with_other(**kwargs) as_interpolated.deep_transform_values do |value| next value unless value.is_a? String - value.cnfs_sub(**kwargs) + value.interpolate(**kwargs) end end end diff --git a/cnfs/lib/cnfs/loader.rb b/solid_support/lib/solid_support/loader.rb similarity index 76% rename from cnfs/lib/cnfs/loader.rb rename to solid_support/lib/solid_support/loader.rb index 2184d9fe..1fc048f4 100644 --- a/cnfs/lib/cnfs/loader.rb +++ b/solid_support/lib/solid_support/loader.rb @@ -2,11 +2,13 @@ require 'zeitwerk' -module Cnfs +module SolidSupport class << self + def load_all = loaders.values.map(&:setup) + def add_loader(name:, path:, notifier: nil, logger: nil) name = name.to_s - loaders[name] ||= Cnfs::Loader.new(name: name, logger: logger) + loaders[name] ||= Loader.new(name: name, logger: logger) loaders[name].add_path(path) loaders[name].add_notifier(notifier) loaders[name] @@ -47,7 +49,7 @@ def add_path(root_path) # Store all paths to be added to loader and ensure they are unique def paths() = @paths ||= Set.new - def load_paths() = @load_paths ||= %w[controllers generators helpers models views] + def load_paths() = @load_paths ||= %w[commands controllers generators models views] # Gems may want to set the loader's inflector and other work before classes are loaded def add_notifier(notifier) @@ -57,7 +59,7 @@ def add_notifier(notifier) def notifiers() = @notifiers ||= Set.new # Zeitwerk loader methods - def setup + def setup # rubocop:disable Metrics/AbcSize paths.each do |path| loader.push_dir(path) next unless path.basename.to_s.eql?('generators') @@ -67,14 +69,16 @@ def setup end notify(:before_loader_setup) loader.enable_reloading - loader.setup unless loader.instance_variable_get('@setup') + loader.setup unless loader.instance_variable_get(:@setup) + # NOTE: Plugin classes may depend on classes that do not exist + # before a project has been created so only eager load when in an application loader.eager_load if defined? ::APP_ROOT end # Return list of autoloads for a specified plugin optionally prefixed with path - # Example: Cnfs.loaders.first.last.select(Cnfs::Core, 'app/models') - def select(plugin = Cnfs, path = '') - loader.autoloads.select{ |k, v| k.start_with?(plugin.gem_root.join(path).to_s) } + # Example: SolidSupport.loaders.first.last.select(SolidSupport::Core, 'app/models') + def select(plugin = SolidSupport, path = '') + loader.autoloads.select { |k, _v| k.start_with?(plugin.gem_root.join(path).to_s) } end # TODO: Catch a reload error @@ -83,7 +87,7 @@ def reload loader.eager_load notify(:after_reload) if result text = result ? 'Reloaded' : 'Reload error on' - loader.logger.debug(text, name) if loader.logger + loader.logger&.debug(text, name) result end diff --git a/solid_support/lib/solid_support/models/command.rb b/solid_support/lib/solid_support/models/command.rb new file mode 100644 index 00000000..decaefc0 --- /dev/null +++ b/solid_support/lib/solid_support/models/command.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# exec: The string to pass to the shell to run +# env: Hash of ENV vars to pass to the shell to run +# opts: Hash of custom options converted into: +# 1. a Hash of command_options for a TTY::Command instance +# 2. a Hash of run_options for the instance's run method +require 'tty-command' + +module SolidSupport + class Command + attr_accessor :exec, :env, :opts + # attr_reader :cmd_opts, :run_opts, :result, :exit_error + attr_reader :result, :exit_error + + delegate :out, :err, to: :result, allow_nil: true + + def initialize(**attrs) + attrs.each { |k, v| send("#{k}=", v) } + @env ||= {} + @opts ||= {} + @opts.transform_keys!(&:to_sym) + @opts = opts_defaults.merge(@opts) + end + + def run!(exec: nil, env: {}, opts: {}) = run(method: :run!, exec: exec, env: env, opts: opts) + + # run will throw an exception if the command fails + # run! will do what? + def run(method: :run, exec: nil, env: {}, opts: {}) + return if @result || @exit_error + + @exec = exec if exec + @env.merge!(env) + @opts.merge!(opts).transform_keys!(&:to_sym) + @result = command.send(method, @env, @exec, run_opts) + self + rescue TTY::Command::ExitError => e + @exit_error = e + ensure + # binding.pry + self + end + + def command = TTY::Command.new(**cmd_opts) + + def to_a(term: "\n") + out ? out.split(term) : [] + end + + def run_opts = @run_opts ||= opts.slice(*run_keys) + + # The options keys that are attributes of the run and run! methods + def run_keys = %i[verbose pty only_output_on_error in out err] + + def cmd_opts = @cmd_opts ||= opts.slice(*cmd_keys) + + # The options keys that are attributes of the command object + def cmd_keys = %i[uuid dry_run printer] + + def opts_defaults = { uuid: false } + + private + + # TODO: It will be a complex map of an external key to a hash of TTY::Command cmd and run options + def transform_keys = { a: 1, b: 2 }.transform_keys { |key| key_map[key] || key } + + def key_map = { silent: :only_output_on_error } + + # options for the TTY command + def x_set_command_options(context) + defaults = { uuid: false } + defaults.merge!(dry_run: true) if context.options.key?(:dry_run) + defaults + end + + def x_set_run_options(context) + defaults = {} + defaults.merge!(verbose: true) if context.options.verbose + defaults.merge!(pty: true) if 1 == 2 + defaults.merge!(only_output_on_error: true) if context.options.quiet + defaults + end + end +end diff --git a/solid_support/lib/solid_support/models/command_queue.rb b/solid_support/lib/solid_support/models/command_queue.rb new file mode 100644 index 00000000..75e4728d --- /dev/null +++ b/solid_support/lib/solid_support/models/command_queue.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module SolidSupport + class CommandQueue + attr_reader :queue, :results, :on_failure + + delegate :each, :map, :shfit, :unshift, :first, :last, :pop, :size, to: :queue + + def initialize(**attrs) + attrs.each { |k, v| send("#{k}=", v) } + @queue = [] + @results = [] + # @on_failure ||= :raise + @on_failure ||= ActiveSupport::StringInquirer.new('raise') + end + + def run! + run(cmd: :run!) + end + + # rubocop:disable Metrics/CyclomaticComplexity + def run(cmd: :run) + queue.each do |command| + command.send(cmd) + # binding.pry + next unless command.exit_error || command.result.failure? + + msg = command.exit_error&.to_s || command.result.err + binding.pry + raise Error, msg if on_failure.raise? + + return false if on_failure.halt? + end + end + # rubocop:enable Metrics/CyclomaticComplexity + + def append(*commands) + commands.each do |command| + raise ArgumentError, 'Incorrect command format' unless command.instance_of?(Command) + + queue.append(command) + end + end + + def on_failure=(value) + binding.pry + @on_failure ||= ActiveSupport::StringInquirer.new(value.to_s) + end + + # def execute + # current_command = queue.shift + # result = command.run!(*current_command) + # results.append(result) + # true + # end + # + # def failure? + # failures.any? + # end + # + # def success? + # failures.empty? + # end + # + # def failure_messages + # failures.map(&:err) + # end + # + # def failures + # results.select(&:failure?) + # end + # + # def successes + # results.select(&:success?) + # end + # + # def runtime + # results.map(&:runtime).reduce(:+) + # end + end +end diff --git a/core/app/models/concerns/download.rb b/solid_support/lib/solid_support/models/download.rb similarity index 92% rename from core/app/models/concerns/download.rb rename to solid_support/lib/solid_support/models/download.rb index 1f2034e0..64de48fc 100644 --- a/core/app/models/concerns/download.rb +++ b/solid_support/lib/solid_support/models/download.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -module Concerns +module SolidSupport module Download extend ActiveSupport::Concern included do - include Concerns::Git + include Git end def download(url:, path:, spinner: false) @@ -20,7 +20,7 @@ def download(url:, path:, spinner: false) file = url.basename if file.exist? # && !options.clean FileUtils.rm(file) - Cnfs.logger.warn("#{file} exists") + SolidSupport.logger.warn("#{file} exists") # Cnfs.logger.info "Dependency #{dependency[:name]} exists locally. To overwrite run command with --clean flag." # next end diff --git a/core/app/models/concerns/extendable.rb b/solid_support/lib/solid_support/models/extendable.rb similarity index 87% rename from core/app/models/concerns/extendable.rb rename to solid_support/lib/solid_support/models/extendable.rb index a00a7e66..84430224 100644 --- a/core/app/models/concerns/extendable.rb +++ b/solid_support/lib/solid_support/models/extendable.rb @@ -2,7 +2,7 @@ # Extendable should be included last as it includes plugin modules that may depend on methods # defined in previously loaded modules -module Concerns +module SolidSupport module Extendable extend ActiveSupport::Concern @@ -12,7 +12,7 @@ module Extendable # Example: # The terraform plugin adds methods to the Resource model by including A/S Concern in the module # Terraform::Concerns::Resource declared in file Terraform.gem_root/app/models/terraform/concerns/resource.rb - Cnfs.modules_for(self).each { |mod| include mod } + # SolidSupport.modules_for(self).each { |mod| include mod } end end end diff --git a/core/app/models/concerns/git.rb b/solid_support/lib/solid_support/models/git.rb similarity index 99% rename from core/app/models/concerns/git.rb rename to solid_support/lib/solid_support/models/git.rb index 095d8541..199e0f9a 100644 --- a/core/app/models/concerns/git.rb +++ b/solid_support/lib/solid_support/models/git.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Concerns +module SolidSupport module Git extend ActiveSupport::Concern diff --git a/solid_support/lib/solid_support/platform.rb b/solid_support/lib/solid_support/platform.rb new file mode 100644 index 00000000..0de84169 --- /dev/null +++ b/solid_support/lib/solid_support/platform.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# OS methods +module SolidSupport + class Platform + include ActiveModel::Validations + + validate :supported_platform + + def supported_platform + errors.add(:platform, 'not supported') if os.eql?('unknown') + end + + def to_hash() = @to_hash ||= JSON.parse(as_hash.to_json) + + def as_hash() = { arch: arch, os: os }.merge(gid) + + def arch + case config['host_cpu'] + when 'x86_64' + 'amd64' + else + config['host_cpu'] + end + end + + def gid + ext_info = {} + if os.eql?('linux') && Etc.getlogin + shell_info = Etc.getpwnam(Etc.getlogin) + ext_info[:puid] = shell_info.uid + ext_info[:pgid] = shell_info.gid + end + ext_info + end + + def os + case config['host_os'] + when /linux/ + 'linux' + when /darwin/ + 'darwin' + else + 'unknown' + end + end + + def config() = @config ||= RbConfig::CONFIG + end +end diff --git a/cnfs/lib/cnfs/plugin.rb b/solid_support/lib/solid_support/plugin.rb similarity index 57% rename from cnfs/lib/cnfs/plugin.rb rename to solid_support/lib/solid_support/plugin.rb index 939f2ee9..a153e82d 100644 --- a/cnfs/lib/cnfs/plugin.rb +++ b/solid_support/lib/solid_support/plugin.rb @@ -1,26 +1,25 @@ # frozen_string_literal: true -# -# Plugins add to Extension with support for -# app controllers, generators, models and views -# config and inline initializers -require 'cnfs/extension' +# Plugins add to Extensions with support for +# app commands, controllers, generators, models and views -module Cnfs - class << self - def run_initializers - plugins.each do |name, plugin| - initializers.select{ |init| init[:name].eql?(plugin.to_s) }.each { |init| init[:block].call } - plugin.initializer_files.each { |file| require file } - end - end - - # Record initializers to be run after the application has loaded - def initializers() = @initializers ||= [] +require_relative 'extension' +module SolidSupport + class << self # Maintain a Hash of all plugins def plugins() = @plugins ||= {} + # Use Zeitwerk to load classes in each plugin's autoload_paths + def load_plugins + plugins.values.each do |plugin| + paths = ['app'] if plugin.config.autoload_paths.empty? + paths.each do |path| + add_loader(name: :framework, path: plugin.gem_root.join(path), notifier: plugin) + end + end + end + # https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/lib/aws-sdk-s3/bucket.rb#L221 # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html @@ -31,7 +30,7 @@ def plugins() = @plugins ||= {} # bucket.create({ # acl: "private", # accepts private, public-read, public-read-write, authenticated-read # create_bucket_configuration: { - # location_constraint: "af-south-1", # accepts af-south-1, ap-east-1, ap-northeast-1, ap-northeast-2, ap-northeast-3, ap-south-1, ap-southeast-1, ap-southeast-2, ca-central-1, cn-north-1, cn-northwest-1, EU, eu-central-1, eu-north-1, eu-south-1, eu-west-1, eu-west-2, eu-west-3, me-south-1, sa-east-1, us-east-2, us-gov-east-1, us-gov-west-1, us-west-1, us-west-2 + # location_constraint: "af-south-1", # accepts af-south-1, ap-east-1, etc # }, # grant_full_control: "GrantFullControl", # grant_read: "GrantRead", @@ -49,31 +48,33 @@ def plugins() = @plugins ||= {} # @option options [String] :grant_full_control # Allows grantee the read, write, read ACP, and write ACP permissions on # the bucket. - def modules_for(klass) + def modules_for(klass) # rubocop:disable Metrics/MethodLength base_name = "Concerns::#{klass}" - Cnfs.logger.debug('Searching for', base_name) + logger.debug('Searching for', base_name) - Cnfs.plugins.keys.each_with_object([]) do |plugin_name, ary| + plugins.keys.each_with_object([]) do |plugin_name, ary| module_name = plugin_name.to_s.classify plugin_module_name = "#{module_name}::#{base_name}" next unless (plugin_module = plugin_module_name.safe_constantize) - Cnfs.logger.debug('Found', plugin_module_name) + logger.debug('Found', plugin_module_name) # binding.pry if klass.eql?(RepositoriesController) - # binding.pry if klass.eql?(Cnfs::MainController) + # binding.pry if klass.eql?(SolidSupport::MainController) # Ignore anything that is not an A/S::Concern next unless plugin_module.is_a?(ActiveSupport::Concern) - Cnfs.logger.info('Extending', klass, 'from', plugin_module_name) + logger.info('Extending', klass, 'from', plugin_module_name) ary.append(plugin_module) end end + # Return an array of paths within loaded plugins that contain a file by name + # Used by Generators to get an array of available templates def app_paths(type:, klass:, suffix: nil) type = type.to_s.pluralize klass = klass.to_s.singularize suffix = suffix&.to_s - Cnfs.plugins.each_with_object([]) do |(name, plugin), ary| + plugins.each_with_object([]) do |(name, plugin), ary| search_path = [type, name.to_s, klass, suffix].compact path = plugin.app_path.join(search_path) ary.append(path) if path.exist? @@ -83,29 +84,22 @@ def app_paths(type:, klass:, suffix: nil) class Plugin < Extension class << self - # Called one or more times by subclasses to execute a block of code after application initialization - def initializer(init_name, &block) - Cnfs.initializers.append({ name: name, init_name: init_name, block: block }) - end - - def initializer_files() = initializers_path.exist? ? initializers_path.glob('**/*.rb') : [] - - def initializers_path() = config_path.join('initializers') - - def config_path() = gem_root.join('config') - - def app_path() = gem_root.join('app') - - def root() = gem_root - # https://github.com/rails/rails/blob/main/railties/lib/rails/application.rb#L68 def inherited(base) - name = name_from_base(base) - return if base.to_s.eql?('Cnfs::Application') + return if Extension.abstract_extension?(base) - Cnfs.plugins[name] = base + SolidSupport.plugins[base.to_s] = base super end + + def initialize! + # TODO: This is about plugins; Extensions don't ahve an app path + # Configure all classes in each extension's app path to be autoloaded + SolidSupport.load_plugins + + # Setup the autoloader; Requires all classes in the app dir + SolidSupport.loaders.values.map(&:setup) + end end end end diff --git a/solid_support/lib/solid_support/plugin/configuration.rb b/solid_support/lib/solid_support/plugin/configuration.rb new file mode 100644 index 00000000..6158db67 --- /dev/null +++ b/solid_support/lib/solid_support/plugin/configuration.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative '../extension/configuration' + +module SolidSupport + class Plugin + def config() = @config ||= Configuration.new + + class Configuration < Extension::Configuration + end + end +end diff --git a/core/spec/spec_helper.rb b/solid_support/lib/solid_support/spec_loader.rb similarity index 70% rename from core/spec/spec_helper.rb rename to solid_support/lib/solid_support/spec_loader.rb index d701e520..046440a1 100644 --- a/core/spec/spec_helper.rb +++ b/solid_support/lib/solid_support/spec_loader.rb @@ -7,8 +7,6 @@ git_path = SPEC_DIR.join('../../.git') if File.exist?(git_path) - plugin_path = SPEC_DIR.join('../../cnfs/lib') + plugin_path = SPEC_DIR.join('../../hendrix/lib') $:.unshift(plugin_path) end - -require 'cnfs/spec_loader' diff --git a/cnfs/lib/cnfs/timer.rb b/solid_support/lib/solid_support/timer.rb similarity index 100% rename from cnfs/lib/cnfs/timer.rb rename to solid_support/lib/solid_support/timer.rb diff --git a/cnfs/lib/cnfs/tty/prompt.rb b/solid_support/lib/solid_support/tty/prompt.rb similarity index 91% rename from cnfs/lib/cnfs/tty/prompt.rb rename to solid_support/lib/solid_support/tty/prompt.rb index f90e28ad..cf5931b2 100644 --- a/cnfs/lib/cnfs/tty/prompt.rb +++ b/solid_support/lib/solid_support/tty/prompt.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # NOTE: This one is not currently in use -module Cnfs::TTY +module SolidSupport::TTY class Prompt < TTY::Prompt attr_accessor :model, :prompt @@ -49,14 +49,14 @@ def p_multi_select(key, title: nil, values: nil, choices: [], **options) def modified_options(key, value, **options) if value.is_a?(Array) value = value.join(', ') - options.merge!(convert: :array) + options[:convert] = :array end value = (value || '').to_s if options.key?(:convert) && options[:convert].eql?(:boolean) - options.merge!(value: value) if value - required = model.class.validators_on(key).select do |v| + options[:value] = value if value + required = model.class.validators_on(key).count do |v| v.is_a?(ActiveRecord::Validations::PresenceValidator) - end.size.positive? - options.merge!(required: true) if required + end.positive? + options[:required] = true if required options end # rubocop:enable Metrics/CyclomaticComplexity @@ -94,7 +94,7 @@ def collect_model(**options, &block) # Added code to return a model with associations association_names = model.class.reflect_on_all_associations(:has_many).map(&:name) - association_names.select { |name| ret_val.keys.include?(name) }.each do |name| + association_names.select { |name| ret_val.key?(name) }.each do |name| klass = name.to_s.classify.safe_constantize ret_val[name].map! { |params| klass.new(params.merge(model.class.name.underscore => model)) } # binding.pry diff --git a/cnfs/lib/cnfs/tty/prompt/answers_collector.rb b/solid_support/lib/solid_support/tty/prompt/answers_collector.rb similarity index 95% rename from cnfs/lib/cnfs/tty/prompt/answers_collector.rb rename to solid_support/lib/solid_support/tty/prompt/answers_collector.rb index 6c6fb1d4..b904a39e 100644 --- a/cnfs/lib/cnfs/tty/prompt/answers_collector.rb +++ b/solid_support/lib/solid_support/tty/prompt/answers_collector.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Cnfs::TTY +module Hendrix::TTY class Prompt::AnswersCollector < TTY::Prompt::AnswersCollector def assign_answers(model, *attributes) attributes.each do |attribute| diff --git a/solid-support/lib/solid_support/version.rb b/solid_support/lib/solid_support/version.rb similarity index 100% rename from solid-support/lib/solid_support/version.rb rename to solid_support/lib/solid_support/version.rb diff --git a/solid_support/set_path b/solid_support/set_path new file mode 100755 index 00000000..fb040f78 --- /dev/null +++ b/solid_support/set_path @@ -0,0 +1,8 @@ +# Use to access cnfs cli in development mode + +RUBYOPT='-W:no-deprecated' + +SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +echo $SCRIPT_PATH +export PATH=$PATH:$SCRIPT_PATH/exe +unset SCRIPT_PATH diff --git a/solid_support/solid_support.gemspec b/solid_support/solid_support.gemspec new file mode 100644 index 00000000..7ea20d8b --- /dev/null +++ b/solid_support/solid_support.gemspec @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require_relative 'lib/solid_support/version' + +Gem::Specification.new do |spec| + spec.name = 'solid_support' + spec.version = SolidSupport::VERSION + spec.authors = ['Robert Roach'] + spec.email = ['rjayroach@gmail.com'] + + spec.summary = 'Solid Application Framework' + spec.description = 'The Solid Application Framework cli command, controller, view and generator base classes' + spec.homepage = 'https://cnfs.io' + spec.license = 'MIT' + spec.required_ruby_version = '>= 3.0.0' + + spec.metadata['allowed_push_host'] = "TODO: Set to 'https://mygemserver.com'" + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/cnfs.io/solid_support' + # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject do |f| + (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) + end + end + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_dependency 'activemodel', '~> 6.1.0' + spec.add_dependency 'activesupport', '~> 6.1.0' + spec.add_dependency 'pry', '~> 0.13' + spec.add_dependency 'thor', '~> 1.0' + spec.add_dependency 'tty-prompt', '~> 0.22' + spec.add_dependency 'tty-screen', '~> 0.8' + spec.add_dependency 'tty-table', '~> 0.12.0' + # spec.add_dependency 'tty-tree', '~> 0.4' + spec.add_dependency 'tty-command', '~> 0.10' + spec.add_dependency 'tty-file', '~> 0.10' + # spec.add_dependency 'tty-logger', '~> 0.5' + spec.add_dependency 'tty-spinner', '~> 0.9' + spec.add_dependency 'tty-tree', '~> 0.4' + spec.add_dependency 'tty-which', '~> 0.5' + spec.add_dependency 'xdg', '~> 5' + spec.add_dependency 'zeitwerk', '~> 2.4' + + spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'guard-rspec', '~> 4.7' + spec.add_development_dependency 'guard-rubocop' + spec.add_development_dependency 'pry-byebug', '~> 3.9' + spec.add_development_dependency 'rspec', '~> 3.10' + spec.add_development_dependency 'rubocop', '~> 1.41' + spec.add_development_dependency 'rubocop-performance' + spec.add_development_dependency 'rubocop-rspec', '~> 2.18' +end diff --git a/solid_support/spec/lib/ext/string_spec.rb b/solid_support/spec/lib/ext/string_spec.rb new file mode 100644 index 00000000..b81a5e0e --- /dev/null +++ b/solid_support/spec/lib/ext/string_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +RSpec.describe String do + describe '#interpolate' do + let(:tld) { { 'tld' => 'context.com' } } + let(:domain) { { 'domain' => 'backend.${parent.tld}' } } + let(:invalid) { { 'invalid' => { 'integer' => 1 } } } + + context 'when invalid reference' do + context 'with an Integer' do + it { expect { 'error${tld}'.interpolate(1) }.to raise_error(ArgumentError) } + end + + context 'with a Boolean' do + it { expect { 'error${tld}'.interpolate(true) }.to raise_error(ArgumentError) } + end + + context 'with an Array' do + it { expect { 'error${tld}'.interpolate(default: []) }.to raise_error(ArgumentError) } + end + + context "with no reference, 'backend.${tld}'" do + let(:string) { 'backend.${tld}' } + + it { expect(string.interpolate).to eq(string) } + end + end + + context "with no interpolation string, 'backend'" do + let(:string) { 'backend' } + + it { expect(string.interpolate(parent: tld)).to eq(string) } + end + + context 'when valid reference' do + context "with 'backend.${tld}'" do + let(:string) { 'backend.${parent.tld}' } + + it { expect(string.interpolate(parent: tld)).to eq('backend.context.com') } + end + + context "with invalid interpolation 'host.${parent.domain.invalid}'" do + let(:string) { 'host.${parent.domain.invalid}' } + + it { expect(string.interpolate(parent: tld)).to eq(string) } + end + + context "with invalid second element 'host.${parent.invalid.domain}'" do + let(:string) { 'host.${parent.invalid.domain}' } + + it { expect(string.interpolate(parent: tld)).to eq(string) } + end + + context "with an empty interpolation 'host.${}'" do + let(:string) { 'host.${}' } + + it { expect(string.interpolate(parent: {})).to eq('host.${}') } + end + + context 'with the found reference returns an Integer' do + let(:string) { 'host.${parent.invalid.integer}' } + + it { expect(string.interpolate(parent: invalid)).to eq('host.${parent.invalid.integer}') } + end + + context "with one valid and one invalid interpolation 'host.${parent.tld}.${parent.invalid}'" do + let(:string) { 'host.${parent.tld}.${parent.invalid}' } + + it { expect(string.interpolate(parent: tld)).to eq('host.context.com.${parent.invalid}') } + end + + context 'with multiple interpolations required' do + context "when string is 'host.${child.domain}'" do + let(:string) { 'host.${child.domain}' } + + it { expect(string.interpolate(parent: tld, child: domain)).to eq('host.backend.context.com') } + end + + context "with recursive interpolatitons 'host.${domain}.this.${domain}'" do + let(:string) { 'host.${domain}.this.${domain}' } + + it { + expect(string.interpolate(parent: tld, + default: domain)).to eq('host.backend.context.com.this.backend.context.com') + } + end + end + end + end +end diff --git a/solid_support/spec/lib/solid_support/interpolation_spec.rb b/solid_support/spec/lib/solid_support/interpolation_spec.rb new file mode 100644 index 00000000..889e4a60 --- /dev/null +++ b/solid_support/spec/lib/solid_support/interpolation_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidSupport + RSpec.shared_examples_for 'Interpolation' do + let(:valid_path) { described_class.new(model_type: 'test', path: '.') } + let(:invalid_path) { described_class.new(model_type: 'test', path: 'invalid_path') } + let(:no_path) { described_class.new(model_type: 'test') } + + describe '#interpolation' do + let(:options) { { stack: :backend, environment: :production, target: :lambda } } + + describe 'does the correct interpolation for production' do + let(:subject) { Component.find_by(name: :production) } + let(:result) do + { 'config' => { 'domain' => 'production-backend.cnfs.io' }, 'default' => 'lambda', + 'segment' => 'target', 'name' => 'context_spec' } + end + # it_behaves_like 'interpolated' + end + + describe 'does the correct interpolation for lambda' do + let(:subject) { Component.find_by(name: :lambda) } + let(:result) do + { 'config' => { 'host' => 'lambda.production-backend.cnfs.io' }, 'segment' => 'target', + 'default' => 'lambda', 'name' => 'context_spec' } + end + # it_behaves_like 'interpolated' + end + end + end +end diff --git a/solid_support/spec/spec_helper.rb b/solid_support/spec/spec_helper.rb new file mode 100644 index 00000000..e44030ad --- /dev/null +++ b/solid_support/spec/spec_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'bundler/setup' +require 'pry-byebug' + +binding.pry + +SPEC_ROOT = Pathname.new(__dir__).join('..') + +RSpec.configure do |config| + # SolidRecord.logger.level = :warn # debug + # SolidRecord.config.sandbox = true +end diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 00000000..bbde7c2f --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1 @@ +spec/dummy/proxmox diff --git a/terraform/.rspec b/terraform/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/terraform/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/terraform/Gemfile b/terraform/Gemfile new file mode 100644 index 00000000..87000333 --- /dev/null +++ b/terraform/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'onestack', path: '../onestack' +gem 'solid_app', path: '../solid_app' +gem 'solid_record', path: '../solid_record' +gem 'solid_support', path: '../solid_support' + +gemspec diff --git a/terraform/app/generators/terraform/provisioner_generator.rb b/terraform/app/generators/terraform/provisioner_generator.rb index c9dd5f06..f2d75253 100644 --- a/terraform/app/generators/terraform/provisioner_generator.rb +++ b/terraform/app/generators/terraform/provisioner_generator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Terraform::ProvisionerGenerator < ProvisionerGenerator +class Terraform::ProvisionerGenerator < OneStack::ProvisionerGenerator def manifests # cnfs_template('variables.tf') diff --git a/terraform/app/models/terraform/plan.rb b/terraform/app/models/terraform/plan.rb index db4e5357..e4f22a2b 100644 --- a/terraform/app/models/terraform/plan.rb +++ b/terraform/app/models/terraform/plan.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Terraform - class Plan < Plan + class Plan < OneStack::Plan store :modules, coder: YAML store :variables, coder: YAML store :outputs, coder: YAML diff --git a/terraform/app/models/terraform/provisioner.rb b/terraform/app/models/terraform/provisioner.rb index 0bcd0579..54747163 100644 --- a/terraform/app/models/terraform/provisioner.rb +++ b/terraform/app/models/terraform/provisioner.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Terraform::Provisioner < Provisioner +class Terraform::Provisioner < OneStack::Provisioner store :config, accessors: %i[state_file] before_validation :set_defaults diff --git a/terraform/app/views/terraform/plan_view.rb b/terraform/app/views/terraform/plan_view.rb index f84f60c2..4a1a1e0f 100644 --- a/terraform/app/views/terraform/plan_view.rb +++ b/terraform/app/views/terraform/plan_view.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Terraform::PlanView < ApplicationView +class Terraform::PlanView < OneStack::ApplicationView def modify # ask_attr(:modules) diff --git a/terraform/app/views/terraform/provisioner_view.rb b/terraform/app/views/terraform/provisioner_view.rb index 5c95cb6f..199f15b7 100644 --- a/terraform/app/views/terraform/provisioner_view.rb +++ b/terraform/app/views/terraform/provisioner_view.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Terraform::ProvisionerView < ApplicationView +class Terraform::ProvisionerView < OneStack::ApplicationView def modify model.set_defaults ask_attr(:state_file) diff --git a/terraform/bin/console b/terraform/bin/console index d59f6a21..aa32a757 100755 --- a/terraform/bin/console +++ b/terraform/bin/console @@ -2,14 +2,8 @@ # frozen_string_literal: true require 'bundler/setup' -require 'cnfs/terraform' -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require 'irb' -IRB.start(__FILE__) +ARGV.unshift 'console' +ENV['HENDRIX_CLI_ENV'] ||= 'development' +BOOT_MODULE = OneStack +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_app/boot_loader' } diff --git a/terraform/lib/cnfs/terraform.rb b/terraform/lib/cnfs/terraform.rb deleted file mode 100644 index e2d5b617..00000000 --- a/terraform/lib/cnfs/terraform.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'ruby-terraform' - -require 'cnfs/terraform/version' -require 'cnfs/terraform/plugin' - -# Core Extensions -require_relative '../ext/hash' - -module Terraform - module Concerns; end -end - -module Cnfs - module Terraform - def self.gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') - end -end diff --git a/terraform/lib/cnfs/terraform/plugin.rb b/terraform/lib/cnfs/terraform/plugin.rb deleted file mode 100644 index 879f2c0f..00000000 --- a/terraform/lib/cnfs/terraform/plugin.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Cnfs - module Terraform - class Plugin < Cnfs::Plugin - def self.gem_root() = Cnfs::Terraform.gem_root - end - end -end diff --git a/terraform/lib/one_stack/terraform/plugin.rb b/terraform/lib/one_stack/terraform/plugin.rb new file mode 100644 index 00000000..bf41fe05 --- /dev/null +++ b/terraform/lib/one_stack/terraform/plugin.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module OneStack + module Terraform + class Plugin < OneStack::Plugin + config.before_initialize { |config| OneStack::Terraform.config.merge!(config.terraform) } + + def self.gem_root() = OneStack::Terraform.gem_root + end + end +end diff --git a/terraform/lib/cnfs/terraform/version.rb b/terraform/lib/one_stack/terraform/version.rb similarity index 83% rename from terraform/lib/cnfs/terraform/version.rb rename to terraform/lib/one_stack/terraform/version.rb index b499084e..ac526a6c 100644 --- a/terraform/lib/cnfs/terraform/version.rb +++ b/terraform/lib/one_stack/terraform/version.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Cnfs +module OneStack module Terraform VERSION = '0.1.0' end diff --git a/terraform/lib/onestack-terraform.rb b/terraform/lib/onestack-terraform.rb new file mode 100644 index 00000000..196cb93c --- /dev/null +++ b/terraform/lib/onestack-terraform.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'ruby-terraform' + +require_relative 'one_stack/terraform/version' +require_relative 'one_stack/terraform/plugin' + +# Core Extensions +require_relative 'ext/hash' + +module Terraform + module Concerns; end +end + +module OneStack + module Terraform + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('..') + + def config() = @config ||= ActiveSupport::OrderedOptions.new + end + end +end diff --git a/core/cnfs-core.gemspec b/terraform/onestack-terraform.gemspec similarity index 59% rename from core/cnfs-core.gemspec rename to terraform/onestack-terraform.gemspec index e74ec03e..e1362572 100644 --- a/core/cnfs-core.gemspec +++ b/terraform/onestack-terraform.gemspec @@ -1,17 +1,18 @@ # frozen_string_literal: true -require_relative 'lib/cnfs/core/version' +require_relative 'lib/one_stack/terraform/version' Gem::Specification.new do |spec| - spec.name = 'cnfs-core' - spec.version = Cnfs::Core::VERSION + spec.name = 'onestack-terraform' + spec.version = OneStack::Terraform::VERSION spec.authors = ['Robert Roach'] spec.email = ['rjayroach@gmail.com'] - spec.summary = 'CNFS CLI framework' - spec.description = 'CNFS CLI is a pluggable framework to configure and manage CNFS projects' + spec.summary = 'OneStack plugin for the Terraform Provisioning Tool' + spec.description = 'OneStack plugin to create Terraform templates for resources and blueprints' spec.homepage = 'https://cnfs.io' spec.license = 'MIT' + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" @@ -28,11 +29,15 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'cnfs', '~> 0.1.0' - spec.add_dependency 'lockbox', '~> 0.4' + spec.add_dependency 'onestack', '~> 0.1.0' + spec.add_dependency 'ruby-terraform', '~> 1.3.1' spec.add_development_dependency 'bundler', '~> 2.0' - spec.add_development_dependency 'guard-rspec', '~> 4.7.3' + spec.add_development_dependency 'guard-rspec', '~> 4.7' + spec.add_development_dependency 'pry-byebug', '~> 3.9' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'rubocop', '~> 1.22' + spec.add_development_dependency 'rubocop-rspec', '~> 2.7' + spec.add_development_dependency 'rubocop-performance', '~> 1.13' end diff --git a/terraform/spec/dummy/config/application.rb b/terraform/spec/dummy/config/application.rb new file mode 100644 index 00000000..91548f28 --- /dev/null +++ b/terraform/spec/dummy/config/application.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*.groups) + +module Spec + module Concerns; end + class Application < OneStack::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + + # initializer 'based' do |app| + # binding.pry + # end + + # The default value is :warn + # config.logging = :warn + + # Comment out to remove the segment name and/or type from the console prompt + # Use the pwd command to show the full path + config.cli.show_segment_name = true + config.cli.show_segment_type = true + + # Comment out to ignore segment color settings + config.cli.colorize = true + config.paths.data = 'data' + + # Example configurations for segments + # basic colors: blue green purple magenta cyan yellow red white black + config.segments.environment = { aliases: '-e', env: 'env', color: + Proc.new { |env| env.eql?('production') ? 'red' : env.eql?('staging') ? 'yellow' : 'green' } } + config.segments.namespace = { aliases: '-n', env: 'ns', color: 'purple' } + config.segments.stack = { aliases: '-s', color: 'cyan' } + config.segments.target = { aliases: '-t', color: 'magenta' } + + config.solid_record.sandbox = true + # TODO: This should be teh default and comment out and set to false with an instruction to user + config.solid_record.flush_cache_on_exit = true + config.solid_record.namespace = :one_stack + config.solid_record.load_paths = [ + { path: 'config/segment.yml', model_type: 'OneStack::SegmentRoot' }, + { path: 'segments', owner: -> { OneStack::SegmentRoot.first } } + ] + + # Configuration for the application, plugins, and extensions goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + end +end diff --git a/terraform/spec/dummy/config/boot.rb b/terraform/spec/dummy/config/boot.rb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/terraform/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/terraform/spec/dummy/config/environment.rb b/terraform/spec/dummy/config/environment.rb new file mode 100644 index 00000000..2ff3c59d --- /dev/null +++ b/terraform/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +OneStack.application.initialize! diff --git a/cnfs/spec/fixtures/config/segments.yml.bak b/terraform/spec/dummy/config/segment.yml similarity index 100% rename from cnfs/spec/fixtures/config/segments.yml.bak rename to terraform/spec/dummy/config/segment.yml diff --git a/core/spec/fixtures/segments/node/stacks/plans.yml b/terraform/spec/dummy/segments/plans.yml similarity index 100% rename from core/spec/fixtures/segments/node/stacks/plans.yml rename to terraform/spec/dummy/segments/plans.yml diff --git a/terraform/spec/lib/ext/hash_spec.rb b/terraform/spec/lib/ext/hash_spec.rb index 6ea1d98c..404a40b6 100644 --- a/terraform/spec/lib/ext/hash_spec.rb +++ b/terraform/spec/lib/ext/hash_spec.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require_relative '../../../lib/ext/hash' -require 'pry' - RSpec.describe 'hash' do describe 'to_hcl' do it 'repsponds to to_hcl' do diff --git a/terraform/spec/models/terraform/plan_spec.rb b/terraform/spec/models/terraform/plan_spec.rb index dd98ea0e..3c541a6e 100644 --- a/terraform/spec/models/terraform/plan_spec.rb +++ b/terraform/spec/models/terraform/plan_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../spec_helper' - # module Proxmox # class Provider < Provider # def terraform_resources_map @@ -17,33 +15,26 @@ # end -RSpec.describe 'Plan' do - let(:source_path) { SPEC_DIR.join('fixtures/segments') } - let(:target_path) { SPEC_DIR.join('../../spec/segments') } - let(:root) { SegmentRoot.first } - let(:a_context) { Context.create(root: root, options: options) } - - let(:subject) { Plan.create(name: 'test', owner: Context.create, creates: 'Proxmox::Resource::Vm::Qemu') } - let(:provider) { Proxmox::Provider.create(name: 'proxmox') } +module Terraform + RSpec.describe Plan do + let(:segment_root) { OneStack::SegmentRoot.first } + let(:context) { OneStack::Navigator.new.context } - before do - binding.pry - setup_project(segment: :plan) - end + # let(:subject) { Plan.create(name: 'test', owner: Context.create, creates: 'Proxmox::Resource::Vm::Qemu') } + # let(:provider) { Proxmox::Provider.create(name: 'proxmox') } - # after do - # target_path.rmtree if target_path.basename.to_s.eql?('segments') - # end + before { OneStack::SpecHelper.setup_segment(self) } - describe 'create_resources' do - it 'generates the correct number of contexts and context_components' do - binding.pry - # binding.pry - # subject.create_resources - end + describe 'create_resources' do + it 'generates the correct number of contexts and context_components' do + # ab = described_class + binding.pry + # subject.create_resources + end - xit 'generates the correct number of providers' do - expect(a_context.providers.count).to eq(3) + xit 'generates the correct number of providers' do + expect(a_context.providers.count).to eq(3) + end end end end diff --git a/terraform/spec/spec_helper.rb b/terraform/spec/spec_helper.rb index 86bc93e2..fda41792 100644 --- a/terraform/spec/spec_helper.rb +++ b/terraform/spec/spec_helper.rb @@ -1,10 +1,5 @@ # frozen_string_literal: true -require 'pathname' - -SPEC_DIR = Pathname.new(__dir__) - -plugin_path = SPEC_DIR.join('../../cnfs/lib') -$:.unshift(plugin_path) - -require 'cnfs/spec_loader' +require 'bundler/setup' +SPEC_PATH = Pathname.new(__dir__) +require 'one_stack/spec_helper' diff --git a/solid-record/.gitignore b/typeaway/.gitignore similarity index 81% rename from solid-record/.gitignore rename to typeaway/.gitignore index 55ed22b8..9106b2a3 100644 --- a/solid-record/.gitignore +++ b/typeaway/.gitignore @@ -6,4 +6,3 @@ /pkg/ /spec/reports/ /tmp/ -spec/dummy/infra diff --git a/typeaway/.rspec b/typeaway/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/typeaway/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/typeaway/.rubocop.yml b/typeaway/.rubocop.yml new file mode 100644 index 00000000..e5164ded --- /dev/null +++ b/typeaway/.rubocop.yml @@ -0,0 +1,3 @@ +--- +inherit_from: + - ../.rubocop.yml diff --git a/cnfs/CODE_OF_CONDUCT.md b/typeaway/CODE_OF_CONDUCT.md similarity index 100% rename from cnfs/CODE_OF_CONDUCT.md rename to typeaway/CODE_OF_CONDUCT.md diff --git a/typeaway/Gemfile b/typeaway/Gemfile new file mode 100644 index 00000000..b063b5fc --- /dev/null +++ b/typeaway/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem_path = '..' + +gem 'solid_support', path: "#{gem_path}/solid_support" +gem 'solid_record', path: "#{gem_path}/solid_record" + +gemspec diff --git a/cnfs/LICENSE.txt b/typeaway/LICENSE.txt similarity index 100% rename from cnfs/LICENSE.txt rename to typeaway/LICENSE.txt diff --git a/cnfs/README.md b/typeaway/README.md similarity index 100% rename from cnfs/README.md rename to typeaway/README.md diff --git a/cnfs/Rakefile b/typeaway/Rakefile similarity index 100% rename from cnfs/Rakefile rename to typeaway/Rakefile diff --git a/typeaway/app/commands/typeaway/application_command.rb b/typeaway/app/commands/typeaway/application_command.rb new file mode 100644 index 00000000..c861236c --- /dev/null +++ b/typeaway/app/commands/typeaway/application_command.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class ApplicationCommand < SolidSupport::ApplicationCommand + end +end diff --git a/typeaway/app/commands/typeaway/main_command.rb b/typeaway/app/commands/typeaway/main_command.rb new file mode 100644 index 00000000..1bba9949 --- /dev/null +++ b/typeaway/app/commands/typeaway/main_command.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class MainCommand < SolidSupport::MainCommand + end +end diff --git a/typeaway/app/commands/typeaway/plugin_command.rb b/typeaway/app/commands/typeaway/plugin_command.rb new file mode 100644 index 00000000..2521c882 --- /dev/null +++ b/typeaway/app/commands/typeaway/plugin_command.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class PluginCommand < SolidSupport::PluginCommand + end +end diff --git a/typeaway/app/commands/typeaway/project_command.rb b/typeaway/app/commands/typeaway/project_command.rb new file mode 100644 index 00000000..25566b16 --- /dev/null +++ b/typeaway/app/commands/typeaway/project_command.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class ProjectCommand < SolidSupport::ProjectCommand + end +end diff --git a/typeaway/app/controllers/typeaway/application_controller.rb b/typeaway/app/controllers/typeaway/application_controller.rb new file mode 100644 index 00000000..e757f6b9 --- /dev/null +++ b/typeaway/app/controllers/typeaway/application_controller.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class ApplicationController < SolidSupport::ApplicationController + end +end diff --git a/typeaway/app/controllers/typeaway/console_controller.rb b/typeaway/app/controllers/typeaway/console_controller.rb new file mode 100644 index 00000000..6d6d4d91 --- /dev/null +++ b/typeaway/app/controllers/typeaway/console_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Typeaway + class ConsoleController < ApplicationController + include SolidSupport::ConsoleController + end +end diff --git a/typeaway/app/controllers/typeaway/project_controller.rb b/typeaway/app/controllers/typeaway/project_controller.rb new file mode 100644 index 00000000..6283ed9e --- /dev/null +++ b/typeaway/app/controllers/typeaway/project_controller.rb @@ -0,0 +1,8 @@ + +module Typeaway + # class ProjectController < ApplicationController + class ProjectController < SolidSupport::ProjectController + binding.pry + # include SolidSupport::ConsoleController + end +end diff --git a/typeaway/app/generators/typeaway/application_generator.rb b/typeaway/app/generators/typeaway/application_generator.rb new file mode 100644 index 00000000..5406b347 --- /dev/null +++ b/typeaway/app/generators/typeaway/application_generator.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class ApplicationGenerator < SolidSupport::ApplicationGenerator + end +end diff --git a/typeaway/app/generators/typeaway/project/application/files/.gitignore b/typeaway/app/generators/typeaway/project/application/files/.gitignore new file mode 100644 index 00000000..8bf76c56 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/application/files/.gitignore @@ -0,0 +1,3 @@ +.env +tmp/ +src/ diff --git a/cnfs/app/generators/cnfs/new/project/files/config/initializers/logging.rb b/typeaway/app/generators/typeaway/project/application/files/config/initializers/logging.rb similarity index 91% rename from cnfs/app/generators/cnfs/new/project/files/config/initializers/logging.rb rename to typeaway/app/generators/typeaway/project/application/files/config/initializers/logging.rb index 9bb57c27..00dd9ba4 100644 --- a/cnfs/app/generators/cnfs/new/project/files/config/initializers/logging.rb +++ b/typeaway/app/generators/typeaway/project/application/files/config/initializers/logging.rb @@ -3,7 +3,7 @@ # Documentation: https://github.com/piotrmurach/tty-logger # Custom logger -Cnfs.logger = TTY::Logger.new do |config| +Hendrix.logger = TTY::Logger.new do |config| config.level = :warn # config.handlers = [[:stream, formatter: :json]] # config.output = File.open('/tmp/errors.log', 'w') diff --git a/cnfs/app/generators/cnfs/new/plugin/templates/Gemfile.erb b/typeaway/app/generators/typeaway/project/application/templates/Gemfile.erb similarity index 100% rename from cnfs/app/generators/cnfs/new/plugin/templates/Gemfile.erb rename to typeaway/app/generators/typeaway/project/application/templates/Gemfile.erb diff --git a/typeaway/app/generators/typeaway/project/application/templates/README.md.erb b/typeaway/app/generators/typeaway/project/application/templates/README.md.erb new file mode 100644 index 00000000..585c8cf1 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/application/templates/README.md.erb @@ -0,0 +1,11 @@ + +# Overview + +This project was created with the cnfs-cli + +<%= name %> + +## Getting Started + +See documentation [here](http://guides.cnfs.io/getting-started.html) +and [here](https://github.com/rails-on-services/setup) diff --git a/typeaway/app/generators/typeaway/project/application_generator.rb b/typeaway/app/generators/typeaway/project/application_generator.rb new file mode 100644 index 00000000..9c66801d --- /dev/null +++ b/typeaway/app/generators/typeaway/project/application_generator.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Typeaway + class Project::ApplicationGenerator < ProjectGenerator + # def data_files + # data_path.rmtree if data_path.exist? + # create_file(data_path.join('keys.yml'), { name => Lockbox.generate_key }.to_yaml) + # end + + def project_files() = directory('files', '.') + + def app_structure() = super + + private + + def gemfile_gem_string(name) + gem_name = name.empty? ? gem_name_root : "#{gem_name_root}-#{name}" + return "gem '#{gem_name}'" if ENV['CNFS_ENV'].eql?('production') + + name = gem_name_root if name.empty? + "gem '#{gem_name}', path: '#{gems_path.join(name)}'" + end + + def gems_path() = internal_path.join('../../../../../') + + def internal_path() = Pathname.new(__dir__) + + # def data_path() = CnfsCli.config.data_home.join('projects', uuid) + end +end diff --git a/typeaway/app/generators/typeaway/project/extension/templates/.rubocop.yml.erb b/typeaway/app/generators/typeaway/project/extension/templates/.rubocop.yml.erb new file mode 100644 index 00000000..e8d8fbcf --- /dev/null +++ b/typeaway/app/generators/typeaway/project/extension/templates/.rubocop.yml.erb @@ -0,0 +1,4 @@ +--- +inherit_from: + - ../.rubocop.yml + # - .rubocop_todo.yml diff --git a/typeaway/app/generators/typeaway/project/extension/templates/Gemfile.erb b/typeaway/app/generators/typeaway/project/extension/templates/Gemfile.erb new file mode 100644 index 00000000..e079b113 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/extension/templates/Gemfile.erb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem_path = '<%= internal_path.join('../../../../..') %>' + +gem 'hendrix', path: "#{gem_path}/hendrix" +gem 'solid-record', path: "#{gem_path}/solid-record" +gem 'solid-support', path: "#{gem_path}/solid-support" + +gemspec diff --git a/typeaway/app/generators/typeaway/project/extension/templates/bin/console.erb b/typeaway/app/generators/typeaway/project/extension/templates/bin/console.erb new file mode 100644 index 00000000..97c22ab5 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/extension/templates/bin/console.erb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' + +ARGV.unshift 'console' +DUMMY_PATH = Pathname.new(__dir__).join('../spec/dummy') + +Dir.chdir(DUMMY_PATH) { require 'hendrix/boot_loader' } diff --git a/typeaway/app/generators/typeaway/project/extension/templates/lib/module.rb.erb b/typeaway/app/generators/typeaway/project/extension/templates/lib/module.rb.erb new file mode 100644 index 00000000..56560c1c --- /dev/null +++ b/typeaway/app/generators/typeaway/project/extension/templates/lib/module.rb.erb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative '<%= "#{gem_name_root}/#{name}/version.rb" %>' + +require '<%= gem_name_root %>' + +require_relative '<%= "#{gem_name_root}/#{name}/plugin.rb" %>' + + +module <%= name.camelize %> + module Concerns; end +end + +module <%= gem_name_root.camelize %> + module <%= name.camelize %> + class << self + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') + end + end +end diff --git a/typeaway/app/generators/typeaway/project/extension/templates/lib/plugin.rb.erb b/typeaway/app/generators/typeaway/project/extension/templates/lib/plugin.rb.erb new file mode 100644 index 00000000..b175c0a5 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/extension/templates/lib/plugin.rb.erb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module <%= gem_name_root.camelize %> + module <%= name.camelize %> + class Plugin < <%= gem_name_root.camelize %>::Plugin + # initializer 'logger' do |app| + # Hendrix.logger.info('[<%= name.camelize %>] Initializing from', gem_root) + # end + + class << self + def gem_root() = <%= gem_name_root.camelize %>::<%= name.camelize %>.gem_root + end + end + end +end diff --git a/typeaway/app/generators/typeaway/project/extension_generator.rb b/typeaway/app/generators/typeaway/project/extension_generator.rb new file mode 100644 index 00000000..4621a3b3 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/extension_generator.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Hendrix + class Project::ExtensionGenerator < ProjectGenerator + def gem + if options.noop + puts cmd + return + end + + # These methods are not Thor aware and so execute in Dir.pwd + # rather than #destination_root + Pathname.new(name).rmtree if Pathname.new(name).exist? + system(cmd) + FileUtils.mv(gem_name, name) + end + + def gem_libs + remove_file("lib/#{gem_name_root}/#{name}.rb") + template('templates/lib/module.rb.erb', "lib/#{gem_name_root}-#{name}.rb") + template('templates/lib/plugin.rb.erb', "lib/#{gem_name_root}/#{name}/plugin.rb") + inject_into_file "#{gem_name_root}-#{name}.gemspec", + " spec.add_dependency '#{gem_name_root}', '~> 0.1.0'\n", before: /^end/ + end + + def gem_root_files + remove_file('Gemfile') + end + + # renders templates + def app_structure() = super + + def gem_cleanup + return if options.noop + + if options.config + remove_dir('.git') + remove_file('.travis.yml') + remove_file('.gitignore') + end + end + + private + def manual_templates() = %w[lib/module.rb.erb lib/plugin.rb.erb] + + def cmd() = "bundle gem --test=rspec --ci=none --no-coc --no-rubocop --mit --changelog #{gem_name}" + + def gem_name() = "#{gem_name_root}-#{name}" + + def internal_path() = Pathname.new(__dir__) + end +end diff --git a/typeaway/app/generators/typeaway/project/plugin/m_templates/application.rb.erb b/typeaway/app/generators/typeaway/project/plugin/m_templates/application.rb.erb new file mode 100644 index 00000000..a56e45e3 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/plugin/m_templates/application.rb.erb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Application<%= type.classify %> < <%= gem_name_root.camelize %>::Application<%= type.classify %> +end diff --git a/cnfs/app/generators/cnfs/new/project/templates/config/application.rb.erb b/typeaway/app/generators/typeaway/project/plugin/templates/config/application.rb.erb similarity index 93% rename from cnfs/app/generators/cnfs/new/project/templates/config/application.rb.erb rename to typeaway/app/generators/typeaway/project/plugin/templates/config/application.rb.erb index 4818a7b1..369e41c5 100644 --- a/cnfs/app/generators/cnfs/new/project/templates/config/application.rb.erb +++ b/typeaway/app/generators/typeaway/project/plugin/templates/config/application.rb.erb @@ -4,11 +4,11 @@ require_relative 'boot' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. -Bundler.require # (*Cnfs.groups) +Bundler.require # (*.groups) module <%= name.camelize %> module Concerns; end - class Application < Cnfs::Application + class Application < <%= gem_name_root.camelize %>::Application # Reference id for encryption keys and local file access config.project_id = '<%= uuid %>' diff --git a/typeaway/app/generators/typeaway/project/plugin/templates/config/boot.rb.erb b/typeaway/app/generators/typeaway/project/plugin/templates/config/boot.rb.erb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/plugin/templates/config/boot.rb.erb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/typeaway/app/generators/typeaway/project/plugin/templates/config/environment.rb.erb b/typeaway/app/generators/typeaway/project/plugin/templates/config/environment.rb.erb new file mode 100644 index 00000000..1f198813 --- /dev/null +++ b/typeaway/app/generators/typeaway/project/plugin/templates/config/environment.rb.erb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +<%= gem_name_root.camelize %>.application.initialize! diff --git a/typeaway/app/generators/typeaway/project/plugin_generator.rb b/typeaway/app/generators/typeaway/project/plugin_generator.rb new file mode 100644 index 00000000..29e9e32e --- /dev/null +++ b/typeaway/app/generators/typeaway/project/plugin_generator.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Typeaway + class Project::PluginGenerator < ProjectGenerator + attr_accessor :type + + def app_dir_structure + %w[commands controllers generators records views].each do |type| + @type = type + app_file(type) + end + end + + # Set this to spec/dummy so that app_structure templates files in this directory + def set_dest_root() = self.destination_root = "#{name}/spec/dummy" + + def app_structure() = super + + private + + def app_file(app_path) + ap = app_path.eql?('records') ? 'models' : app_path + template("m_templates/application.rb.erb", path.join('app', ap, "application_#{app_path.singularize}.rb")) + end + + def uuid() = @uuid ||= SecureRandom.uuid + + def internal_path() = Pathname.new(__dir__) + end +end + diff --git a/typeaway/app/generators/typeaway/project_generator.rb b/typeaway/app/generators/typeaway/project_generator.rb new file mode 100644 index 00000000..b259a6b0 --- /dev/null +++ b/typeaway/app/generators/typeaway/project_generator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Typeaway + class ProjectGenerator < ApplicationGenerator + argument :name + argument :gem_name_root + + private + + # Used by Plugin and Application + def app_structure + to_render.each do |template| + destination = template.relative_path_from(templates_path).to_s.delete_suffix('.erb') + template(template, destination) + end + end + + def to_render() = (templates - manual_templates.map{ |path| templates_path.join(path) }) + + def manual_templates() = [] + end +end +=begin + # def component_files() = _component_files + # keep('config/initializers') + + def _component_files + binding.pry + # keep("app/controllers/#{name}/concerns") + # keep("app/controllers/#{name}/concerns") + # keep("app/generators/#{name}/concerns") + # keep("app/models/#{name}/concerns") + # keep("app/views/#{name}/concerns") + keep('config/initializers') + end + + # def keep(keep_path) = create_file(path.join(keep_path, '.keep')) +=end diff --git a/typeaway/app/views/typeaway/model_view.rb b/typeaway/app/views/typeaway/model_view.rb new file mode 100644 index 00000000..3a43261f --- /dev/null +++ b/typeaway/app/views/typeaway/model_view.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Typeaway + class ModelView < SolidRecord::ModelView + end +end diff --git a/typeaway/bin/console b/typeaway/bin/console new file mode 100755 index 00000000..7d29c2cf --- /dev/null +++ b/typeaway/bin/console @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' +Bundler.require + +ARGV.unshift 'console' +ENV['TYPEAWAY_ENV'] ||= 'development' +BOOT_MODULE = Typeaway +Dir.chdir(Pathname.new(__dir__).join('../spec/dummy')) { require 'solid_support/boot_loader' } diff --git a/typeaway/bin/setup b/typeaway/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/typeaway/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/typeaway/exe/typeaway b/typeaway/exe/typeaway new file mode 100755 index 00000000..947a5b0a --- /dev/null +++ b/typeaway/exe/typeaway @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +git_path = File.expand_path('../../.git', __dir__) + +if File.exist?(git_path) + plugin_path = File.expand_path('../lib', __dir__) + $:.unshift(plugin_path) + plugin_path = File.expand_path('../../solid_support/lib', __dir__) + $:.unshift(plugin_path) + plugin_path = File.expand_path('../../solid_record/lib', __dir__) + $:.unshift(plugin_path) +end + +require 'typeaway' + +BOOT_MODULE = Typeaway + +# require 'solid_support' +require 'solid_support/boot_loader' diff --git a/typeaway/lib/typeaway.rb b/typeaway/lib/typeaway.rb new file mode 100644 index 00000000..71367678 --- /dev/null +++ b/typeaway/lib/typeaway.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'solid_support' +require 'solid_record' unless defined? APP_ROOT + +module Typeaway + def self.logger() = SolidSupport.logger + class Error < StandardError; end +end + +require 'typeaway/version' +require 'typeaway/application' +require 'typeaway/plugin' +require 'typeaway/extension' + +load_path = Pathname.new(__dir__).join('../app') +SolidSupport.add_loader(name: :typeaway, path: load_path) +SolidSupport.load_all diff --git a/typeaway/lib/typeaway/application.rb b/typeaway/lib/typeaway/application.rb new file mode 100644 index 00000000..8cfded2a --- /dev/null +++ b/typeaway/lib/typeaway/application.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Typeaway + class << self + def application() = SolidSupport.application + + def config() = SolidSupport.config + end + + class Application < SolidSupport::Application + config.before_initialize do |_config| + end + + config.after_initialize do |_config| + end + + class Configuration < SolidSupport::Application::Configuration + end + end +end diff --git a/typeaway/lib/typeaway/extension.rb b/typeaway/lib/typeaway/extension.rb new file mode 100644 index 00000000..375c35a5 --- /dev/null +++ b/typeaway/lib/typeaway/extension.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Typeaway + class << self + def extensions() = SolidSupport.extensions + end + + class Extension < SolidSupport::Extension + def self.gem_root() = Plugin.gem_root + end +end diff --git a/typeaway/lib/typeaway/plugin.rb b/typeaway/lib/typeaway/plugin.rb new file mode 100644 index 00000000..f2fde131 --- /dev/null +++ b/typeaway/lib/typeaway/plugin.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Typeaway + module New; end + module Concerns; end + + class Plugin < SolidSupport::Plugin + class << self + def before_loader_setup(loader) = loader.ignore(loader_ignore_files) + + def loader_ignore_files + [ + gem_root.join('app/generators/typeaway/project/extension'), + gem_root.join('app/generators/typeaway/project/plugin'), + gem_root.join('app/generators/typeaway/project/application') + ] + end + + def gem_root() = @gem_root ||= Pathname.new(__dir__).join('../..') + end + + config.after_initialize do |config| + # binding.pry + # # Set defaults + # # self.dev = false + # # self.dry_run = false + config.logging = :fatal + # config.quiet = false + + # # Default paths + # # paths.data = 'data' + # # paths.tmp = 'tmp' + end + end +end diff --git a/typeaway/lib/typeaway/version.rb b/typeaway/lib/typeaway/version.rb new file mode 100644 index 00000000..2e0c0ea4 --- /dev/null +++ b/typeaway/lib/typeaway/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Typeaway + VERSION = '0.1.0' +end diff --git a/typeaway/set_path b/typeaway/set_path new file mode 100755 index 00000000..fb040f78 --- /dev/null +++ b/typeaway/set_path @@ -0,0 +1,8 @@ +# Use to access cnfs cli in development mode + +RUBYOPT='-W:no-deprecated' + +SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +echo $SCRIPT_PATH +export PATH=$PATH:$SCRIPT_PATH/exe +unset SCRIPT_PATH diff --git a/typeaway/spec/dummy/Gemfile b/typeaway/spec/dummy/Gemfile new file mode 100644 index 00000000..8ffdda12 --- /dev/null +++ b/typeaway/spec/dummy/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem_path = '../../..' + +gem 'solid_support', path: "#{gem_path}/solid_support" +gem 'solid_record', path: "#{gem_path}/solid_record" + +gemspec diff --git a/typeaway/spec/dummy/config/application.rb b/typeaway/spec/dummy/config/application.rb new file mode 100644 index 00000000..e3c29576 --- /dev/null +++ b/typeaway/spec/dummy/config/application.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'boot' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require # (*Cnfs.groups) + +module Spec + class Application < Typeaway::Application + # Reference id for encryption keys and local file access + config.project_id = '3273b2fc-ff9c-4d64-8835-34ee3328ad68' + end +end diff --git a/typeaway/spec/dummy/config/boot.rb b/typeaway/spec/dummy/config/boot.rb new file mode 100644 index 00000000..30e594e2 --- /dev/null +++ b/typeaway/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/typeaway/spec/dummy/config/environment.rb b/typeaway/spec/dummy/config/environment.rb new file mode 100644 index 00000000..21677c46 --- /dev/null +++ b/typeaway/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the application +require_relative 'application' + +# Initialize the application +Typeaway.application.initialize! diff --git a/typeaway/spec/dummy/config/initializers/.keep b/typeaway/spec/dummy/config/initializers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/cnfs/spec/fixtures/config/initializers/logging.rb b/typeaway/spec/dummy/config/initializers/logging.rb similarity index 77% rename from cnfs/spec/fixtures/config/initializers/logging.rb rename to typeaway/spec/dummy/config/initializers/logging.rb index 9bb57c27..31ff16b9 100644 --- a/cnfs/spec/fixtures/config/initializers/logging.rb +++ b/typeaway/spec/dummy/config/initializers/logging.rb @@ -3,14 +3,14 @@ # Documentation: https://github.com/piotrmurach/tty-logger # Custom logger -Cnfs.logger = TTY::Logger.new do |config| - config.level = :warn +# Cnfs.logger = TTY::Logger.new do |config| + # config.level = :warn # config.handlers = [[:stream, formatter: :json]] # config.output = File.open('/tmp/errors.log', 'w') # https://github.com/piotrmurach/tty-logger#241-metadata # config.metadata = [:file] - config.metadata = [:date, :time] -end + # config.metadata = [:date, :time] +# end # require 'tty-logger-raven' diff --git a/typeaway/typeaway.gemspec b/typeaway/typeaway.gemspec new file mode 100644 index 00000000..fd7a7ead --- /dev/null +++ b/typeaway/typeaway.gemspec @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require_relative 'lib/typeaway/version' + +Gem::Specification.new do |spec| + spec.name = 'typeaway' + spec.version = Typeaway::VERSION + spec.authors = ['Robert Roach'] + spec.email = ['rjayroach@gmail.com'] + + spec.summary = 'Typeaway CLI Application Framework' + spec.description = 'The Typeaway CLI Framework boots the application, plugins and extensions' + spec.homepage = 'https://cnfs.io' + spec.license = 'MIT' + spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') + + # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/rails-on-services/cnfs-cli' + # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_dependency 'solid_support', '~> 0.1' + spec.add_dependency 'solid_record', '~> 0.1' + # spec.add_dependency 'json_schemer' + + spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'guard-rspec', '~> 4.7.3' + spec.add_development_dependency 'guard-rubocop' + spec.add_development_dependency 'pry-byebug', '~> 3.9' + spec.add_development_dependency 'rspec', '~> 3.10' + spec.add_development_dependency 'rubocop', '~> 1.41' + spec.add_development_dependency 'rubocop-performance' + spec.add_development_dependency 'rubocop-rspec', '~> 2.18' +end