rrx_api 0.1.0 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'base'
3
+
4
+ module RrxApi
5
+ module Generators
6
+ class GithubGenerator < Base
7
+ init! 'Generates GitHub Actions workflows for building and deploying the application using Docker.'
8
+
9
+ class_option :deploy,
10
+ type: :boolean,
11
+ default: false,
12
+ desc: 'Include deployment workflow'
13
+
14
+ class_option :terraform_repo,
15
+ type: :string,
16
+ default: 'terraform',
17
+ desc: 'Terraform repository name (if using deployment workflow)'
18
+
19
+ class_option :terraform_module,
20
+ type: :string,
21
+ desc: 'Terraform module name (if using deployment workflow). Default is the application name.'
22
+
23
+ class_option :database,
24
+ type: :string,
25
+ default: 'auto',
26
+ enum: %w[auto postgresql mysql mariadb sqlite none],
27
+ desc: 'Database type'
28
+
29
+ def github
30
+ directory 'github/build', '.github'
31
+ directory 'github/deploy', '.github' if deploy?
32
+ end
33
+
34
+ private
35
+
36
+ def deploy?
37
+ options[:deploy]
38
+ end
39
+
40
+ def database
41
+ @database ||= options[:database] == 'auto' ? detect_database : options[:database]
42
+ end
43
+
44
+ def terraform_repo
45
+ options[:terraform_repo]
46
+ end
47
+
48
+ def terraform_module
49
+ options[:terraform_module] || app_name
50
+ end
51
+
52
+ def docker_packages
53
+ ''
54
+ end
55
+
56
+ def detect_database
57
+ config_path = destination_path.join('config/database.yml')
58
+ if config_path.exist?
59
+ # @type {Hash}
60
+ config = YAML.safe_load(config_path.read, symbolize_names: true, aliases: true)
61
+ adapter = config.dig(:test, :adapter).to_s.downcase
62
+ case adapter
63
+ when /postgresql/, /psql/
64
+ 'postgresql'
65
+ when /mysql/
66
+ if yes?('Detected MySQL adapter in database.yml. Are you using MariaDB? (Yn)')
67
+ 'mariadb'
68
+ else
69
+ 'mysql'
70
+ end
71
+ when /sqlite/
72
+ 'sqlite'
73
+ else
74
+ say_error 'Unsupported database adapter detected in config/database.yml. Please specify the database type explicitly using --database option.'
75
+ exit 1
76
+ end
77
+ else
78
+ 'none'
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+ # frozen_string_literal: true
3
+ require_relative 'base'
4
+
5
+ module RrxApi
6
+ module Generators
7
+ class InstallGenerator < Base
8
+ init! 'Installs the RRX API gem and its dependencies.'
9
+
10
+ class_option :skip_rrx_dev, type: :boolean, default: false, hide: true
11
+
12
+ # Updates the application configuration file with specific dependencies and settings.
13
+ # The method performs the following operations:
14
+ # - Reads the application configuration file located at 'config/application.rb'.
15
+ # - Removes existing comments and unnecessary `require` statements from the file.
16
+ # - Includes necessary `require` directives for the application's gem dependencies.
17
+ # - Injects or updates the Bundler require statement to include the necessary gems.
18
+ # - Cleans up unwanted whitespace in the file content.
19
+ # - Rewrites the configuration file with the updated content.
20
+ # - Appends additional configuration settings for time zone, schema format, and session management.
21
+ #
22
+ # @return [void] Since the primary purpose is file modification, it does not return a value directly.
23
+ def update_application
24
+ # @type [Pathname]
25
+ app_file = Pathname(destination_root).join('config', 'application.rb')
26
+ app_code = app_file.read
27
+
28
+ # Assume full replace if we've never modified before.
29
+ # Otherwise, create_file will prompt to replace it.
30
+ remove_file app_file unless app_code =~ /rrx_api/
31
+
32
+ app_code.gsub!(/^\s*#.*\r?\n/, '')
33
+ app_code.gsub!(/^(?:#\s+)?require ["'].*\r?\n/, '')
34
+
35
+ requires = application_gems.map do |gem|
36
+ "require '#{gem}'"
37
+ end.join("\n")
38
+
39
+ app_code.sub!(/^(Bundler.require.*)$/) do |str|
40
+ <<~REQ
41
+ #{requires}
42
+
43
+ #{str}
44
+ REQ
45
+ end
46
+
47
+ # Remove existing application config lines
48
+ APPLICATION_CONFIG.each do |line|
49
+ app_code.gsub!(/^\s*#{line}\W*.*\n/, '')
50
+ end
51
+
52
+ # Remove unnecessary whitespace
53
+ app_code.lstrip!
54
+ app_code.gsub!(/^\s*\r?\n(\s*\r?\n)+/, "\n")
55
+
56
+ # puts app_code
57
+ create_file app_file, app_code
58
+ end
59
+
60
+ def update_base_classes
61
+ gsub_file 'app/models/application_record.rb',
62
+ /ApplicationRecord.*/,
63
+ 'ApplicationRecord < RrxApi::Record'
64
+
65
+ gsub_file 'app/controllers/application_controller.rb',
66
+ /ApplicationController.*/,
67
+ 'ApplicationController < RrxApi::Controller'
68
+ end
69
+
70
+ def routes
71
+ inject_into_file 'config/routes.rb',
72
+ after: "Rails.application.routes.draw do\n" do
73
+ <<~RUBY
74
+ mount RrxApi::Engine => '/'
75
+ RUBY
76
+ end
77
+ end
78
+
79
+ def rrx_dev
80
+ generate 'rrx_dev:install' unless options[:skip_rrx_dev]
81
+ end
82
+
83
+ def asdf_versions
84
+ create_file '.tool-versions', <<~VERSIONS
85
+ ruby #{ruby_version}
86
+ VERSIONS
87
+ end
88
+
89
+ private
90
+
91
+ # Configs to remove from the application.rb file
92
+ APPLICATION_CONFIG = <<~CONFIG.split("\n").map(&:strip).freeze
93
+ config.time_zone
94
+ config.active_support.to_time_preserves_timezone
95
+ config.active_record.schema_format
96
+ config.session_store
97
+ config.middleware.use ActionDispatch::Cookies
98
+ config.middleware.use ActionDispatch::Session::CookieStore
99
+ CONFIG
100
+
101
+ # @return [Array<String>] The list of application gem names that are dependencies
102
+ def application_gems
103
+ gems = %w[rrx_api]
104
+ gems.concat(%w[rrx_jobs active_job].select { |name| gem?(name) })
105
+ end
106
+
107
+ # @param [String] name
108
+ # @return [Boolean] True if gem is a dependency
109
+ def gem?(name)
110
+ bundle_gems.include?(name)
111
+ end
112
+
113
+ # @return [Set<String>]
114
+ def bundle_gems
115
+ @bundle_gems ||= bundle.dependencies.map(&:name).to_set
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -0,0 +1 @@
1
+ FROM ddrew555/rrx_docker:<%= ruby_version %>
@@ -0,0 +1,71 @@
1
+ name: Build
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ <% if deploy? %>
6
+ inputs:
7
+ dev_deploy:
8
+ type: boolean
9
+ default: true
10
+ description: 'Deploy build to development environment'
11
+ <% end %>
12
+
13
+ push:
14
+ branches: [main]
15
+
16
+ env:
17
+ image_name: '<%= app_name %>'
18
+ major_version: 1
19
+ minor_version: 0
20
+ development: ${{ github.ref_name != 'main' }}
21
+
22
+ permissions: write-all
23
+
24
+ jobs:
25
+ build:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 0
31
+
32
+ - uses: dan-drew/asdf-actions/tool-versions@v1
33
+
34
+ - name: Test
35
+ uses: rails-rrx/actions/test@main
36
+ with:
37
+ ruby_version: <%= ruby_version %>
38
+ database: <%= database %>
39
+
40
+ - uses: dan-drew/actions/next-version@main
41
+ with:
42
+ suffix: ${{ env.development && '-dev' || '' }}
43
+
44
+ - name: Build
45
+ uses: rails-rrx/actions/docker-build@main
46
+ with:
47
+ repository: ${{ vars.DOCKER_REPOSITORY }}
48
+ username: ${{ secrets.DOCKER_USERNAME }}
49
+ password: ${{ secrets.DOCKER_PASSWORD }}
50
+ image_name: $${{ env.image_name }}
51
+ image_version: ${{ env.next_version }}
52
+ database: <%= database %>
53
+ latest: true
54
+ packages: '<%= docker_packages %>'
55
+
56
+ - uses: dan-drew/actions/push-version@main
57
+ if: ${{ ! env.development }}
58
+ <% if deploy? %>
59
+ - name: Deploy Development
60
+ uses: actions/github-script@v7
61
+ condition: ${{ inputs.dev_deploy }}
62
+ with:
63
+ script: |
64
+ github.rest.actions.createWorkflowDispatch({
65
+ owner: context.repo.owner,
66
+ repo: context.repo.repo,
67
+ workflow_id: 'deploy.yml',
68
+ ref: context.ref,
69
+ inputs: { version: '${{ env.next_version }}' }
70
+ });
71
+ <% end %>
@@ -0,0 +1,62 @@
1
+ name: Deploy
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ required: true
8
+ type: string
9
+ description: 'Version to deploy'
10
+ environment:
11
+ default: development
12
+ type: string
13
+ description: 'Environment to deploy'
14
+
15
+ env:
16
+ image_name: '<%= app_name %>'
17
+ image_tag: "${{ inputs.version }}"
18
+ terraform_repo: '<%= terraform_repo %>'
19
+ terraform_module: '<%= terraform_module %>'
20
+ repository: ${{ vars.DOCKER_REPOSITORY }}
21
+ username: ${{ secrets.DOCKER_USERNAME }}
22
+ password: ${{ secrets.DOCKER_PASSWORD }}
23
+
24
+ permissions: write-all
25
+
26
+ jobs:
27
+ deploy:
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - name: Tag Docker Image
31
+ run: |
32
+ readonly image_ref="${repository}/${image_name}"
33
+ readonly source_image="${image_ref}:${image_tag}"
34
+ readonly dest_image="${image_ref}:${{ inputs.environment }}"
35
+
36
+ echo ::group::Login
37
+ cat <<-PASSWORD | docker login "$repository" -u "$username" --password-stdin
38
+ ${password}
39
+ PASSWORD
40
+ echo ::endgroup::
41
+
42
+ echo ::group::Tag
43
+ docker pull -q "${source_image}"
44
+ docker tag "${source_image}" "${dest_image}"
45
+ docker push -q "${dest_image}"
46
+ echo ::endgroup::
47
+
48
+ - name: Trigger Terraform
49
+ uses: actions/github-script@v7
50
+ with:
51
+ github-token: ${{ secrets.TERRAFORM_GITHUB_TOKEN }}
52
+ script: |
53
+ github.rest.repos.createDispatchEvent({
54
+ owner: context.repo.owner,
55
+ repo: 'terraform',
56
+ event_type: 'apply',
57
+
58
+ client_payload: {
59
+ path: '${{ env.terraform_module }}',
60
+ environment: '${{ inputs.environment }}'
61
+ }
62
+ });
@@ -0,0 +1,37 @@
1
+ data "aws_iam_policy_document" "app_policy" {
2
+ source_policy_documents = [module.rrx_config.role_policy.json]
3
+
4
+ statement {
5
+ sid = "webrtc"
6
+ resources = ["*"]
7
+ actions = [
8
+ "ivs:CreateStage",
9
+ "ivs:CreateParticipantToken",
10
+ "ivs:DeleteStage",
11
+ "ivs:DisconnectParticipant",
12
+ "ivs:GetStage",
13
+ "ivs:GetStageSession",
14
+ "ivs:ListStages",
15
+ "ivs:ListStageSessions",
16
+ "ivs:TagResource",
17
+ "cloudwatch:DescribeAlarms",
18
+ "cloudwatch:GetMetricData",
19
+ "servicequotas:ListAWSDefaultServiceQuotas",
20
+ "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota",
21
+ "servicequotas:ListServiceQuotas",
22
+ "servicequotas:ListServices",
23
+ "servicequotas:ListTagsForResource"
24
+ ]
25
+ }
26
+ }
27
+
28
+ resource "aws_iam_user" "app" {
29
+ name = local.app_env_name
30
+ path = "/${module.tagging.environment}/services/"
31
+ force_destroy = true
32
+ }
33
+
34
+ resource "aws_iam_user_policy" "api" {
35
+ user = aws_iam_user.app.name
36
+ policy = data.aws_iam_policy_document.api.json
37
+ }
@@ -0,0 +1,44 @@
1
+ terraform {
2
+ required_version = "<%= terraform_version %>"
3
+
4
+ required_providers {
5
+ aws = {}
6
+ }
7
+
8
+ <% if s3_bucket.present? %>
9
+ # Configure the backend to store the state file in S3
10
+ backend "s3" {
11
+ bucket = "<%= s3_bucket %>"
12
+ key = "<%= s3_key %>"
13
+ <% if aws_profile %>
14
+ profile = "<%= aws_profile %>"
15
+ <% end %>
16
+ region = "<%= aws_region %>"
17
+ encrypt = true
18
+ }
19
+ <% end %>
20
+ }
21
+
22
+ provider "aws" {
23
+ <% if aws_profile %>
24
+ profile = "<%= aws_profile %>"
25
+ <% end %>
26
+ region = "<%= aws_region %>"
27
+ default_tags {
28
+ tags = module.tagging.default_tags
29
+ }
30
+ }
31
+
32
+ module "aws" {
33
+ source = "github.com/tfext/terraform-aws-base"
34
+ }
35
+
36
+ module "tagging" {
37
+ source = "github.com/tfext/terraform-utilities-tagging"
38
+ environments = <%= environments? %>
39
+ }
40
+
41
+ locals {
42
+ app_name = "<%= app_name %>"
43
+ app_env_name = "${local.app_name}${module.tagging.environment_suffix}"
44
+ }
@@ -0,0 +1,67 @@
1
+ module "rrx_config" {
2
+ source = "github.com/tfext/terraform-rrx-config"
3
+ environment = terraform.workspace
4
+ # aws_secret = "..."
5
+
6
+ db = {
7
+ resource_id = data.aws_db_instance.db.resource_id
8
+ type = "mariadb"
9
+ host = data.aws_db_instance.db.address
10
+ port = data.aws_db_instance.db.port
11
+ name = "liaisun"
12
+ user = "liaisun_api"
13
+ iam = true
14
+ }
15
+
16
+ config = {
17
+ default_identity_provider = {
18
+ user_pool_id = "us-west-2_Q8lbwSDZ3"
19
+ client_id = "27j0fkk6jdnc8fdcf1psuechtd"
20
+ domain = "auth-dev.liaisun.com"
21
+ }
22
+ }
23
+ }
24
+
25
+ resource "aws_cloudwatch_log_group" "service" {
26
+ name = "/${module.tagging.environment}/<%= app_name %>"
27
+ retention_in_days = module.tagging.production ? 3 : 1
28
+ }
29
+
30
+ module "container_definition" {
31
+ source = "github.com/tfext/terraform-aws-ecs-container-definition"
32
+ name = local.app_env_name
33
+ repository = module.ilibrium.docker_repository
34
+ image = "<%= app_name %>"
35
+ image_tag = "latest" # terraform.workspace
36
+ cpu = 1
37
+ memory_required = 512
38
+ environment = module.rrx_config.environment
39
+
40
+ ports = [{
41
+ port = 3000
42
+ public_port = 443
43
+ health_check = { path = "/healthcheck" }
44
+ }]
45
+
46
+ aws_logging = {
47
+ group = aws_cloudwatch_log_group.service.name
48
+ }
49
+ }
50
+
51
+ module "service" {
52
+ source = "github.com/tfext/terraform-aws-ecs-service"
53
+ name = local.app_env_name
54
+ cluster = <%= ecs_cluster %>
55
+ containers = [module.container_definition]
56
+ role_policy = data.aws_iam_policy_document.app.json
57
+ target_group_prefix = "<%= app_name %>${module.tagging.environment_suffix}"
58
+ wait_for_stable = false
59
+
60
+ load_balancers = [{
61
+ name = module.tagging.environment
62
+ short_name = module.tagging.short_env
63
+ dns_zone = "liaisun.com"
64
+ dns_subdomain = "api${module.tagging.environment_suffix}"
65
+ vpc = module.tagging.environment
66
+ }]
67
+ }
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'base'
3
+
4
+ module RrxApi
5
+ module Generators
6
+ class TerraformGenerator < Base
7
+ init! 'Generates Terraform scripts for deploying the service as a docker container.'
8
+
9
+ class_option :cloud,
10
+ default: 'aws',
11
+ enum: %w[aws],
12
+ desc: 'Cloud provider for deployment (currently only AWS is supported)'
13
+
14
+ class_option :bucket,
15
+ type: :string,
16
+ desc: 'S3 bucket name for storing Terraform state files (default: store state locally)'
17
+
18
+ class_option :key,
19
+ type: :string,
20
+ desc: 'S3 key for the Terraform state file (default: app name)'
21
+
22
+ class_option :aws_profile,
23
+ type: :string,
24
+ desc: 'AWS profile to use for deployment (default: current profile)'
25
+
26
+ class_option :aws_region,
27
+ type: :string,
28
+ default: 'us-west-2',
29
+ desc: 'AWS region for deployment (default: us-west-2)'
30
+
31
+ class_option :ecs_cluster,
32
+ type: :string,
33
+ desc: 'ECS cluster name for deployment (default: deployment environment name)'
34
+
35
+ class_option :environments,
36
+ type: :boolean,
37
+ default: false,
38
+ aliases: '-e',
39
+ desc: 'Generate terraform files that targets multiple environments (default: false)'
40
+
41
+ def terraform
42
+ directory "terraform/#{options[:cloud]}", 'terraform'
43
+ end
44
+
45
+ private
46
+
47
+ def environments?
48
+ options[:environments]
49
+ end
50
+
51
+ def terraform_version
52
+ '1.9'
53
+ end
54
+
55
+ def s3_bucket
56
+ options[:bucket]
57
+ end
58
+
59
+ def s3_key
60
+ options[:key] || app_name
61
+ end
62
+
63
+ def aws_profile
64
+ options[:aws_profile]
65
+ end
66
+
67
+ def aws_region
68
+ options[:aws_region]
69
+ end
70
+
71
+ def ecs_cluster
72
+ options.include?(:ecs_cluster) ? "'#{options[:ecs_cluster]}'" : 'terraform.workspace'
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,88 @@
1
+ require 'rails'
2
+ require 'active_model/railtie'
3
+ require 'active_record/railtie'
4
+ require 'action_controller/railtie'
5
+ require 'action_text/engine'
6
+ require 'action_view/railtie'
7
+ require 'jbuilder'
8
+ require 'rack/cors'
9
+ require 'actionpack/action_caching'
10
+
11
+ module RrxApi
12
+ class Engine < ::Rails::Engine
13
+ CORS_LOCALHOST_PATTERN = /\Ahttp:\/\/localhost(?::\d{4})?\z/.freeze
14
+
15
+ config.cors_origins = []
16
+ config.healthcheck = nil
17
+ config.healthcheck_route = 'healthcheck'
18
+
19
+ initializer 'rrx.active_support', before: 'active_support.set_configs' do |app|
20
+ app.config.active_support.to_time_preserves_timezone = :zone
21
+ end
22
+
23
+ initializer 'rrx.application', before: :initialize do |app|
24
+ app.config.time_zone = :utc
25
+ app.config.active_support.to_time_preserves_timezone = :zone
26
+ app.config.active_record.schema_format = :sql # Use SQL schema format for UUID support
27
+
28
+ app.config.generators.orm :active_record, primary_key_type: :uuid
29
+ app.config.session_store :cookie_store, key: '_rrx_session' # Make configurable in the future?
30
+ app.config.middleware.use ActionDispatch::Cookies # Required for all session management
31
+ app.config.middleware.use ActionDispatch::Session::CookieStore, app.config.session_options
32
+ end
33
+
34
+ initializer 'rrx.cors', before: :load_config_initializers do |app|
35
+ require 'rack/cors'
36
+
37
+ Rails.application.config.middleware.insert_before 0, Rack::Cors do
38
+ allow do
39
+ origins do |source, _env|
40
+ if Rails.env.development?
41
+ CORS_LOCALHOST_PATTERN.match? source
42
+ else
43
+ app.config.cors_origins.include?(source)
44
+ end
45
+ end
46
+
47
+ resource '*',
48
+ headers: :any,
49
+ credentials: true,
50
+ methods: [:get, :post, :put, :patch, :delete, :options, :head]
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ initializer 'rrx.api_docs_config', before: :load_config_initializers do |_app|
57
+ Rails.configuration.api_docs = { 'API' => 'swagger.yaml' }
58
+ end
59
+
60
+ initializer 'rrx.api_docs', after: :load_config_initializers do |app|
61
+ # Setup Swagger endpoints if docs exist
62
+ if swagger_root?
63
+ require 'rswag/api'
64
+ require 'rswag/ui'
65
+
66
+ Rswag::Api.configure do |c|
67
+ c.swagger_root = Rails.root.join('swagger')
68
+ end
69
+
70
+ Rswag::Ui.configure do |c|
71
+ app.config.api_docs.each_pair do |name, file|
72
+ c.swagger_endpoint "/api-docs/#{file}", name
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def swagger_root
81
+ @swagger_root ||= Rails.root.join('swagger')
82
+ end
83
+
84
+ def swagger_root?
85
+ swagger_root.exist?
86
+ end
87
+ end
88
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RrxApi
4
- VERSION = "0.1.0"
5
- RAILS_VERSION = "~> 7.0"
4
+ VERSION = '8.0.2'
5
+ DEPENDENCY_VERSION = "~> #{VERSION}"
6
+ RAILS_VERSION = DEPENDENCY_VERSION
6
7
  end
data/lib/rrx_api.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'rrx_api/version'
4
- require_relative 'rrx_api/railtie'
4
+ require_relative 'rrx_api/engine'
5
5
  require 'rrx_config'
6
6
  require 'rrx_logging'
7
7
  require 'rack/cors'