diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..2a885bf9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +.byebug_history diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c2016835..78d3ae63 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,13 @@ -name: Uffizzi CLI CI/CD +name: Test, Lint, Build, and Publish Image on: push: branches: - - "**" + - qa + - develop + - main + pull_request: + types: [opened,synchronize,reopened] + jobs: lint: runs-on: ubuntu-latest @@ -26,10 +31,12 @@ jobs: bundler-cache: true - name: Run tests run: bundle exec rake test - deploy: + build-and-push-some-branches: runs-on: ubuntu-latest - needs: [lint, test] - if: github.ref == 'refs/heads/main' + needs: + - lint + - test + if: ${{ github.ref_name == 'main' || github.ref_name == 'qa' || github.ref_name == 'develop' || github.event_name == 'pull_request' }} steps: - name: Checkout uses: actions/checkout@v2 @@ -40,8 +47,25 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + - name: Docker metadata + id: meta + uses: docker/metadata-action@v3 + with: + images: uffizzi/cli + tags: | + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + type=ref,event=branch,enable=${{ github.ref_name == 'qa' || github.ref_name == 'develop' }} + type=ref,event=pr + - name: Build and Push Image to Docker Hub uses: docker/build-push-action@v2 with: push: true - tags: uffizzi/cli:latest + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + - name: Update Docker Hub Description for Default Branch + uses: peter-evans/dockerhub-description@v2.4.3 + if: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + repository: uffizzi/cli diff --git a/Dockerfile b/Dockerfile index c10298d6..3ad52e64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,17 @@ -FROM ruby:3.0.2-alpine3.14 +FROM ruby:3.0.3-alpine AS builder -RUN apk update && apk upgrade -RUN apk add bash -RUN apk add curl-dev ruby-dev build-base git curl ruby-json openssl groff mandoc man-pages +RUN apk --update add --no-cache \ + curl-dev \ + ruby-dev \ + build-base \ + git \ + curl \ + ruby-json \ + openssl \ + groff \ + mandoc \ + man-pages \ + bash RUN mkdir -p /gem WORKDIR /gem @@ -10,14 +19,28 @@ WORKDIR /gem ENV GEM_HOME="/usr/local/bundle" ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH -RUN gem install uffizzi-cli -RUN gem install bundler -v 2.3.8 +RUN gem install bundler -v 2.3.9 -COPY lib/uffizzi/version.rb /gem/lib/uffizzi/ -COPY uffizzi.gemspec /gem/ -COPY Gemfile* /gem/ +COPY lib/uffizzi/version.rb ./lib/uffizzi/ +COPY uffizzi.gemspec . +COPY Gemfile* . RUN bundle install --jobs 4 -COPY . /gem +COPY . . -CMD ["uffizzi"] +RUN bundle exec rake install + +# M-M-M-M-MULTISTAGE!!! +FROM ruby:3.0.3-alpine + +RUN apk --update add --no-cache mandoc + +WORKDIR /root/ + +COPY docker-entrypoint.sh . +RUN chmod +x docker-entrypoint.sh + +COPY --from=builder /gem/pkg/uffizzi-cli* . +RUN gem install ./uffizzi-cli* + +ENTRYPOINT ["/root/docker-entrypoint.sh"] diff --git a/Gemfile.lock b/Gemfile.lock index b56de7e4..cdd2f667 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - uffizzi-cli (0.2.2) + uffizzi-cli (0.3.2) thor GEM @@ -16,6 +16,7 @@ GEM addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) ast (2.4.2) + bump (0.10.0) byebug (11.1.3) coderay (1.1.3) concurrent-ruby (1.1.9) @@ -23,6 +24,8 @@ GEM rexml factory_bot (6.2.0) activesupport (>= 5.0.0) + faker (2.20.0) + i18n (>= 1.8.11, < 2) hashdiff (1.0.1) i18n (1.8.11) concurrent-ruby (~> 1.0) @@ -95,9 +98,11 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + bump bundler (~> 2.2) byebug factory_bot + faker minitest minitest-power_assert mocha diff --git a/README.md b/README.md index a5b54cf2..5363149f 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,10 @@ To host Uffizzi yourself, you will also need the following external dependencies ## Installation -Add this line to your application's Gemfile: +Add this line to your application's `Gemfile`: ```ruby -gem 'uffizzi' +gem 'uffizzi-cli' ``` And then execute: @@ -41,7 +41,23 @@ And then execute: Or install it yourself as: - $ gem install uffizzi + $ gem install uffizzi-cli + +### Docker image + +We also provide an image on Docker Hub: + +```bash +docker run -it --rm uffizzi/cli project list +``` + +If you specify the following environment variables, the Docker image's +entrypoint script can log you into Uffizzi before executing your command. + +- `UFFIZZI_USER` +- `UFFIZZI_HOSTNAME` +- `UFFIZZI_PASSWORD` +- `UFFIZZI_PROJECT` (optional) ## Development @@ -52,12 +68,23 @@ To install this gem onto your local machine, run `bundle exec rake install`. To Run rubocop: `bundle exec rubocop -A` +## Testing + +Run tests: +`bundle exec rake test` + +Run tests from a file: +`bundle exec rake test TEST=test/uffizzi/cli/preview_test.rb` + +Run single test +`bundle exec rake test TEST=test/uffizzi/cli/preview_test.rb TESTOPTS="--name=test_name"` + ## Commands ### login ``` -$ uffizzi login -u your@email.com --hostname localhost:8080 +$ uffizzi login --user your@email.com --hostname localhost:8080 ``` Logging you into the app which you set in the hostname option. @@ -167,7 +194,7 @@ git checkout -b feature/short_issue_description (e.g. feature/add_domain_setting ``` git add . git commit -m 'short commit description' (e.g. git commit -m 'added domain settings') -git push origin FEATURE_NAME +git push origin BRANCH_NAME ``` 4. You already can create PR with develop branch as a target. Once the feature is ready let us know in the channel - we will review @@ -176,7 +203,7 @@ git push origin FEATURE_NAME ``` git checkout qa git pull --rebase qa -git merge --no-ff FEATURE_NAME +git merge --no-ff BRANCH_NAME git push origin qa ``` diff --git a/config/config.rb b/config/config.rb deleted file mode 100644 index 671d3a0e..00000000 --- a/config/config.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'ostruct' - -module Uffizzi - def self.configuration - @configuration ||= OpenStruct.new - end - - def self.configure - yield(configuration) - end - - configure do |config| - config.hostname = 'http://web:7000' - end -end diff --git a/config/uffizzi.rb b/config/uffizzi.rb index 6baf3ec0..748689f2 100644 --- a/config/uffizzi.rb +++ b/config/uffizzi.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'ostruct' + module Uffizzi def self.configuration @configuration ||= OpenStruct.new @@ -12,5 +13,11 @@ def self.configure configure do |config| config.hostname = 'http://web:7000' + config.credential_types = { + dockerhub: 'UffizziCore::Credential::DockerHub', + azure: 'UffizziCore::Credential::Azure', + google: 'UffizziCore::Credential::Google', + amazon: 'UffizziCore::Credential::Amazon', + } end end diff --git a/docker-compose.yml b/docker-compose.yml index 48bed08f..6e3bef00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,9 @@ version: "3.9" services: gem: - build: . + build: + context: . + target: builder volumes: - ./:/gem:cached - ~/.ssh:/root/.ssh diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 00000000..3d30f132 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e # Exit immediately if anything below exits with non-zero status. + +if + [ $UFFIZZI_USER ] && + [ $UFFIZZI_HOSTNAME ] && + [ $UFFIZZI_PASSWORD ] +then + uffizzi login --username "${UFFIZZI_USER}" --hostname "${UFFIZZI_HOSTNAME}" + if [ $UFFIZZI_PROJECT ] + then + uffizzi config set project "${UFFIZZI_PROJECT}" + fi +else + echo "Specify environment variables to login before executing Uffizzi CLI." + echo "UFFIZZI_USER, UFFIZZI_HOSTNAME, UFFIZZI_PASSWORD, and optionally UFFIZZI_PROJECT" +fi + +exec uffizzi "$@" diff --git a/lib/uffizzi.rb b/lib/uffizzi.rb index 1c4c60db..98a2f386 100644 --- a/lib/uffizzi.rb +++ b/lib/uffizzi.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true +require 'io/console' + require 'uffizzi/shell' require 'uffizzi/version' require 'uffizzi/clients/api/api_client' require 'uffizzi/clients/api/api_routes' require 'uffizzi/config_file' +require_relative '../config/uffizzi' module Uffizzi class Error < StandardError; end diff --git a/lib/uffizzi/cli.rb b/lib/uffizzi/cli.rb index 11354db8..d19e34ad 100644 --- a/lib/uffizzi/cli.rb +++ b/lib/uffizzi/cli.rb @@ -30,6 +30,12 @@ def logout(help = nil) Logout.new.run end + desc 'projects', 'projects' + def projects + require_relative 'cli/projects' + Projects.new.run + end + desc 'project', 'project' require_relative 'cli/project' subcommand 'project', CLI::Project @@ -42,5 +48,11 @@ def logout(help = nil) method_option :project, required: false require_relative 'cli/preview' subcommand 'preview', CLI::Preview + + desc 'connect CREDENTIAL_TYPE', 'Connect credentials into Uffizzi' + def connect(credential_type, credential_file_path = nil) + require_relative 'cli/connect' + Connect.new.run(credential_type, credential_file_path) + end end end diff --git a/lib/uffizzi/cli/config.rb b/lib/uffizzi/cli/config.rb index e3c4bc85..7bc48ab1 100644 --- a/lib/uffizzi/cli/config.rb +++ b/lib/uffizzi/cli/config.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'io/console' require 'uffizzi' require 'uffizzi/clients/api/api_client' diff --git a/lib/uffizzi/cli/connect.rb b/lib/uffizzi/cli/connect.rb new file mode 100644 index 00000000..630bce74 --- /dev/null +++ b/lib/uffizzi/cli/connect.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'uffizzi' + +module Uffizzi + class CLI::Connect + include ApiClient + + def run(credential_type, credential_file_path) + case credential_type + when 'docker-hub' + handle_docker_hub + when 'acr' + handle_azure + when 'ecr' + handle_amazon + when 'gcr' + handle_google(credential_file_path) + else + Uffizzi.ui.say('Unsupported credential type.') + end + end + + private + + def handle_docker_hub + username = Uffizzi.ui.ask('Username: ') + password = Uffizzi.ui.ask('Password: ', echo: false) + + params = { + username: username, + password: password, + type: Uffizzi.configuration.credential_types[:dockerhub], + } + + hostname = ConfigFile.read_option(:hostname) + response = create_credential(hostname, params) + + if ResponseHelper.created?(response) + print_success_message('DockerHub') + else + ResponseHelper.handle_failed_response(response) + end + end + + def handle_azure + registry_url = prepare_registry_url(Uffizzi.ui.ask('Registry Domain: ')) + username = Uffizzi.ui.ask('Docker ID: ') + password = Uffizzi.ui.ask('Password/Access Token: ', echo: false) + + params = { + username: username, + password: password, + registry_url: registry_url, + type: Uffizzi.configuration.credential_types[:azure], + } + + hostname = ConfigFile.read_option(:hostname) + response = create_credential(hostname, params) + + if ResponseHelper.created?(response) + print_success_message('ACR') + else + ResponseHelper.handle_failed_response(response) + end + end + + def handle_amazon + registry_url = prepare_registry_url(Uffizzi.ui.ask('Registry Domain: ')) + username = Uffizzi.ui.ask('Access key ID: ') + password = Uffizzi.ui.ask('Secret access key: ', echo: false) + + params = { + username: username, + password: password, + registry_url: registry_url, + type: Uffizzi.configuration.credential_types[:amazon], + } + + hostname = ConfigFile.read_option(:hostname) + response = create_credential(hostname, params) + + if ResponseHelper.created?(response) + print_success_message('ECR') + else + ResponseHelper.handle_failed_response(response) + end + end + + def handle_google(credential_file_path) + return Uffizzi.ui.say('Path to google service account key file wasn\'t specified.') if credential_file_path.nil? + + begin + credential_content = File.read(credential_file_path) + rescue Errno::ENOENT => e + return Uffizzi.ui.say(e) + end + + params = { + password: credential_content, + type: Uffizzi.configuration.credential_types[:google], + } + + hostname = ConfigFile.read_option(:hostname) + response = create_credential(hostname, params) + + if ResponseHelper.created?(response) + print_success_message('GCR') + else + ResponseHelper.handle_failed_response(response) + end + end + + def prepare_registry_url(registry_url) + return registry_url if registry_url.match?(/^(?:http(s)?:\/\/)/) + + "https://#{registry_url}" + end + + def print_success_message(connection_name) + Uffizzi.ui.say("Successfully connected to #{connection_name}") + end + end +end diff --git a/lib/uffizzi/cli/login.rb b/lib/uffizzi/cli/login.rb index 8605ec64..3a23e83e 100644 --- a/lib/uffizzi/cli/login.rb +++ b/lib/uffizzi/cli/login.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'io/console' require 'uffizzi' require 'uffizzi/response_helper' require 'uffizzi/clients/api/api_client' @@ -14,7 +13,8 @@ def initialize(options) end def run - password = IO::console.getpass('Enter Password: ') + password = ENV['UFFIZZI_PASSWORD'] || IO::console.getpass('Enter Password: ') + params = prepare_request_params(password) response = create_session(@options[:hostname], params) diff --git a/lib/uffizzi/cli/logout.rb b/lib/uffizzi/cli/logout.rb index dcfc4fb7..cdf4f81c 100644 --- a/lib/uffizzi/cli/logout.rb +++ b/lib/uffizzi/cli/logout.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'io/console' require 'uffizzi' require 'uffizzi/auth_helper' diff --git a/lib/uffizzi/cli/preview.rb b/lib/uffizzi/cli/preview.rb index 7546db40..e965919e 100644 --- a/lib/uffizzi/cli/preview.rb +++ b/lib/uffizzi/cli/preview.rb @@ -3,6 +3,7 @@ require 'uffizzi' require 'tty-spinner' require 'uffizzi/auth_helper' +require 'uffizzi/services/preview_service' module Uffizzi class CLI::Preview < Thor @@ -16,37 +17,48 @@ def help(_shell, _subcommand) end end + desc 'service', 'service' + require_relative 'preview/service' + subcommand 'service', Uffizzi::CLI::Preview::Service + desc 'list', 'list' def list return Cli::Common.show_manual(:list) if options[:help] - run(options, 'list', nil, nil) + run(options, 'list') end desc 'create', 'create' def create(file_path = nil) return Cli::Common.show_manual(:create) if options[:help] - run(options, 'create', file_path, nil) + run(options, 'create', file_path: file_path) end desc 'delete', 'delete' - def delete(deployment) + def delete(deployment_name) return Cli::Common.show_manual(:delete) if options[:help] - run(options, 'delete', nil, deployment) + run(options, 'delete', deployment_name: deployment_name) end desc 'describe', 'describe' - def describe(deployment) + def describe(deployment_name) return Cli::Common.show_manual(:describe) if options[:help] - run(options, 'describe', nil, deployment) + run(options, 'describe', deployment_name: deployment_name) + end + + desc 'events', 'events' + def events(deployment_name) + return Cli::Common.show_manual(:events) if options[:help] + + run(options, 'events', deployment_name: deployment_name) end private - def run(options, command, file_path, deployment) + def run(options, command, file_path: nil, deployment_name: nil) return Uffizzi.ui.say('You are not logged in.') unless Uffizzi::AuthHelper.signed_in? return Uffizzi.ui.say('This command needs project to be set in config file') unless Uffizzi::AuthHelper.project_set? @@ -58,15 +70,16 @@ def run(options, command, file_path, deployment) when 'create' handle_create_command(file_path, project_slug) when 'delete' - handle_delete_command(deployment, project_slug) + handle_delete_command(deployment_name, project_slug) when 'describe' - handle_describe_command(deployment, project_slug) + handle_describe_command(deployment_name, project_slug) + when 'events' + handle_events_command(deployment_name, project_slug) end end def handle_list_command(project_slug) - hostname = ConfigFile.read_option(:hostname) - response = fetch_deployments(hostname, project_slug) + response = fetch_deployments(ConfigFile.read_option(:hostname), project_slug) if ResponseHelper.ok?(response) handle_succeed_list_response(response) @@ -76,33 +89,50 @@ def handle_list_command(project_slug) end def handle_create_command(file_path, project_slug) - hostname = ConfigFile.read_option(:hostname) params = file_path.nil? ? {} : prepare_params(file_path) - response = create_deployment(hostname, project_slug, params) + response = create_deployment(ConfigFile.read_option(:hostname), project_slug, params) if ResponseHelper.created?(response) - handle_succeed_create_response(hostname, project_slug, response) + handle_succeed_create_response(project_slug, response) + else + ResponseHelper.handle_failed_response(response) + end + end + + def handle_events_command(deployment_name, project_slug) + deployment_id = PreviewService.read_deployment_id(deployment_name) + + return Uffizzi.ui.say("Preview should be specified in 'deployment-PREVIEW_ID' format") if deployment_id.nil? + + response = fetch_events(ConfigFile.read_option(:hostname), project_slug, deployment_id) + + if ResponseHelper.ok?(response) + handle_succeed_events_response(response) else ResponseHelper.handle_failed_response(response) end end - def handle_succeed_create_response(hostname, project_slug, response) + def handle_succeed_events_response(response) + Uffizzi.ui.print_in_columns(response[:body][:events]) + end + + def handle_succeed_create_response(project_slug, response) deployment = response[:body][:deployment] deployment_id = deployment[:id] params = { id: deployment_id } - response = deploy_containers(hostname, project_slug, deployment_id, params) + response = deploy_containers(ConfigFile.read_option(:hostname), project_slug, deployment_id, params) if ResponseHelper.no_content?(response) Uffizzi.ui.say("Preview created with name deployment-#{deployment_id}") - print_deployment_progress(hostname, deployment, project_slug) + print_deployment_progress(deployment, project_slug) else ResponseHelper.handle_failed_response(response) end end - def print_deployment_progress(hostname, deployment, project_slug) + def print_deployment_progress(deployment, project_slug) deployment_id = deployment[:id] @spinner = TTY::Spinner.new('[:spinner] Creating containers...', format: :dots) @@ -111,7 +141,7 @@ def print_deployment_progress(hostname, deployment, project_slug) activity_items = [] loop do - response = get_activity_items(hostname, project_slug, deployment_id) + response = get_activity_items(ConfigFile.read_option(:hostname), project_slug, deployment_id) handle_activity_items_response(response) return unless @spinner.spinning? @@ -133,7 +163,7 @@ def print_deployment_progress(hostname, deployment, project_slug) containers_spinners = create_containers_spinners(activity_items) loop do - response = get_activity_items(hostname, project_slug, deployment_id) + response = get_activity_items(ConfigFile.read_option(:hostname), project_slug, deployment_id) handle_activity_items_response(response) return if @spinner.done? @@ -176,13 +206,12 @@ def check_activity_items_state(activity_items, containers_spinners) end end - def handle_delete_command(deployment, project_slug) - return Uffizzi.ui.say("Preview should be specified in 'deployment-PREVIEW_ID' format") unless deployment_name_valid?(deployment) + def handle_delete_command(deployment_name, project_slug) + deployment_id = PreviewService.read_deployment_id(deployment_name) - hostname = ConfigFile.read_option(:hostname) - deployment_id = deployment.split('-').last + return Uffizzi.ui.say("Preview should be specified in 'deployment-PREVIEW_ID' format") if deployment_id.nil? - response = delete_deployment(hostname, project_slug, deployment_id) + response = delete_deployment(ConfigFile.read_option(:hostname), project_slug, deployment_id) if ResponseHelper.no_content?(response) handle_succeed_delete_response(deployment_id) @@ -191,13 +220,12 @@ def handle_delete_command(deployment, project_slug) end end - def handle_describe_command(deployment, project_slug) - return Uffizzi.ui.say("Preview should be specified in 'deployment-PREVIEW_ID' format") unless deployment_name_valid?(deployment) + def handle_describe_command(deployment_name, project_slug) + deployment_id = PreviewService.read_deployment_id(deployment_name) - hostname = ConfigFile.read_option(:hostname) - deployment_id = deployment.split('-').last + return Uffizzi.ui.say("Preview should be specified in 'deployment-PREVIEW_ID' format") if deployment_id.nil? - response = describe_deployment(hostname, project_slug, deployment_id) + response = describe_deployment(ConfigFile.read_option(:hostname), project_slug, deployment_id) if ResponseHelper.ok?(response) handle_succeed_describe_response(response) @@ -255,13 +283,5 @@ def prepare_params(file_path) dependencies: dependencies, } end - - def deployment_name_valid?(deployment) - return false unless deployment.start_with?('deployment-') - return false unless deployment.split('-').size == 2 - - deployment_id = deployment.split('-').last - deployment_id.to_i.to_s == deployment_id - end end end diff --git a/lib/uffizzi/cli/preview/service.rb b/lib/uffizzi/cli/preview/service.rb new file mode 100644 index 00000000..cdb1425b --- /dev/null +++ b/lib/uffizzi/cli/preview/service.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'uffizzi' +require 'uffizzi/auth_helper' +require 'uffizzi/response_helper' +require 'uffizzi/services/preview_service' + +module Uffizzi + class CLI::Preview::Service < Thor + include ApiClient + + desc 'list', 'list' + def list(deployment_name) + return Uffizzi.ui.say('You are not logged in.') unless Uffizzi::AuthHelper.signed_in? + return Uffizzi.ui.say('This command needs project to be set in config file') unless Uffizzi::AuthHelper.project_set? + + project_slug = ConfigFile.read_option(:project) + hostname = ConfigFile.read_option(:hostname) + deployment_id = PreviewService.read_deployment_id(deployment_name) + response = fetch_deployment_services(hostname, project_slug, deployment_id) + + if ResponseHelper.ok?(response) + handle_succeed_response(response, deployment_name) + else + ResponseHelper.handle_failed_response(response) + end + end + + private + + def handle_succeed_response(response, deployment_name) + services = response[:body][:containers] || [] + return Uffizzi.ui.say("There are no services associated with the preview #{deployment_name}") if services.empty? + + services.each do |service| + Uffizzi.ui.say(service) + end + end + end +end diff --git a/lib/uffizzi/cli/project.rb b/lib/uffizzi/cli/project.rb index 8dc48db1..04c7707d 100644 --- a/lib/uffizzi/cli/project.rb +++ b/lib/uffizzi/cli/project.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -require 'io/console' require 'uffizzi' require 'uffizzi/auth_helper' require 'uffizzi/response_helper' -require 'thor' module Uffizzi class CLI::Project < Thor @@ -15,6 +13,10 @@ class CLI::Project < Thor require_relative 'project/compose' subcommand 'compose', Uffizzi::CLI::Project::Compose + desc 'secret', 'Secrets Actions' + require_relative 'project/secret' + subcommand 'secret', Uffizzi::CLI::Project::Secret + desc 'list', 'list' def list run('list') diff --git a/lib/uffizzi/cli/project/compose.rb b/lib/uffizzi/cli/project/compose.rb index 2e9bc00e..30780221 100644 --- a/lib/uffizzi/cli/project/compose.rb +++ b/lib/uffizzi/cli/project/compose.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -require 'io/console' require 'uffizzi' require 'uffizzi/auth_helper' require 'uffizzi/response_helper' require 'uffizzi/services/compose_file_service' require 'uffizzi/services/env_variables_service' -require 'thor' module Uffizzi class CLI::Project::Compose < Thor diff --git a/lib/uffizzi/cli/project/secret.rb b/lib/uffizzi/cli/project/secret.rb new file mode 100644 index 00000000..c4f8ba63 --- /dev/null +++ b/lib/uffizzi/cli/project/secret.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'uffizzi' +require 'uffizzi/auth_helper' +require 'uffizzi/response_helper' +require 'uffizzi/shell' + +module Uffizzi + class CLI::Project::Secret < Thor + include ApiClient + + desc 'list', 'List Secrets' + def list + run('list') + end + + desc 'create', 'Create secrets' + def create(id) + run('create', id) + end + + desc 'delete', 'Delete a secret' + def delete(id) + run('delete', id) + end + + private + + def run(command, args = {}) + return Uffizzi.ui.say('You are not logged in') unless AuthHelper.signed_in? + + project_slug = ConfigFile.read_option(:project) + return Uffizzi.ui.say('Please use the --project option to specify the project name') if project_slug.nil? + + case command + when 'list' + handle_list_command(project_slug) + when 'create' + handle_create_command(project_slug, args) + when 'delete' + handle_delete_command(project_slug, args) + else + error_message = "The subcommand #{command} does not exist, please run 'uffizzi project secret help' \ + to get the list of available subcommands" + Uffizzi.ui.say(error_message) + end + end + + def handle_list_command(project_slug) + hostname = ConfigFile.read_option(:hostname) + response = fetch_secrets(hostname, project_slug) + secrets = response[:body][:secrets].map { |secret| [secret[:name]] } + return Uffizzi.ui.say('There are no secrets for the project') if secrets.empty? + + table_header = 'NAME' + table_data = [[table_header], *secrets] + return Uffizzi.ui.print_table(table_data) if ResponseHelper.ok?(response) + + ResponseHelper.handle_failed_response(response) + end + + def handle_create_command(project_slug, id) + hostname = ConfigFile.read_option(:hostname) + secret_value = $stdin.read + return Uffizzi.ui.say('Please provide the secret value') if secret_value.nil? + + params = { secrets: [{ name: id, value: secret_value }] } + response = bulk_create_secrets(hostname, project_slug, params) + return Uffizzi.ui.say('The secret was successfully created') if ResponseHelper.created?(response) + + ResponseHelper.handle_failed_response(response) + end + + def handle_delete_command(project_slug, id) + hostname = ConfigFile.read_option(:hostname) + response = delete_secret(hostname, project_slug, id) + + if ResponseHelper.no_content?(response) + Uffizzi.ui.say('The secret was successfully deleted') + else + ResponseHelper.handle_failed_response(response) + end + end + end +end diff --git a/lib/uffizzi/clients/api/api_client.rb b/lib/uffizzi/clients/api/api_client.rb index 8861c2df..57acb2cd 100644 --- a/lib/uffizzi/clients/api/api_client.rb +++ b/lib/uffizzi/clients/api/api_client.rb @@ -5,6 +5,7 @@ module ApiClient include ApiRoutes + def create_session(hostname, params = {}) uri = session_uri(hostname) response = Uffizzi::HttpClient.make_post_request(uri, params, false) @@ -26,6 +27,20 @@ def fetch_projects(hostname) build_response(response) end + def create_credential(hostname, params) + uri = credentials_uri(hostname) + response = Uffizzi::HttpClient.make_post_request(uri, params) + + build_response(response) + end + + def fetch_deployment_services(hostname, project_slug, deployment_id) + uri = preview_services_uri(hostname, project_slug, deployment_id) + response = Uffizzi::HttpClient.make_get_request(uri) + + build_response(response) + end + def set_compose_file(hostname, params, project_slug) uri = compose_file_uri(hostname, project_slug) response = Uffizzi::HttpClient.make_post_request(uri, params) @@ -35,7 +50,28 @@ def set_compose_file(hostname, params, project_slug) def unset_compose_file(hostname, project_slug) uri = compose_file_uri(hostname, project_slug) - response = Uffizzi::HttpClient.make_delete_request(uri, true) + response = Uffizzi::HttpClient.make_delete_request(uri) + + build_response(response) + end + + def fetch_secrets(hostname, project_slug) + uri = secrets_uri(hostname, project_slug) + response = Uffizzi::HttpClient.make_get_request(uri) + + build_response(response) + end + + def bulk_create_secrets(hostname, project_slug, params) + uri = "#{secrets_uri(hostname, project_slug)}/bulk_create" + response = Uffizzi::HttpClient.make_post_request(uri, params) + + build_response(response) + end + + def delete_secret(hostname, project_slug, id) + uri = secret_uri(hostname, project_slug, id) + response = Uffizzi::HttpClient.make_delete_request(uri) build_response(response) end @@ -70,7 +106,7 @@ def create_deployment(hostname, project_slug, params) def delete_deployment(hostname, project_slug, deployment_id) uri = deployment_uri(hostname, project_slug, deployment_id) - response = Uffizzi::HttpClient.make_delete_request(uri, true) + response = Uffizzi::HttpClient.make_delete_request(uri) build_response(response) end @@ -82,6 +118,13 @@ def describe_deployment(hostname, project_slug, deployment_id) build_response(response) end + def fetch_events(hostname, project_slug, deployment_id) + uri = events_uri(hostname, project_slug, deployment_id) + response = Uffizzi::HttpClient.make_get_request(uri) + + build_response(response) + end + def get_activity_items(hostname, project_slug, deployment_id) uri = activity_items_uri(hostname, project_slug, deployment_id) response = Uffizzi::HttpClient.make_get_request(uri) diff --git a/lib/uffizzi/clients/api/api_routes.rb b/lib/uffizzi/clients/api/api_routes.rb index 0c1206a3..eea86260 100644 --- a/lib/uffizzi/clients/api/api_routes.rb +++ b/lib/uffizzi/clients/api/api_routes.rb @@ -1,16 +1,27 @@ # frozen_string_literal: true +require 'cgi' + module ApiRoutes - def session_uri(hostname) - "#{hostname}/api/cli/v1/session" + def compose_file_uri(hostname, project_slug) + "#{hostname}/api/cli/v1/projects/#{project_slug}/compose_file" end def projects_uri(hostname) "#{hostname}/api/cli/v1/projects" end - def compose_file_uri(hostname, project_slug) - "#{hostname}/api/cli/v1/projects/#{project_slug}/compose_file" + def secret_uri(hostname, project_slug, id) + path_id = CGI.escape(id) + "#{hostname}/api/cli/v1/projects/#{project_slug}/secrets/#{path_id}" + end + + def secrets_uri(hostname, project_slug) + "#{hostname}/api/cli/v1/projects/#{project_slug}/secrets" + end + + def session_uri(hostname) + "#{hostname}/api/cli/v1/session" end def validate_compose_file_uri(hostname, project_slug) @@ -32,4 +43,16 @@ def activity_items_uri(hostname, project_slug, deployment_id) def deploy_containers_uri(hostname, project_slug, deployment_id) "#{hostname}/api/cli/v1/projects/#{project_slug}/deployments/#{deployment_id}/deploy_containers" end + + def events_uri(hostname, project_slug, deployment_id) + "#{hostname}/api/cli/v1/projects/#{project_slug}/deployments/#{deployment_id}/events" + end + + def credentials_uri(hostname) + "#{hostname}/api/cli/v1/account/credentials" + end + + def preview_services_uri(hostname, project_slug, deployment_id) + "#{hostname}/api/cli/v1/projects/#{project_slug}/deployments/#{deployment_id}/containers" + end end diff --git a/lib/uffizzi/services/preview_service.rb b/lib/uffizzi/services/preview_service.rb new file mode 100644 index 00000000..fb17ff03 --- /dev/null +++ b/lib/uffizzi/services/preview_service.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class PreviewService + class << self + def read_deployment_id(deployment_name) + return nil unless deployment_name.start_with?('deployment-') + return nil unless deployment_name.split('-').size == 2 + + deployment_id = deployment_name.split('-').last + return nil if deployment_id.to_i.to_s != deployment_id + + deployment_id + end + end +end diff --git a/lib/uffizzi/shell.rb b/lib/uffizzi/shell.rb index f3459a9e..cea63d69 100644 --- a/lib/uffizzi/shell.rb +++ b/lib/uffizzi/shell.rb @@ -9,8 +9,16 @@ def initialize @shell = Thor::Shell::Basic.new end - def say(msg) - @shell.say(msg) + def say(message) + @shell.say(message) + end + + def print_in_columns(messages) + @shell.print_in_columns(messages) + end + + def print_table(table_data) + @shell.print_table(table_data) end def last_message diff --git a/lib/uffizzi/version.rb b/lib/uffizzi/version.rb index 0f1cf8ff..497a11e4 100644 --- a/lib/uffizzi/version.rb +++ b/lib/uffizzi/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Uffizzi - VERSION = '0.2.2' + VERSION = '0.3.2' end diff --git a/man/uffizzi-events b/man/uffizzi-events new file mode 100644 index 00000000..015577f3 --- /dev/null +++ b/man/uffizzi-events @@ -0,0 +1,30 @@ +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "UFFIZZI\-EVENTS" "" "March 2022" "" +.SH "NAME" +\fBuffizzi preview events \- show the deployment event logs for a preview\fR +.P +.SH SYNOPSIS +uffizzi preview events [PREVIEW_ID] [UFFIZZI_WIDE_FLAG \|\.\|\.\|\.] +.P +.SH DESCRIPTION +Shows the deployment event logs for a given preview\. + +This command can fail for the following reasons: + \- There is no preview with the given PREVIEW_ID + +For more information on event logs, see: +https://docs\.uffizzi\.com/cli + +.SH POSITIONAL ARGUMENTS +[PREVIEW_ID] + The ID of the preview that you want to see events for\. +.P +.SH UFFIZZI WIDE FLAGS +These flags are available to all commands: \-\-project\. Run $ uffizzi help for details\. +.P +.SH EXAMPLES +The following command shows deployment events for the preview with ID deployment\-67: +.P + $ uffizzi preview events deployment\-67 + diff --git a/man/uffizzi-events.ronn b/man/uffizzi-events.ronn new file mode 100644 index 00000000..9c523600 --- /dev/null +++ b/man/uffizzi-events.ronn @@ -0,0 +1,29 @@ +NAME + uffizzi preview events - show the deployment event logs for a + preview + +SYNOPSIS + uffizzi preview events [PREVIEW_ID] [UFFIZZI_WIDE_FLAG ...] + +DESCRIPTION + Shows the deployment event logs for a given preview. + + This command can fail for the following reasons: + - There is no preview with the given PREVIEW_ID + + For more information on event logs, see: + https://docs.uffizzi.com/cli + +POSITIONAL ARGUMENTS + [PREVIEW_ID] + The ID of the preview that you want to see events for. + +UFFIZZI WIDE FLAGS + These flags are available to all commands: --project. Run $ uffizzi + help for details. + +EXAMPLES + The following command shows deployment events for the preview with + ID deployment-67: + + $ uffizzi preview events deployment-67 diff --git a/test/factories/sequences.rb b/test/factories/sequences.rb index 3fafe6b6..2da7f96f 100644 --- a/test/factories/sequences.rb +++ b/test/factories/sequences.rb @@ -8,4 +8,8 @@ sequence :email do |n| "user#{n}@example.com" end + + sequence :url do + Faker::Internet.url + end end diff --git a/test/fixtures/files/google/service-account.json b/test/fixtures/files/google/service-account.json new file mode 100644 index 00000000..92dae426 --- /dev/null +++ b/test/fixtures/files/google/service-account.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "project_id", + "private_key_id": "key", + "private_key": "private_key", + "client_email": "email@example.com", + "client_id": "123", + "auth_uri": "https://example.com", + "token_uri": "https://example.com", + "auth_provider_x509_cert_url": "https://example.com", + "client_x509_cert_url": "https://example.com" +} diff --git a/test/fixtures/files/uffizzi/uffizzi_amazon_credential.json b/test/fixtures/files/uffizzi/uffizzi_amazon_credential.json new file mode 100644 index 00000000..9aab77f7 --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_amazon_credential.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "username": "test", + "password": "********", + "type": "UffizziCore::Credential::Amazon", + "state": "active" +} diff --git a/test/fixtures/files/uffizzi/uffizzi_azure_credential.json b/test/fixtures/files/uffizzi/uffizzi_azure_credential.json new file mode 100644 index 00000000..85b8efc8 --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_azure_credential.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "username": "test", + "password": "********", + "type": "UffizziCore::Credential::Azure", + "state": "active" +} diff --git a/test/fixtures/files/uffizzi/uffizzi_credential_failed.json b/test/fixtures/files/uffizzi/uffizzi_credential_failed.json new file mode 100644 index 00000000..784c97ba --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_credential_failed.json @@ -0,0 +1,7 @@ +{ + "errors": { + "username": [ + "We were unable to log you in to docker registry. Please verify that your username and password are correct." + ] + } +} diff --git a/test/fixtures/files/uffizzi/uffizzi_dockerhub_credential.json b/test/fixtures/files/uffizzi/uffizzi_dockerhub_credential.json new file mode 100644 index 00000000..1ec366fb --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_dockerhub_credential.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "username": "test", + "password": "********", + "type": "UffizziCore::Credential::DockerHub", + "state": "active" +} diff --git a/test/fixtures/files/uffizzi/uffizzi_google_credential.json b/test/fixtures/files/uffizzi/uffizzi_google_credential.json new file mode 100644 index 00000000..a59cf85d --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_google_credential.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "username": "test", + "password": "********", + "type": "UffizziCore::Credential::Google", + "state": "active" +} diff --git a/test/fixtures/files/uffizzi/uffizzi_preview_events_success.json b/test/fixtures/files/uffizzi/uffizzi_preview_events_success.json new file mode 100644 index 00000000..83eab0cd --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_preview_events_success.json @@ -0,0 +1,10 @@ +{ + "events": [ + { + "first_timestamp": "2022-03-23T10:54:52Z", + "last_timestamp": "2022-03-23T13:49:37Z", + "reason": "Unhealthy", + "message": "Startup probe failed: dial tcp 10.20.0.46:80: connect: connection refused" + } + ] +} diff --git a/test/fixtures/files/uffizzi/uffizzi_preview_services_list.json b/test/fixtures/files/uffizzi/uffizzi_preview_services_list.json new file mode 100644 index 00000000..c79bdc34 --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_preview_services_list.json @@ -0,0 +1,14 @@ +{ + "containers": [ + { + "id": 321, + "name": "webhooks-test-app:latest", + "memory_limit": null, + "memory_request": null, + "continuously_deploy": "disabled", + "variables": null, + "secret_variables": null, + "container_config_files": [] + } + ] +} diff --git a/test/fixtures/files/uffizzi/uffizzi_project_secrets_success.json b/test/fixtures/files/uffizzi/uffizzi_project_secrets_success.json new file mode 100644 index 00000000..d5a1711c --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_project_secrets_success.json @@ -0,0 +1,10 @@ +{ + "secrets": [ + { + "name": "my first secret" + }, + { + "name": "my second secret" + } + ] +} diff --git a/test/support/mocks/mock_shell.rb b/test/support/mocks/mock_shell.rb new file mode 100644 index 00000000..d674aad9 --- /dev/null +++ b/test/support/mocks/mock_shell.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class MockShell + attr_accessor :messages + + def initialize + @messages = [] + end + + def say(message) + @messages << message + end + + def last_message + @messages.last + end + + def ask(answer, *_args) + answer + end + + def print_table(table_data) + table_data + end + + def print_in_columns(columns_data) + columns_data + end +end diff --git a/test/support/uffizzi_preview_stub_support.rb b/test/support/uffizzi_preview_stub_support.rb index 82e15271..b0f20fa1 100644 --- a/test/support/uffizzi_preview_stub_support.rb +++ b/test/support/uffizzi_preview_stub_support.rb @@ -40,4 +40,16 @@ def stub_uffizzi_preview_activity_items(base_url, status, body, headers, project stub_request(:get, url).to_return(status: status, body: body.to_json, headers: headers) end + + def stub_uffizzi_preview_events_success(body, deployment_id, project_slug) + url = events_uri(Uffizzi.configuration.hostname, project_slug, deployment_id) + + stub_request(:get, url).to_return(status: 200, body: body.to_json) + end + + def stub_uffizzi_preview_services_list(body, project_slug, deployment_id) + url = preview_services_uri(Uffizzi.configuration.hostname, project_slug, deployment_id) + + stub_request(:get, url).to_return(status: 200, body: body.to_json, headers: {}) + end end diff --git a/test/support/uffizzi_stub_support.rb b/test/support/uffizzi_stub_support.rb index 29f164ee..e4a183b2 100644 --- a/test/support/uffizzi_stub_support.rb +++ b/test/support/uffizzi_stub_support.rb @@ -48,4 +48,34 @@ def stub_uffizzi_create_deployment(base_url, status, body, headers) stub_request(:post, url).to_return(status: status, body: body.to_json, headers: headers) end + + def stub_uffizzi_project_secret_list(body, project_slug) + url = secrets_uri(Uffizzi.configuration.hostname, project_slug) + + stub_request(:get, url).to_return(status: 200, body: body.to_json) + end + + def stub_uffizzi_project_secret_create(body, project_slug) + url = "#{secrets_uri(Uffizzi.configuration.hostname, project_slug)}/bulk_create" + + stub_request(:post, url).to_return(status: 201, body: body.to_json) + end + + def stub_uffizzi_project_secret_delete(project_slug, secret_id) + url = secret_uri(Uffizzi.configuration.hostname, project_slug, secret_id) + + stub_request(:delete, url).to_return(status: 204, body: '') + end + + def stub_uffizzi_create_credential(body) + uri = credentials_uri(Uffizzi.configuration.hostname) + + stub_request(:post, uri).to_return(status: 201, body: body.to_json) + end + + def stub_uffizzi_create_credential_fail(body) + uri = credentials_uri(Uffizzi.configuration.hostname) + + stub_request(:post, uri).to_return(status: 422, body: body.to_json) + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index d3832211..8eb9ca81 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,6 +4,7 @@ Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |f| require f } require 'factory_bot' +require 'faker' require 'net/http' require 'io/console' require 'byebug' @@ -31,8 +32,8 @@ class Minitest::Test def before_setup super - $stdout = StringIO.new - $stdout.truncate(0) + @mock_shell = MockShell.new + Uffizzi::UI::Shell.stubs(:new).returns(@mock_shell) Uffizzi::ConfigFile.delete end diff --git a/test/uffizzi/cli/connect_test.rb b/test/uffizzi/cli/connect_test.rb new file mode 100644 index 00000000..af368844 --- /dev/null +++ b/test/uffizzi/cli/connect_test.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ConnectTest < Minitest::Test + def setup + @cli = Uffizzi::CLI.new + + sign_in + end + + def test_connect_docker_hub_success + body = json_fixture('files/uffizzi/uffizzi_dockerhub_credential.json') + stubbed_uffizzi_create_credential = stub_uffizzi_create_credential(body) + + credential_params = { + username: generate(:string), + password: generate(:string), + } + + console_mock = mock('console_mock') + console_mock.stubs(:write) + console_mock.stubs(:gets).returns(credential_params[:username]) + console_mock.stubs(:getpass).returns(credential_params[:password]) + IO.stubs(:console).returns(console_mock) + + @cli.connect('docker-hub') + + assert_equal('Successfully connected to DockerHub', Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_create_credential) + end + + def test_connect_azure_success + body = json_fixture('files/uffizzi/uffizzi_azure_credential.json') + stubbed_uffizzi_create_credential = stub_uffizzi_create_credential(body) + + credential_params = { + registry_url: generate(:url), + username: generate(:string), + password: generate(:string), + } + + console_mock = mock('console_mock') + console_mock.stubs(:write) + console_mock.stubs(:gets).returns(credential_params[:registry_url], credential_params[:username]) + console_mock.stubs(:getpass).returns(credential_params[:password]) + IO.stubs(:console).returns(console_mock) + + @cli.connect('acr') + + assert_equal('Successfully connected to ACR', Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_create_credential) + end + + def test_connect_amazon_success + body = json_fixture('files/uffizzi/uffizzi_amazon_credential.json') + stubbed_uffizzi_create_credential = stub_uffizzi_create_credential(body) + + credential_params = { + registry_url: generate(:url), + username: generate(:string), + password: generate(:string), + } + + console_mock = mock('console_mock') + console_mock.stubs(:write) + console_mock.stubs(:gets).returns(credential_params[:registry_url], credential_params[:username]) + console_mock.stubs(:getpass).returns(credential_params[:password]) + IO.stubs(:console).returns(console_mock) + + @cli.connect('ecr') + + assert_equal('Successfully connected to ECR', Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_create_credential) + end + + def test_connect_google_success + body = json_fixture('files/uffizzi/uffizzi_google_credential.json') + credential_path = "#{Dir.pwd}/test/fixtures/files/google/service-account.json" + + stubbed_uffizzi_create_credential = stub_uffizzi_create_credential(body) + + @cli.connect('gcr', credential_path) + + assert_equal('Successfully connected to GCR', Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_create_credential) + end + + def test_unknown_credential_type + credential_type = generate(:string) + + @cli.connect(credential_type) + + assert_equal('Unsupported credential type.', Uffizzi.ui.last_message) + end + + def test_connect_credential_failed + body = json_fixture('files/uffizzi/uffizzi_credential_failed.json') + stubbed_uffizzi_create_credential = stub_uffizzi_create_credential_fail(body) + + credential_params = { + username: generate(:string), + password: generate(:string), + } + + console_mock = mock('console_mock') + console_mock.stubs(:write) + console_mock.stubs(:gets).returns(credential_params[:username]) + console_mock.stubs(:getpass).returns(credential_params[:password]) + IO.stubs(:console).returns(console_mock) + + @cli.connect('docker-hub') + + assert_equal(body[:errors][:username].first, Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_create_credential) + end +end diff --git a/test/uffizzi/cli/preview/service_test.rb b/test/uffizzi/cli/preview/service_test.rb new file mode 100644 index 00000000..06d68b52 --- /dev/null +++ b/test/uffizzi/cli/preview/service_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ServiceTest < Minitest::Test + def setup + @service = Uffizzi::CLI::Preview::Service.new + + sign_in + Uffizzi::ConfigFile.write_option(:project, 'dbp') + @project_slug = Uffizzi::ConfigFile.read_option(:project) + end + + def test_list_preview_services + body = json_fixture('files/uffizzi/uffizzi_preview_services_list.json') + deployment_id = 318 + stubbed_uffizzi_preview_services_list = stub_uffizzi_preview_services_list(body, @project_slug, deployment_id) + + @service.list("deployment-#{deployment_id}") + + assert_requested(stubbed_uffizzi_preview_services_list) + end +end diff --git a/test/uffizzi/cli/preview_test.rb b/test/uffizzi/cli/preview_test.rb index 07814e09..c7824c1e 100644 --- a/test/uffizzi/cli/preview_test.rb +++ b/test/uffizzi/cli/preview_test.rb @@ -161,4 +161,14 @@ def test_create_preview_with_deleteing_preview_during_deployment assert_requested(stubbed_uffizzi_preview_deploy_containers) assert_requested(stubbed_uffizzi_preview_create) end + + def test_events_preview_success + events_body = json_fixture('files/uffizzi/uffizzi_preview_events_success.json') + deployment_id = 1 + stubbed_uffizzi_preview_events_success = stub_uffizzi_preview_events_success(events_body, deployment_id, @project_slug) + + @preview.events("deployment-#{deployment_id}") + + assert_requested(stubbed_uffizzi_preview_events_success) + end end diff --git a/test/uffizzi/cli/project/secret_test.rb b/test/uffizzi/cli/project/secret_test.rb new file mode 100644 index 00000000..afe748c8 --- /dev/null +++ b/test/uffizzi/cli/project/secret_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'test_helper' + +class SecretTest < Minitest::Test + def setup + @secret = Uffizzi::CLI::Project::Secret.new + @project_slug = 'default' + Uffizzi::ConfigFile.write_option(:project, @project_slug) + + sign_in + end + + def test_secret_list_success + body = json_fixture('files/uffizzi/uffizzi_project_secrets_success.json') + stubbed_uffizzi_secrets = stub_uffizzi_project_secret_list(body, @project_slug) + + result = @secret.list + _table_header, *secrets = result + expected_secrets_response = body[:secrets].map { |secret| secret[:name] } + + assert_equal(expected_secrets_response, secrets.flatten) + assert_requested(stubbed_uffizzi_secrets) + end + + def test_secret_create_success + body = json_fixture('files/uffizzi/uffizzi_projects_success_one_project.json') + secret_name = 'my secret' + secret_value = 'password' + stubbed_uffizzi_secrets = stub_uffizzi_project_secret_create(body, @project_slug) + + $stdin = StringIO.new(secret_value) + @secret.create(secret_name) + + assert_equal('The secret was successfully created', Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_secrets) + end + + def test_secret_delete_success + secret_name = 'my secret' + stubbed_uffizzi_secrets = stub_uffizzi_project_secret_delete(@project_slug, secret_name) + + @secret.delete(secret_name) + + assert_equal('The secret was successfully deleted', Uffizzi.ui.last_message) + assert_requested(stubbed_uffizzi_secrets) + end +end diff --git a/uffizzi.gemspec b/uffizzi.gemspec index a9130bc6..f0e84c31 100644 --- a/uffizzi.gemspec +++ b/uffizzi.gemspec @@ -27,9 +27,11 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + spec.add_development_dependency 'bump' spec.add_development_dependency 'bundler', '~> 2.2' spec.add_development_dependency 'byebug' spec.add_development_dependency 'factory_bot' + spec.add_development_dependency 'faker' spec.add_development_dependency 'minitest' spec.add_development_dependency 'minitest-power_assert' spec.add_development_dependency 'mocha'