uffizzi-cli 0.8.0 → 0.10.1

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/config/uffizzi.rb +1 -1
  3. data/lib/uffizzi/auth_helper.rb +5 -0
  4. data/lib/uffizzi/cli/config.rb +1 -1
  5. data/lib/uffizzi/cli/connect.rb +54 -31
  6. data/lib/uffizzi/cli/disconnect.rb +1 -1
  7. data/lib/uffizzi/cli/login.rb +76 -2
  8. data/lib/uffizzi/cli/logout.rb +1 -1
  9. data/lib/uffizzi/cli/preview.rb +4 -1
  10. data/lib/uffizzi/cli/project.rb +82 -2
  11. data/lib/uffizzi/cli.rb +3 -1
  12. data/lib/uffizzi/clients/api/api_client.rb +15 -1
  13. data/lib/uffizzi/config_file.rb +8 -18
  14. data/lib/uffizzi/helpers/project_helper.rb +22 -0
  15. data/lib/uffizzi/promt.rb +21 -0
  16. data/lib/uffizzi/services/project_service.rb +40 -0
  17. data/lib/uffizzi/version.rb +1 -1
  18. data/lib/uffizzi.rb +5 -0
  19. data/man/uffizzi +2 -2
  20. data/man/uffizzi-config +2 -2
  21. data/man/uffizzi-config.ronn +1 -1
  22. data/man/uffizzi-connect +2 -2
  23. data/man/uffizzi-connect-acr +2 -2
  24. data/man/uffizzi-connect-acr.ronn +1 -1
  25. data/man/uffizzi-connect-docker-hub +2 -2
  26. data/man/uffizzi-connect-docker-hub.ronn +1 -1
  27. data/man/uffizzi-connect-ecr +2 -2
  28. data/man/uffizzi-connect-ecr.ronn +1 -1
  29. data/man/uffizzi-connect-gcr +2 -2
  30. data/man/uffizzi-connect-gcr.ronn +1 -1
  31. data/man/uffizzi-connect-ghcr +2 -2
  32. data/man/uffizzi-connect-ghcr.ronn +1 -1
  33. data/man/uffizzi-connect.ronn +1 -1
  34. data/man/uffizzi-disconnect +2 -2
  35. data/man/uffizzi-disconnect.ronn +1 -1
  36. data/man/uffizzi-login +2 -2
  37. data/man/uffizzi-login.ronn +1 -1
  38. data/man/uffizzi-logout +2 -2
  39. data/man/uffizzi-logout.ronn +1 -1
  40. data/man/uffizzi-preview +2 -2
  41. data/man/uffizzi-preview-create +2 -2
  42. data/man/uffizzi-preview-create.ronn +1 -1
  43. data/man/uffizzi-preview-delete +2 -2
  44. data/man/uffizzi-preview-delete.ronn +1 -1
  45. data/man/uffizzi-preview-describe +2 -2
  46. data/man/uffizzi-preview-describe.ronn +1 -1
  47. data/man/uffizzi-preview-events +2 -2
  48. data/man/uffizzi-preview-events.ronn +1 -1
  49. data/man/uffizzi-preview-list +2 -2
  50. data/man/uffizzi-preview-list.ronn +1 -1
  51. data/man/uffizzi-preview-service-list +2 -2
  52. data/man/uffizzi-preview-service-list.ronn +1 -1
  53. data/man/uffizzi-preview-service-logs +2 -2
  54. data/man/uffizzi-preview-service-logs.ronn +1 -1
  55. data/man/uffizzi-preview-update +20 -12
  56. data/man/uffizzi-preview-update.ronn +6 -6
  57. data/man/uffizzi-preview.ronn +1 -1
  58. data/man/uffizzi-preview_service_logs +2 -2
  59. data/man/uffizzi-preview_service_logs.ronn +1 -1
  60. data/man/uffizzi-project +2 -2
  61. data/man/uffizzi-project-compose +2 -2
  62. data/man/uffizzi-project-compose-describe +2 -2
  63. data/man/uffizzi-project-compose-describe.ronn +1 -1
  64. data/man/uffizzi-project-compose-set +2 -2
  65. data/man/uffizzi-project-compose-set.ronn +1 -1
  66. data/man/uffizzi-project-compose-unset +2 -2
  67. data/man/uffizzi-project-compose-unset.ronn +1 -1
  68. data/man/uffizzi-project-compose.ronn +1 -1
  69. data/man/uffizzi-project-create +50 -0
  70. data/man/uffizzi-project-create.ronn +41 -0
  71. data/man/uffizzi-project-delete +32 -0
  72. data/man/uffizzi-project-delete.ronn +24 -0
  73. data/man/uffizzi-project-describe +43 -0
  74. data/man/uffizzi-project-describe.ronn +34 -0
  75. data/man/uffizzi-project-secret +2 -2
  76. data/man/uffizzi-project-secret-create +2 -2
  77. data/man/uffizzi-project-secret-create.ronn +1 -1
  78. data/man/uffizzi-project-secret-delete +2 -2
  79. data/man/uffizzi-project-secret-delete.ronn +1 -1
  80. data/man/uffizzi-project-secret-list +2 -2
  81. data/man/uffizzi-project-secret-list.ronn +1 -1
  82. data/man/uffizzi-project-secret.ronn +1 -1
  83. data/man/uffizzi-project-set-default +2 -2
  84. data/man/uffizzi-project-set-default.ronn +1 -1
  85. data/man/uffizzi-project.ronn +1 -1
  86. data/man/uffizzi.html +1 -1
  87. data/man/uffizzi.ronn +1 -1
  88. metadata +25 -22
  89. data/man/uffizzi-config.html +0 -144
  90. data/man/uffizzi-login.html +0 -113
  91. data/man/uffizzi-logout.html +0 -102
  92. data/man/uffizzi-preview-create.html +0 -128
  93. data/man/uffizzi-preview-delete.html +0 -115
  94. data/man/uffizzi-preview-describe.html +0 -116
  95. data/man/uffizzi-preview-events.html +0 -110
  96. data/man/uffizzi-preview-list.html +0 -110
  97. data/man/uffizzi-preview.html +0 -120
  98. data/man/uffizzi-preview_service_logs.html +0 -142
  99. data/man/uffizzi-project-compose-describe.html +0 -118
  100. data/man/uffizzi-project-compose-set.html +0 -149
  101. data/man/uffizzi-project-compose-unset.html +0 -116
  102. data/man/uffizzi-project-compose.html +0 -123
  103. data/man/uffizzi-project-secret-create.html +0 -110
  104. data/man/uffizzi-project-secret-delete.html +0 -110
  105. data/man/uffizzi-project-secret-list.html +0 -110
  106. data/man/uffizzi-project-secret.html +0 -119
  107. data/man/uffizzi-project-set-default.html +0 -111
  108. data/man/uffizzi-project.html +0 -128
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17ac6475e11de6c24b044f64e19541587683aaad4bd3b72cafbc5160b92364a8
4
- data.tar.gz: 228b5a14b359af42f9698eba6bd98c94155dfd05791b55378dfc99cf6ffe365d
3
+ metadata.gz: aad4e300e1250ee113d65b436b58045b368c707c5840bc26375918a33c894a5a
4
+ data.tar.gz: 711dc99c47c9d073405da14ce9409e2bfe99eaca6028143da0eccb7961ba8426
5
5
  SHA512:
