cpflow 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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}}