uffizzi-cli 2.3.3 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27943054f960693c2d1b8539a823f15f28e0f12d5e4763440cba3ee2f03f3412
4
- data.tar.gz: 352f258d3a5a3d40b21e4d2c10d01028c4e16ec12a8902a3995cb3a64b687d1f
3
+ metadata.gz: 1267ece15e161c558b60dd16dc1d6562417f1555eefb3fcf2f65cb2f32223295
4
+ data.tar.gz: 4d5a80140a3436822c56a5cb1f3930ccb18082d93359c5cda3cf2343348cc282
5
5
  SHA512:
6
- metadata.gz: 025a64d8bc2e14ad93b21592d0b1ad5460e960019ef119fbf5afc78622317c0fb600f16fe4132f3bab6fb9c652176e4bae6a54ab49617db55e94f5c860238a1f
7
- data.tar.gz: '09b7dfaab197c2aefe59cb187b570ce818d1fe2952f6545b3fccc432f05a4bdc75151f4f589d3d3abc65d8325f56dc3156a335ae41231e5d02815770b976e48d'
6
+ metadata.gz: f2092483072296f1fd039d8acf049b53f4ef2b799c09063eb7ce0251f747837865c09f5d222f5e011c65826991fc4eb35cc7cd85d08dfaa8b3418c7270a6bb06
7
+ data.tar.gz: 20a062cf54e7f5b9a04d618e0066cacbf837254bb5f5c6abc7e26a0ed959a814012cceb8af3bfbefb675ad2d431fee6fa0c250229e57a4480a838929b9ff2fa1
data/README.md CHANGED
@@ -1,178 +1,10 @@
1
1
  # Uffizzi CLI
2
2
 