6
- metadata.gz: 4d9e8d2f99a297d2fac8452bc03e180c71f7b1e05d71ac333704b2e1649fffdfd2bb4f6b86ffb0d186e441629cb809b2d5c450984b2a36af49080b9b08fab700
7
- data.tar.gz: ce3cdb192caa7ab39a7edec3b5d01788505cae7a4a2d5f440b99ef203f86f18d67c22ec5c07ef67603dc6ebb35871857f3f536ac71720b4634c46579f8e771d6
6
+ metadata.gz: aebda9657557027599278995e7a97bef373b118cdf4a8055d17706f418ffbd7b6a126703dfa5901833ab2c7198f91d91c5f07da5c836b0b602382a6fe860c235
7
+ data.tar.gz: 951c78b92cbee8f70a5b41231fa56cd9ea0cf3ec278c174b703a065567eb1d4ddfca18484396c5db208ea85d6fd1ad8f59c8c9a8a8c29ebbbc5fbe8b308e0ff8
data/config/uffizzi.rb CHANGED
@@ -18,7 +18,7 @@ module Uffizzi
18
18
  azure: 'UffizziCore::Credential::Azure',
19
19
  google: 'UffizziCore::Credential::Google',
20
20
  amazon: 'UffizziCore::Credential::Amazon',
