cpflow 3.0.0

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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/check_cpln_links.yml +19 -0
  3. data/.github/workflows/command_docs.yml +24 -0
  4. data/.github/workflows/rspec-shared.yml +56 -0
  5. data/.github/workflows/rspec.yml +28 -0
  6. data/.github/workflows/rubocop.yml +24 -0
  7. data/.gitignore +18 -0
  8. data/.overcommit.yml +16 -0
  9. data/.rubocop.yml +22 -0
  10. data/.simplecov_spawn.rb +10 -0
  11. data/CHANGELOG.md +259 -0
  12. data/CONTRIBUTING.md +73 -0
  13. data/Gemfile +7 -0
  14. data/Gemfile.lock +126 -0
  15. data/LICENSE +21 -0
  16. data/README.md +546 -0
  17. data/Rakefile +21 -0
  18. data/bin/cpflow +6 -0
  19. data/cpflow +6 -0
  20. data/cpflow.gemspec +41 -0
  21. data/docs/assets/grafana-alert.png +0 -0
  22. data/docs/assets/memcached.png +0 -0
  23. data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
  24. data/docs/commands.md +454 -0
  25. data/docs/dns.md +15 -0
  26. data/docs/migrating.md +262 -0
  27. data/docs/postgres.md +436 -0
  28. data/docs/redis.md +128 -0
  29. data/docs/secrets-and-env-values.md +42 -0
  30. data/docs/tips.md +150 -0
  31. data/docs/troubleshooting.md +6 -0
  32. data/examples/circleci.yml +104 -0
  33. data/examples/controlplane.yml +159 -0
  34. data/lib/command/apply_template.rb +209 -0
  35. data/lib/command/base.rb +540 -0
  36. data/lib/command/build_image.rb +49 -0
  37. data/lib/command/cleanup_images.rb +136 -0
  38. data/lib/command/cleanup_stale_apps.rb +79 -0
  39. data/lib/command/config.rb +48 -0
  40. data/lib/command/copy_image_from_upstream.rb +108 -0
  41. data/lib/command/delete.rb +149 -0
  42. data/lib/command/deploy_image.rb +56 -0
  43. data/lib/command/doctor.rb +47 -0
  44. data/lib/command/env.rb +22 -0
  45. data/lib/command/exists.rb +23 -0
  46. data/lib/command/generate.rb +45 -0
  47. data/lib/command/info.rb +222 -0
  48. data/lib/command/latest_image.rb +19 -0
  49. data/lib/command/logs.rb +49 -0
  50. data/lib/command/maintenance.rb +42 -0
  51. data/lib/command/maintenance_off.rb +62 -0
  52. data/lib/command/maintenance_on.rb +62 -0
  53. data/lib/command/maintenance_set_page.rb +34 -0
  54. data/lib/command/no_command.rb +23 -0
  55. data/lib/command/open.rb +33 -0
  56. data/lib/command/open_console.rb +26 -0
  57. data/lib/command/promote_app_from_upstream.rb +38 -0
  58. data/lib/command/ps.rb +41 -0
  59. data/lib/command/ps_restart.rb +37 -0
  60. data/lib/command/ps_start.rb +51 -0
  61. data/lib/command/ps_stop.rb +82 -0
  62. data/lib/command/ps_wait.rb +40 -0
  63. data/lib/command/run.rb +573 -0
  64. data/lib/command/setup_app.rb +113 -0
  65. data/lib/command/test.rb +23 -0
  66. data/lib/command/version.rb +18 -0
  67. data/lib/constants/exit_code.rb +7 -0
  68. data/lib/core/config.rb +316 -0
  69. data/lib/core/controlplane.rb +552 -0
  70. data/lib/core/controlplane_api.rb +170 -0
  71. data/lib/core/controlplane_api_direct.rb +112 -0
  72. data/lib/core/doctor_service.rb +104 -0
  73. data/lib/core/helpers.rb +26 -0
  74. data/lib/core/shell.rb +100 -0
  75. data/lib/core/template_parser.rb +76 -0
  76. data/lib/cpflow/version.rb +6 -0
  77. data/lib/cpflow.rb +288 -0
  78. data/lib/deprecated_commands.json +9 -0
  79. data/lib/generator_templates/Dockerfile +27 -0
  80. data/lib/generator_templates/controlplane.yml +62 -0
  81. data/lib/generator_templates/entrypoint.sh +8 -0
  82. data/lib/generator_templates/templates/app.yml +21 -0
  83. data/lib/generator_templates/templates/postgres.yml +176 -0
  84. data/lib/generator_templates/templates/rails.yml +36 -0
  85. data/rakelib/create_release.rake +81 -0
  86. data/script/add_command +37 -0
  87. data/script/check_command_docs +3 -0
  88. data/script/check_cpln_links +45 -0
  89. data/script/rename_command +43 -0
  90. data/script/update_command_docs +62 -0
  91. data/templates/app.yml +13 -0
  92. data/templates/daily-task.yml +32 -0
  93. data/templates/maintenance.yml +25 -0
  94. data/templates/memcached.yml +24 -0
  95. data/templates/postgres.yml +32 -0
  96. data/templates/rails.yml +27 -0
  97. data/templates/redis.yml +21 -0
  98. data/templates/redis2.yml +37 -0
  99. data/templates/sidekiq.yml +38 -0
  100. metadata +341 -0
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+
5
+ desc("Releases the gem package using the given version.
6
+
7
+ IMPORTANT: the gem version must be in valid rubygem format (no dashes).
8
+ This task depends on the gem-release ruby gem.
9
+
10
+ 1st argument: The new version in rubygem format (no dashes). Pass no argument to
11
+ automatically perform a patch version bump.
12
+ 2nd argument: Perform a dry run by passing 'true' as a second argument.
13
+
14
+ Example: `rake create_release[2.1.0,false]`")
15
+
16
+ task :create_release, %i[gem_version dry_run] do |_t, args|
17
+ args_hash = args.to_hash
18
+
19
+ is_dry_run = Release.object_to_boolean(args_hash[:dry_run])
20
+ gem_version = args_hash.fetch(:gem_version, "").strip
21
+ gem_root = Release.gem_root
22
+
23
+ Release.update_the_local_project
24
+ Release.ensure_there_is_nothing_to_commit
25
+ Release.sh_in_dir(gem_root,
26
+ "gem bump --no-commit #{gem_version == '' ? '' : %(--version #{gem_version})}")
27
+ Release.sh_in_dir(gem_root, "bundle install")
28
+ Release.sh_in_dir(gem_root, "git commit -am 'Bump version to #{gem_version}'")
29
+ Release.sh_in_dir(gem_root, "git push")
30
+
31
+ # See https://github.com/svenfuchs/gem-release
32
+ Release.release_the_new_gem_version unless is_dry_run
33
+ end
34
+
35
+ module Release
36
+ extend FileUtils
37
+ class << self
38
+ def gem_root
39
+ File.expand_path("..", __dir__)
40
+ end
41
+
42
+ # Executes a string or an array of strings in a shell in the given directory in an unbundled environment
43
+ def sh_in_dir(dir, *shell_commands)
44
+ shell_commands.flatten.each { |shell_command| sh %(cd #{dir} && #{shell_command.strip}) }
45
+ end
46
+
47
+ def ensure_there_is_nothing_to_commit
48
+ status = `git status --porcelain`
49
+
50
+ return if $CHILD_STATUS.success? && status == ""
51
+
52
+ error = if $CHILD_STATUS.success?
53
+ "You have uncommitted code. Please commit or stash your changes before continuing"
54
+ else
55
+ "You do not have Git installed. Please install Git, and commit your changes before continuing"
56
+ end
57
+ raise(error)
58
+ end
59
+
60
+ def object_to_boolean(value)
61
+ [true, "true", "yes", 1, "1", "t"].include?(value.instance_of?(String) ? value.downcase : value)
62
+ end
63
+
64
+ def update_the_local_project
65
+ puts "Pulling latest commits from remote repository"
66
+
67
+ sh_in_dir(gem_root, "git pull --rebase")
68
+ raise "Failed in pulling latest changes from default remote repository." unless $CHILD_STATUS.success?
69
+ rescue Errno::ENOENT
70
+ raise "Ensure you have Git and Bundler installed before continuing."
71
+ end
72
+
73
+ def release_the_new_gem_version
74
+ puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
75
+ puts "Use the OTP for RubyGems!"
76
+ puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
77
+
78
+ sh_in_dir(gem_root, "gem release --push --tag")
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ command_name = ARGV[0]&.downcase
5
+
6
+ abort("ERROR: Must provide command name.") unless command_name
7
+
8
+ file_name = command_name.gsub(/[^A-Za-z]/, "_")
9
+ file_path = "#{__dir__}/../lib/command/#{file_name}.rb"
10
+
11
+ abort("ERROR: Command '#{command_name}' already exists.") if File.exist?(file_path)
12
+
13
+ class_name = file_name.split("_").map(&:capitalize).join
14
+
15
+ file_data =
16
+ <<~DATA
17
+ # frozen_string_literal: true
18
+
19
+ module Command
20
+ class #{class_name} < Base
21
+ # See `base.rb` for other constants to add here
22
+ NAME = "#{command_name}"
23
+ OPTIONS = [
24
+ # Add options here
25
+ ].freeze
26
+ DESCRIPTION = "Add description here"
27
+ LONG_DESCRIPTION = <<~DESC
28
+ - Add long description here
29
+ DESC
30
+
31
+ def call
32
+ # Add command logic here
33
+ end
34
+ end
35
+ end
36
+ DATA
37
+ File.binwrite(file_path, file_data)
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ bundle exec rake update_command_docs
3
+ git diff --exit-code || exit 1
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+
3
+ bad_links=("controlplane.com/shakacode" "https://docs.controlplane.com")
4
+ proper_links=("shakacode.controlplane.com" "https://shakadocs.controlplane.com")
5
+
6
+ bold=$(tput bold)
7
+ normal=$(tput sgr0)
8
+
9
+ exit_status=0
10
+ accumulated_results=""
11
+ seen_bad_links_indexes=()
12
+
13
+ for ((idx = 0; idx < ${#bad_links[@]}; idx++)); do
14
+ results=$(git grep \
15
+ --recursive \
16
+ --line-number \
17
+ --fixed-strings \
18
+ --break \
19
+ --heading \
20
+ --color=always -- \
21
+ "${bad_links[idx]}" \
22
+ ':!script/check_cpln_links' '*.md')
23
+
24
+ # Line would become really unwieldly if everything was mushed into the
25
+ # conditional, so let's ignore this check here.
26
+ # shellcheck disable=SC2181
27
+ if [ $? -eq 0 ]; then
28
+ accumulated_results+="$results"
29
+ seen_bad_links_indexes+=("$idx")
30
+ exit_status=1
31
+ fi
32
+ done
33
+
34
+ if [ "$exit_status" -eq 1 ]; then
35
+ echo "${bold}[!] Found the following bad links:${normal}"
36
+ echo ""
37
+ echo "$accumulated_results"
38
+ echo ""
39
+ echo "${bold}[*] Please update accordingly:${normal}"
40
+ for bad_link_index in "${seen_bad_links_indexes[@]}"; do
41
+ echo " ${bad_links[bad_link_index]} -> ${proper_links[bad_link_index]}"
42
+ done
43
+ fi
44
+
45
+ exit "$exit_status"
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+
6
+ old_command_name = ARGV[0]&.downcase
7
+ new_command_name = ARGV[1]&.downcase
8
+
9
+ abort("ERROR: Must provide old and new command names.") unless old_command_name && new_command_name
10
+
11
+ old_file_name = old_command_name.gsub(/[^A-Za-z]/, "_")
12
+ old_file_path = "#{__dir__}/../lib/command/#{old_file_name}.rb"
13
+
14
+ abort("ERROR: Command '#{old_command_name}' does not exist.") unless File.exist?(old_file_path)
15
+
16
+ new_file_name = new_command_name.gsub(/[^A-Za-z]/, "_")
17
+ new_file_path = "#{__dir__}/../lib/command/#{new_file_name}.rb"
18
+
19
+ abort("ERROR: Command '#{new_command_name}' already exists.") if File.exist?(new_file_path)
20
+
21
+ old_class_name = old_file_name.split("_").map(&:capitalize).join
22
+ new_class_name = new_file_name.split("_").map(&:capitalize).join
23
+
24
+ file_data = File.binread(old_file_path)
25
+ file_data.gsub!(old_class_name, new_class_name)
26
+ file_data.gsub!(old_command_name, new_command_name)
27
+ File.binwrite(new_file_path, file_data)
28
+ File.delete(old_file_path)
29
+
30
+ # Add old command name to deprecated commands
31
+ deprecated_commands_file_path = "#{__dir__}/../lib/deprecated_commands.json"
32
+ old_deprecated_commands_data = File.binread(deprecated_commands_file_path)
33
+ deprecated_commands = JSON.parse(old_deprecated_commands_data)
34
+ deprecated_commands = deprecated_commands.to_h do |current_old_command_name, current_new_command_name|
35
+ if current_new_command_name == old_command_name
36
+ [current_old_command_name, new_command_name]
37
+ else
38
+ [current_old_command_name, current_new_command_name]
39
+ end
40
+ end
41
+ deprecated_commands[old_command_name] = new_command_name
42
+ new_deprecated_commands_data = "#{JSON.pretty_generate(deprecated_commands.sort.to_h)}\n"
43
+ File.binwrite(deprecated_commands_file_path, new_deprecated_commands_data)
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/cpflow"
5
+
6
+ commands_str_arr = []
7
+
8
+ commands = Command::Base.all_commands
9
+ commands.keys.sort.each do |command_key|
10
+ command_class = commands[command_key]
11
+
12
+ next if command_class::HIDE
13
+
14
+ name = command_class::NAME
15
+ usage = command_class::USAGE.empty? ? name : command_class::USAGE
16
+ options = command_class::OPTIONS
17
+ long_description = command_class::LONG_DESCRIPTION
18
+ examples = command_class::EXAMPLES
19
+
20
+ command_str = "### `#{name}`\n\n"
21
+ command_str += "#{long_description.strip}\n\n"
22
+
23
+ if examples.empty?
24
+ options_str_arr = []
25
+ options.each do |option|
26
+ next unless option[:params][:required]
27
+
28
+ options_str_arr.push("#{option[:params][:aliases][0]} $#{option[:params][:banner]}")
29
+ end
30
+ options_str = options_str_arr.join(" ")
31
+
32
+ command_str += "```sh\ncpflow #{usage}"
33
+ command_str += " #{options_str}" unless options_str.empty?
34
+ command_str += "\n```"
35
+ else
36
+ command_str += examples.strip
37
+ end
38
+
39
+ commands_str_arr.push(command_str)
40
+ end
41
+
42
+ commands_str = commands_str_arr.join("\n\n")
43
+
44
+ file_path = "#{__dir__}/../docs/commands.md"
45
+ file_data =
46
+ <<~DATA
47
+ <!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
48
+
49
+ ## Common Options
50
+
51
+ ```
52
+ -a XXX, --app XXX app ref on Control Plane (GVC)
53
+ ```
54
+
55
+ This `-a` option is used in most of the commands and will pick all other app configurations from the project-specific
56
+ `.controlplane/controlplane.yml` file.
57
+
58
+ ## Commands
59
+
60
+ #{commands_str}
61
+ DATA
62
+ File.binwrite(file_path, file_data)
data/templates/app.yml ADDED
@@ -0,0 +1,13 @@
1
+ kind: gvc
2
+ name: {{APP_NAME}}
3
+ spec:
4
+ env:
5
+ - name: MEMCACHE_SERVERS
6
+ value: memcached.{{APP_NAME}}.cpln.local
7
+ - name: REDIS_URL
8
+ value: redis://redis.{{APP_NAME}}.cpln.local:6379
9
+ - name: DATABASE_URL
10
+ value: postgres://postgres:password123@postgres.{{APP_NAME}}.cpln.local:5432/{{APP_NAME}}
11
+ staticPlacement:
12
+ locationLinks:
13
+ - {{APP_LOCATION_LINK}}
@@ -0,0 +1,32 @@
1
+ kind: workload
2
+ name: daily-task
3
+ spec:
4
+ # https://docs.controlplane.com/reference/workload#cron-configuration
5
+ type: cron
6
+ job:
7
+ # Run daily job at 2am (see cron docs)
8
+ schedule: 0 2 * * *
9
+ # Never or OnFailure
10
+ restartPolicy: Never
11
+ containers:
12
+ - name: daily-task
13
+ cpu: 50m
14
+ memory: 256Mi
15
+ args:
16
+ - bundle
17
+ - exec
18
+ - rails
19
+ - db:prepare
20
+ inheritEnv: true
21
+ image: {{APP_IMAGE_LINK}}
22
+ defaultOptions:
23
+ autoscaling:
24
+ minScale: 1
25
+ maxScale: 1
26
+ capacityAI: false
27
+ firewallConfig:
28
+ external:
29
+ outboundAllowCIDR:
30
+ - 0.0.0.0/0
31
+ # Identity is used for binding workload to secrets
32
+ identityLink: {{APP_IDENTITY_LINK}}
@@ -0,0 +1,25 @@
1
+ kind: workload
2
+ name: maintenance
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: maintenance
7
+ env:
8
+ - name: PORT
9
+ value: "3000"
10
+ - name: PAGE_URL
11
+ value: ""
12
+ image: "shakacode/maintenance-mode"
13
+ ports:
14
+ - number: 3000
15
+ protocol: http
16
+ defaultOptions:
17
+ autoscaling:
18
+ minScale: 1
19
+ maxScale: 1
20
+ capacityAI: false
21
+ timeoutSeconds: 60
22
+ firewallConfig:
23
+ external:
24
+ inboundAllowCIDR:
25
+ - 0.0.0.0/0
@@ -0,0 +1,24 @@
1
+ kind: workload
2
+ name: memcached
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: memcached
7
+ cpu: 25m
8
+ memory: 32Mi
9
+ args:
10
+ - "-l"
11
+ - 0.0.0.0
12
+ image: "memcached:alpine"
13
+ ports:
14
+ - number: 11211
15
+ protocol: tcp
16
+ defaultOptions:
17
+ autoscaling:
18
+ metric: disabled
19
+ minScale: 1
20
+ maxScale: 1
21
+ capacityAI: false
22
+ firewallConfig:
23
+ internal:
24
+ inboundAllowType: same-gvc
@@ -0,0 +1,32 @@
1
+ kind: workload
2
+ name: postgres
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: postgres
7
+ cpu: 50m
8
+ memory: 200Mi
9
+ env:
10
+ - name: PGUSER
11
+ value: postgres
12
+ - name: POSTGRES_PASSWORD
13
+ value: password123
14
+ - name: POSTGRES_USER
15
+ value: postgres
16
+ image: "postgres:13.8-alpine"
17
+ ports:
18
+ - number: 5432
19
+ protocol: tcp
20
+ volumes:
21
+ - path: /var/lib/postgresql/data
22
+ recoveryPolicy: retain
23
+ uri: "scratch://postgres-vol"
24
+ defaultOptions:
25
+ autoscaling:
26
+ metric: disabled
27
+ minScale: 1
28
+ maxScale: 1
29
+ capacityAI: false
30
+ firewallConfig:
31
+ internal:
32
+ inboundAllowType: same-gvc
@@ -0,0 +1,27 @@
1
+ kind: workload
2
+ name: rails
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: rails
7
+ cpu: 512m
8
+ memory: 1Gi
9
+ inheritEnv: true
10
+ image: {{APP_IMAGE_LINK}}
11
+ ports:
12
+ - number: 3000
13
+ protocol: http
14
+ defaultOptions:
15
+ autoscaling:
16
+ minScale: 1
17
+ maxScale: 1
18
+ capacityAI: false
19
+ timeoutSeconds: 60
20
+ firewallConfig:
21
+ external:
22
+ inboundAllowCIDR:
23
+ - 0.0.0.0/0
24
+ outboundAllowCIDR:
25
+ - 0.0.0.0/0
26
+ # Identity is used for binding workload to secrets
27
+ identityLink: {{APP_IDENTITY_LINK}}
@@ -0,0 +1,21 @@
1
+ kind: workload
2
+ name: redis
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: redis
7
+ cpu: 25m
8
+ memory: 32Mi
9
+ image: "redis:latest"
10
+ ports:
11
+ - number: 6379
12
+ protocol: tcp
13
+ defaultOptions:
14
+ autoscaling:
15
+ metric: disabled
16
+ minScale: 1
17
+ maxScale: 1
18
+ capacityAI: false
19
+ firewallConfig:
20
+ internal:
21
+ inboundAllowType: same-gvc
@@ -0,0 +1,37 @@
1
+ kind: volumeset
2
+ name: redis-data
3
+ spec:
4
+ fileSystemType: ext4
5
+ initialCapacity: 10
6
+ performanceClass: general-purpose-ssd
7
+ ---
8
+ kind: workload
9
+ name: redis2
10
+ spec:
11
+ type: stateful
12
+ containers:
13
+ - name: redis
14
+ args:
15
+ - '--appendonly'
16
+ - 'yes'
17
+ - '--maxmemory'
18
+ - 25mb
19
+ cpu: 25m
20
+ memory: 32Mi
21
+ image: "redis:latest"
22
+ ports:
23
+ - number: 6379
24
+ protocol: tcp
25
+ volumes:
26
+ - path: /data
27
+ recoveryPolicy: retain
28
+ uri: cpln://volumeset/redis-data
29
+ defaultOptions:
30
+ autoscaling:
31
+ metric: disabled
32
+ minScale: 1
33
+ maxScale: 1
34
+ capacityAI: false
35
+ firewallConfig:
36
+ internal:
37
+ inboundAllowType: same-gvc
@@ -0,0 +1,38 @@
1
+ kind: workload
2
+ name: sidekiq
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: sidekiq
7
+ cpu: 50m
8
+ memory: 256Mi
9
+ args:
10
+ - bundle
11
+ - exec
12
+ - sidekiq
13
+ - "-C"
14
+ - config/sidekiq.yml
15
+ inheritEnv: true
16
+ image: {{APP_IMAGE_LINK}}
17
+ ports:
18
+ - number: 7433
19
+ protocol: http
20
+ lifecycle:
21
+ preStop:
22
+ exec:
23
+ command:
24
+ - pkill
25
+ - "-TSTP"
26
+ - "-f"
27
+ - ^sidekiq\s
28
+ defaultOptions:
29
+ autoscaling:
30
+ minScale: 1
31
+ maxScale: 1
32
+ capacityAI: false
33
+ firewallConfig:
34
+ external:
35
+ outboundAllowCIDR:
36
+ - 0.0.0.0/0
37
+ # Identity is used for binding workload to secrets
38
+ identityLink: {{APP_IDENTITY_LINK}}