3
- A command-line interace (CLI) for [Uffizzi App](https://github.com/UffizziCloud/uffizzi_app)
4
-
5
- ## Uffizzi Overview
6
-
7
- Uffizzi is an open-source engine for creating lightweight, ephemeral test environments for APIs and full-stack applications. Uffizzi enables teams to preview new features before merging and to mitigate the risk of introducing regressions into a codebase. Each preview gets a shareable URL that's updated when you push new commits or image tags, so teams can provide continual feedback during the development/QA process. Previews can be configured to expire or be destroyed when a pull request is closed, so environments exist only as long as they are needed. Uffizzi also helps deconflict shared development environments since previews are deployed as isolated namespaces—there is no risk of clobbering another developer's preview.
8
-
9
- While Uffizzi depends on Kubernetes, it does not require end-users to interface with Kubernetes directly. Instead, Uffizzi leverages Docker Compose as its configuration file format, so developers do not need modify Kubernetes manifests or even know about Kubernetes.
10
-
11
- Uffizzi is designed to integrate with any CI/CD system.
12
-
13
- ## Uffizzi Architecture
14
- <img src="https://github.com/UffizziCloud/uffizzi_app/blob/main/docs/images/uffizzi-architecture.png" description="Uffizzi Architecture" width="320"/>
15
-
16
- Uffizzi consists of the following components:
17
-
18
- - [Uffizzi App](https://github.com/UffizziCloud/uffizzi_app) - The primary REST API for creating and managing Previews
19
- - [Uffizzi Controller](https://github.com/UffizziCloud/uffizzi_controller) - A smart proxy service that handles requests from Uffizzi App to the Kubernetes API
20
- - Uffizzi CLI (this repository) - A command-line interface for Uffizzi App
21
-
22
- To host Uffizzi yourself, you will also need the following external dependencies:
23
-
24
- - Kubernetes (k8s) cluster
25
- - Postgres database
26
- - Redis cache
3
+ A command-line interace (CLI) for the [Uffizzi API](https://github.com/UffizziCloud/uffizzi)
27
4
 
28
5
  ## Installation
29
6
 
30
- The Uffizzi CLI can be used interactively or as part of an automated workflow (e.g. GitHub Actions). Both options use the `uffizzi/cli` container image available on Docker Hub.
31
-
32
- ### Interactive mode
33
-
34
- Run the CLI as a Docker container in interactive mode:
35
- ```
36
- docker run --interactive --rm --tty --entrypoint=sh uffizzi/cli
37
- ```
38
-
39
- If you specify the following environment variables, the Docker image's
40
- entrypoint script can log you into Uffizzi before executing your command.
41
-
42
- - `UFFIZZI_USER`
43
- - `UFFIZZI_SERVER`
44
- - `UFFIZZI_PASSWORD`
45
- - `UFFIZZI_PROJECT` (optional)
46
-
47
- ### Automated mode
48
-
49
- If you want to use Uffizzi as part of an automated workflow, you can pass the Uffizzi commands to the Docker run command. For example:
50
-
51
- ```
52
- docker run -it --rm uffizzi/cli project list
53
- ```
54
-
55
- ## Sample commands and examples
56
-
57
- ### help
58
-
59
- The `help` subcommand can be used to see more information about a particular command.
60
-
61
- Examples:
62
-
63
- ```
64
- uffizzi help
65
- ```
66
-
67
- ```
68
- uffizzi preview help
69
- ```
70
-
71
- ```
72
- uffizzi project compose help
73
- ```
74
-
75
- ### login
76
-
77
- ```
78
- uffizzi login --server=localhost:8080 --username=your@email.com
79
- ```
80
-
81
- Log in to the app with the specified server.
82
-
83
- #### login options
84
-
85
- | Option | Aliase | Description |
86
- | ------------ | ------ | ------------------------- |
87
- | `--username` | `-u` | Your email for logging in |
88
- | `--server` | | The URL of the Uffizzi installation |
89
-
90
- If server uses basic authentication you can specify options for it by setting `basic_auth_user` and `basic_auth_password` via `config set` command.
91
-
92
- ### config
93
-
94
- Use this command to configure your cli app.
95
-
96
- ```
97
- $ uffizzi config
98
- ```
99
-
100
- Launching interactive setup guide that sets the values for `server`, `username` and `project`
101
-
102
- ### config subcommands
103
-
104
- This command has 4 subcommands `list`, `get`, `set`, and `delete`.
105
-
106
- ```
107
- uffizzi config list
108
- ```
109
-
110
- Shows all options and their values from the config file.
111
-
112
- ```
113
- uffizzi config get-value OPTION
114
- ```
115
-
116
- Shows the value of the specified option.
117
-
118
- ```
119
- uffizzi config set OPTION VALUE
120
- ```
121
-
122
- Sets specified value for specified option. If a specified option already exists and has value it will be overwritten.
123
-
124
- ```
125
- uffizzi config unset OPTION
126
- ```
127
-
128
- Unsets specified option.
129
-
130
- ### project
131
-
132
- ```
133
- uffizzi project
134
- ```
135
-
136
- Use this command to configure your projects. This command has 2 subcommands `list` and `compose`.
137
-
138
- ```
139
- uffizzi project list
140
- ```
141
-
142
- Shows all your projects' slugs
143
-
144
- If you have only one project it will be added to your config file automatically, if there's more than one project you need to set up your project manually with the commands `uffizzi config set YOUR_PROJECT_SLUG` or `uffizzi project set-default YOUR_PROJECT_SLUG`
145
-
146
- ```
147
- $ uffizzi project set-default PROJECT_SLUG
148
- ```
149
- Create a preview from a compose file.
150
-
151
- Sets the default project given with the given project slug. When set, all commands use this project as the default context unless overridden by the --project flag.
152
-
153
- ### preview
154
-
155
- Create and manage previews
156
-
157
- ```
158
- uffizzi preview create docker-compose.uffizzi.yml
159
- ```
160
- Create a preview from a compose file.
161
-
162
- ```
163
- uffizzi preview delete deployment-21
164
- ```
165
- Delete a preview with preview ID `deployment-21`.
166
-
167
- ### disconnect
168
-
169
- ```
170
- uffizzi disconnect CREDENTIAL_TYPE
171
- ```
172
-
173
- Deletes credential of specified type
174
-
175
- Supported credential types - `docker-hub`, `acr`, `ecr`, `gcr`
7
+ See the [Uffizzi Documentation](https://docs.uffizzi.com) for installation instructions.
176
8
 
177
9
  ## Contributing
178
10
 
@@ -96,9 +96,9 @@ module Uffizzi
96
96
  when 'disconnect'
97
97
  ClusterDisconnectService.handle(options)
98
98
  when 'sleep'
99
- handle_sleep_command(project_slug, command_args)
99
+ handle_sleep_command(command_args)
100
100
  when 'wake'
101
- handle_wake_command(project_slug, command_args)
101
+ handle_wake_command(command_args)
102
102
  end
103
103
  end
104
104
 
@@ -141,7 +141,7 @@ module Uffizzi
141
141
 
142
142
  spinner = TTY::Spinner.new("[:spinner] Creating cluster #{cluster_name}...", format: :dots)
143
143
  spinner.auto_spin
144
- cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, oidc_token)
144
+ cluster_data = ClusterService.wait_cluster_deploy(cluster_name, cluster_api_connection_params)
145
145
 
146
146
  if ClusterService.failed?(cluster_data[:state])
147
147
  spinner.error
@@ -220,41 +220,27 @@ module Uffizzi
220
220
  return if options[:quiet]
221
221
 
222
222
  Uffizzi.ui.say("Kubeconfig was updated by the path: #{kubeconfig_path}")
223
+
224
+ synced_cluster_data = ClusterService.sync_cluster_data(command_args[:cluster_name], server: server, project_slug: project_slug)
225
+ cluster_state = synced_cluster_data[:state]
226
+ return if ClusterService.deployed?(cluster_state)
227
+
228
+ Uffizzi.ui.say(ClusterService.cluster_status_text_map[cluster_state])
229
+ handle_scale_up_cluster(cluster_name, cluster_api_connection_params) if ClusterService.scaled_down?(cluster_state)
223
230
  end
224
231
 
225
- def handle_sleep_command(project_slug, command_args)
232
+ def handle_sleep_command(command_args)
226
233
  cluster_name = command_args[:cluster_name] || ConfigFile.read_option(:current_cluster)&.fetch(:name)
227
234
  return handle_missing_cluster_name_error if cluster_name.nil?
228
235
 
229
- response = scale_down_cluster(ConfigFile.read_option(:server), project_slug, cluster_name)
230
- return ResponseHelper.handle_failed_response(response) unless ResponseHelper.ok?(response)
231
-
232
- spinner = TTY::Spinner.new("[:spinner] Scaling down cluster #{cluster_name}...", format: :dots)
233
- spinner.auto_spin
234
- ClusterService.wait_cluster_scale_down(project_slug, cluster_name)
235
-
236
- spinner.success
237
- Uffizzi.ui.say("Cluster #{cluster_name} was successfully scaled down")
236
+ handle_scale_down_cluster(cluster_name, cluster_api_connection_params)
238
237
  end
239
238
 
240
- def handle_wake_command(project_slug, command_args)
239
+ def handle_wake_command(command_args)
241
240
  cluster_name = command_args[:cluster_name] || ConfigFile.read_option(:current_cluster)&.fetch(:name)
242
241
  return handle_missing_cluster_name_error if cluster_name.nil?
243
242
 
244
- response = scale_up_cluster(ConfigFile.read_option(:server), project_slug, cluster_name)
245
- return ResponseHelper.handle_failed_response(response) unless ResponseHelper.ok?(response)
246
-
247
- spinner = TTY::Spinner.new("[:spinner] Waking up cluster #{cluster_name}...", format: :dots)
248
- spinner.auto_spin
249
- cluster_data = ClusterService.wait_cluster_scale_up(project_slug, cluster_name)
250
-
251
- if ClusterService.failed_scaling_up?(cluster_data[:state])
252
- spinner.error
253
- Uffizzi.ui.say_error_and_exit("Failed to wake up cluster #{cluster_name}.")
254
- end
255
-
256
- spinner.success
257
- Uffizzi.ui.say("Cluster #{cluster_name} was successfully scaled up")
243
+ handle_scale_up_cluster(cluster_name, cluster_api_connection_params)
258
244
  end
259
245
 
260
246
  def say_error_update_kubeconfig(cluster_data)
@@ -355,6 +341,35 @@ module Uffizzi
355
341
  Psych.safe_load(Base64.decode64(kubeconfig))
356
342
  end
357
343
 
344
+ def handle_scale_up_cluster(cluster_name, cluster_api_connection_params)
345
+ response = scale_up_cluster(cluster_api_connection_params[:server], project_slug, cluster_name)
346
+ return ResponseHelper.handle_failed_response(response) unless ResponseHelper.ok?(response)
347
+
348
+ spinner = TTY::Spinner.new("[:spinner] Waking up cluster #{cluster_name}...", format: :dots)
349
+ spinner.auto_spin
350
+ cluster_data = ClusterService.wait_cluster_scale_up(cluster_name, cluster_api_connection_params)
351
+
352
+ if ClusterService.failed_scaling_up?(cluster_data[:state])
353
+ spinner.error
354
+ Uffizzi.ui.say_error_and_exit("Failed to scale up cluster #{cluster_name}.")
355
+ end
356
+
357
+ spinner.success
358
+ Uffizzi.ui.say("Cluster #{cluster_name} was successfully scaled up")
359
+ end
360
+
361
+ def handle_scale_down_cluster(cluster_name, cluster_api_connection_params)
362
+ response = scale_down_cluster(cluster_api_connection_params[:server], project_slug, cluster_name)
363
+ return ResponseHelper.handle_failed_response(response) unless ResponseHelper.ok?(response)
364
+
365
+ spinner = TTY::Spinner.new("[:spinner] Scaling down cluster #{cluster_name}...", format: :dots)
366
+ spinner.auto_spin
367
+ ClusterService.wait_cluster_scale_down(cluster_name, cluster_api_connection_params)
368
+
369
+ spinner.success
370
+ Uffizzi.ui.say("Cluster #{cluster_name} was successfully scaled down")
371
+ end
372
+
358
373
  def handle_missing_cluster_name_error
359
374
  Uffizzi.ui.say("No kubeconfig found at #{KubeconfigService.default_path}")
360
375
  Uffizzi.ui.say('Please update the current context or provide a cluster name.')
@@ -116,7 +116,7 @@ module Uffizzi
116
116
 
117
117
  def wait_cluster_creation(cluster_name)
118
118
  Uffizzi.ui.say('Checking the cluster status...')
119
- cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, oidc_token)
119
+ cluster_data = ClusterService.wait_cluster_deploy(cluster_name, cluster_api_connection_params)
120
120
 
121
121
  if ClusterService.failed?(cluster_data[:state])
122
122
  Uffizzi.ui.say_error_and_exit("Cluster with name: #{cluster_name} failed to be created.")
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uffizzi'
4
+ require 'uffizzi/config_file'
5
+ require 'uffizzi/services/kubeconfig_service'
6
+
7
+ module Uffizzi
8
+ class Cli::Install < Thor
9
+ include ApiClient
10
+
11
+ default_task :controller
12
+
13
+ desc 'controller [HOSTNMAE]', 'Install uffizzi controller to cluster'
14
+ method_option :namespace, type: :string
15
+ method_option :email, type: :string, required: true
16
+ method_option :context, type: :string
17
+ method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl']
18
+ method_option :'repo-url', type: :string
19
+ def controller(hostname)
20
+ Uffizzi::AuthHelper.check_login
21
+
22
+ InstallService.kubectl_exists?
23
+ InstallService.helm_exists?
24
+
25
+ if options[:context].present? && options[:context] != InstallService.kubeconfig_current_context
26
+ InstallService.set_current_context(options[:context])
27
+ end
28
+
29
+ ask_confirmation
30
+
31
+ uri = parse_hostname(hostname)
32
+ installation_options = build_installation_options(uri)
33
+ check_existence_controller_settings(uri, installation_options)
34
+ helm_values = build_helm_values(installation_options)
35
+
36
+ InstallService.create_helm_values_file(helm_values)
37
+ helm_set_repo
38
+ InstallService.helm_install!(namespace)
39
+ InstallService.delete_helm_values_file
40
+ Uffizzi.ui.say('Helm release is deployed')
41
+
42
+ controller_setting_params = build_controller_setting_params(uri, installation_options)
43
+ create_controller_settings(controller_setting_params) if existing_controller_setting.blank?
44
+ Uffizzi.ui.say('Controller settings are saved')
45
+ say_success(uri)
46
+ end
47
+
48
+ private
49
+
50
+ def helm_set_repo
51
+ return if InstallService.helm_repo_search.present?
52
+
53
+ InstallService.helm_repo_add(options[:'repo-url'])
54
+ end
55
+
56
+ def build_installation_options(uri)
57
+ {
58
+ uri: uri,
59
+ controller_username: Faker::Lorem.characters(number: 10),
60
+ controller_password: generate_password,
61
+ cert_email: options[:email],
62
+ cluster_issuer: options[:issuer] || InstallService::DEFAULT_CLUSTER_ISSUER,
63
+ }
64
+ end
65
+
66
+ def wait_ip
67
+ spinner = TTY::Spinner.new('[:spinner] Waiting IP addess...', format: :dots)
68
+ spinner.auto_spin
69
+
70
+ ip = nil
71
+ try = 0
72
+
73
+ loop do
74
+ ip = InstallService.get_controller_ip(namespace)
75
+ break if ip.present?
76
+
77
+ if try == 30
78
+ spinner.error
79
+
80
+ return 'unknown'
81
+ end
82
+
83
+ try += 1
84
+ sleep(2)
85
+ end
86
+
87
+ spinner.success
88
+
89
+ ip
90
+ end
91
+
92
+ def wait_certificate_request_ready(uri)
93
+ spinner = TTY::Spinner.new('[:spinner] Waiting create certificate for controller host...', format: :dots)
94
+ spinner.auto_spin
95
+
96
+ try = 0
97
+
98
+ loop do
99
+ requests = InstallService.get_certificate_request(namespace, uri)
100
+ break if requests.all? { |r| r['status'].downcase == 'true' }
101
+
102
+ if try == 60
103
+ spinner.error
104
+
105
+ return Uffizzi.ui.say('Stop waiting creation certificate')
106
+ end
107
+
108
+ try += 1
109
+ sleep(2)
110
+ end
111
+
112
+ spinner.success
113
+ end
114
+
115
+ def build_helm_values(params)
116
+ {
117
+ global: {
118
+ uffizzi: {
119
+ controller: {
120
+ username: params[:controller_username],
121
+ password: params[:controller_password],
122
+ },
123
+ },
124
+ },
125
+ clusterIssuer: params.fetch(:cluster_issuer),
126
+ tlsPerDeploymentEnabled: true.to_s,
127
+ certEmail: params.fetch(:cert_email),
128
+ 'ingress-nginx' => {
129
+ controller: {
130
+ ingressClassResource: {
131
+ default: true,
132
+ },
133
+ },
134
+ },
135
+ ingress: {
136
+ hostname: InstallService.build_controller_host(params[:uri].host),
137
+ },
138
+ }.deep_stringify_keys
139
+ end
140
+
141
+ def generate_password
142
+ hexatridecimal_base = 36
143
+ length = 8
144
+ rand(hexatridecimal_base**length).to_s(hexatridecimal_base)
145
+ end
146
+
147
+ def check_existence_controller_settings(uri, installation_options)
148
+ return if existing_controller_setting.blank?
149
+
150
+ Uffizzi.ui.say_error_and_exit('Installation canceled') unless update_and_continue?
151
+
152
+ controller_setting_params = build_controller_setting_params(uri, installation_options)
153
+ update_controller_settings(existing_controller_setting[:id], controller_setting_params)
154
+ end
155
+
156
+ def ask_confirmation
157
+ msg = "\r\n"\
158
+ 'This command will install Uffizzi into the default namespace of'\
159
+ " the '#{InstallService.kubeconfig_current_context}' context."\
160
+ "\r\n"\
161
+ "To install in a different place, use options '--namespace' and/or '--context'."\
162
+ "\r\n\r\n"\
163
+ "After installation, new environments created for account '#{account_name}' will be deployed to this host cluster."\
164
+ "\r\n\r\n"
165
+
166
+ Uffizzi.ui.say(msg)
167
+
168
+ question = 'Okay to proceed?'
169
+ Uffizzi.ui.say_error_and_exit('Installation canceled') unless Uffizzi.prompt.yes?(question)
170
+ end
171
+
172
+ def update_and_continue?
173
+ msg = "\r\n"\
174
+ 'You already have installation controller params. '\
175
+ "\r\n"\
176
+ 'You can update previous params and continue installation or cancel installation.'\
177
+ "\r\n"
178
+
179
+ Uffizzi.ui.say(msg)
180
+
181
+ question = 'Do you want update the controller settings?'
182
+ Uffizzi.prompt.yes?(question)
183
+ end
184
+
185
+ def fetch_controller_settings
186
+ response = get_account_controller_settings(server, account_id)
187
+ return Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response)
188
+
189
+ response.dig(:body, :controller_settings)
190
+ end
191
+
192
+ def update_controller_settings(controller_setting_id, params)
193
+ response = update_account_controller_settings(server, account_id, controller_setting_id, params)
194
+ Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response)
195
+ end
196
+
197
+ def create_controller_settings(params)
198
+ response = create_account_controller_settings(server, account_id, params)
199
+ Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.created?(response)
200
+ end
201
+
202
+ def build_controller_setting_params(uri, installation_options)
203
+ {
204
+ url: URI::HTTPS.build(host: InstallService.build_controller_host(uri.host)).to_s,
205
+ managed_dns_zone: uri.host,
206
+ login: installation_options[:controller_username],
207
+ password: installation_options[:controller_password],
208
+ }
209
+ end
210
+
211
+ def say_success(uri)
212
+ ip_address = wait_ip
213
+ wait_certificate_request_ready(uri)
214
+
215
+ msg = 'Your Uffizzi controller is ready. To configure DNS,'\
216
+ " create a record for the hostname '*.#{uri.host}' pointing to '#{ip_address}'"
217
+ Uffizzi.ui.say(msg)
218
+ end
219
+
220
+ def parse_hostname(hostname)
221
+ uri = URI.parse(hostname)
222
+ host = uri.host || hostname
223
+
224
+ case uri
225
+ when URI::HTTP, URI::HTTPS
226
+ uri
227
+ else
228
+ URI::HTTPS.build(host: host)
229
+ end
230
+ end
231
+
232
+ def namespace
233
+ options[:namespace] || InstallService::DEFAULT_NAMESPACE
234
+ end
235
+
236
+ def server
237
+ @server ||= ConfigFile.read_option(:server)
238
+ end
239
+
240
+ def account_id
241
+ @account_id ||= ConfigFile.read_option(:account, :id)
242
+ end
243
+
244
+ def account_name
245
+ @account_name ||= ConfigFile.read_option(:account, :name)
246
+ end
247
+
248
+ def existing_controller_setting
249
+ @existing_controller_setting ||= fetch_controller_settings[0]
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uffizzi'
4
+ require 'uffizzi/config_file'
5
+
6
+ module Uffizzi
7
+ class Cli::Status < Thor
8
+ include ApiClient
9
+
10
+ default_task :describe
11
+
12
+ desc 'describe', 'Show account status'
13
+ def describe
14
+ Uffizzi::AuthHelper.check_login
15
+
16
+ account_name = ConfigFile.read_option(:account)[:name]
17
+ response = fetch_account(ConfigFile.read_option(:server), account_name)
18
+
19
+ if ResponseHelper.ok?(response)
20
+ handle_describe_success_response(response)
21
+ elsif ResponseHelper.not_found?(response)
22
+ Uffizzi.ui.say("Account with name #{account_name} does not exist")
23
+ else
24
+ ResponseHelper.handle_failed_response(response)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def handle_describe_success_response(response)
31
+ account = response[:body][:account]
32
+ account_rendered_params = {
33
+ account: account[:name],
34
+ plan: "Uffizzi #{account[:product_name]}",
35
+ api: account[:api_url],
36
+ controller: account[:vclusters_controller_url],
37
+ }
38
+
39
+ Uffizzi.ui.output_format = Uffizzi::UI::Shell::PRETTY_LIST
40
+ Uffizzi.ui.say(account_rendered_params)
41
+ end
42
+ end
43
+ end