21
- github_container_registry: 'UffizziCore::Credential::GithubContainerRegistry',
21
+ github_registry: 'UffizziCore::Credential::GithubContainerRegistry',
22
22
  }
23
23
  config.default_server = 'app.uffizzi.com'
24
24
  end
@@ -9,6 +9,11 @@ module Uffizzi
9
9
  ConfigFile.option_has_value?(:cookie) &&
10
10
  ConfigFile.option_has_value?(:server)
11
11
  end
12
+
13
+ def sign_out
14
+ Uffizzi::ConfigFile.unset_option(:cookie)
15
+ Uffizzi::ConfigFile.unset_option(:account_id)
16
+ end
12
17
  end
13
18
  end
14
19
  end
@@ -58,7 +58,7 @@ module Uffizzi
58
58
  \nUffizzi API service and manage previews.\n")
59
59
  server = Uffizzi.ui.ask('Server: ', default: Uffizzi.configuration.default_server.to_s)
60
60
  username = Uffizzi.ui.ask('Username: ')
61
- project = Uffizzi.ui.ask('Project: ')
61
+ project = Uffizzi.ui.ask('Project: ', default: 'default')
62
62
  ConfigFile.delete
63
63
  ConfigFile.write_option(:server, server)
64
64
  ConfigFile.write_option(:username, username)
@@ -18,12 +18,14 @@ module Uffizzi
18
18
  end
19
19
 
20
20
  desc 'docker-hub', 'Connect to Docker Hub (hub.docker.com)'
21
+ method_option :skip_raise_existence_error, type: :boolean, default: false,
22
+ desc: 'Skip raising an error within check the credential'
21
23
  def docker_hub
22
24
  type = Uffizzi.configuration.credential_types[:dockerhub]
23
- check_credential_existance(type, 'docker-hub')
25
+ check_credential_existence(type, 'docker-hub')
24
26
 
25
- username = Uffizzi.ui.ask('Username: ')
26
- password = Uffizzi.ui.ask('Password: ', echo: false)
27
+ username = ENV['DOCKERHUB_USERNAME'] || Uffizzi.ui.ask('Username: ')
28
+ password = ENV['DOCKERHUB_PASSWORD'] || Uffizzi.ui.ask('Password: ', echo: false)
27
29
 
28
30
  params = {
29
31
  username: username,
@@ -42,18 +44,20 @@ module Uffizzi
42
44
  end
43
45
 
44
46
  desc 'acr', 'Connect to Azure Container Registry (azurecr.io)'
47
+ method_option :skip_raise_existence_error, type: :boolean, default: false,
48
+ desc: 'Skip raising an error within check the credential'
45
49
  def acr
46
50
  type = Uffizzi.configuration.credential_types[:azure]
47
- check_credential_existance(type, 'acr')
51
+ check_credential_existence(type, 'acr')
48
52
 
49
- registry_url = prepare_registry_url(Uffizzi.ui.ask('Registry Domain: '))
50
- username = Uffizzi.ui.ask('Docker ID: ')
51
- password = Uffizzi.ui.ask('Password/Access Token: ', echo: false)
53
+ registry_url = ENV['ACR_REGISTRY_URL'] || Uffizzi.ui.ask('Registry Domain: ')
54
+ username = ENV['ACR_USERNAME'] || Uffizzi.ui.ask('Docker ID: ')
55
+ password = ENV['ACR_PASSWORD'] || Uffizzi.ui.ask('Password/Access Token: ', echo: false)
52
56
 
53
57
  params = {
54
58
  username: username,
55
59
  password: password,
56
- registry_url: registry_url,
60
+ registry_url: prepare_registry_url(registry_url),
57
61
  type: type,
58
62
  }
59
63
 
@@ -68,18 +72,20 @@ module Uffizzi
68
72
  end
69
73
 
70
74
  desc 'ecr', 'Connect to Amazon Elastic Container Registry'
75
+ method_option :skip_raise_existence_error, type: :boolean, default: false,
76
+ desc: 'Skip raising an error within check the credential'
71
77
  def ecr
72
78
  type = Uffizzi.configuration.credential_types[:amazon]
73
- check_credential_existance(type, 'ecr')
79
+ check_credential_existence(type, 'ecr')
74
80
 
75
- registry_url = prepare_registry_url(Uffizzi.ui.ask('Registry Domain: '))
76
- username = Uffizzi.ui.ask('Access key ID: ')
77
- password = Uffizzi.ui.ask('Secret access key: ', echo: false)
81
+ registry_url = ENV['AWS_REGISTRY_URL'] || Uffizzi.ui.ask('Registry Domain: ')
82
+ access_key = ENV['AWS_ACCESS_KEY_ID'] || Uffizzi.ui.ask('Access key ID: ')
83
+ secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] || Uffizzi.ui.ask('Secret access key: ', echo: false)
78
84
 
79
85
  params = {
80
- username: username,
81
- password: password,
82
- registry_url: registry_url,
86
+ username: access_key,
87
+ password: secret_access_key,
88
+ registry_url: prepare_registry_url(registry_url),
83
89
  type: type,
84
90
  }
85
91
 
@@ -94,17 +100,13 @@ module Uffizzi
94
100
  end
95
101
 
96
102
  desc 'gcr', 'Connect to Google Container Registry (gcr.io)'
103
+ method_option :skip_raise_existence_error, type: :boolean, default: false,
104
+ desc: 'Skip raising an error within check the credential'
97
105
  def gcr(credential_file_path = nil)
98
106
  type = Uffizzi.configuration.credential_types[:google]
99
- check_credential_existance(type, 'gcr')
107
+ check_credential_existence(type, 'gcr')
100
108
 
101
- return Uffizzi.ui.say('Path to google service account key file wasn\'t specified.') if credential_file_path.nil?
102
-
103
- begin
104
- credential_content = File.read(credential_file_path)
105
- rescue Errno::ENOENT => e
106
- return Uffizzi.ui.say(e)
107
- end
109
+ credential_content = google_service_account_content(credential_file_path)
108
110
 
109
111
  params = {
110
112
  password: credential_content,
@@ -122,12 +124,14 @@ module Uffizzi
122
124
  end
123
125
 
124
126
  desc 'ghcr', 'Connect to GitHub Container Registry (ghcr.io)'
127
+ method_option :skip_raise_existence_error, type: :boolean, default: false,
128
+ desc: 'Skip raising an error within check the credential'
125
129
  def ghcr
126
- type = Uffizzi.configuration.credential_types[:github_container_registry]
127
- check_credential_existance(type, 'gchr')
130
+ type = Uffizzi.configuration.credential_types[:github_registry]
131
+ check_credential_existence(type, 'ghcr')
128
132
 
129
- username = Uffizzi.ui.ask('Github Username: ')
130
- password = Uffizzi.ui.ask('Access Token: ', echo: false)
133
+ username = ENV['GITHUB_USERNAME'] || Uffizzi.ui.ask('Github Username: ')
134
+ password = ENV['GITHUB_ACCESS_TOKEN'] || Uffizzi.ui.ask('Access Token: ', echo: false)
131
135
 
132
136
  params = {
133
137
  username: username,
@@ -160,14 +164,19 @@ module Uffizzi
160
164
  Uffizzi.ui.say("Successfully connected to #{connection_name}")
161
165
  end
162
166
 
163
- def check_credential_existance(type, connection_name)
167
+ def check_credential_existence(type, connection_name)
164
168
  server = ConfigFile.read_option(:server)
165
169
  response = check_credential(server, type)
166
170
  return if ResponseHelper.ok?(response)
167
171
 
168
- message = "Credentials of type #{connection_name} already exist for this account. " \
169
- "To remove them, run $ uffizzi disconnect #{connection_name}."
170
- raise Uffizzi::Error.new(message)
172
+ if options.skip_raise_existence_error?
173
+ Uffizzi.ui.say("Credentials of type #{connection_name} already exist for this account.")
174
+ exit(true)
175
+ else
176
+ message = "Credentials of type #{connection_name} already exist for this account. " \
177
+ "To remove them, run $ uffizzi disconnect #{connection_name}."
178
+ raise Uffizzi::Error.new(message)
179
+ end
171
180
  end
172
181
 
173
182
  def handle_list_credentials_success(response)
@@ -189,5 +198,19 @@ module Uffizzi
189
198
 
190
199
  map[credential]
191
200
  end
201
+
202
+ def google_service_account_content(credential_file_path = nil)
203
+ return ENV['GCLOUD_SERVICE_KEY'] if ENV['GCLOUD_SERVICE_KEY']
204
+
205
+ return Uffizzi.ui.say('Path to google service account key file wasn\'t specified.') if credential_file_path.nil?
206
+
207
+ begin
208
+ credential_content = File.read(credential_file_path)
209
+ rescue Errno::ENOENT => e
210
+ raise Uffizzi::Error.new(e.message)
211
+ end
212
+
213
+ credential_content
214
+ end
192
215
  end
193
216
  end
@@ -17,7 +17,7 @@ module Uffizzi
17
17
  when 'gcr'
18
18
  Uffizzi.configuration.credential_types[:google]
19
19
  when 'ghcr'
20
- Uffizzi.configuration.credential_types[:github_container_registry]
20
+ Uffizzi.configuration.credential_types[:github_registry]
21
21
  else
22
22
  raise Uffizzi::Error.new('Unsupported credential type.')
23
23
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'uffizzi'
4
4
  require 'uffizzi/response_helper'
5
+ require 'uffizzi/helpers/project_helper'
5
6
  require 'uffizzi/clients/api/api_client'
7
+ require 'tty-prompt'
6
8
 
7
9
  module Uffizzi
8
10
  class Cli::Login
@@ -15,6 +17,7 @@ module Uffizzi
15
17
  def run
16
18
  Uffizzi.ui.say('Login to Uffizzi to view and manage your previews.')
17
19
  server = set_server
20
+
18
21
  username = set_username
19
22
  password = set_password
20
23
  params = prepare_request_params(username, password)
@@ -30,12 +33,12 @@ module Uffizzi
30
33
  private
31
34
 
32
35
  def set_server
33
- config_server = ConfigFile.exists? && ConfigFile.option_has_value?(:server) ? ConfigFile.read_option(:server) : nil
36
+ config_server = ConfigFile.exists? ? read_option_from_config(:server) : nil
34
37
  @options[:server] || config_server || Uffizzi.ui.ask('Server: ')
35
38
  end
36
39
 
37
40
  def set_username
38
- config_username = ConfigFile.exists? && ConfigFile.option_has_value?(:username) ? ConfigFile.read_option(:username) : nil
41
+ config_username = ConfigFile.exists? ? read_option_from_config(:username) : nil
39
42
  @options[:username] || config_username || Uffizzi.ui.ask('Username: ')
40
43
  end
41
44
 
@@ -43,6 +46,10 @@ module Uffizzi
43
46
  ENV['UFFIZZI_PASSWORD'] || Uffizzi.ui.ask('Password: ', echo: false)
44
47
  end
45
48
 
49
+ def read_option_from_config(option)
50
+ ConfigFile.option_has_value?(option) ? ConfigFile.read_option(option) : nil
51
+ end
52
+
46
53
  def prepare_request_params(username, password)
47
54
  {
48
55
  user: {
@@ -60,10 +67,77 @@ module Uffizzi
60
67
  ConfigFile.write_option(:username, username)
61
68
  ConfigFile.write_option(:cookie, response[:headers])
62
69
  ConfigFile.write_option(:account_id, account[:id])
70
+
71
+ default_project = ConfigFile.read_option(:project)
72
+ return unless default_project
73
+
74
+ check_default_project(default_project, server)
63
75
  end
64
76
 
65
77
  def account_valid?(account)
66
78
  account[:state] == 'active'
67
79
  end
80
+
81
+ def check_default_project(default_project, server)
82
+ check_project_response = fetch_projects(server)
83
+ return ResponseHelper.handle_failed_response(check_project_response) unless ResponseHelper.ok?(check_project_response)
84
+
85
+ projects = check_project_response[:body][:projects]
86
+ slugs = projects.map { |project| project[:slug] }
87
+ return if slugs.include?(default_project)
88
+
89
+ question = "Project '#{default_project}' does not exist. Select one of the following projects or create a new project:"
90
+ choices = projects.map do |project|
91
+ { name: project[:name], value: project[:slug] }
92
+ end
93
+ all_choices = choices + [{ name: 'Create a new project', value: nil }]
94
+ answer = Uffizzi.prompt.select(question, all_choices)
95
+ return ConfigFile.write_option(:project, answer) if answer
96
+
97
+ create_new_project(server)
98
+ end
99
+
100
+ def create_new_project(server)
101
+ project_name = Uffizzi.prompt.ask('Project name: ', required: true)
102
+ generated_slug = Uffizzi::ProjectHelper.generate_slug(project_name)
103
+ project_slug = Uffizzi.prompt.ask('Project slug: ', default: generated_slug)
104
+ raise Uffizzi::Error.new('Slug must not content spaces or special characters') unless project_slug.match?(/^[a-zA-Z0-9\-_]+\Z/i)
105
+
106
+ project_description = Uffizzi.prompt.ask('Project desciption: ')
107
+
108
+ params = {
109
+ project: {
110
+ name: project_name.strip,
111
+ slug: project_slug,
112
+ description: project_description,
113
+ },
114
+ }
115
+
116
+ response = create_project(server, params)
117
+
118
+ if ResponseHelper.created?(response)
119
+ handle_create_project_succeess(response)
120
+ else
121
+ handle_create_project_failed(response)
122
+ end
123
+ end
124
+
125
+ def handle_create_project_failed(response)
126
+ name_error = response[:body][:errors][:name].first
127
+ name_already_exists = name_error && name_error.first == 'Name already exists'
128
+ message = "Project with name #{project_name} already exists. " \
129
+ 'Please run $ uffizzi config to set it as a default project'
130
+ raise Uffizzi::Error.new(message) if name_already_exists
131
+
132
+ ResponseHelper.handle_failed_response(response)
133
+ end
134
+
135
+ def handle_create_project_succeess(response)
136
+ project = response[:body][:project]
137
+
138
+ ConfigFile.write_option(:project, project[:slug])
139
+
140
+ Uffizzi.ui.say("Project #{project[:name]} was successfully created")
141
+ end
68
142
  end
69
143
  end
@@ -17,7 +17,7 @@ module Uffizzi
17
17
  server = ConfigFile.read_option(:server)
18
18
  destroy_session(server)
19
19
 
20
- ConfigFile.delete
20
+ AuthHelper.sign_out
21
21
  Uffizzi.ui.say('You have been successfully logged out')
22
22
  end
23
23
  end
@@ -119,6 +119,9 @@ module Uffizzi
119
119
  success = PreviewService.run_containers_deploy(project_slug, deployment)
120
120
 
121
121
  display_deployment_data(deployment, success)
122
+ rescue SystemExit, Interrupt, SocketError
123
+ deployment_id = response[:body][:deployment][:id]
124
+ handle_preview_interruption(deployment_id, ConfigFile.read_option(:server), project_slug)
122
125
  end
123
126
 
124
127
  def handle_events_command(deployment_name, project_slug)
@@ -204,7 +207,7 @@ module Uffizzi
204
207
 
205
208
  def prepare_params(file_path)
206
209
  begin
207
- compose_file_data = File.read(file_path)
210
+ compose_file_data = EnvVariablesService.substitute_env_variables(File.read(file_path))
208
211
  rescue Errno::ENOENT => e
209
212
  raise Uffizzi::Error.new(e.message)
210
213
  end
@@ -3,6 +3,8 @@
3
3
  require 'uffizzi'
4
4
  require 'uffizzi/auth_helper'
5
5
  require 'uffizzi/response_helper'
6
+ require 'uffizzi/helpers/project_helper'
7
+ require 'uffizzi/services/project_service'
6
8
 
7
9
  module Uffizzi
8
10
  class Cli::Project < Thor
@@ -27,8 +29,27 @@ module Uffizzi
27
29
  run('set-default', project_slug: project_slug)
28
30
  end
29
31
 
32
+ desc 'describe [PROJECT_SLUG]', 'describe'
33
+ method_option :output, type: :string, aliases: '-o', enum: ['json', 'pretty'], default: 'json'
34
+ def describe(project_slug)
35
+ run('describe', project_slug: project_slug)
36
+ end
37
+
30
38
  map('set-default' => :set_default)
31
39
 
40
+ method_option :name, required: true
41
+ method_option :slug, default: ''
42
+ method_option :description, required: false
43
+ desc 'create', 'Create a project'
44
+ def create
45
+ run('create')
46
+ end
47
+
48
+ desc 'delete [PROJECT_SLUG]', 'Delete a project'
49
+ def delete(project_slug)
50
+ run('delete', project_slug: project_slug)
51
+ end
52
+
32
53
  private
33
54
 
34
55
  def run(command, project_slug: nil)
@@ -39,21 +60,74 @@ module Uffizzi
39
60
  handle_list_command
40
61
  when 'set-default'
41
62
  handle_set_default_command(project_slug)
63
+ when 'create'
64
+ handle_create_command
65
+ when 'delete'
66
+ handle_delete_command(project_slug)
67
+ when 'describe'
68
+ handle_describe_command(project_slug)
42
69
  end
43
70
  end
44
71
 
72
+ def handle_describe_command(project_slug)
73
+ response = describe_project(ConfigFile.read_option(:server), project_slug)
74
+
75
+ if ResponseHelper.ok?(response)
76
+ handle_succeed_describe_response(response)
77
+ else
78
+ ResponseHelper.handle_failed_response(response)
79
+ end
80
+ end
81
+
82
+ def handle_succeed_describe_response(response)
83
+ project = response[:body][:project]
84
+ project[:deployments] = ProjectService.select_active_deployments(project)
85
+ ProjectService.describe_project(project, options[:output])
86
+ end
87
+
45
88
  def handle_list_command
46
89
  server = ConfigFile.read_option(:server)
47
90
  response = fetch_projects(server)
48
91
 
49
92
  if ResponseHelper.ok?(response)
50
- handle_succeed_response(response)
93
+ handle_list_success_response(response)
51
94
  else
52
95
  ResponseHelper.handle_failed_response(response)
53
96
  end
54
97
  end
55
98
 
56
- def handle_succeed_response(response)
99
+ def handle_create_command
100
+ name = options[:name]
101
+ slug = options[:slug].empty? ? Uffizzi::ProjectHelper.generate_slug(name) : options[:slug]
102
+ raise Uffizzi::Error.new('Slug must not content spaces or special characters') unless slug.match?(/^[a-zA-Z0-9\-_]+\Z/i)
103
+
104
+ server = ConfigFile.read_option(:server)
105
+ params = {
106
+ name: name,
107
+ description: options[:description],
108
+ slug: slug,
109
+ }
110
+ response = create_project(server, params)
111
+
112
+ if ResponseHelper.created?(response)
113
+ handle_create_success_response(response)
114
+ else
115
+ ResponseHelper.handle_failed_response(response)
116
+ end
117
+ end
118
+
119
+ def handle_delete_command(project_slug)
120
+ server = ConfigFile.read_option(:server)
121
+ response = delete_project(server, project_slug)
122
+
123
+ if ResponseHelper.no_content?(response)
124
+ Uffizzi.ui.say("Project with slug #{project_slug} was deleted successfully")
125
+ else
126
+ ResponseHelper.handle_failed_response(response)
127
+ end
128
+ end
129
+
130
+ def handle_list_success_response(response)
57
131
  projects = response[:body][:projects]
58
132
  return Uffizzi.ui.say('No projects related to this email') if projects.empty?
59
133
 
@@ -76,6 +150,12 @@ module Uffizzi
76
150
  Uffizzi.ui.say('Default project has been updated.')
77
151
  end
78
152
 
153
+ def handle_create_success_response(response)
154
+ project_name = response[:body][:project][:name]
155
+
156
+ Uffizzi.ui.say("Project #{project_name} was successfully created")
157
+ end
158
+
79
159
  def print_projects(projects)
80
160
  projects_list = projects.reduce('') do |acc, project|
81
161
  "#{acc}#{project[:slug]}\n"
data/lib/uffizzi/cli.rb CHANGED
@@ -62,8 +62,10 @@ module Uffizzi
62
62
  return Common.show_manual(filename(args)) if show_help?(args, opts)
63
63
 
64
64
  super
65
- rescue SystemExit, Interrupt, SocketError
65
+ rescue Interrupt
66
66
  raise Uffizzi::Error.new('The command was interrupted')
67
+ rescue SocketError
68
+ raise Uffizzi::Error.new('A request was not sent to Uffizzi app')
67
69
  end
68
70
 
69
71
  private
@@ -49,6 +49,20 @@ module ApiClient
49
49
  build_response(response)
50
50
  end
51
51
 
52
+ def create_project(server, params)
53
+ uri = projects_uri(server)
54
+ response = http_client.make_post_request(uri, params)
55
+
56
+ build_response(response)
57
+ end
58
+
59
+ def delete_project(server, project_slug)
60
+ uri = project_uri(server, project_slug)
61
+ response = http_client.make_delete_request(uri)
62
+
63
+ build_response(response)
64
+ end
65
+
52
66
  def create_credential(server, params)
53
67
  uri = credentials_uri(server)
54
68
  response = http_client.make_post_request(uri, params)
@@ -221,7 +235,7 @@ module ApiClient
221
235
 
222
236
  cookie_content = cookies.first
223
237
  cookie = cookie_content.split(';').first
224
- Uffizzi::ConfigFile.rewrite_cookie(cookie) if Uffizzi::ConfigFile.exists?
238
+ Uffizzi::ConfigFile.rewrite_cookie(cookie) if Uffizzi::ConfigFile.exists? && Uffizzi::ConfigFile.option_has_value?(:cookie)
225
239
  http_client.auth_cookie = cookie
226
240
 
227
241
  cookie
@@ -5,20 +5,15 @@ require 'fileutils'
5
5
 
6
6
  module Uffizzi
7
7
  class ConfigFile
8
- CONFIG_PATH = "#{Dir.home}/.config/uffizzi/config_default.json"
8
+ CONFIG_PATH = "#{Dir.home}/.config/uffizzi/config_default"
9
9
 
10
10
  class << self
11
11
  def config_path
12
12
  CONFIG_PATH
13
13
  end
14
14
 
15
- def create(account_id, cookie, server)
16
- data = prepare_config_data(account_id, cookie, server)
17
- data.each_pair { |key, value| write_option(key, value) }
18
- end
19
-
20
15
  def delete
21
- File.delete(config_path) if exists?
16
+ File.truncate(config_path, 0) if exists?
22
17
  end
23
18
 
24
19
  def exists?
@@ -73,8 +68,6 @@ module Uffizzi
73
68
  data
74
69
  end
75
70
 
76
- private
77
-
78
71
  def option_exists?(option)
79
72
  data = read
80
73
  return false unless data.is_a?(Hash)
@@ -82,6 +75,8 @@ module Uffizzi
82
75
  data.key?(option)
83
76
  end
84
77
 
78
+ private
79
+
85
80
  def read
86
81
  data = File.read(config_path)
87
82
  options = data.split("\n")
@@ -90,7 +85,10 @@ module Uffizzi
90
85
  acc.merge({ key.strip.to_sym => value.strip })
91
86
  end
92
87
  rescue Errno::ENOENT => e
93
- Uffizzi.ui.say(e)
88
+ file_path = e.message.split(' ').last
89
+ message = "Configuration file not found: #{file_path}\n" \
90
+ 'To configure the uffizzi CLI interactively, run $ uffizzi config'
91
+ raise Uffizzi::Error.new(message)
94
92
  end
95
93
 
96
94
  def write(data)
@@ -107,14 +105,6 @@ module Uffizzi
107
105
  end
108
106
  end
109
107
 
110
- def prepare_config_data(account_id, cookie, server)
111
- {
112
- account_id: account_id,
113
- server: server,
114
- cookie: cookie,
115
- }
116
- end
117
-
118
108
  def create_file
119
109
  dir = File.dirname(config_path)
120
110
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uffizzi
4
+ module ProjectHelper
5
+ SLUG_ENDING_LENGTH = 6
6
+ class << self
7
+ def generate_slug(name)
8
+ formatted_name = name.downcase.gsub(/ /, '-').gsub(/[^\w-]+/, '')
9
+ slug_ending = generate_random_string(SLUG_ENDING_LENGTH)
10
+
11
+ "#{formatted_name}-#{slug_ending}"
12
+ end
13
+
14
+ private
15
+
16
+ def generate_random_string(length)
17
+ hexatridecimal_base = 36
18
+ rand(hexatridecimal_base**length).to_s(hexatridecimal_base)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-prompt'
4
+
5
+ module Uffizzi
6
+ module UI
7
+ class Prompt
8
+ def initialize
9
+ @prompt = TTY::Prompt.new
10
+ end
11
+
12
+ def select(question, choices)
13
+ @prompt.select(question, choices)
14
+ end
15
+
16
+ def ask(message, **args)
17
+ @prompt.ask(message, **args)
18
+ end
19
+ end
20
+ end
21
+ end