Repository: UffizziCloud/uffizzi
Branch: develop
Commit: a57ce80acba3
Files: 713
Total size: 857.5 KB
Directory structure:
gitextract_iyyaf1qh/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── helm-release.yml
│ ├── main.yml
│ └── release.yml
├── .gitignore
├── .rubocop.yml
├── .ruby-version
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Gemfile
├── INSTALL.md
├── LICENSE
├── Makefile
├── README.md
├── Rakefile
├── app/
│ ├── assets/
│ │ ├── config/
│ │ │ └── manifest.js
│ │ ├── fonts/
│ │ │ └── mtiFontTrackingCode.js
│ │ ├── images/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ └── application.css
│ ├── channels/
│ │ └── application_cable/
│ │ ├── channel.rb
│ │ └── connection.rb
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ └── concerns/
│ │ └── .keep
│ ├── helpers/
│ │ └── application_helper.rb
│ ├── javascript/
│ │ ├── channels/
│ │ │ ├── consumer.js
│ │ │ └── index.js
│ │ └── packs/
│ │ └── application.js
│ ├── jobs/
│ │ └── application_job.rb
│ ├── mailers/
│ │ └── application_mailer.rb
│ ├── models/
│ │ ├── application_record.rb
│ │ └── concerns/
│ │ └── .keep
│ └── views/
│ └── layouts/
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
├── bin/
│ ├── rails
│ ├── rake
│ ├── setup
│ └── yarn
├── charts/
│ └── uffizzi-app/
│ ├── Chart.yaml
│ ├── README.md
│ ├── templates/
│ │ ├── configmap-common.yaml
│ │ ├── configmap-sidekiq.yaml
│ │ ├── configmap-web.yaml
│ │ ├── secret-web.yaml
│ │ ├── sidekiq-deployment.yaml
│ │ ├── web-deployment.yaml
│ │ ├── web-ingress.yaml
│ │ └── web-service.yaml
│ └── values.yaml
├── ci/
│ ├── github-actions/
│ │ └── README.md
│ └── gitlab/
│ └── README.md
├── config/
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── credentials.yml.enc
│ ├── database.yml
│ ├── environment.rb
│ ├── environments/
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers/
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── health_check.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── sidekiq.rb
│ │ └── wrap_parameters.rb
│ ├── locales/
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── secrets.yml
│ ├── settings.yml
│ ├── sidekiq.yml
│ └── storage.yml
├── config.ru
├── core/
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── Dockerfile
│ ├── Gemfile
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ ├── config/
│ │ │ │ └── uffizzi_core_manifest.js
│ │ │ ├── images/
│ │ │ │ └── uffizzi_core/
│ │ │ │ └── .keep
│ │ │ └── stylesheets/
│ │ │ └── uffizzi_core/
│ │ │ └── application.css
│ │ ├── clients/
│ │ │ └── uffizzi_core/
│ │ │ ├── amazon_registry_client.rb
│ │ │ ├── azure_registry_client/
│ │ │ │ └── request_result.rb
│ │ │ ├── azure_registry_client.rb
│ │ │ ├── container_registry_request_decorator.rb
│ │ │ ├── controller_client/
│ │ │ │ └── request_result.rb
│ │ │ ├── controller_client.rb
│ │ │ ├── docker_hub_client/
│ │ │ │ ├── not_authorized_error.rb
│ │ │ │ └── request_result.rb
│ │ │ ├── docker_hub_client.rb
│ │ │ ├── docker_registry_client/
│ │ │ │ └── request_result.rb
│ │ │ ├── docker_registry_client.rb
│ │ │ ├── github_container_registry_client/
│ │ │ │ └── request_result.rb
│ │ │ ├── github_container_registry_client.rb
│ │ │ ├── google_registry_client/
│ │ │ │ └── request_result.rb
│ │ │ └── google_registry_client.rb
│ │ ├── contexts/
│ │ │ └── uffizzi_core/
│ │ │ ├── account_context.rb
│ │ │ ├── base_context.rb
│ │ │ ├── project/
│ │ │ │ └── cluster_context.rb
│ │ │ ├── project_context.rb
│ │ │ └── webhooks_context.rb
│ │ ├── controller_modules/
│ │ │ └── uffizzi_core/
│ │ │ └── api/
│ │ │ └── cli/
│ │ │ └── v1/
│ │ │ ├── accounts_controller_module.rb
│ │ │ ├── projects/
│ │ │ │ ├── clusters_controller_module.rb
│ │ │ │ └── deployments_controller_module.rb
│ │ │ └── projects_controller_module.rb
│ │ ├── controllers/
│ │ │ ├── concerns/
│ │ │ │ └── uffizzi_core/
│ │ │ │ ├── auth_management.rb
│ │ │ │ ├── authorization_concern.rb
│ │ │ │ └── dependency_injection_concern.rb
│ │ │ └── uffizzi_core/
│ │ │ ├── api/
│ │ │ │ └── cli/
│ │ │ │ └── v1/
│ │ │ │ ├── accounts/
│ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ ├── clusters_controller.rb
│ │ │ │ │ ├── credentials_controller.rb
│ │ │ │ │ └── projects_controller.rb
│ │ │ │ ├── accounts_controller.rb
│ │ │ │ ├── application_controller.rb
│ │ │ │ ├── ci/
│ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ └── sessions_controller.rb
│ │ │ │ ├── projects/
│ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ ├── clusters/
│ │ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ │ └── ingresses_controller.rb
│ │ │ │ │ ├── clusters_controller.rb
│ │ │ │ │ ├── compose_files_controller.rb
│ │ │ │ │ ├── deployments/
│ │ │ │ │ │ ├── activity_items_controller.rb
│ │ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ │ ├── containers/
│ │ │ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ │ │ └── logs_controller.rb
│ │ │ │ │ │ ├── containers_controller.rb
│ │ │ │ │ │ └── events_controller.rb
│ │ │ │ │ ├── deployments_controller.rb
│ │ │ │ │ └── secrets_controller.rb
│ │ │ │ ├── projects_controller.rb
│ │ │ │ └── sessions_controller.rb
│ │ │ └── application_controller.rb
│ │ ├── errors/
│ │ │ └── uffizzi_core/
│ │ │ ├── cluster_scale_error.rb
│ │ │ ├── compose_file/
│ │ │ │ ├── build_error.rb
│ │ │ │ ├── credential_error.rb
│ │ │ │ ├── parse_error.rb
│ │ │ │ └── secrets_error.rb
│ │ │ ├── compose_file_error.rb
│ │ │ ├── container_registry_error.rb
│ │ │ ├── deployment/
│ │ │ │ └── image_pull_error.rb
│ │ │ ├── deployment_not_found_error.rb
│ │ │ └── registry_not_supported_error.rb
│ │ ├── forms/
│ │ │ └── uffizzi_core/
│ │ │ ├── api/
│ │ │ │ └── cli/
│ │ │ │ └── v1/
│ │ │ │ ├── account/
│ │ │ │ │ └── credential/
│ │ │ │ │ ├── check_credential_form.rb
│ │ │ │ │ ├── create_form.rb
│ │ │ │ │ └── update_form.rb
│ │ │ │ ├── cluster/
│ │ │ │ │ ├── create_form.rb
│ │ │ │ │ └── sync_form.rb
│ │ │ │ ├── compose_file/
│ │ │ │ │ ├── check_credentials_form.rb
│ │ │ │ │ ├── cli_form.rb
│ │ │ │ │ ├── create_form.rb
│ │ │ │ │ ├── template_form.rb
│ │ │ │ │ └── update_form.rb
│ │ │ │ ├── config_file/
│ │ │ │ │ └── create_form.rb
│ │ │ │ ├── deployment/
│ │ │ │ │ ├── create_form.rb
│ │ │ │ │ └── update_form.rb
│ │ │ │ ├── project/
│ │ │ │ │ ├── create_form.rb
│ │ │ │ │ └── update_form.rb
│ │ │ │ ├── secret/
│ │ │ │ │ └── bulk_assign_form.rb
│ │ │ │ ├── session_create_form.rb
│ │ │ │ └── template/
│ │ │ │ └── create_form.rb
│ │ │ ├── application_form.rb
│ │ │ ├── application_form_without_active_record.rb
│ │ │ └── mass_assignment_control_concern.rb
│ │ ├── helpers/
│ │ │ └── uffizzi_core/
│ │ │ └── application_helper.rb
│ │ ├── jobs/
│ │ │ └── uffizzi_core/
│ │ │ ├── account/
│ │ │ │ ├── create_credential_job.rb
│ │ │ │ └── update_credential_job.rb
│ │ │ ├── activity_item/
│ │ │ │ └── docker/
│ │ │ │ └── update_digest_job.rb
│ │ │ ├── application_job.rb
│ │ │ ├── cluster/
│ │ │ │ ├── delete_job.rb
│ │ │ │ ├── deploy_job.rb
│ │ │ │ ├── manage_deploying_job.rb
│ │ │ │ ├── manage_scaling_down_job.rb
│ │ │ │ └── manage_scaling_up_job.rb
│ │ │ ├── config_file/
│ │ │ │ └── apply_job.rb
│ │ │ └── deployment/
│ │ │ ├── create_credential_job.rb
│ │ │ ├── create_credentials_job.rb
│ │ │ ├── create_job.rb
│ │ │ ├── delete_credential_job.rb
│ │ │ ├── delete_job.rb
│ │ │ ├── deploy_containers_job.rb
│ │ │ ├── manage_deploy_activity_item_job.rb
│ │ │ └── update_credential_job.rb
│ │ ├── lib/
│ │ │ └── uffizzi_core/
│ │ │ ├── concerns/
│ │ │ │ └── models/
│ │ │ │ ├── account.rb
│ │ │ │ ├── activity_item.rb
│ │ │ │ ├── cluster.rb
│ │ │ │ ├── comment.rb
│ │ │ │ ├── compose_file.rb
│ │ │ │ ├── config_file.rb
│ │ │ │ ├── container.rb
│ │ │ │ ├── container_config_file.rb
│ │ │ │ ├── container_host_volume_file.rb
│ │ │ │ ├── coupon.rb
│ │ │ │ ├── credential.rb
│ │ │ │ ├── deployment.rb
│ │ │ │ ├── deployment_event.rb
│ │ │ │ ├── event.rb
│ │ │ │ ├── host_volume_file.rb
│ │ │ │ ├── kubernetes_distribution.rb
│ │ │ │ ├── membership.rb
│ │ │ │ ├── payment.rb
│ │ │ │ ├── price.rb
│ │ │ │ ├── product.rb
│ │ │ │ ├── project.rb
│ │ │ │ ├── rating.rb
│ │ │ │ ├── repo.rb
│ │ │ │ ├── role.rb
│ │ │ │ ├── secret.rb
│ │ │ │ ├── template.rb
│ │ │ │ ├── user.rb
│ │ │ │ └── user_project.rb
│ │ │ └── rbac/
│ │ │ └── user_access_service.rb
│ │ ├── mailers/
│ │ │ └── uffizzi_core/
│ │ │ └── application_mailer.rb
│ │ ├── models/
│ │ │ ├── concerns/
│ │ │ │ └── uffizzi_core/
│ │ │ │ ├── hashid_concern.rb
│ │ │ │ └── state_machine_concern.rb
│ │ │ └── uffizzi_core/
│ │ │ ├── account.rb
│ │ │ ├── activity_item/
│ │ │ │ ├── docker.rb
│ │ │ │ ├── github.rb
│ │ │ │ └── memory_limit.rb
│ │ │ ├── activity_item.rb
│ │ │ ├── application_record.rb
│ │ │ ├── cluster.rb
│ │ │ ├── comment.rb
│ │ │ ├── compose_file.rb
│ │ │ ├── config_file.rb
│ │ │ ├── container.rb
│ │ │ ├── container_config_file.rb
│ │ │ ├── container_host_volume_file.rb
│ │ │ ├── continuous_preview.rb
│ │ │ ├── coupon.rb
│ │ │ ├── credential/
│ │ │ │ ├── amazon.rb
│ │ │ │ ├── azure.rb
│ │ │ │ ├── docker_hub.rb
│ │ │ │ ├── docker_registry.rb
│ │ │ │ ├── github_container_registry.rb
│ │ │ │ └── google.rb
│ │ │ ├── credential.rb
│ │ │ ├── database.rb
│ │ │ ├── database_offering.rb
│ │ │ ├── deployment.rb
│ │ │ ├── deployment_event.rb
│ │ │ ├── event.rb
│ │ │ ├── host_volume_file.rb
│ │ │ ├── kubernetes_distribution.rb
│ │ │ ├── membership.rb
│ │ │ ├── payment.rb
│ │ │ ├── price.rb
│ │ │ ├── product.rb
│ │ │ ├── project.rb
│ │ │ ├── rating.rb
│ │ │ ├── repo/
│ │ │ │ ├── amazon.rb
│ │ │ │ ├── azure.rb
│ │ │ │ ├── docker_hub.rb
│ │ │ │ ├── docker_registry.rb
│ │ │ │ ├── github_container_registry.rb
│ │ │ │ └── google.rb
│ │ │ ├── repo.rb
│ │ │ ├── role.rb
│ │ │ ├── secret.rb
│ │ │ ├── template.rb
│ │ │ ├── user.rb
│ │ │ └── user_project.rb
│ │ ├── policies/
│ │ │ └── uffizzi_core/
│ │ │ ├── api/
│ │ │ │ └── cli/
│ │ │ │ └── v1/
│ │ │ │ ├── accounts/
│ │ │ │ │ ├── clusters_policy.rb
│ │ │ │ │ ├── credentials_policy.rb
│ │ │ │ │ └── projects_policy.rb
│ │ │ │ ├── accounts_policy.rb
│ │ │ │ ├── projects/
│ │ │ │ │ ├── clusters/
│ │ │ │ │ │ └── ingresses_policy.rb
│ │ │ │ │ ├── clusters_policy.rb
│ │ │ │ │ ├── compose_files_policy.rb
│ │ │ │ │ ├── deployments/
│ │ │ │ │ │ ├── activity_items_policy.rb
│ │ │ │ │ │ ├── containers_policy.rb
│ │ │ │ │ │ └── events_policy.rb
│ │ │ │ │ ├── deployments_policy.rb
│ │ │ │ │ └── secrets_policy.rb
│ │ │ │ └── projects_policy.rb
│ │ │ └── application_policy.rb
│ │ ├── repositories/
│ │ │ └── uffizzi_core/
│ │ │ ├── account_repo.rb
│ │ │ ├── activity_item_repo.rb
│ │ │ ├── basic_order_repo.rb
│ │ │ ├── cluster_repo.rb
│ │ │ ├── comment_repo.rb
│ │ │ ├── compose_file_repo.rb
│ │ │ ├── config_file_repo.rb
│ │ │ ├── container_repo.rb
│ │ │ ├── credential_repo.rb
│ │ │ ├── deployment_repo.rb
│ │ │ ├── event_repo.rb
│ │ │ ├── host_volume_file_repo.rb
│ │ │ ├── membership_repo.rb
│ │ │ ├── price_repo.rb
│ │ │ ├── product_repo.rb
│ │ │ ├── project_repo.rb
│ │ │ ├── repo_repo.rb
│ │ │ ├── template_repo.rb
│ │ │ ├── usage_repo.rb
│ │ │ └── user_repo.rb
│ │ ├── responders/
│ │ │ └── uffizzi_core/
│ │ │ └── json_responder.rb
│ │ ├── serializers/
│ │ │ └── uffizzi_core/
│ │ │ ├── api/
│ │ │ │ └── cli/
│ │ │ │ └── v1/
│ │ │ │ ├── account_serializer.rb
│ │ │ │ ├── accounts/
│ │ │ │ │ ├── cluster_serializer/
│ │ │ │ │ │ └── project_serializer.rb
│ │ │ │ │ ├── cluster_serializer.rb
│ │ │ │ │ ├── credential_serializer.rb
│ │ │ │ │ └── project_serializer.rb
│ │ │ │ ├── project_serializer/
│ │ │ │ │ ├── account_serializer.rb
│ │ │ │ │ ├── compose_file_serializer.rb
│ │ │ │ │ └── deployment_serializer.rb
│ │ │ │ ├── project_serializer.rb
│ │ │ │ ├── projects/
│ │ │ │ │ ├── cluster_serializer.rb
│ │ │ │ │ ├── compose_file_serializer.rb
│ │ │ │ │ ├── deployment_serializer/
│ │ │ │ │ │ ├── container_serializer.rb
│ │ │ │ │ │ └── user_serializer.rb
│ │ │ │ │ ├── deployment_serializer.rb
│ │ │ │ │ ├── deployments/
│ │ │ │ │ │ ├── activity_item_serializer.rb
│ │ │ │ │ │ ├── container_serializer/
│ │ │ │ │ │ │ ├── container_config_file_serializer/
│ │ │ │ │ │ │ │ └── config_file_serializer.rb
│ │ │ │ │ │ │ └── container_config_file_serializer.rb
│ │ │ │ │ │ └── container_serializer.rb
│ │ │ │ │ ├── deployments_serializer/
│ │ │ │ │ │ └── user_serializer.rb
│ │ │ │ │ ├── deployments_serializer.rb
│ │ │ │ │ ├── secret_serializer.rb
│ │ │ │ │ └── short_cluster_serializer.rb
│ │ │ │ ├── short_project_serializer.rb
│ │ │ │ ├── user_serializer/
│ │ │ │ │ └── account_serializer.rb
│ │ │ │ └── user_serializer.rb
│ │ │ ├── base_serializer.rb
│ │ │ └── controller/
│ │ │ ├── apply_config_file/
│ │ │ │ └── config_file_serializer.rb
│ │ │ ├── create_cluster/
│ │ │ │ └── cluster_serializer.rb
│ │ │ ├── create_credential/
│ │ │ │ └── credential_serializer.rb
│ │ │ ├── create_deployment/
│ │ │ │ └── deployment_serializer.rb
│ │ │ ├── deploy_containers/
│ │ │ │ ├── compose_file_serializer.rb
│ │ │ │ ├── container_serializer/
│ │ │ │ │ ├── container_config_file_serializer/
│ │ │ │ │ │ └── config_file_serializer.rb
│ │ │ │ │ ├── container_config_file_serializer.rb
│ │ │ │ │ └── container_host_volume_file_serializer.rb
│ │ │ │ ├── container_serializer.rb
│ │ │ │ ├── credential_serializer.rb
│ │ │ │ └── host_volume_file_serializer.rb
│ │ │ └── update_cluster/
│ │ │ └── cluster_serializer.rb
│ │ ├── services/
│ │ │ └── uffizzi_core/
│ │ │ ├── account_service.rb
│ │ │ ├── activity_item_service.rb
│ │ │ ├── ci_service.rb
│ │ │ ├── cluster_service.rb
│ │ │ ├── compose_file/
│ │ │ │ ├── builders/
│ │ │ │ │ ├── container_builder_service.rb
│ │ │ │ │ ├── container_config_files_builder_service.rb
│ │ │ │ │ ├── container_host_volume_files_builder_service.rb
│ │ │ │ │ ├── docker_repo_builder_service.rb
│ │ │ │ │ ├── template_builder_service.rb
│ │ │ │ │ └── variables_builder_service.rb
│ │ │ │ ├── config_files_service.rb
│ │ │ │ ├── config_option_service.rb
│ │ │ │ ├── container_service.rb
│ │ │ │ ├── dependencies_service.rb
│ │ │ │ ├── errors_service.rb
│ │ │ │ ├── github_dependencies_service.rb
│ │ │ │ ├── host_volume_files_service.rb
│ │ │ │ ├── parsers/
│ │ │ │ │ ├── configs_parser_service.rb
│ │ │ │ │ ├── continuous_preview_parser_service.rb
│ │ │ │ │ ├── ingress_parser_service.rb
│ │ │ │ │ ├── named_volumes_parser_service.rb
│ │ │ │ │ ├── secrets_parser_service.rb
│ │ │ │ │ ├── services/
│ │ │ │ │ │ ├── command_parser_service.rb
│ │ │ │ │ │ ├── configs_parser_service.rb
│ │ │ │ │ │ ├── deploy_parser_service.rb
│ │ │ │ │ │ ├── entrypoint_parser_service.rb
│ │ │ │ │ │ ├── env_file_parser_service.rb
│ │ │ │ │ │ ├── environment_parser_service.rb
│ │ │ │ │ │ ├── healthcheck_parser_service.rb
│ │ │ │ │ │ ├── image_parser_service.rb
│ │ │ │ │ │ ├── secrets_parser_service.rb
│ │ │ │ │ │ └── volumes_parser_service.rb
│ │ │ │ │ ├── services_parser_service.rb
│ │ │ │ │ └── variables_parser_service.rb
│ │ │ │ └── template_service.rb
│ │ │ ├── compose_file_service.rb
│ │ │ ├── container_registry/
│ │ │ │ ├── amazon_service.rb
│ │ │ │ ├── azure_service.rb
│ │ │ │ ├── docker_hub_service.rb
│ │ │ │ ├── docker_registry_service.rb
│ │ │ │ ├── github_container_registry_service.rb
│ │ │ │ └── google_service.rb
│ │ │ ├── container_registry_service.rb
│ │ │ ├── container_service.rb
│ │ │ ├── controller_service.rb
│ │ │ ├── controller_settings_service.rb
│ │ │ ├── deployment/
│ │ │ │ ├── domain_service.rb
│ │ │ │ └── memory_service.rb
│ │ │ ├── deployment_service.rb
│ │ │ ├── logs_service.rb
│ │ │ ├── manage_activity_items_service.rb
│ │ │ ├── project_service.rb
│ │ │ ├── repo_service.rb
│ │ │ ├── response_service.rb
│ │ │ ├── template/
│ │ │ │ └── memory_service.rb
│ │ │ ├── token_service.rb
│ │ │ ├── user_access_service.rb
│ │ │ └── user_generator_service.rb
│ │ ├── utils/
│ │ │ └── uffizzi_core/
│ │ │ └── converters.rb
│ │ └── validators/
│ │ └── uffizzi_core/
│ │ ├── email_validator.rb
│ │ ├── environment_variable_list_validator.rb
│ │ └── image_command_args_validator.rb
│ ├── bin/
│ │ └── rails
│ ├── config/
│ │ ├── initializers/
│ │ │ ├── rswag_api.rb
│ │ │ ├── rswag_ui.rb
│ │ │ └── swagger_yard.rb
│ │ ├── locales/
│ │ │ ├── en.activerecord.yml
│ │ │ └── en.yml
│ │ └── routes.rb
│ ├── db/
│ │ ├── migrate/
│ │ │ ├── 20220218121438_create_uffizzi_core_tables.rb
│ │ │ ├── 20220309110201_remove_secrets_from_projects.rb
│ │ │ ├── 20220310110150_create_project_secrets.rb
│ │ │ ├── 20220325113342_add_name_to_uffizzi_containers.rb
│ │ │ ├── 20220329123323_rename_project_secrets_to_secrets.rb
│ │ │ ├── 20220329124542_add_resource_to_secrets.rb
│ │ │ ├── 20220329143241_remove_project_ref_from_secrets.rb
│ │ │ ├── 20220419074956_add_health_check_to_containers.rb
│ │ │ ├── 20220422151523_add_volumes_to_uffizzi_core_containers.rb
│ │ │ ├── 20220525113412_rename_name_to_uffizzi_containers.rb
│ │ │ ├── 20220704135629_add_disabled_at_to_deployments.rb
│ │ │ ├── 20220805164628_add_metadata_to_deployment.rb
│ │ │ ├── 20220901110752_create_host_volume_files.rb
│ │ │ ├── 20220901165313_create_container_host_volume_files.rb
│ │ │ ├── 20220927113647_add_additional_subdomains_to_containers.rb
│ │ │ ├── 20230111000000_add_state_to_memberships.rb
│ │ │ ├── 20230306142513_add_last_deploy_at_to_deployments.rb
│ │ │ ├── 20230406154451_add_full_image_name_to_container.rb
│ │ │ ├── 20230531135739_create_deployment_events.rb
│ │ │ ├── 20230613101901_create_clusters.rb
│ │ │ ├── 20230711101901_add_host_to_clusters.rb
│ │ │ ├── 20230810140316_add_source_to_uffizzi_core_clusters.rb
│ │ │ ├── 20230824150022_update_name_constraint_to_projects.rb
│ │ │ ├── 20231009162719_create_uffizzi_core_kubernetes_distributions.rb
│ │ │ ├── 20231009182412_add_kubernetes_distribution_id_to_uffizzi_core_clusters.rb
│ │ │ ├── 20240301200235_add_node_selector_to_cluster.rb
│ │ │ └── 20240314170113_delete_node_selector_from_cluster.rb
│ │ └── seeds.rb
│ ├── lib/
│ │ ├── tasks/
│ │ │ └── uffizzi_core_tasks.rake
│ │ ├── uffizzi_core/
│ │ │ ├── engine.rb
│ │ │ └── version.rb
│ │ └── uffizzi_core.rb
│ ├── swagger/
│ │ └── v1/
│ │ └── swagger.json
│ ├── test/
│ │ ├── controllers/
│ │ │ └── uffizzi_core/
│ │ │ └── api/
│ │ │ └── cli/
│ │ │ └── v1/
│ │ │ ├── accounts/
│ │ │ │ ├── clusters_controller_test.rb
│ │ │ │ ├── credentials_controller_test.rb
│ │ │ │ └── projects_controller_test.rb
│ │ │ ├── accounts_controller_test.rb
│ │ │ ├── projects/
│ │ │ │ ├── clusters/
│ │ │ │ │ └── ingresses_controller_test.rb
│ │ │ │ ├── clusters_controller_test.rb
│ │ │ │ ├── compose_files_controller_test.rb
│ │ │ │ ├── deployments/
│ │ │ │ │ ├── activity_items_controller_test.rb
│ │ │ │ │ ├── containers/
│ │ │ │ │ │ └── logs_controller_test.rb
│ │ │ │ │ ├── containers_controller_test.rb
│ │ │ │ │ └── events_controller_test.rb
│ │ │ │ ├── deployments_controller/
│ │ │ │ │ ├── create_test.rb
│ │ │ │ │ ├── deploy_containers_test.rb
│ │ │ │ │ ├── destroy_test.rb
│ │ │ │ │ └── update_test.rb
│ │ │ │ └── secrets_controller_test.rb
│ │ │ ├── projects_controller_test.rb
│ │ │ └── sessions_controller_test.rb
│ │ ├── dummy/
│ │ │ ├── Rakefile
│ │ │ ├── app/
│ │ │ │ ├── assets/
│ │ │ │ │ ├── config/
│ │ │ │ │ │ └── manifest.js
│ │ │ │ │ ├── images/
│ │ │ │ │ │ └── .keep
│ │ │ │ │ └── stylesheets/
│ │ │ │ │ └── application.css
│ │ │ │ ├── channels/
│ │ │ │ │ └── application_cable/
│ │ │ │ │ ├── channel.rb
│ │ │ │ │ └── connection.rb
│ │ │ │ ├── controllers/
│ │ │ │ │ ├── application_controller.rb
│ │ │ │ │ └── concerns/
│ │ │ │ │ └── .keep
│ │ │ │ ├── helpers/
│ │ │ │ │ └── application_helper.rb
│ │ │ │ ├── javascript/
│ │ │ │ │ └── packs/
│ │ │ │ │ └── application.js
│ │ │ │ ├── jobs/
│ │ │ │ │ └── application_job.rb
│ │ │ │ ├── mailers/
│ │ │ │ │ └── application_mailer.rb
│ │ │ │ ├── models/
│ │ │ │ │ ├── application_record.rb
│ │ │ │ │ └── concerns/
│ │ │ │ │ └── .keep
│ │ │ │ └── views/
│ │ │ │ └── layouts/
│ │ │ │ ├── application.html.erb
│ │ │ │ ├── mailer.html.erb
│ │ │ │ └── mailer.text.erb
│ │ │ ├── bin/
│ │ │ │ ├── rails
│ │ │ │ ├── rake
│ │ │ │ ├── setup
│ │ │ │ └── spring
│ │ │ ├── config/
│ │ │ │ ├── application.rb
│ │ │ │ ├── boot.rb
│ │ │ │ ├── cable.yml
│ │ │ │ ├── database.yml
│ │ │ │ ├── environment.rb
│ │ │ │ ├── environments/
│ │ │ │ │ ├── development.rb
│ │ │ │ │ ├── production.rb
│ │ │ │ │ └── test.rb
│ │ │ │ ├── initializers/
│ │ │ │ │ ├── application_controller_renderer.rb
│ │ │ │ │ ├── assets.rb
│ │ │ │ │ ├── backtrace_silencers.rb
│ │ │ │ │ ├── config.rb
│ │ │ │ │ ├── content_security_policy.rb
│ │ │ │ │ ├── cookies_serializer.rb
│ │ │ │ │ ├── filter_parameter_logging.rb
│ │ │ │ │ ├── inflections.rb
│ │ │ │ │ ├── mime_types.rb
│ │ │ │ │ ├── octokit.rb
│ │ │ │ │ ├── permissions_policy.rb
│ │ │ │ │ └── wrap_parameters.rb
│ │ │ │ ├── locales/
│ │ │ │ │ └── en.yml
│ │ │ │ ├── puma.rb
│ │ │ │ ├── routes.rb
│ │ │ │ ├── settings.yml
│ │ │ │ ├── spring.rb
│ │ │ │ └── storage.yml
│ │ │ ├── config.ru
│ │ │ ├── db/
│ │ │ │ └── schema.rb
│ │ │ ├── lib/
│ │ │ │ └── assets/
│ │ │ │ └── .keep
│ │ │ ├── log/
│ │ │ │ └── .keep
│ │ │ └── public/
│ │ │ ├── 404.html
│ │ │ ├── 422.html
│ │ │ └── 500.html
│ │ ├── factories/
│ │ │ ├── account.rb
│ │ │ ├── activity_item.rb
│ │ │ ├── cluster.rb
│ │ │ ├── comments.rb
│ │ │ ├── compose_files.rb
│ │ │ ├── config_files.rb
│ │ │ ├── container_config_files.rb
│ │ │ ├── container_host_volume_files.rb
│ │ │ ├── containers.rb
│ │ │ ├── credentials.rb
│ │ │ ├── deployment.rb
│ │ │ ├── host_volume_files.rb
│ │ │ ├── kubernetes_distribution.rb
│ │ │ ├── membership.rb
│ │ │ ├── payments.rb
│ │ │ ├── project.rb
│ │ │ ├── ratings.rb
│ │ │ ├── repos.rb
│ │ │ ├── secrets.rb
│ │ │ ├── sequences.rb
│ │ │ ├── templates.rb
│ │ │ ├── user.rb
│ │ │ └── user_project.rb
│ │ ├── fixtures/
│ │ │ └── files/
│ │ │ ├── cluster/
│ │ │ │ └── manifest.yml
│ │ │ ├── compose_dependencies/
│ │ │ │ └── configs/
│ │ │ │ └── vote_conf.json
│ │ │ ├── compose_files/
│ │ │ │ ├── azure_services/
│ │ │ │ │ └── nginx.yml
│ │ │ │ ├── boolean_option.yml
│ │ │ │ ├── compose_empty.yml
│ │ │ │ ├── compose_memory.yml
│ │ │ │ ├── compose_vote_app_github.yml
│ │ │ │ ├── compose_with_continuous_preview.yml
│ │ │ │ ├── compose_with_only_line.yml
│ │ │ │ ├── compose_with_syntax_error.yml
│ │ │ │ ├── compose_with_volumes.yml
│ │ │ │ ├── compose_without_image.yml
│ │ │ │ ├── compose_without_services.yml
│ │ │ │ ├── config_file_dependencies.yml
│ │ │ │ ├── dockerhub_services/
│ │ │ │ │ ├── account_custom_image.yml
│ │ │ │ │ ├── nginx.yml
│ │ │ │ │ ├── nginx_auto_deploy_off.yml
│ │ │ │ │ ├── nginx_config_files.yml
│ │ │ │ │ ├── nginx_config_invalid_ingress_service.yml
│ │ │ │ │ ├── nginx_configs_long_syntax.yml
│ │ │ │ │ ├── nginx_configs_long_syntax_without_target.yml
│ │ │ │ │ ├── nginx_configs_short_syntax.yml
│ │ │ │ │ ├── nginx_configs_short_syntax_invalid_path.yml
│ │ │ │ │ ├── nginx_configs_short_syntax_unknown_config.yml
│ │ │ │ │ ├── nginx_cp_delete_after_integer.yml
│ │ │ │ │ ├── nginx_cp_invalid_delete_after_hours.yml
│ │ │ │ │ ├── nginx_cp_invalid_delete_after_max.yml
│ │ │ │ │ ├── nginx_cp_invalid_delete_after_min.yml
│ │ │ │ │ ├── nginx_cp_invalid_delete_after_postfix.yml
│ │ │ │ │ ├── nginx_env_file.yml
│ │ │ │ │ ├── nginx_env_file_duplicates.yml
│ │ │ │ │ ├── nginx_env_file_empty.yml
│ │ │ │ │ ├── nginx_env_file_empty_in_array.yml
│ │ │ │ │ ├── nginx_envs.yml
│ │ │ │ │ ├── nginx_ingress_port_non_integer.yml
│ │ │ │ │ ├── nginx_invalid_deploy_auto.yml
│ │ │ │ │ ├── nginx_invalid_memory.yml
│ │ │ │ │ ├── nginx_invalid_memory_min_value.yml
│ │ │ │ │ ├── nginx_invalid_memory_postfix.yml
│ │ │ │ │ ├── nginx_invalid_memory_type.yml
│ │ │ │ │ ├── nginx_invalid_port.yml
│ │ │ │ │ ├── nginx_uffizzi_ingress.yml
│ │ │ │ │ ├── nginx_with_continuous_preview_without_x-uffizzi.yml
│ │ │ │ │ ├── nginx_with_ingress_without_x-uffizzi.yml
│ │ │ │ │ ├── nginx_without_ingress.yml
│ │ │ │ │ ├── nginx_without_ingress_port.yml
│ │ │ │ │ ├── nginx_without_ingress_service.yml
│ │ │ │ │ ├── nginx_without_tag.yml
│ │ │ │ │ ├── postgres_secrets.yml
│ │ │ │ │ ├── postgres_secrets_duplicates.yml
│ │ │ │ │ ├── postgres_secrets_unknown.yml
│ │ │ │ │ ├── postgres_secrets_without_external.yml
│ │ │ │ │ ├── postgres_secrets_without_name.yml
│ │ │ │ │ ├── volumes_anonymous.yml
│ │ │ │ │ └── volumes_named.yml
│ │ │ │ ├── github_services/
│ │ │ │ │ └── hello_world.yml
│ │ │ │ ├── google_services/
│ │ │ │ │ ├── cloudsql.yml
│ │ │ │ │ ├── cloudsql_entrypoint.yml
│ │ │ │ │ └── nginx.yml
│ │ │ │ ├── healthcheck/
│ │ │ │ │ ├── array_command_success.yml
│ │ │ │ │ ├── disabled_healthcheck.yml
│ │ │ │ │ ├── healthcheck_with_disable.yml
│ │ │ │ │ ├── invalid_command.yml
│ │ │ │ │ ├── invalid_interval.yml
│ │ │ │ │ ├── invalid_options.yml
│ │ │ │ │ ├── invalid_retries.yml
│ │ │ │ │ └── string_command_success.yml
│ │ │ │ └── invalid_service.yml
│ │ │ ├── controller/
│ │ │ │ ├── cluster_asleep.json
│ │ │ │ ├── cluster_awake.json
│ │ │ │ ├── cluster_not_ready.json
│ │ │ │ ├── cluster_ready.json
│ │ │ │ ├── deployment_containers.json
│ │ │ │ ├── deployment_containers_with_error.json
│ │ │ │ ├── deployments.json
│ │ │ │ ├── ingresses.json
│ │ │ │ └── logs.json
│ │ │ ├── dockerhub/
│ │ │ │ ├── digest.json
│ │ │ │ ├── login_fail.json
│ │ │ │ ├── repository.json
│ │ │ │ └── webhooks/
│ │ │ │ └── push/
│ │ │ │ └── event_data.json
│ │ │ ├── github/
│ │ │ │ └── compose_files/
│ │ │ │ └── hello_world_compose_github_container_registry.json
│ │ │ ├── test-compose-full.yml
│ │ │ ├── test-compose-success-jfrog.yml
│ │ │ ├── test-compose-success-without-dependencies.yml
│ │ │ ├── test-compose-success.yml
│ │ │ ├── test-compose-without-images.yml
│ │ │ ├── uffizzi-compose-amazon.yml
│ │ │ ├── uffizzi-compose-azure.yml
│ │ │ ├── uffizzi-compose-docker-registry-anonymous.yml
│ │ │ ├── uffizzi-compose-dockerhub.yml
│ │ │ ├── uffizzi-compose-ghcr.yml
│ │ │ ├── uffizzi-compose-google.yml
│ │ │ ├── uffizzi-compose-invalid-service-name.yml
│ │ │ ├── uffizzi-compose-vote-app-docker-with-memory-request.yml
│ │ │ ├── uffizzi-compose-vote-app-docker.yml
│ │ │ ├── uffizzi-compose-vote-app-with-command-as-string.yml
│ │ │ ├── uffizzi-compose-with-host-volumes.yml
│ │ │ └── uffizzi-compose-with_alias.yml
│ │ ├── services/
│ │ │ ├── activity_item_service_test.rb
│ │ │ ├── compose_file_service_test.rb
│ │ │ ├── deployment_service_test.rb
│ │ │ ├── google_service_test.rb
│ │ │ ├── image_parser_service_test.rb
│ │ │ ├── manage_activity_items_service_test.rb
│ │ │ └── user_generator_service_test.rb
│ │ ├── support/
│ │ │ ├── azure_registry_stub_support.rb
│ │ │ ├── controller_stub_support.rb
│ │ │ ├── docker_hub_stub_support.rb
│ │ │ ├── docker_registry_stub_support.rb
│ │ │ ├── fixture_support.rb
│ │ │ ├── github_container_registry_support.rb
│ │ │ ├── google_registry_stub_support.rb
│ │ │ └── stub_support.rb
│ │ ├── test_helper.rb
│ │ └── uffizzi_core_test.rb
│ └── uffizzi_core.gemspec
├── db/
│ ├── migrate/
│ │ ├── 20220219114713_create_uffizzi_core_tables.uffizzi_core.rb
│ │ ├── 20220317112742_remove_secrets_from_projects.uffizzi_core.rb
│ │ ├── 20220317112743_create_project_secrets.uffizzi_core.rb
│ │ ├── 20220325113623_add_name_to_uffizzi_containers.uffizzi_core.rb
│ │ ├── 20220412133606_rename_project_secrets_to_secrets.uffizzi_core.rb
│ │ ├── 20220412133607_add_resource_to_secrets.uffizzi_core.rb
│ │ ├── 20220412133608_remove_project_ref_from_secrets.uffizzi_core.rb
│ │ ├── 20220420103952_add_health_check_to_containers.uffizzi_core.rb
│ │ ├── 20220527135654_rename_name_to_uffizzi_containers.uffizzi_core.rb
│ │ ├── 20220617184754_add_volumes_to_uffizzi_core_containers.uffizzi_core.rb
│ │ ├── 20220708093405_add_disabled_at_to_deployments.uffizzi_core.rb
│ │ ├── 20220817140346_add_metadata_to_deployment.uffizzi_core.rb
│ │ ├── 20220901110752_create_host_volume_files.rb
│ │ ├── 20220901165313_create_container_host_volume_files.rb
│ │ ├── 20220927113647_add_additional_subdomains_to_containers.rb
│ │ ├── 20230111000000_add_state_to_memberships.rb
│ │ ├── 20230306142805_add_last_deploy_at_to_deployments.uffizzi_core.rb
│ │ ├── 20230406154547_add_full_image_name_to_container.uffizzi_core.rb
│ │ ├── 20230531135739_create_deployment_events.rb
│ │ ├── 20230613110517_create_clusters.uffizzi_core.rb
│ │ ├── 20230711101901_add_host_to_clusters.rb
│ │ ├── 20230824150022_update_name_constraint_to_projects.rb
│ │ ├── 20231009103942_add_source_to_uffizzi_core_clusters.uffizzi_core.rb
│ │ ├── 20231009163516_create_uffizzi_core_kubernetes_distributions.uffizzi_core.rb
│ │ ├── 20231009201239_add_kubernetes_distribution_id_to_uffizzi_core_clusters.uffizzi_core.rb
│ │ ├── 20240301200916_add_node_selector_to_cluster.uffizzi_core.rb
│ │ └── 20240314170425_delete_node_selector_from_cluster.uffizzi_core.rb
│ ├── schema.rb
│ └── seeds.rb
├── docker-compose.yml
├── docs/
│ ├── continuous-previews.md
│ └── quickstart-guide.md
├── lib/
│ ├── assets/
│ │ └── .keep
│ └── tasks/
│ └── .keep
├── log/
│ └── .keep
├── public/
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ └── robots.txt
├── storage/
│ └── .keep
├── test/
│ ├── application_system_test_case.rb
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection_test.rb
│ ├── controllers/
│ │ └── .keep
│ ├── fixtures/
│ │ ├── .keep
│ │ └── files/
│ │ └── .keep
│ ├── helpers/
│ │ └── .keep
│ ├── integration/
│ │ └── .keep
│ ├── mailers/
│ │ └── .keep
│ ├── models/
│ │ └── .keep
│ ├── system/
│ │ └── .keep
│ └── test_helper.rb
├── tmp/
│ └── .keep
├── uffizzi-compose-example.yml
└── vendor/
└── .keep
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: Bug report
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Configuration:**
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Open Source, CLI, Cloud, Docker Desktop Extension
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: community-new
assignees: ''
---
**Tell us about your request**
A clear and concise description of what you want to happen or the change you would like to see
**Which service(s) is this request for?**
Let us know which product(s) you want this for - Open Source or Cloud solution or both?
**Tell us about the problem you're trying to solve. What are you trying to do, and why is it hard?**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Are you currently working around the issue?**
A clear and concise description of any alternative solutions or features you've considered or are using today.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/helm-release.yml
================================================
name: Release Helm Charts
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.3.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
================================================
FILE: .github/workflows/main.yml
================================================
name: Test, Lint, Build, and Publish Image
on:
push:
branches:
- qa
- develop
- main
pull_request:
types: [opened,synchronize,reopened]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.5
bundler-cache: true
- name: Run rubocop
run: bundle exec rubocop
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create env file
uses: SpicyPizza/create-envfile@v1.3
- name: Run tests
run: docker-compose run --rm core bash -c "bundle install && bundle exec rails db:create && bundle exec rails test"
build-and-push-some-branches:
runs-on: ubuntu-latest
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
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: uffizzi/app
tags: |
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
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: ${{ 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/app
notify:
needs:
- lint
- test
- build-and-push-some-branches
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- uses: technote-space/workflow-conclusion-action@v2
- uses: 8398a7/action-slack@v3
with:
status: ${{ env.WORKFLOW_CONCLUSION }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: ${{ (github.ref_name == 'main' && env.WORKFLOW_CONCLUSION == 'success') || ((github.ref_name == 'main' || github.ref_name == 'qa' || github.ref_name == 'develop') && env.WORKFLOW_CONCLUSION == 'failure') }}
================================================
FILE: .github/workflows/release.yml
================================================
name: Uffizzi Core Release
on:
push:
tags:
- core_v*
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.5.0
- name: Create env file
uses: SpicyPizza/create-envfile@v1.3
- name: Release Gem
run: docker compose run --rm core bash -c "make release_gem"
env:
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: core
push: true
tags: uffizzi/core:${{ github.ref_name }}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
/db/*.sqlite3-*
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
# Ignore uploaded files in development.
/storage/*
!/storage/.keep
/public/assets
.byebug_history
# Ignore master key for decrypting credentials and more.
/config/master.key
/credentials/*.json
.env
# Ignore (intellij) idea config directory
/.idea
================================================
FILE: .rubocop.yml
================================================
require:
- rubocop-minitest
AllCops:
NewCops: disable
SuggestExtensions: false
Exclude:
- core/test/dummy/**/*
- db/schema.rb
- vendor/**/*
Naming/AccessorMethodName:
Description: Check the naming of accessor methods for get_/set_.
Enabled: false
Naming/MethodParameterName:
Enabled: false
Style/Alias:
Description: 'Use alias_method instead of alias.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'
Enabled: false
Style/ArrayJoin:
Description: 'Use Array#join instead of Array#*.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join'
Enabled: false
Style/AsciiComments:
Description: 'Use only ascii symbols in comments.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments'
Enabled: false
Naming/AsciiIdentifiers:
Description: 'Use only ascii symbols in identifiers.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers'
Enabled: false
Style/Attr:
Description: 'Checks for uses of Module#attr.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr'
Enabled: false
Metrics/BlockNesting:
Description: 'Avoid excessive block nesting'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
Enabled: false
Style/CaseEquality:
Description: 'Avoid explicit use of the case equality operator(===).'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'
Enabled: false
Style/CharacterLiteral:
Description: 'Checks for uses of character literals.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals'
Enabled: false
Style/ClassAndModuleChildren:
Description: 'Checks style of children classes and modules.'
Enabled: false
Metrics/ClassLength:
Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false
Metrics/ModuleLength:
Description: 'Avoid modules longer than 100 lines of code.'
Enabled: false
Style/ClassVars:
Description: 'Avoid the use of class variables.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'
Enabled: false
Style/CollectionMethods:
Enabled: true
PreferredMethods:
find: detect
inject: reduce
collect: map
find_all: select
Style/ColonMethodCall:
Description: 'Do not use :: for method call.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons'
Enabled: false
Style/CommentAnnotation:
Description: >-
Checks formatting of special comments
(TODO, FIXME, OPTIMIZE, HACK, REVIEW).
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords'
Enabled: false
Style/PreferredHashMethods:
Description: 'Checks use of `has_key?` and `has_value?` Hash methods.'
StyleGuide: '#hash-key'
Enabled: false
Style/Documentation:
Description: 'Document classes and non-namespace modules.'
Enabled: false
Style/DoubleNegation:
Description: 'Checks for uses of double negation (!!).'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang'
Enabled: false
Style/EachWithObject:
Description: 'Prefer `each_with_object` over `inject` or `reduce`.'
Enabled: false
Style/EmptyLiteral:
Description: 'Prefer literals to Array.new/Hash.new/String.new.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash'
Enabled: false
Style/Encoding:
Enabled: false
Style/EvenOdd:
Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: false
Naming/FileName:
Description: 'Use snake_case for source file names.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
Enabled: false
Style/FrozenStringLiteralComment:
Description: >-
Add the frozen_string_literal comment to the top of files
to help transition from Ruby 2.3.0 to Ruby 3.0.
Enabled: true
Style/FormatString:
Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'
Enabled: false
Style/GlobalVars:
Description: 'Do not introduce global variables.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'
Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html'
Enabled: false
Style/GuardClause:
Description: 'Check for conditionals that can be replaced with guard clauses'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
Enabled: false
Style/IfUnlessModifier:
Description: >-
Favor modifier if/unless usage when you have a
single-line body.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'
Enabled: false
Style/IfWithSemicolon:
Description: 'Do not use if x; .... Use the ternary operator instead.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs'
Enabled: false
Style/InlineComment:
Description: 'Avoid inline comments.'
Enabled: false
Style/Lambda:
Description: 'Use the new lambda literal syntax for single-line blocks.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line'
Enabled: false
Style/LambdaCall:
Description: 'Use lambda.call(...) instead of lambda.(...).'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call'
Enabled: false
Style/LineEndConcatenation:
Description: >-
Use \ instead of + or << to concatenate two string literals at
line end.
Enabled: false
Metrics/AbcSize:
Description: >-
A calculated magnitude based on number of assignments,
branches, and conditions.
Enabled: false
Metrics/BlockLength:
CountComments: true
Max: 50
IgnoredMethods: []
Exclude:
- test/**/*
- app/repositories/**/*
- config/environments/**/*
- core/test/**/*
- core/app/repositories/**/*
- core/config/routes.rb
Metrics/CyclomaticComplexity:
Description: >-
A complexity metric that is strongly correlated to the number
of test cases needed to validate a method.
Enabled: false
Metrics/MethodLength:
Description: 'Avoid methods longer than 10 lines of code.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
Style/MixinUsage:
Enabled: true
Exclude:
- test/test_helper.rb
Style/ModuleFunction:
Description: 'Checks for usage of `extend self` in modules.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function'
Enabled: false
Style/MultilineBlockChain:
Description: 'Avoid multi-line chains of blocks.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
Enabled: false
Style/NegatedIf:
Description: >-
Favor unless over if for negative conditions
(or control flow or).
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'
Enabled: false
Style/NegatedWhile:
Description: 'Favor until over while for negative conditions.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives'
Enabled: false
Style/Next:
Description: 'Use `next` to skip iteration instead of a condition at the end.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
Enabled: false
Style/NilComparison:
Description: 'Prefer x.nil? to x == nil.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: false
Style/Not:
Description: 'Use ! instead of not.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not'
Enabled: false
Style/NumericLiterals:
Description: >-
Add underscores to large numeric literals to improve their
readability.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics'
Enabled: false
Style/OneLineConditional:
Description: >-
Favor the ternary operator(?:) over
if/then/else/end constructs.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'
Enabled: false
Naming/BinaryOperatorParameterName:
Description: 'When defining binary operators, name the argument other.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
Enabled: false
Metrics/ParameterLists:
Description: 'Avoid parameter lists longer than three or four parameters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
Enabled: false
Style/PercentLiteralDelimiters:
Description: 'Use `%`-literal delimiters consistently'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces'
Enabled: false
Style/PerlBackrefs:
Description: 'Avoid Perl-style regex back references.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
Enabled: false
Naming/PredicateName:
Description: 'Check the names of predicate methods.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'
ForbiddenPrefixes:
- is_
Exclude:
- spec/**/*
Style/Proc:
Description: 'Use proc instead of Proc.new.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc'
Enabled: false
Style/RaiseArgs:
Description: 'Checks the arguments passed to raise/fail.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages'
Enabled: false
Style/RegexpLiteral:
Description: 'Use / or %r around regular expressions.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r'
Enabled: false
Style/Sample:
Description: >-
Use `sample` instead of `shuffle.first`,
`shuffle.last`, and `shuffle[Fixnum]`.
Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code'
Enabled: false
Style/SelfAssignment:
Description: >-
Checks for places where self-assignment shorthand should have
been used.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment'
Enabled: false
Style/SingleLineBlockParams:
Description: 'Enforces the names of some block params.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'
Enabled: false
Style/SingleLineMethods:
Description: 'Avoid single-line methods.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods'
Enabled: false
Style/SignalException:
Description: 'Checks for proper usage of fail and raise.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method'
Enabled: false
Style/SpecialGlobalVars:
Description: 'Avoid Perl-style global variables.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms'
Enabled: false
Style/StringLiterals:
Description: 'Checks if uses of quotes match the configured preference.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
EnforcedStyle: single_quotes
Enabled: true
Style/StructInheritance:
Enabled: true
Style/SymbolArray:
EnforcedStyle: brackets
Style/SymbolProc:
Enabled: true
Style/TrailingCommaInArguments:
Description: 'Checks for trailing comma in argument lists.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
EnforcedStyleForMultiline: comma
SupportedStylesForMultiline:
- comma
- consistent_comma
- no_comma
Enabled: true
Style/TrailingCommaInArrayLiteral:
Description: 'Checks for trailing comma in array literals.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
EnforcedStyleForMultiline: comma
SupportedStylesForMultiline:
- comma
- consistent_comma
- no_comma
Enabled: true
Style/TrailingCommaInHashLiteral:
Description: 'Checks for trailing comma in hash literals.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
EnforcedStyleForMultiline: comma
SupportedStylesForMultiline:
- comma
- consistent_comma
- no_comma
Enabled: true
Style/TrivialAccessors:
Description: 'Prefer attr_* methods to trivial readers/writers.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
Enabled: false
Style/VariableInterpolation:
Description: >-
Don't interpolate global, instance and class variables
directly in strings.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate'
Enabled: false
Style/WhenThen:
Description: 'Use when x then ... for one-line cases.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases'
Enabled: false
Style/WhileUntilModifier:
Description: >-
Favor modifier while/until usage when you have a
single-line body.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier'
Enabled: false
Style/WordArray:
Description: 'Use %w or %W for arrays of words.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w'
EnforcedStyle: brackets
Style/MethodCallWithArgsParentheses:
Description: 'Use parentheses for method calls with arguments.'
Enabled: true
IgnoredMethods:
- require
- require_relative
- raise
- head
- render
- respond_with
- puts
Exclude:
- Gemfile
- test/test_helper.rb
- db/migrate/**/*
- core/uffizzi_core.gemspec
- core/db/migrate/**/*
Layout/ParameterAlignment:
Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
Enabled: false
Layout/ConditionPosition:
Description: >-
Checks for condition placed in a confusing position relative to
the keyword.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
Enabled: false
Layout/DotPosition:
Description: 'Checks the position of the dot in multi-line method calls.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'
EnforcedStyle: leading
Layout/ExtraSpacing:
Description: 'Do not use unnecessary spacing.'
Enabled: true
Layout/MultilineOperationIndentation:
Description: >-
Checks indentation of binary operations that span more than
one line.
Enabled: true
EnforcedStyle: indented
Layout/MultilineMethodCallIndentation:
Description: >-
Checks indentation of method calls with the dot operator
that span more than one line.
Enabled: true
EnforcedStyle: indented
Layout/InitialIndentation:
Description: >-
Checks the indentation of the first non-blank non-comment line in a file.
Enabled: false
Layout/EndAlignment:
Description: >-
Checks whether the end keywords are aligned properly.
EnforcedStyleAlignWith: variable
Layout/LineLength:
Description: 'Limit lines to 140 characters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
Max: 140
Lint/AmbiguousOperator:
Description: >-
Checks for ambiguous operators in the first argument of a
method invocation without parentheses.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args'
Enabled: false
Lint/AmbiguousRegexpLiteral:
Description: >-
Checks for ambiguous regexp literals in the first argument of
a method invocation without parenthesis.
Enabled: false
Lint/AssignmentInCondition:
Description: "Don't use assignment in conditions."
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition'
Enabled: false
Lint/CircularArgumentReference:
Description: "Don't refer to the keyword argument in the default value."
Enabled: false
Lint/DeprecatedClassMethods:
Description: 'Check for deprecated class method calls.'
Enabled: false
Lint/DuplicateHashKey:
Description: 'Check for duplicate keys in hash literals.'
Enabled: false
Lint/EachWithObjectArgument:
Description: 'Check for immutable argument given to each_with_object.'
Enabled: false
Lint/ElseLayout:
Description: 'Check for odd code arrangement in an else block.'
Enabled: false
Lint/FlipFlop:
Description: 'Checks for flip flops'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'
Enabled: false
Lint/FormatParameterMismatch:
Description: 'The number of parameters to format/sprint must match the fields.'
Enabled: false
Lint/SuppressedException:
Description: "Don't suppress exception."
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
Enabled: false
Lint/LiteralAsCondition:
Description: 'Checks of literals used in conditions.'
Enabled: false
Lint/LiteralInInterpolation:
Description: 'Checks for literals used in interpolation.'
Enabled: false
Lint/Loop:
Description: >-
Use Kernel#loop with break rather than begin/end/until or
begin/end/while for post-loop tests.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break'
Enabled: false
Lint/NestedMethodDefinition:
Description: 'Do not use nested method definitions.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods'
Enabled: false
Lint/NonLocalExitFromIterator:
Description: 'Do not use return in iterator to cause non-local exit.'
Enabled: false
Lint/ParenthesesAsGroupedExpression:
Description: >-
Checks for method calls with a space before the opening
parenthesis.
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
Enabled: false
Lint/RedundantCopDisableDirective:
Description: >-
Checks for rubocop:disable comments that can be removed.
Note: this cop is not disabled when disabling all cops.
It must be explicitly disabled.
Enabled: false
Lint/RequireParentheses:
Description: >-
Use parentheses in the method call to avoid confusion
about precedence.
Enabled: false
Lint/UnderscorePrefixedVariableName:
Description: 'Do not use prefix `_` for a variable that is used.'
Enabled: false
Lint/Void:
Description: 'Possible use of operator/literal/variable in void context.'
Enabled: false
Gemspec/DateAssignment:
Enabled: true
Gemspec/RequireMFA:
Enabled: false
Layout/LineEndStringConcatenationIndentation:
Enabled: true
Layout/SpaceBeforeBrackets:
Enabled: true
Lint/AmbiguousAssignment:
Enabled: true
Lint/AmbiguousOperatorPrecedence:
Enabled: true
Lint/AmbiguousRange:
Enabled: true
Lint/DeprecatedConstants:
Enabled: true
Lint/DuplicateBranch:
Enabled: true
Lint/DuplicateRegexpCharacterClassElement:
Enabled: true
Lint/EmptyBlock:
Enabled: false
Lint/EmptyClass:
Enabled: true
Lint/EmptyInPattern:
Enabled: true
Lint/IncompatibleIoSelectWithFiberScheduler:
Enabled: true
Lint/LambdaWithoutLiteralBlock:
Enabled: true
Lint/NoReturnInBeginEndBlocks:
Enabled: true
Lint/NumberedParameterAssignment:
Enabled: true
Lint/OrAssignmentToConstant:
Enabled: true
Lint/RedundantDirGlobSort:
Enabled: true
Lint/RequireRelativeSelfPath:
Enabled: true
Lint/SymbolConversion:
Enabled: true
Lint/ToEnumArguments:
Enabled: true
Lint/TripleQuotes:
Enabled: true
Lint/UnexpectedBlockArity:
Enabled: true
Lint/UnmodifiedReduceAccumulator:
Enabled: true
Lint/UselessRuby2Keywords:
Enabled: true
Naming/BlockForwarding:
Enabled: true
Security/IoMethods:
Enabled: true
Style/ArgumentsForwarding:
Enabled: true
Style/CollectionCompact:
Enabled: true
Style/DocumentDynamicEvalDefinition:
Enabled: true
Style/EndlessMethod:
Enabled: true
Style/FileRead:
Enabled: true
Style/FileWrite:
Enabled: true
Style/HashConversion:
Enabled: true
Style/HashExcept:
Enabled: true
Style/IfWithBooleanLiteralBranches:
Enabled: true
Style/InPatternThen:
Enabled: true
Style/MapToHash:
Enabled: true
Style/MultilineInPatternThen:
Enabled: true
Style/NegatedIfElseCondition:
Enabled: true
Style/NilLambda:
Enabled: true
Style/NumberedParameters:
Enabled: true
Style/NumberedParametersLimit:
Enabled: true
Style/OpenStructUse:
Enabled: false
Style/OptionalBooleanParameter:
Enabled: false
Style/QuotedSymbols:
Enabled: true
Style/RedundantArgument:
Enabled: true
Style/RedundantSelfAssignmentBranch:
Enabled: true
Style/SelectByRegexp:
Enabled: true
Style/StringChars:
Enabled: true
Style/SwapValues:
Enabled: true
Minitest/AssertInDelta:
Enabled: true
Minitest/AssertionInLifecycleHook:
Enabled: true
Minitest/AssertKindOf:
Enabled: true
Minitest/AssertOutput:
Enabled: true
Minitest/AssertPathExists:
Enabled: true
Minitest/AssertSilent:
Enabled: true
Minitest/AssertWithExpectedArgument:
Enabled: true
Minitest/LiteralAsActualArgument:
Enabled: true
Minitest/MultipleAssertions:
Enabled: false
Minitest/RefuteInDelta:
Enabled: true
Minitest/RefuteKindOf:
Enabled: true
Minitest/RefutePathExists:
Enabled: true
Minitest/TestMethodName:
Enabled: true
Minitest/UnreachableAssertion:
Enabled: true
Minitest/UnspecifiedException:
Enabled: true
================================================
FILE: .ruby-version
================================================
ruby-2.5.3
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct
This Code of Conduct is based on the [Mission Protocol Code of Conduct v1.1](https://missionprotocol.org/codeofconduct/), Creative Commons 4.0 License (CC BY 4.0), 2020.
## Mission
Our mission is to support the creators of the next generation of great products and services by facilitating high quality and rapid software delivery through lightweight, event-driven test environments.
## Objective
Our objective is to focus on the mission we set out to accomplish, which we believe will produce an important social good in the world. Our standards are directed towards achieving this mission. We will conduct ourselves with professional integrity and set aside divisive discourse that doesn’t help us achieve our mission.
## Standards
- Leadership principle: Project leaders are responsible and authorized to make judgments and take appropriate actions to define the mission and keep the project in line with this mission.
- **Mission focus**: We will focus our efforts on accomplishing the project’s mission, so that it is fulfilled to its highest potential.
- **Creative conflict**: We acknowledge that discussions and disagreements are a natural part of problem-solving and should happen in a constructive way.
- **The whole is greater than the parts**: We are a project with diverse opinions and fields of engagement, but we are here to focus on what unites us, instead of what divides us.
- **Principle of charity**: We assume positive intentions of members, contributors, and policies.
## Prohibited Behavior
- Harassment, i.e., excessive, hostile, or bad-faith communication directed at another member or contributor
- Attacks based on personal characteristics
- Threats of violence
- Threatening to publish or publishing people’s personal information without their consent
- Repeatedly attempting to involve the project in issues outside of its mission
- Posting illegal content
- Abuse of the system for reporting code of conduct violations
- Promoting any behavior listed above
## Responsibilities
In line with the leadership principle, project leaders have the responsibility to clarify and interpret the mission, as well as the intentions and standards in this code of conduct, in order to maintain mission focus. When individuals engage in prohibited behavior, project leaders are expected to take expedient, fair, and appropriate action to address the violation(s). The standards and prohibitions in this document also apply to project leaders.
## Conflict Resolution
### Reporting
We highly encourage contributors to resolve conflicts by directly reaching out to the other party or parties involved in the dispute. When this is insufficient, members can report the issue to project leaders via email at support@uffizzi.com within 31 days of the inciting event and request a formal resolution.
Project leaders will make reasonable efforts to adjudicate incidents shortly after they are brought to their attention.
### Enforcement
Project leaders shall take all reasonable actions to ensure the successful execution of the mission statement and the maximum effectiveness of the project.
All material in official project spaces is subject to the code of conduct, and as such, can be deleted, modified, or rejected by project leaders if it is found to be in violation of the code of conduct. In repeated or severe cases, project leaders may exclude individuals from further contribution to the project on a temporary or permanent basis.
## Scope
This code of conduct applies to official project spaces, which include but are not limited to: social media, conferences, code repositories, and discussion boards. This code of conduct also applies to members and contributors who represent the project in non-project spaces, such as when contributors give a talk on behalf of the project at a conference. In official spaces, members and contributors should operate with decorum that reflects positively on the project, its objectives, and its community.
## Licensing
The Mission Protocol Code of Conduct was created under the CC BY 4.0 License in 2020. Frequently asked questions about Mission Protocol are available here.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to `uffizzi_app`
Thanks for your interest! We are actively working to release `uffizzi_app` and define how you can contribute - please follow for updates!
Uffizzi welcomes contributions from everyone!
## Communication:
If you need any help contributing, several maintainers are on the Uffizzi Users Slack group https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A.
## Releases
We're using Semantic Versioning 2.0.0 to name our release tags: https://semver.org/
Be sure to update the `appVersion` within `charts/uffizzi-app/Chart.yaml` whenever you create a new release! And also update the tag for `image` within `charts/uffizzi-app/values.yaml`.
## Helm Chart Releases
When you change the Helm chart, even if it's just bumping the `appVersion` and `image` tag, also increment the `version` within `charts/uffizzi-app/Chart.yaml`. This chart version does not need to match the app version, and it's probably better if it does not.
When the new `Chart.yaml` makes it into the default branch, then the `chart-releaser` GitHub Action will create a new tag with a `uffizzi-app-` prefix. It will also update our Helm repo within the `gh-pages` branch. Let the automation handle this.
# Procedures for outside collaborators:
- Fork the project on Github.
- Make any changes you want to `uffizzi_app`, commit them, and push them to your fork.
- Create a Pull Request against `UffizziCloud/uffizzi_app:main`, and a maintainer will come by and review your inputs. They may ask for some changes or more information, and hopefully your contribution will be merged to the `main` branch!
# Procedures for Uffizzi team members:
1. Clone the repository and checkout to `develop` branch.
2. Pull repository to ensure you have the latest changes.
```bash
git pull --rebase develop
```
3. Start new branch from `develop`
```bash
git checkout -b feature/ISSUE_NUMBER_short_issue_description (e.g. feature/53_add_domain_settings)
```
4. Make changes you need for the feature, commit them to the repo
```bash
git add .
git commit -m '[#ISSUE_NUMBER] short commit description' (e.g. git commit -m '[#53] added domain settings')
git push origin BRANCH_NAME
```
5. 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
6. Merge your feature to `qa` branch and push. Ensure your pipeline is successful.
```bash
git checkout qa
git pull --rebase qa
git merge --no-ff BRANCH_NAME
git push origin qa
```
# Running linter
```bash
docker-compose run --rm web bundle exec rubocop -A
```
# Running test
```bash
docker-compose run --rm core bash
bin/rails test
```
# Migrations
In order to add a new migration do the following steps:
1. Add a new migration inside the core
2. Run the command inside the `web` container
```bash
rake uffizzi_core:install:migrations
```
This command copies the new migration to the `db/migrate` folder
================================================
FILE: Dockerfile
================================================
FROM ruby:2.7.5-slim
ARG RAILS_ROOT=/app
RUN mkdir -p $RAILS_ROOT
WORKDIR $RAILS_ROOT
RUN apt-get update \
&& apt-get install -qq -y --no-install-recommends \
vim-tiny \
python2-dev \
libpq-dev \
build-essential \
curl \
less \
tzdata \
git \
postgresql-client \
bash \
screen \
shared-mime-info \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
RUN gem install bundler:2.3.9
COPY Gemfile Gemfile.lock $RAILS_ROOT/
COPY core/lib/uffizzi_core/version.rb $RAILS_ROOT/core/lib/uffizzi_core/version.rb
COPY core/uffizzi_core.gemspec $RAILS_ROOT/core/uffizzi_core.gemspec
COPY core/Gemfile* $RAILS_ROOT/core/
RUN bundle install --jobs 5
COPY . $RAILS_ROOT
ENV PATH $RAILS_ROOT/bin:$PATH
EXPOSE 7000
CMD /bin/bash -c "bundle exec rails db:create db:migrate && bundle exec puma -C config/puma.rb"
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.7.5'
gem 'bcrypt', '~> 3.1.7'
gem 'bootsnap', '>= 1.4.2', require: false
gem 'config'
gem 'health_check'
gem 'pg', '>= 0.18', '< 2.0'
gem 'puma', '~> 4.1'
gem 'rack-cors'
gem 'rails', '~> 6.1.0'
gem 'sidekiq'
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'uffizzi_core', path: './core'
group :development, :test do
gem 'awesome_print'
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'rubocop'
gem 'rubocop-minitest'
gem 'rubocop-rake'
end
================================================
FILE: INSTALL.md
================================================
# Installation overview
See the [Helm chart guide](https://github.com/UffizziCloud/uffizzi_app/blob/main/charts/uffizzi-app/README.md) for installing Uffizzi on your own Kubernetes cluster.
Once Uffizzi is installed, you can use the [Uffizzi CLI](https://github.com/UffizziCloud/uffizzi_cli) to create and manage previews. Additionally, you can continuously deploy previews of your branches using [the GitHub Action](https://github.com/UffizziCloud/preview-action).
## Uffizzi Architecture
Uffizzi consists of the following components:
* Uffizzi App (this repository) - The primary REST API for creating and managing Previews
* [Uffizzi Controller](https://github.com/UffizziCloud/uffizzi_controller) - A smart proxy service that handles requests from Uffizzi App to the Kubernetes API
* [Uffizzi CLI](https://github.com/UffizziCloud/uffizzi_cli) - A command-line interface for Uffizzi App
Uffizzi App requires the following external dependencies:
* Kubernetes (k8s) cluster
* PostgreSQL database
* Redis cache
## Controller Design
This `uffizzi_app` acts as a REST API for [`uffizzi_cli`](https://github.com/UffizziCloud/uffizzi_app) interface. It requires [`uffizzi_controller`](https://github.com/UffizziCloud/uffizzi_controller) as a supporting service.
## Uffizzi App Environment Variables
- `RAILS_SECRET_KEY_BASE` - secret_key_base of Rails::Application
- `DATABASE_HOST` - the database hostname (default: 127.0.0.1)
- `DATABASE_USER` - the database username (default: postgres)
- `DATABASE_PASSWORD` - the database password
- `DATABASE_PORT` - the database port (default: 5432)
- `BUNDLE_PATH` - the location of gems for `bundle install` command (not required)
- `GEM_PATH` - the location where gems can be found (not required)
- `GEM_HOME` - where gems will be installed (not required)
- `RAILS_WORKERS_COUNT` - the number of `puma` workers (default: 18)
- `RAILS_THREADS_COUNT` - the number of `puma` threads (default: 5)
- `RAILS_PORT` - the `puma` port (default: 7000)
- `RAILS_ENV` - the rails environment (default: development)
- `SIDEKIQ_CONCURRENCY` - sidekiq concurrency (default: 5)
- `ALLOWED_HOSTS` - allowed hosts for rails app used for Rack::Cors (default: [])
- `APP_URL` - URL of the application
- `REDIS_URL` - URL of a Redis server
- `CONTROLLER_URL` - URL of the controller application (default: http://controller:8080)
- `CONTROLLER_LOGIN` - the login of the controller application (default: '')
- `CONTROLLER_PASSWORD` - the password of the controller application (default: '')
# Test Uffizzi App Locally
If you want to run Uffizzi on your workstation instead of using [the Helm chart](charts/uffizzi-app/README.md), then you can run it using Docker Compose.
## Prepare
```bash
docker-compose run --rm web bash -c "bundle install && bundle exec rails db:setup"
docker-compose up
```
## Create a new user
Run the following command and follow instructions:
```bash
docker-compose run --rm web bash -c "rake uffizzi_core:create_user"
```
or run the command with environment variables:
- `UFFIZZI_USER_EMAIL` - user's email
- `UFFIZZI_USER_PASSWORD` - user's password
- `UFFIZZI_PROJECT_NAME` - user's project name
```bash
docker-compose run --rm -e UFFIZZI_USER_EMAIL=user@uffizzi.com -e UFFIZZI_USER_PASSWORD=password -e UFFIZZI_PROJECT_NAME=project web bash -c "rake uffizzi_core:create_user"
```
## Connect from uffizzi-cli to the app
```bash
docker-compose run --rm gem bash
bundle exec uffizzi login --hostname http://web:7000 -u admin@uffizzi.com
```
## API Documentation
* [Development](http://lvh.me:7000/api-docs/index.html)
Rebuild documentation locally:
```bash
docker-compose run --rm core bash
bundle exec rake core:generate_docs
```
# Health checks
The default health check uri is `health_check`. To use a custom uri please add the `HEALTH_CHECK_URI` environment variable to the docker-compose.yml
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2021] [Uffizzi]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
.PHONY: release release_patch release_minor release_major test
NEXT_PATCH=$(shell docker compose run --rm core bash -c "bundle exec bump show-next patch")
NEXT_MINOR=$(shell docker compose run --rm core bash -c "bundle exec bump show-next minor")
NEXT_MAJOR=$(shell docker compose run --rm core bash -c "bundle exec bump show-next major")
release_patch: export VERSION=${NEXT_PATCH}
release_patch:
make release
release_minor: export VERSION=${NEXT_MINOR}
release_minor:
make release
release_major: export VERSION=${NEXT_MAJOR}
release_major:
make release
release:
git checkout develop
@echo 'Set a new version'
docker compose run --rm core bash -c "bundle exec bump set ${VERSION}"
docker compose run --rm core bash -c "bundle update uffizzi_core --conservative "
docker compose run --rm web bash -c "bundle update uffizzi_core --conservative "
git commit -am "Change version to ${VERSION}"
@echo 'Update remote origin'
git push origin develop
git checkout main
git pull origin --rebase main
git merge --no-ff --no-edit develop
git push origin main
@echo 'Create a new tag'
git tag core_v${VERSION}
git push origin core_v${VERSION}
test:
docker compose run --rm core bash -c "bundle exec rails test"
lint:
docker compose run --rm web bash -c "bundle exec rubocop -A"
================================================
FILE: README.md
================================================
Uffizzi helps teams build [internal developer platforms (IDPs)](/core-concepts/internal-developer-platform) in minutes, not months, by providing out-of-the-box [Kubernetes multi-tenancy](https://www.uffizzi.com/kubernetes-multi-tenancy), [virtual clusters](/core-concepts/ephemeral-environments/virtual-clusters), cloud-based [dev environments](/core-concepts/ephemeral-environments/dev-clusters), customizable templating, and more.
Uffizzi provides a foundation for building IDPs, so platform teams can build end-to-end workflows, giving every developer access to self-service, [ephemeral environments](/core-concepts/ephemeral-environments) for development, testing, PRs, staging and more. Use Uffizzi environments to preview pull requests before merging or integrate with your CI pipeline for automated, end-to-end testing.
Trusted by top teams
Backstage •
NocoDB •
Forem •
Lazygit •
D2IQ •
ParseDashboard •
Fonoster
Answer •
Windmill •
Flagsmith •
Memos •
Crater •
Livebook •
OnlineGo •
BoxyHQ
Teams like [Backstage](https://github.com/backstage/backstage/tree/master/.github/uffizzi), [NocoDB](https://github.com/nocodb/nocodb/tree/develop/.github/uffizzi), and [Forem](https://github.com/forem/forem/blob/main/.github/workflows/uffizzi-preview.yml) have adopted Uffizzi because it's lightweight, fast, scalable, and more cost effective than competing solutions. Did you know that Spotify's Backstage team achieves rapid releases at scale using nearly 400 ephemeral environments per month? [Learn how →](https://www.uffizzi.com/ephemeral-environments)

## Quickstart (~2 minute)
Go to the [Quickstart Guide](https://docs.uffizzi.com/quickstart) to get started creating ephemeral environments.
## How it works
Spin up ephemeral environments on demand from the CLI, web dashboard, or from a CI pipeline. Each ephemeral environment is continually refreshed when you push new commits. Uffizzi also handles clean up, so your environments last only as long as you need them.
Uffizzi's modular design works with GitHub, GitLab, BitBucket, and any CI provider.
## Give us a star ⭐️
If you're interested in Uffizzi, give us a star. It helps others discover the project.
## Use cases
Uffizzi is designed to integrate with any CI platform as a step in your pipeline. You can use Uffizzi to rapidly create:
- Cloud dev environments with hot reloading of deployed services
- On-demand test environments for Kubernetes applications
- Pull request environments
- Debugging environments
- Hotfix environments
- Demo environments
- Release environments
- Staging environments
## What types of apps are supported by Uffizzi?
Uffizzi supports application configurations in Kubernetes manifests, Helm, kustomize, or Docker Compose. See [Using Uffizzi](https://docs.uffizzi.com/usage) to learn about the ways you can use Uffizzi.
## Why Uffizzi?
Uffizzi provides a foundation for building IDPs, so platform teams can build end-to-end workflows, giving every developer access to self-service, ephemeral environments for development, testing, PRs, staging and more.
Uffizzi is also useful for helping busy open source project leaders approve pull requests faster. Testing a live preview provides a more holistic way to assess a new feature or bug fix, rather than simply reviewing code changes. Uffizzi also removes the added step of pulling down the branch to test it locally: Uffizzi seamlessly integrates with CI providers like GitHub Actions and posts comments directly to pull request issues, so there is no additional step for the maintainer or the contributor. Learn how Uffizzi is helping [Backstage accelerate their development velocity by 20%](https://www.uffizzi.com/ephemeral-environments).
## Set up ephemeral environments for your application
(If you haven't completed the [quickstart guide](https://docs.uffizzi.com/quickstart), we recommend starting there to understand how Uffizzi works and how it's configured.)
There are three options to get Uffizzi:
1. **[Uffizzi Cloud](https://docs.uffizzi.com/cloud) (SaaS)** - This is fastest and easiest way to get started. Uffizzi Cloud is our fully managed option, so you don't have to worry about managing any infrastructure. You can get two concurrent environments for free, or unlock unlimited ephemeral environments with Uffizzi Pro. See our [Pricing page](https://www.uffizzi.com/pricing) for details.
2. **[Uffizzi Enterprise](https://docs.uffizzi.com/enterprise)** - Uffizzi Enterprise provides the option to run workloads on your own infrastucture, along with more flexibility in customizing your ephemeral environments experience.
3. **[Uffizzi Open Source](https://docs.uffizzi.com/open-source)** - Alternatively, you can install the open source version of Uffizzi on your own cluster by following the [self-hosted installation guide](INSTALL.md).
## Documentation
- [Main documentation](https://docs.uffizzi.com)
- [Docker Compose for Uffizzi ](https://docs.uffizzi.com/compose)
- [Quickstart guide](https://docs.uffizzi.com/quickstart)
## Community
- [Slack channel](https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A) - Get support or discuss the project
- [Subscribe to our newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7011448505391042560) - Receive monthly updates about new features and special events
- [Contributing to Uffizzi](CONTRIBUTING.md) - Start here if you want to contribute
- [Code of Conduct](CODE_OF_CONDUCT.md) - Let's keep it professional
## FAQs
My team tests locally. Why do I need Ephemeral Environments?
Ephemeral Environments more closely resemble production . Uffizzi deploys images built from your CI pipeline—similar to the ones deployed to a production environment. Uffizzi Ephemeral Environments also include a full network stack, including a domain and TLS certificate.
Ephemeral Environments provide many benefits including standardizing development configurations, avoiding the bottleneck of a single test/staging environment, acting as a quality gate to help keep dirty code out of your main branch. Teams can develop and test new features or bug fixes in clean, isolated environments.
Public preview URLs allow every stakeholder on a team to review features and bug fixes. This helps shorten the feedback loop between developer and reviewer/tester, resulting in faster releases.
How is Uffizzi different from Codespaces, Gitpod, etc.?
Codespaces, Gitpod, and similar tools focus soley on providing development environments hosted in the cloud. They let you code locally (or in a browser-emulated editor) and see your changes in a live deployed environments. They can also provide developers access to more powerful machines than typically available on a laptop or desktop.
Uffizzi is a more full-featured platform designed for building self-serve developer platforms and for standardizing end-to-end developer workflows through on-demand dev, test, CI, and staging environments. Similar to Codespaces and Gitpod, Uffizzi offers cloud-based dev environments, but unlike these tools, Uffizzi users have access to the underlying Kubernetes clusters, enabling more complex configurations and customization via kubectl and similar tools. Uffizzi also supports creating virtual clusters for ephemeral test environments, as well as, CI integrations for pull request previews.
See our documentation for other common uses and guides.
How is Uffizzi different from GitHub Actions (or other CI providers)?
Uffizzi does not replace GitHub Actions or any other CI provider. Uffizzi can be added as a step in your existing CI pipeline, after your container images are built and pushed to a container registry. For example, when you open a pull request, a GitHub Actions workflow can trigger the creation of new virtual cluster on Uffizzi and deploy that branch onto it. See our CI Recipes for configuration help.
What about my database?
All services deployed to Uffizzi ephemeral environments are deployed as containers—this includes databases, caches, and other stateful services This means that even if you use a managed database service like Amazon RDS for production, you should use a database image in your configuration (See our Ephemeral Environments Handbook for strategies on managing stateful services on Uffizzi.
What do you mean by "environments"?
See our documentaion for what we mean.
Does Uffizzi support monorepos/polyrepos?
Yes. Whether created via you're creating ephemeral environments from the CLI, dashboard, or CI pipeline, Uffizzi can deploy applications from one source or many. If you're using Uffizzi virtual clusters, you should define the sources in your Helm Charts, kustomizations, or manifests. For Docker Compose users, Uffizzi just needs to know the fully qualified container registry URL for where to find these built images. See the Uffizzi Compose reference for details.
Does Uffizzi support _____________?
In general, if your application can be containerized, described with Kubernetes, Helm, kustomize, or Docker Compose, then it is likely compatible with Uffizzi. The one notable exception to this is that Uffizzi does not support Node-level access, such as Kubernetes DaemonSets.
How can my application services communicate?
See Uffizzi Networking for details.
Is Uffizzi open source?
Yes. If you have access to a Kubernetes cluster, you can install Uffizzi via Helm. Follow the self-hosted installation guide .
## License
This library is licensed under the [Apache License, Version 2.0](LICENSE).
## Security
If you discover a security related issues, please do **not** create a public github issue. Notify the Uffizzi team privately by sending an email to `security@uffizzi.com`.
================================================
FILE: Rakefile
================================================
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks
================================================
FILE: app/assets/config/manifest.js
================================================
//= link_tree ../images
//= link_directory ../stylesheets .css
================================================
FILE: app/assets/fonts/mtiFontTrackingCode.js
================================================
eval(function (p, a, c, k, e, d) { e = function (c) { return c.toString(36) }; if (!''.replace(/^/, String)) { while (c--) { d[c.toString(a)] = k[c] || c.toString(a) } k = [function (e) { return d[e] }]; e = function () { return '\\w+' }; c = 1 }; while (c--) { if (k[c]) { p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]) } } return p }('4 8=9.f;4 6=9.k.m(",");4 2=3.j(\'l\');2.h=\'g/5\';2.d=\'b\';2.e=(\'7:\'==3.i.s?\'7:\':\'u:\')+\'//v.n.w/x/1.5?t=5&c=\'+8+\'&o=\'+6;(3.a(\'p\')[0]||3.a(\'q\')[0]).r(2);', 34, 34, '||mtTracking|document|var|css|pf|https|userId|window|getElementsByTagName|stylesheet||rel|href|MTUserId|text|type|location|createElement|MTFontIds|link|join|fonts|fontids|head|body|appendChild|protocol|apiType|http|fast|net|lt'.split('|'), 0, {}))
================================================
FILE: app/assets/images/.keep
================================================
================================================
FILE: app/assets/stylesheets/application.css
================================================
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
================================================
FILE: app/channels/application_cable/channel.rb
================================================
# frozen_string_literal: true
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
================================================
FILE: app/channels/application_cable/connection.rb
================================================
# frozen_string_literal: true
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
================================================
FILE: app/controllers/application_controller.rb
================================================
# frozen_string_literal: true
class ApplicationController < ActionController::Base
end
================================================
FILE: app/controllers/concerns/.keep
================================================
================================================
FILE: app/helpers/application_helper.rb
================================================
# frozen_string_literal: true
module ApplicationHelper
end
================================================
FILE: app/javascript/channels/consumer.js
================================================
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
================================================
FILE: app/javascript/channels/index.js
================================================
// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.
const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)
================================================
FILE: app/javascript/packs/application.js
================================================
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
================================================
FILE: app/jobs/application_job.rb
================================================
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end
================================================
FILE: app/mailers/application_mailer.rb
================================================
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
end
================================================
FILE: app/models/application_record.rb
================================================
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
================================================
FILE: app/models/concerns/.keep
================================================
================================================
FILE: app/views/layouts/application.html.erb
================================================
Uffizzi App
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= yield %>
================================================
FILE: app/views/layouts/mailer.html.erb
================================================
<%= yield %>
================================================
FILE: app/views/layouts/mailer.text.erb
================================================
<%= yield %>
================================================
FILE: bin/rails
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
================================================
FILE: bin/rake
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative '../config/boot'
require 'rake'
Rake.application.run
================================================
FILE: bin/setup
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils'
# path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
FileUtils.chdir(APP_ROOT) do
# This script is a way to setup or update your development environment automatically.
# This script is idempotent, so that you can run it at anytime and get an expectable outcome.
# Add necessary setup steps to this file.
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
# Install JavaScript dependencies
# system('bin/yarn')
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
# end
puts "\n== Preparing database =="
system! 'bin/rails db:prepare'
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
system! 'bin/rails restart'
end
================================================
FILE: bin/yarn
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do
exec('yarnpkg', *ARGV)
rescue Errno::ENOENT
warn('Yarn executable was not detected in the system.')
warn('Download Yarn at https://yarnpkg.com/en/docs/install')
exit(1)
end
================================================
FILE: charts/uffizzi-app/Chart.yaml
================================================
apiVersion: v2
name: uffizzi-app
version: 1.3.0
kubeVersion: ">= 1.21.0-0" # https://issuetracker.google.com/issues/77503699
description: "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."
type: application
keywords:
- devops
- uffizzi
- continuous-previews
- ephemeral
- environments
- pull-request
- merge-request
- on-demand
- ci
- cd
- cp
- idp
home: https://uffizzi.com/
sources:
- https://github.com/UffizziCloud/uffizzi
dependencies:
- name: uffizzi-controller
version: "^2"
repository: https://uffizzicloud.github.io/uffizzi_controller/
- name: postgresql
version: "~13"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "~18"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
maintainers:
- name: Uffizzi
email: info@uffizzi.com
url: https://uffizzi.com
- name: Adam Vollrath
email: adam.vollrath@uffizzi.com
url: https://github.com/axisofentropy
icon: https://app.uffizzi.com/favicon.png
appVersion: "2.3.0"
deprecated: false
annotations:
# Use this annotation to indicate that this chart version is a pre-release.
# https://artifacthub.io/docs/topics/annotations/helm/
artifacthub.io/prerelease: "false"
================================================
FILE: charts/uffizzi-app/README.md
================================================
# Helm chart guide
This chart installs Uffizzi.
## Requirements
This chart requires a Kubernetes Cluster. While it will likely function on k8s >= 1.19, we have only tested upon k8s 1.21 - 1.23.
The Cluster must be capable of provisioning `Ingress` resources that obtain public IP addresses and/or hostnames.
We've tested Uffizzi on:
- Google Kubernetes Engine (GKE)
- Azure Kubernetes Service (AKS)
- Amazon Elastic Kubernetes Service (EKS)
### Dependencies
This chart depends upon three subcharts:
- [`bitnami/postgresql`](https://artifacthub.io/packages/helm/bitnami/postgresql)
- [`bitnami/redis`](https://artifacthub.io/packages/helm/bitnami/redis)
- [`uffizzi-controller`](https://artifacthub.io/packages/helm/uffizzi-controller/uffizzi-controller)
You can disable the `bitnami` subcharts if you want to manage your own datastores.
## Configuration
This Helm chart requires integration with your DNS records and other services, so there are several required values. Create a YAML file with these values before installing this chart. There's an example below and you can read more about Helm Values Files here: https://helm.sh/docs/chart_template_guide/values_files/
### Controller
See the [Controller's Helm Chart](https://artifacthub.io/packages/helm/uffizzi-controller/uffizzi-controller) for its configuration, including its certificate authority.
The controller itself depends upon two other popular Helm charts:
- [`ingress-nginx`](https://kubernetes.github.io/ingress-nginx/)
- [`cert-manager`](https://cert-manager.io/docs/)
If you already have one or both of these applications installed, you may want to disable them for this Helm release. Specifically, your k8s Cluster may already have cert-manager's Custom Resource Definitions defined.
### Secrets
When installing Uffizzi in a sensitive or production environment, it's important to generate strong passwords. Provide new values for the `ChangeMeNow` values in the example below.
### Example Helm Values File
Example values file with required values:
```yaml
global:
postgresql:
auth:
postgresPassword: ChangeMeNow
password: ChangeMeNow
redis:
password: ChangeMeNow
uffizzi:
firstUser:
email: user@example.com
password: ChangeMeNow
controller:
password: ChangeMeNow
app_url: https://uffizzi.example.com
webHostname: uffizzi.example.com
allowed_hosts: uffizzi.example.com
managed_dns_zone_dns_name: uffizzi.example.com
uffizzi-controller:
ingress:
hostname: controller.uffizzi.example.com
clusterIssuer: "letsencrypt"
certEmail: admin@example.com
```
Edit these values and save them in a file named `myvals.yaml` or similar.
## Installation
If this is your first time using Helm, consult their documentation: https://helm.sh/docs/intro/quickstart/
Begin by adding our Helm repository:
```
helm repo add uffizzi https://uffizzicloud.github.io/uffizzi/
```
Then install the lastest version as a new release using the values you specified earlier. We recommend isolating Uffizzi in its own Namespace.
```
helm install my-uffizzi uffizzi/uffizzi-app --values myvals.yaml --namespace uffizzi --create-namespace
```
If you encounter any errors here, tell us about them in [our Slack](https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A).
You should then see the release is installed:
```
helm list --namespace uffizzi
```
### DNS
After the Helm release is installed, add DNS records for the hostnames you specified in your values file. You can obtain the IP or hostname for Uffizzi's Ingress using `kubectl`:
```
kubectl get ingress --namespace uffizzi
```
Be sure to add a "wildcard" record for the domain specified in `managed_dns_zone_dns_name`. In the above example, that's `*.uffizzi.example.com`.
### Provisioning users
You'll need to create at least one User Account to access your Uffizzi installation. The easiest way to do this is specify values for `global.uffizzi.firstUser` as shown in the example values file above. Uffizzi will attempt to provision this User each time it starts.
If you did not specify a `firstUser`, or if you want to provision additional Users, you may execute an interactive `rake` task within the application server container:
```
kubectl exec -it deploy/my-uffizzi-web --namespace uffizzi -- rake uffizzi_core:create_user
Enter User Email (default: user@example.com): user@example.com
Enter Password:
Enter Project Name (default: default):
```
### Troubleshooting
When installing this chart, you may see errors like this:
```
clusterroles.rbac.authorization.k8s.io "my-uffizzi-controller-flux-default-source-controller-helmchart" already exists
```
This happens when more than one resource within a dependency chart (in this case `flux`) has a very long name truncated into the same name as another resource. To avoid this, use shorter release names as in the example above.
## Usage
If everything went well, you can now connect to the Uffizzi API service and begin Continously Deploying Previews! Use [the Uffizzi CLI](https://github.com/UffizziCloud/uffizzi_cli) or [the Uffizzi GitHub Action](https://github.com/UffizziCloud/preview-action) or your own API client.
## More Info
See this project's main repository here: https://github.com/UffizziCloud/uffizzi
And explore Uffizzi https://uffizzi.com
================================================
FILE: charts/uffizzi-app/templates/configmap-common.yaml
================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: uffizzi-web-common-envs
data:
DATABASE_POOL: "16"
DATABASE_PORT: {{ .Values.global.postgresql.service.ports.postgresql | quote }}
DATABASE_HOST: {{ default (print .Release.Name "-postgresql") .Values.db_host | quote }}
RAILS_ENV: {{ .Values.env | quote }}
APP_URL: {{ .Values.app_url }}
CONTROLLER_URL: {{ default (print "http://" .Release.Name "-controller:8080") .Values.controller_url }}
EMAIL_DELIVERY_ENABLED: {{ .Values.feature_email_delivery | quote }}
MANAGED_DNS_ZONE_DNS_NAME: {{ .Values.managed_dns_zone_dns_name | quote }}
UFFIZZI_USER_EMAIL: {{ .Values.global.uffizzi.firstUser.email }}
UFFIZZI_USER_PASSWORD: {{ .Values.global.uffizzi.firstUser.password | quote }}
UFFIZZI_PROJECT_NAME: {{ .Values.global.uffizzi.firstUser.projectName | quote }}
================================================
FILE: charts/uffizzi-app/templates/configmap-sidekiq.yaml
================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: uffizzi-web-sidekiq-envs
data:
SIDEKIQ_CONCURRENCY: "10"
================================================
FILE: charts/uffizzi-app/templates/configmap-web.yaml
================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: uffizzi-web-service-envs
data:
ALLOWED_HOSTS: {{ .Values.allowed_hosts | quote }}
RAILS_PORT: "7000"
RAILS_SERVE_STATIC_FILES: "true"
RAILS_THREADS_COUNT: "8"
RAILS_WORKERS_COUNT: "2"
================================================
FILE: charts/uffizzi-app/templates/secret-web.yaml
================================================
apiVersion: v1
kind: Secret
metadata:
name: uffizzi-web-secret-envs
type: Opaque
data:
DATABASE_USER: {{ .Values.global.postgresql.auth.username | b64enc }}
DATABASE_PASSWORD: {{ .Values.global.postgresql.auth.password | b64enc }}
DATABASE_NAME: {{ .Values.global.postgresql.auth.database | b64enc }}
RAILS_SECRET_KEY_BASE: {{ .Values.rails_secret_key_base | default (printf "%x" (randAscii 64))| b64enc }}
REDIS_URL: {{ default (print "redis://:" .Values.global.redis.password "@" .Release.Name "-redis-master") .Values.redis_url | b64enc }}
APP_LOGIN: {{ .Values.basic_auth_login | b64enc }}
APP_PASSWORD: {{ .Values.basic_auth_password | b64enc }}
SIDEKIQ_LOGIN: {{ .Values.basic_auth_login | b64enc }}
SIDEKIQ_PASSWORD: {{ .Values.basic_auth_password | b64enc }}
CONTROLLER_LOGIN: {{ .Values.global.uffizzi.controller.username | b64enc }}
CONTROLLER_PASSWORD: {{ .Values.global.uffizzi.controller.password | b64enc }}
GITHUB_APP_ID: {{ .Values.github_app_id | b64enc }}
GITHUB_APP_SLUG: {{ .Values.github_app_slug | b64enc }}
GITHUB_CLIENT_ID: {{ .Values.github_client_id | b64enc }}
GITHUB_CLIENT_SECRET: {{ .Values.github_client_secret | b64enc }}
GITHUB_PRIVATE_KEY: {{ .Values.github_private_key | b64enc }}
GITHUB_WEBHOOK_SECRET: {{ .Values.github_webhook_secret | b64enc }}
================================================
FILE: charts/uffizzi-app/templates/sidekiq-deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-sidekiq
labels:
app: uffizzi-sidekiq
spec:
replicas: {{ .Values.sidekiq_replicas }}
selector:
matchLabels:
app: uffizzi-sidekiq
template:
metadata:
labels:
app: uffizzi-sidekiq
spec:
automountServiceAccountToken: false
containers:
- name: uffizzi-sidekiq
image: {{ .Values.image }}
command:
- /bin/bash
- -c
args:
- bundle exec sidekiq -C /app/config/sidekiq.yml
envFrom:
- secretRef:
name: uffizzi-web-secret-envs
optional: false
- configMapRef:
name: uffizzi-web-common-envs
optional: false
- configMapRef:
name: uffizzi-web-sidekiq-envs
optional: false
imagePullPolicy: Always
resources:
requests:
cpu: 400m
memory: 800Mi
================================================
FILE: charts/uffizzi-app/templates/web-deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-web
labels:
app: uffizzi-web
spec:
replicas: {{ .Values.web_replicas }}
selector:
matchLabels:
app: uffizzi-web
template:
metadata:
labels:
app: uffizzi-web
spec:
automountServiceAccountToken: false
containers:
- name: uffizzi-web
image: {{ .Values.image }}
command:
- /bin/bash
- -c
args:
- bundle exec rails db:create db:migrate && bundle exec rake uffizzi_core:create_user && bundle exec puma -C config/puma.rb
envFrom:
- secretRef:
name: uffizzi-web-secret-envs
optional: false
- configMapRef:
name: uffizzi-web-common-envs
optional: false
- configMapRef:
name: uffizzi-web-service-envs
optional: false
imagePullPolicy: Always
ports:
- containerPort: 7000
protocol: TCP
# readinessProbe:
# failureThreshold: 3
# httpGet:
# path: /health_check
# port: 7000
# scheme: HTTP
# periodSeconds: 10
# successThreshold: 1
# timeoutSeconds: 1
resources:
requests:
cpu: 150m
memory: 800Mi
================================================
FILE: charts/uffizzi-app/templates/web-ingress.yaml
================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}-web-ingress
annotations:
cert-manager.io/cluster-issuer: {{ print .Release.Name "-" (index (.Values) "uffizzi-controller" "clusterIssuer") }}
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: {{ .Values.webHostname }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}-web-service
port:
number: 80
tls:
- secretName: {{ .Values.webHostname }}
hosts:
- {{ .Values.webHostname }}
================================================
FILE: charts/uffizzi-app/templates/web-service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-web-service
spec:
type: NodePort
selector:
app: uffizzi-web
ports:
- name: http
port: 80
protocol: TCP
targetPort: 7000
sessionAffinity: ClientIP
================================================
FILE: charts/uffizzi-app/values.yaml
================================================
global:
postgresql:
auth:
postgresPassword: ChangeMeNow
username: uffizzi-user
password: ChangeMeNow
database: uffizzi-app
service:
ports:
postgresql: "5432"
redis:
password: ChangeMeNow
uffizzi:
firstUser:
email: ""
password: ""
projectName: default
controller:
username: username
password: ChangeMeNow
env: production
app_url: https://uffizzi.example.com
webHostname: uffizzi.example.com
image: uffizzi/app
web-replicas: 3
sidekiq-replicas: 1
hostname: localhost
controller_url: ""
allowed_hosts: ""
db_host: "" # default to dependent postgresql
rails_secret_key_base: "" # default to half-random string
redis_url: "" # default to dependent redis
managed_dns_zone_dns_name: uffizzi.example.com
basic_auth_login: username
basic_auth_password: ChangeMeNow
github_app_id: ChangeMeNow
github_app_slug: ChangeMeNow
github_client_id: ChangeMeNow
github_client_secret: ChangeMeNow
github_private_key: ChangeMeNow
github_webhook_secret: ChangeMeNow
sql_credentials_secret_name: example
feature_mailchimp: false
feature_email_delivery: false
feature_google_maps: false
feature_sentry: false
feature_stripe: false
uffizzi-controller: #dependency
clusterIssuer: "letsencrypt"
image: uffizzi/controller:latest
ingress:
hostname: uffizzi.example.com
podCidr: 10.0.0.0/8
zerossl:
eab:
hmacKey: foo
keyId: bar
env: "production"
sandbox: "false"
certEmail: "user@example.com"
cert-manager: # dependency of dependency
installCRDs: true
postgresql: #dependency
enabled: true
redis: # dependency
enabled: true
================================================
FILE: ci/github-actions/README.md
================================================
# Use Uffizzi with GitHub Actions
You can configure Uffizzi to create, update, and delete on-demand test environments with the GitHub Actions [reusable workflow](https://github.com/UffizziCloud/preview-action/blob/master/.github/workflows/reusable.yaml). This reusable workflow will execute the [Uffizzi CLI](https://github.com/UffizziCloud/uffizzi_cli) on a GitHub Actions runner, which then opens a connection to the Uffizzi API.
## Example usage
The following example application demonstrates how to use Uffizzi with GitHub Actions:
[Example voting app](https://github.com/UffizziCloud/example-voting-app/blob/main/.github/workflows/uffizzi-previews.yml)
================================================
FILE: ci/gitlab/README.md
================================================
# Use Uffizzi with GitLab CI
You can configure Uffizzi to create, update, and delete on-demand Preview Environments with the GitLab CI [Environment Action](https://gitlab.com/uffizzi/environment-action). This action will execute the [Uffizzi CLI](https://github.com/UffizziCloud/uffizzi_cli) on a GitLab runner, which then opens a connection to the Uffizzi API.
================================================
FILE: config/application.rb
================================================
# frozen_string_literal: true
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
require 'uffizzi_core'
module UffizziApp
class Application < Rails::Application
config.load_defaults(6.0)
config.hosts = Settings.allowed_hosts
config.middleware.insert_before(0, Rack::Cors) do
allow do
origins do |source|
uri = URI.parse(source)
Settings.allowed_hosts.any? { |host| uri.host == host || uri.host.ends_with?(host) }
end
resource '*', headers: :any, methods: [:get, :post, :options, :put, :patch, :delete], credentials: true
end
end
end
end
================================================
FILE: config/boot.rb
================================================
# frozen_string_literal: true
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
================================================
FILE: config/cable.yml
================================================
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: uffizzi_app_production
================================================
FILE: config/credentials.yml.enc
================================================
/panGyOWYw7RDiAdnBU58UQcCf5LmUrtROWE1pQpIAlZ1BUUsCat6oE8MVO5TDTc4q0XOGjndQ28i+ffjpHCob1TPHlTwGmAWcaKawSOQeLPOgHHvtzumHfS5erwYLMn6DgKyUNqobtk0+0PFTiwY5Cpbukri+gwb7pc8rjweYSRilreS++X/pf7zm606NvRiIKgoVMciie25CVR9g1ll1jgjE/L750rrROrGsobLuMp6XEBdzjqI1O2P9fiyCFND3a2g4kq3ARnK7b/T2yk3NG31UABfSkNoGJk72vyxW+gottHKe1RhZ4kYWQvegycS1FrxSj2bEXcdjnWzOxs1lCzJgj2ImaYm/sN7pyDZ1zXiyYYqWpRWDbGtER0IOUVUg1bAxDK8BfFFj8kX3W4en8VkmmVcJWv7lDX--f9oik+xRebXs6ijD--rZd8unbXjXvCf6ar117b4Q==
================================================
FILE: config/database.yml
================================================
default: &default
adapter: postgresql
encoding: unicode
host: <%= ENV.fetch("DATABASE_HOST") {"127.0.0.1"} %>
port: <%= ENV.fetch("DATABASE_PORT") {5432} %>
pool: <%= ENV.fetch("DATABASE_POOL") {5} %>
username: <%= ENV.fetch("DATABASE_USER") {"postgres"} %>
password: <%= ENV.fetch("DATABASE_PASSWORD") {""} %>
development:
<<: *default
database: uffizzi_development
test:
<<: *default
database: uffizzi_test
production:
<<: *default
database: uffizzi_production
================================================
FILE: config/environment.rb
================================================
# frozen_string_literal: true
# Load the Rails application.
require_relative 'application'
# Initialize the Rails application.
Rails.application.initialize!
================================================
FILE: config/environments/development.rb
================================================
# frozen_string_literal: true
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports.
config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.to_i}",
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
config.logger = Logger.new($stdout)
config.log_level = :debug
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large
# number of complex assets.
config.assets.debug = true
# Suppress logger output for asset requests.
config.assets.quiet = true
# Raises error for missing translations.
# config.action_view.raise_on_missing_translations = true
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::FileUpdateChecker
end
================================================
FILE: config/environments/production.rb
================================================
# frozen_string_literal: true
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress CSS using a preprocessor.
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :debug
# Prepend all log lines with the following tags.
config.log_tags = [:request_id]
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "uffizzi_app_production"
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV['RAILS_LOG_TO_STDOUT'].present?
logger = ActiveSupport::Logger.new($stdout)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Inserts middleware to perform automatic connection switching.
# The `database_selector` hash is used to pass options to the DatabaseSelector
# middleware. The `delay` is used to determine how long to wait after a write
# to send a subsequent read to the primary.
#
# The `database_resolver` class is used by the middleware to determine which
# database is appropriate to use based on the time delay.
#
# The `database_resolver_context` class is used by the middleware to set
# timestamps for the last write to the primary. The resolver uses the context
# class timestamps to determine how long to wait before reading from the
# replica.
#
# By default Rails will store a last write timestamp in the session. The
# DatabaseSelector middleware is designed as such you can define your own
# strategy for connection switching and pass that into the middleware through
# these configuration options.
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end
================================================
FILE: config/environments/test.rb
================================================
# frozen_string_literal: true
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
config.cache_classes = false
config.action_view.cache_template_loading = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}",
}
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raises error for missing translations.
# config.action_view.raise_on_missing_translations = true
end
================================================
FILE: config/initializers/application_controller_renderer.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# ActiveSupport::Reloader.to_prepare do
# ApplicationController.renderer.defaults.merge!(
# http_host: 'example.org',
# https: false
# )
# end
================================================
FILE: config/initializers/assets.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
================================================
FILE: config/initializers/backtrace_silencers.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
================================================
FILE: config/initializers/content_security_policy.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # If you are using webpack-dev-server then specify webpack-dev-server host
# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
# If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
# Set the nonce only to specific directives
# Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
# Report CSP violations to a specified URI
# For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true
================================================
FILE: config/initializers/cookies_serializer.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Specify a serializer for the signed and encrypted cookie jars.
# Valid options are :json, :marshal, and :hybrid.
Rails.application.config.action_dispatch.cookies_serializer = :json
================================================
FILE: config/initializers/filter_parameter_logging.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password]
================================================
FILE: config/initializers/health_check.rb
================================================
# frozen_string_literal: true
HealthCheck.setup do |config|
config.uri = ENV['HEALTH_CHECK_URI'] || 'health_check'
config.success = 'success'
config.http_status_for_error_text = 500
config.http_status_for_error_object = 500
config.standard_checks = ['database', 'migrations']
config.full_checks = ['database', 'migrations']
end
================================================
FILE: config/initializers/inflections.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end
================================================
FILE: config/initializers/mime_types.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
================================================
FILE: config/initializers/sidekiq.rb
================================================
# frozen_string_literal: true
require 'sidekiq/web'
require 'sidekiq-unique-jobs'
Sidekiq.configure_client do |config|
config.redis = { url: ENV['REDIS_URL'], size: 2 }
config.client_middleware do |chain|
chain.add(SidekiqUniqueJobs::Middleware::Client)
end
end
Sidekiq.configure_server do |config|
config.redis = { url: ENV['REDIS_URL'], size: 20 }
config.client_middleware do |chain|
chain.add(SidekiqUniqueJobs::Middleware::Client)
end
config.server_middleware do |chain|
chain.add(SidekiqUniqueJobs::Middleware::Server)
end
SidekiqUniqueJobs::Server.configure(config)
end
if Settings.sidekiq.login.present?
Sidekiq::Web.use(Rack::Auth::Basic) do |username, password|
valid_login = ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(username),
::Digest::SHA256.hexdigest(Settings.sidekiq.login),
)
valid_password = ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(password),
::Digest::SHA256.hexdigest(Settings.sidekiq.password),
)
valid_login & valid_password
end
end
SidekiqUniqueJobs.configure do |config|
config.enabled = !Rails.env.test?
end
================================================
FILE: config/initializers/wrap_parameters.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true
# end
================================================
FILE: config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
# <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# The following keys must be escaped otherwise they will not be retrieved by
# the default I18n backend:
#
# true, false, on, off, yes, no
#
# Instead, surround them with single quotes.
#
# en:
# 'true': 'foo'
#
# To learn more, please read the Rails Internationalization guide
# available at https://guides.rubyonrails.org/i18n.html.
en:
================================================
FILE: config/puma.rb
================================================
# frozen_string_literal: true
threads_count = ENV.fetch('RAILS_THREADS_COUNT', 5)
threads threads_count, threads_count
port ENV.fetch('RAILS_PORT', 7000)
environment ENV.fetch('RAILS_ENV', 'development')
workers ENV.fetch('RAILS_WORKERS_COUNT', 18)
preload_app! if ENV.fetch('RAILS_WORKERS_COUNT').to_i.positive?
before_fork do
ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
================================================
FILE: config/routes.rb
================================================
# frozen_string_literal: true
Rails.application.routes.draw do
mount UffizziCore::Engine => '/'
health_check_routes
end
================================================
FILE: config/secrets.yml
================================================
default: &default
secret_key_base: <%=Settings.rails.secret_key_base%>
development:
<<: *default
test:
<<: *default
production:
<<: *default
================================================
FILE: config/settings.yml
================================================
rails:
secret_key_base: <%= ENV['RAILS_SECRET_KEY_BASE'] %>
app:
ttl_reset_password_token: 900
host: <%= ENV['APP_URL'] %>
login: <%= ENV['APP_LOGIN'] || '' %>
password: <%= ENV['APP_PASSWORD'] || '' %>
managed_dns_zone: <%= ENV['MANAGED_DNS_ZONE_DNS_NAME'] %>
github:
app_id: <%= ENV['GITHUB_APP_ID'] %>
app_slug: <%= ENV['GITHUB_APP_SLUG'] %>
client_id: <%= ENV['GITHUB_CLIENT_ID'] %>
client_secret: <%= ENV['GITHUB_CLIENT_SECRET'] %>
private_key: "<%= ENV['GITHUB_PRIVATE_KEY'] %>"
webhook_secret: <%= ENV['GITHUB_WEBHOOK_SECRET'] %>
docker_hub:
registry_url: 'https://index.docker.io/v1/'
public_namespace: 'library'
sidekiq:
login: <%= ENV['SIDEKIQ_LOGIN'] || '' %>
password: <%= ENV['SIDEKIQ_PASSWORD'] || '' %>
controller:
url: <%= ENV['CONTROLLER_URL'] || 'http://controller:8080' %>
login: <%= ENV['CONTROLLER_LOGIN'] || '' %>
password: <%= ENV['CONTROLLER_PASSWORD'] || '' %>
connection:
retires_count: 1
next_retry_timeout_seconds: 1
timeout: 7
open_timeout: 5
limits:
cpu: '200m'
namespace_prefix: 'app-'
resource_create_retry_time: <%= 15.seconds %>
resource_update_retry_count: 60
vcluster_controller:
url: <%= ENV['VCLUSTER_CONTROLLER_URL'] || 'http://controller:8080' %>
login: <%= ENV['VCLUSTER_CONTROLLER_LOGIN'] || '' %>
password: <%= ENV['VCLUSTER_CONTROLLER_PASSWORD'] || '' %>
managed_dns_zone: <%= ENV['VCLUSTER_MANAGED_DNS_ZONE_DNS_NAME'] %>
connection:
retires_count: 1
next_retry_timeout_seconds: 1
timeout: 15
open_timeout: 10
billing_cycle:
hours_count: 720
slack:
invite_url: https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A
allowed_hosts: <%= ENV['ALLOWED_HOSTS']&.split(',') || [] %>
domain: <%= ENV['APP_URL'] %>
sentry:
dsn: <%= ENV['SENTRY_DSN'] %>
release: <%= ENV['SENTRY_RELEASE'] %>
env: <%= Rails.env %>
features:
email_delivery_enabled: <%= !!ActiveModel::Type::Boolean.new.cast(ENV['EMAIL_DELIVERY_ENABLED']) %>
stripe_enabled: <%= !!ActiveModel::Type::Boolean.new.cast(ENV['STRIPE_ENABLED']) %>
platform_cluster:
project_id: <%= ENV['PLATFORM_PROJECT_ID'] %>
google:
registry_url: 'https://gcr.io/'
github_container_registry:
registry_url: 'https://ghcr.io/'
compose:
default_memory: 125
memory_postfixes: <%= ['b', 'k', 'm', 'g'] %>
memory_values: <%= [125, 250, 500, 1000, 2000, 4000] %>
port_min_value: 1
port_max_value: 65535
delete_after_postfixes: <%= ['h'] %>
delete_after_min_value: 1
delete_after_max_value: 720
default_tag: latest
dockerfile_default_path: Dockerfile
default_branch: 'master'
continuous_preview:
default_delete_preview_after: 72
deployment:
max_memory_limit: 8000
subdomain:
length_limit: 63
default_job_retry_count: 5
vcluster:
max_creation_retry_count: 5
================================================
FILE: config/sidekiq.yml
================================================
---
:concurrency: <%= ENV["SIDEKIQ_CONCURRENCY"].nil? ? 5 : ENV["SIDEKIQ_CONCURRENCY"].to_i %>
:verbose: true
:queues:
- [default, 5]
- [active_storage_analysis, 5]
- [active_storage_purge, 5]
- [mailers, 5]
- [billing, 6]
- [deployments, 10]
- [config_files, 10]
- [compose_files, 10]
- [resources, 10]
- [databases, 10]
- [projects, 10]
- [accounts, 10]
- [containers, 10]
- [github, 10]
- [disable_deployments, 5]
- [clusters, 10]
================================================
FILE: config/storage.yml
================================================
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
================================================
FILE: config.ru
================================================
# frozen_string_literal: true
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run Rails.application
================================================
FILE: core/.gitignore
================================================
/.bundle/
/doc/
/log/*.log
/pkg/
/tmp/
/test/dummy/db/*.sqlite3
/test/dummy/db/*.sqlite3-*
/test/dummy/log/*.log
/test/dummy/storage/
/test/dummy/tmp/
.byebug_history
================================================
FILE: core/CHANGELOG.md
================================================
## [0.1.0] - 2022-04-05
- Initial release
================================================
FILE: core/Dockerfile
================================================
FROM ruby:3.0.2-alpine3.14
RUN apk update && apk upgrade && apk add bash curl-dev ruby-dev build-base git gcompat \
curl ruby-json openssl postgresql-dev postgresql-client tzdata
RUN mkdir -p /gem
WORKDIR /gem
ENV GEM_HOME="/usr/local/bundle"
COPY lib/uffizzi_core/version.rb /gem/lib/uffizzi_core/
COPY uffizzi_core.gemspec /gem/
COPY Gemfile* /gem/
RUN bundle install --jobs 4
COPY . /gem
ENV PATH /gem/bin:$GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
================================================
FILE: core/Gemfile
================================================
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'iri'
gemspec
================================================
FILE: core/LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: core/Makefile
================================================
release_gem:
mkdir -p ${HOME}/.gem
touch ${HOME}/.gem/credentials
chmod 0600 ${HOME}/.gem/credentials
printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > ${HOME}/.gem/credentials
gem build *.gemspec
gem push *.gem
================================================
FILE: core/README.md
================================================
# Uffizzi Core
**Uffizzi CLI API, Models, Services and core libraries**
## Uffizzi Overview
Uffizzi is the Full-stack Previews Engine that makes it easy for your team to preview code changes before merging—whether frontend, backend or microserivce. Define your full-stack apps with a familiar syntax based on Docker Compose, and Uffizzi will create on-demand test environments when you open pull requests or build new images. Preview URLs are updated when there’s a new commit, so your team can catch issues early, iterate quickly, and accelerate your release cycles.
## Getting started with Uffizzi
The fastest and easiest way to get started with Uffizzi is via the fully hosted version available at https://uffizzi.com, which includes free plans for small teams and qualifying open-source projects.
Alternatively, you can self-host Uffizzi via the open-source repositories available here on GitHub. The remainder of this README is intended for users interested in self-hosting Uffizzi or for those who are just curious about how Uffizzi works.
## Uffizzi Architecture
Uffizzi consists of the following components:
* [Uffizzi App](https://github.com/UffizziCloud/uffizzi_app) - The primary REST API for creating and managing Previews
* [Uffizzi Controller](https://github.com/UffizziCloud/uffizzi_controller) - A smart proxy service that handles requests from Uffizzi App to the Kubernetes API
* Uffizzi CLI (this repository) - A command-line interface for Uffizzi App
* [Uffizzi Dashboard](https://app.uffizzi.com) - A graphical user interface for Uffizzi App, available as a paid service at https://uffizzi.com
To host Uffizzi yourself, you will also need the following external dependencies:
* Kubernetes (k8s) cluster
* Postgres database
* Redis cache
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'uffizzi_core'
```
And then execute:
```bash
$ bundle
```
Or install it yourself as:
```bash
$ gem install uffizzi_core
```
================================================
FILE: core/Rakefile
================================================
# frozen_string_literal: true
require 'bundler/setup'
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
load 'rails/tasks/engine.rake'
load 'rails/tasks/statistics.rake'
require 'bundler/gem_tasks'
require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = false
end
task default: :test
namespace :core do
desc 'Generate api docs'
task generate_docs: :environment do
SwaggerYard.register_custom_yard_tags!
spec = SwaggerYard::Swagger.new
File.open('swagger/v1/swagger.json', 'w') { |f| f << JSON.pretty_generate(spec.to_h) }
end
end
================================================
FILE: core/app/assets/config/uffizzi_core_manifest.js
================================================
//= link_directory ../stylesheets/uffizzi_core .css
================================================
FILE: core/app/assets/images/uffizzi_core/.keep
================================================
================================================
FILE: core/app/assets/stylesheets/uffizzi_core/application.css
================================================
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
================================================
FILE: core/app/clients/uffizzi_core/amazon_registry_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::AmazonRegistryClient
attr_accessor :client, :token, :registry_url
def initialize(region:, access_key_id:, secret_access_key:)
credentials = Aws::Credentials.new(access_key_id, secret_access_key)
@client = Aws::ECR::Client.new(region: region, credentials: credentials)
end
def authorization_token
client.get_authorization_token({})
end
def batch_get_image(image:, tag:)
client.batch_get_image({ image_ids: [{ image_tag: tag }], repository_name: image })
end
end
================================================
FILE: core/app/clients/uffizzi_core/azure_registry_client/request_result.rb
================================================
# frozen_string_literal: true
class UffizziCore::AzureRegistryClient::RequestResult < Hashie::Mash
disable_warnings :key
end
================================================
FILE: core/app/clients/uffizzi_core/azure_registry_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::AzureRegistryClient
attr_accessor :connection, :token, :registry_url
def initialize(registry_url:, username:, password:)
@registry_url = registry_url
@connection = build_connection(registry_url, username, password)
@token = oauth2_token&.result&.access_token
end
def manifests(image:, tag:)
url = "/v2/#{image}/manifests/#{tag}"
response = connection.get(url)
RequestResult.quiet.new(result: response.body, headers: response.headers)
end
def oauth2_token
service = URI.parse(registry_url).hostname
url = "/oauth2/token?service=#{service}"
response = connection.get(url, {})
RequestResult.new(result: response.body)
end
def authenticated?
token.present?
end
private
def build_connection(registry_url, username, password)
connection = Faraday.new(registry_url) do |faraday|
faraday.request(:basic_auth, username, password)
faraday.request(:json)
faraday.response(:json)
faraday.response(:raise_error)
faraday.adapter(Faraday.default_adapter)
end
connection.extend(UffizziCore::ContainerRegistryRequestDecorator)
end
end
================================================
FILE: core/app/clients/uffizzi_core/container_registry_request_decorator.rb
================================================
# frozen_string_literal: true
module UffizziCore::ContainerRegistryRequestDecorator
[:get, :post, :head].each do |method|
define_method(method) do |url, params_or_body = nil, headers = nil, &block|
super(url, params_or_body, headers, &block)
rescue Faraday::ClientError => e
raise UffizziCore::ContainerRegistryError.new(e.response)
end
end
end
================================================
FILE: core/app/clients/uffizzi_core/controller_client/request_result.rb
================================================
# frozen_string_literal: true
class UffizziCore::ControllerClient::RequestResult < Hashie::Mash
disable_warnings :key
end
================================================
FILE: core/app/clients/uffizzi_core/controller_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::ControllerClient
class ConnectionError < StandardError; end
attr_accessor :connection
def initialize(connection_settings)
@connection = build_connection(connection_settings)
end
def apply_config_file(deployment_id:, config_file_id:, body:)
connection.post("/deployments/#{deployment_id}/config_files/#{config_file_id}", body)
end
def deployment_containers(deployment_id:)
get("/deployments/#{deployment_id}/containers")
end
def deploy_containers(deployment_id:, body:)
connection.post("/deployments/#{deployment_id}/containers", body)
end
def deployment_containers_metrics(deployment_id:)
get("/deployments/#{deployment_id}/containers/metrics")
end
def deployment_container_logs(deployment_id:, container_name:, limit:, previous:)
get("/deployments/#{deployment_id}/containers/#{container_name}/logs?limit=#{limit}&previous=#{previous}")
end
def deployment_containers_events(deployment_id:)
events = get("/deployments/#{deployment_id}/containers/events").result.items
pods_events = events.select { |event| event.involved_object.kind == 'Pod' }
pods_events.map do |event|
{ first_timestamp: event.first_timestamp, last_timestamp: event.last_timestamp, reason: event.reason, message: event.message }
end
end
def nodes
get('/nodes')
end
def apply_credential(deployment_id:, body:)
connection.post("/deployments/#{deployment_id}/credentials", body)
end
def delete_credential(deployment_id:, credential_id:)
connection.delete("/deployments/#{deployment_id}/credentials/#{credential_id}")
end
def get_deployments_usage_metrics_containers(deployment_ids:, begin_at:, end_at:)
query_params = {
deployment_ids: deployment_ids,
begin_at: begin_at,
end_at: end_at,
}
get('/deployments/usage_metrics/containers', query_params)
end
def create_namespace(body:)
post('/namespaces', body)
end
def namespace(namespace:)
get("/namespaces/#{namespace}")
end
def delete_namespace(namespace:)
connection.delete("/namespaces/#{namespace}")
end
def create_cluster(namespace:, body:)
post("/namespaces/#{namespace}/cluster", body)
end
def show_cluster(namespace:, name:)
get("/namespaces/#{namespace}/cluster/#{name}")
end
def patch_cluster(name:, namespace:, body:)
patch("/namespaces/#{namespace}/cluster/#{name}", body)
end
def ingresses(namespace:)
get("/namespaces/#{namespace}/ingresses")
end
private
def get(url, params = {})
make_request(:get, url, params)
end
def post(url, params = {})
make_request(:post, url, params)
end
def patch(url, params = {})
make_request(:patch, url, params)
end
def make_request(method, url, params)
response = connection.send(method, url, params)
body = response.body
underscored_body = UffizziCore::Converters.deep_underscore_keys(body)
RequestResult.quiet.new(code: response.status, result: underscored_body)
rescue Faraday::ServerError
raise ConnectionError
end
def build_connection(settings)
connection = settings.connection
handled_exceptions = Faraday::Request::Retry::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
Faraday.new(settings.url) do |conn|
conn.options.timeout = connection.timeout
conn.options.open_timeout = connection.open_timeout
conn.request(:basic_auth, settings.login, settings.password)
conn.request(:json)
conn.request(:retry,
max: connection.retires_count,
interval: connection.next_retry_timeout_seconds,
exceptions: handled_exceptions)
conn.response(:json)
conn.response(:raise_error)
conn.adapter(Faraday.default_adapter)
end
end
end
================================================
FILE: core/app/clients/uffizzi_core/docker_hub_client/not_authorized_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::DockerHubClient
class NotAuthorizedError < StandardError
end
end
================================================
FILE: core/app/clients/uffizzi_core/docker_hub_client/request_result.rb
================================================
# frozen_string_literal: true
class UffizziCore::DockerHubClient
class RequestResult < Hashie::Mash
disable_warnings :key
end
end
================================================
FILE: core/app/clients/uffizzi_core/docker_hub_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::DockerHubClient
attr_accessor :connection, :jwt, :credential
BASE_URL = 'https://hub.docker.com'
def initialize(credential = nil)
@connection = build_connection
@credential = credential
return unless credential
@jwt = authenticate
end
def authenticate
params = { username: credential.username, password: credential.password }
url = "#{BASE_URL}/v2/users/login/"
response = connection.post(url, params)
request_result = RequestResult.new(result: response.body)
request_result.result.token
rescue NoMethodError
nil
end
def repository(namespace:, image:)
url = "#{BASE_URL}/v2/repositories/#{namespace}/#{image}"
response = connection.get(url) do |request|
request.headers['Authorization'] = "JWT #{jwt}"
end
RequestResult.new(status: response.status, result: response.body)
end
def public_images(q:, page: 1, per_page: 25)
url = "#{BASE_URL}/api/content/v1/products/search"
params = { page_size: per_page, q: q, type: :image, page: page }
response = connection.get(url, params) do |request|
request.headers['Search-Version'] = 'v3'
end
RequestResult.new(result: response.body)
end
def private_images(account:, page: 1, per_page: 25)
raise NotAuthorizedError if !authenticated? || account.empty?
url = BASE_URL + "/v2/repositories/#{account}/"
params = { page_size: per_page, page: page }
response = connection.get(url, params) do |request|
request.headers['Authorization'] = "JWT #{jwt}"
end
RequestResult.new(result: response.body)
end
def accounts
raise NotAuthorizedError if !authenticated?
url = "#{BASE_URL}/v2/repositories/namespaces/"
response = connection.get(url) do |request|
request.headers['Authorization'] = "JWT #{jwt}"
end
RequestResult.new(result: response.body)
end
def metadata(namespace:, image:)
url = BASE_URL + "/v2/repositories/#{namespace}/#{image}/"
response = connection.get(url) do |request|
request.headers['Authorization'] = "JWT #{jwt}"
end
RequestResult.quiet.new(result: response.body)
end
def tags(namespace:, image:, q: '', page: 1, per_page: 10)
url = BASE_URL + "/v2/repositories/#{namespace}/#{image}/tags"
params = { page_size: per_page, page: page, name: q }
response = connection.get(url, params) do |request|
request.headers['Authorization'] = "JWT #{jwt}"
end
RequestResult.quiet.new(result: response.body)
end
def digest(image:, tag:, token:)
url = "https://index.docker.io/v2/#{image}/manifests/#{tag}"
response = connection.get(url) do |request|
request.headers['Accept'] = 'application/vnd.docker.distribution.manifest.v2+json'
request.headers['Authorization'] = "Bearer #{token}"
end
RequestResult.quiet.new(result: response.body, headers: response.headers)
end
def get_token(repository)
params = { username: credential.username, password: credential.password }
url = "https://auth.docker.io/token?service=registry.docker.io&scope=repository:#{repository}:pull"
response = connection.get(url, params)
RequestResult.new(result: response.body)
end
def authenticated?
jwt.present?
end
private
def build_connection
connection = Faraday.new do |faraday|
faraday.request(:json)
faraday.response(:json)
faraday.response(:raise_error)
faraday.adapter(Faraday.default_adapter)
end
connection.extend(UffizziCore::ContainerRegistryRequestDecorator)
end
end
================================================
FILE: core/app/clients/uffizzi_core/docker_registry_client/request_result.rb
================================================
# frozen_string_literal: true
class UffizziCore::DockerRegistryClient::RequestResult < Hashie::Mash
disable_warnings :key
end
================================================
FILE: core/app/clients/uffizzi_core/docker_registry_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::DockerRegistryClient
ACCEPTED_TYPES = [
'application/vnd.oci.image.index.v1+json',
'application/vnd.oci.image.manifest.v1+json',
'application/vnd.docker.distribution.manifest.v1+json',
'application/vnd.docker.distribution.manifest.v2+json',
'application/vnd.docker.distribution.manifest.list.v2+json',
'*/*',
].freeze
def initialize(registry_url:, username: nil, password: nil)
@registry_url = registry_url
@connection = build_connection(username, password)
end
def authenticated?
@connection.get("#{@registry_url}/v2/")
true
end
def manifests(image:, tag:, namespace: nil)
full_image = [namespace, image].compact.join('/')
url = "#{@registry_url}/v2/#{full_image}/manifests/#{tag}"
response = @connection.get(url)
RequestResult.new(status: response.status, result: response.body)
end
private
def build_connection(username, password)
# initializing Faraday with the registry_url will trim the trailing slash required for the /v2/ request
connection = Faraday.new do |faraday|
faraday.headers['Accept'] = ACCEPTED_TYPES
faraday.request(:basic_auth, username, password) if username.present? && password.present?
faraday.request(:json)
faraday.response(:json)
faraday.response(:follow_redirects)
faraday.response(:raise_error)
faraday.adapter(Faraday.default_adapter)
end
connection.extend(UffizziCore::ContainerRegistryRequestDecorator)
end
end
================================================
FILE: core/app/clients/uffizzi_core/github_container_registry_client/request_result.rb
================================================
# frozen_string_literal: true
class UffizziCore::GithubContainerRegistryClient
class RequestResult < Hashie::Mash
disable_warnings :key
end
end
================================================
FILE: core/app/clients/uffizzi_core/github_container_registry_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::GithubContainerRegistryClient
attr_accessor :token, :registry_url
def initialize(registry_url:, username:, password:)
@registry_url = registry_url
@username = username
@password = password
@token = access_token&.result&.token
end
def access_token
service = URI.parse(registry_url).hostname
url = "/token?service=#{service}"
response = connection.get(url, {})
RequestResult.new(result: response.body)
end
def authenticated?
token.present?
end
def manifests(image:, tag:)
url = "/v2/#{@username}/#{image}/manifests/#{tag}"
response = token_connection.get(url)
RequestResult.quiet.new(result: response.body, headers: response.headers)
end
private
def connection
connection = Faraday.new(registry_url) do |faraday|
faraday.request(:basic_auth, @username, @password)
faraday.request(:json)
faraday.response(:json)
faraday.response(:raise_error)
faraday.adapter(Faraday.default_adapter)
end
connection.extend(UffizziCore::ContainerRegistryRequestDecorator)
end
def token_connection
connection = Faraday.new(registry_url) do |faraday|
faraday.request(:authorization, 'Bearer', token)
faraday.request(:json)
faraday.response(:json)
faraday.response(:raise_error)
faraday.adapter(Faraday.default_adapter)
end
connection.extend(UffizziCore::ContainerRegistryRequestDecorator)
end
end
================================================
FILE: core/app/clients/uffizzi_core/google_registry_client/request_result.rb
================================================
# frozen_string_literal: true
class UffizziCore::GoogleRegistryClient::RequestResult < Hashie::Mash
disable_warnings :key
end
================================================
FILE: core/app/clients/uffizzi_core/google_registry_client.rb
================================================
# frozen_string_literal: true
class UffizziCore::GoogleRegistryClient
attr_accessor :connection, :token, :registry_url
def initialize(registry_url:, username:, password:)
@registry_url = registry_url
@connection = build_connection(registry_url, username, password)
@token = access_token&.result&.token
end
def manifests(image:, tag:)
url = "/v2/#{image}/manifests/#{tag}"
response = connection.get(url)
RequestResult.quiet.new(result: response.body, headers: response.headers)
end
def access_token
service = URI.parse(registry_url).hostname
url = "/v2/token?service=#{service}"
response = connection.get(url, {})
RequestResult.new(result: response.body)
end
def authenticated?
token.present?
end
private
def build_connection(registry_url, username, password)
connection = Faraday.new(registry_url) do |faraday|
faraday.request(:basic_auth, username, password)
faraday.request(:json)
faraday.response(:json)
faraday.response(:raise_error)
faraday.adapter(Faraday.default_adapter)
end
connection.extend(UffizziCore::ContainerRegistryRequestDecorator)
end
end
================================================
FILE: core/app/contexts/uffizzi_core/account_context.rb
================================================
# frozen_string_literal: true
class UffizziCore::AccountContext
attr_reader :user, :user_access_module, :account, :params
def initialize(user, user_access_module, account, params)
@user = user
@user_access_module = user_access_module
@account = account
@params = params
end
end
================================================
FILE: core/app/contexts/uffizzi_core/base_context.rb
================================================
# frozen_string_literal: true
class UffizziCore::BaseContext
attr_reader :user, :user_access_module, :params
def initialize(user, user_access_module, params)
@user = user
@user_access_module = user_access_module
@params = params
end
end
================================================
FILE: core/app/contexts/uffizzi_core/project/cluster_context.rb
================================================
# frozen_string_literal: true
class UffizziCore::Project::ClusterContext
attr_reader :user, :user_access_module, :project, :cluster, :params
def initialize(user, project, user_access_module, cluster, params)
@user = user
@user_access_module = user_access_module
@project = project
@cluster = cluster
@params = params
end
end
================================================
FILE: core/app/contexts/uffizzi_core/project_context.rb
================================================
# frozen_string_literal: true
class UffizziCore::ProjectContext
attr_reader :user, :user_access_module, :project, :account, :params
def initialize(user, user_access_module, project, account, params)
@user = user
@user_access_module = user_access_module
@account = account
@project = project
@params = params
end
end
================================================
FILE: core/app/contexts/uffizzi_core/webhooks_context.rb
================================================
# frozen_string_literal: true
class UffizziCore::WebhooksContext
attr_reader :request
def initialize(request)
@request = request
end
end
================================================
FILE: core/app/controller_modules/uffizzi_core/api/cli/v1/accounts_controller_module.rb
================================================
# frozen_string_literal: true
module UffizziCore::Api::Cli::V1::AccountsControllerModule
def update; end
end
================================================
FILE: core/app/controller_modules/uffizzi_core/api/cli/v1/projects/clusters_controller_module.rb
================================================
# frozen_string_literal: true
module UffizziCore::Api::Cli::V1::Projects::ClustersControllerModule
private
def update_show_trial_quota_exceeded_warning; end
def check_account_quota; end
def check_current_plan; end
end
================================================
FILE: core/app/controller_modules/uffizzi_core/api/cli/v1/projects/deployments_controller_module.rb
================================================
# frozen_string_literal: true
module UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerModule
private
def check_account_quota; end
def update_show_trial_quota_exceeded_warning; end
def check_current_plan; end
end
================================================
FILE: core/app/controller_modules/uffizzi_core/api/cli/v1/projects_controller_module.rb
================================================
# frozen_string_literal: true
module UffizziCore::Api::Cli::V1::ProjectsControllerModule
private
def update_show_trial_quota_exceeded_warning; end
end
================================================
FILE: core/app/controllers/concerns/uffizzi_core/auth_management.rb
================================================
# frozen_string_literal: true
module UffizziCore::AuthManagement
def sign_in(user)
session[:user_id] = user.id
end
def sign_out
session[:user_id] = @current_user = nil
end
def signed_in?
session[:user_id].present? && current_user.present?
end
def current_user
@current_user ||= UffizziCore::User.find_by(id: current_user_id)
end
def auth_token
header = request.headers['Authorization']
header&.split(' ')&.last
end
def current_user_id
return session[:user_id] if session[:user_id].present?
return unless auth_token.present?
decoded_token = UffizziCore::TokenService.decode(auth_token)
return unless decoded_token
return if decoded_token.first['expires_at'] < DateTime.now
decoded_token.first['user_id']
end
def authenticate_request!
current_user ? true : head(:unauthorized)
end
end
================================================
FILE: core/app/controllers/concerns/uffizzi_core/authorization_concern.rb
================================================
# frozen_string_literal: true
module UffizziCore::AuthorizationConcern
extend ActiveSupport::Concern
included do
before_action :init_authorize
end
def init_authorize
return unless self.class.ancestors.include?(UffizziCore::ApplicationController)
self.class.send(:define_method, policy_method_name) { send(:authorize, policy_method_params) }
end
def pundit_user
policy_context
end
private
def policy_method_name
[:authorize, policy_name].join('_')
end
def policy_name
controller_class = self.class.to_s
controller_class.gsub(/::|Controller/, '').underscore
end
def policy_method_params
controller_class = self.class.to_s
params = controller_class.gsub(/Controller/, '').split('::')
params.map(&:underscore).map(&:downcase).map(&:to_sym)
end
end
================================================
FILE: core/app/controllers/concerns/uffizzi_core/dependency_injection_concern.rb
================================================
# frozen_string_literal: true
module UffizziCore::DependencyInjectionConcern
extend ActiveSupport::Concern
class_methods do
def include_module_if_exists(module_name)
include(Object.const_get(module_name)) if Object.const_defined?(module_name)
end
def prepend_module_if_exists(module_name)
prepend(Object.const_get(module_name)) if Object.const_defined?(module_name)
end
end
def user_access_module
return unless module_exists?(:rbac)
UffizziCore::UserAccessService.new(module_class(:rbac))
end
def find_build_parser_module
module_class(:build_parser)
end
def find_volume_parser_module
module_class(:volume_parser)
end
def ci_module
return unless module_exists?(:ci_module)
module_class(:ci_module)
end
def ci_session
return unless module_exists?(:ci_session)
module_class(:ci_session)
end
def password_protection_module
return unless module_exists?(:password_protection)
module_class(:password_protection)
end
def find_ingress_parser_module
module_class(:ingress_parser)
end
def notification_module
return unless module_exists?(:notification_module)
module_class(:notification_module)
end
def domain_module
return unless module_exists?(:domain_module)
module_class(:domain_module)
end
def deployment_memory_module
return unless module_exists?(:deployment_memory_module)
module_class(:deployment_memory_module)
end
def template_memory_module
return unless module_exists?(:template_memory_module)
module_class(:template_memory_module)
end
def controller_settings_service
return unless module_exists?(:controller_settings)
module_class(:controller_settings)
end
private
def module_exists?(module_name)
module_class(module_name).present?
end
def module_class(module_name)
UffizziCore.dependencies[module_name]&.constantize
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/accounts/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ApplicationController < UffizziCore::Api::Cli::V1::ApplicationController
def resource_account
@resource_account ||= current_user.accounts.find(params[:account_id])
end
def policy_context
UffizziCore::AccountContext.new(current_user, user_access_module, resource_account, params)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/accounts/clusters_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ClustersController < UffizziCore::Api::Cli::V1::Accounts::ApplicationController
include UffizziCore::DependencyInjectionConcern
before_action :authorize_uffizzi_core_api_cli_v1_accounts_clusters
def index
return respond_with(clusters_by_account.includes(:project)) if valid_request_from_ci_workflow?
clusters = clusters_by_user.or(clusters_by_admin_projects)
respond_with(clusters.includes(:project))
end
private
def valid_request_from_ci_workflow?
ci_module.valid_request_from_ci_workflow?(params)
end
def clusters_by_admin_projects
projects = UffizziCore::Project
.active
.joins(:user_projects)
.where(account: resource_account)
.where(user_projects: { role: UffizziCore::UserProject.role.admin, user: current_user })
UffizziCore::Cluster.enabled.where(project_id: projects.select(:id))
end
def clusters_by_user
UffizziCore::Cluster.enabled.by_projects(account_projects).deployed_by_user(current_user)
end
def clusters_by_account
UffizziCore::Cluster.enabled.by_projects(account_projects)
end
def account_projects
UffizziCore::Project.active.where(account: resource_account)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/accounts/credentials_controller.rb
================================================
# frozen_string_literal: true
# @resource Account/Credential
class UffizziCore::Api::Cli::V1::Accounts::CredentialsController < UffizziCore::Api::Cli::V1::Accounts::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_accounts_credentials
# Get a list of accounts credential
#
# @path [GET] /api/cli/v1/account/credentials
#
# @parameter credential(required,body) [object]
def index
credentials = resource_account.credentials.pluck(:type)
render json: { credentials: credentials }, status: :ok
end
# Create account credential
#
# @path [POST] /api/cli/v1/account/credentials
#
# @parameter credential(required,body) [object]
# @response [object] 201 Created successfully
# @response [object] 422 Unprocessable entity
#
# @example
# Possible types:
# UffizziCore::Credential::Amazon, UffizziCore::Credential::Azure, UffizziCore::Credential::DockerHub,
# UffizziCore::Credential::DockerRegistry, UffizziCore::Credential::Google, UffizziCore::Credential::GithubContainerRegistry
def create
credential_form = UffizziCore::Api::Cli::V1::Account::Credential::CreateForm.new
credential_form.assign_attributes(credential_params)
credential_form.account = resource_account
credential_form.registry_url = registry_url(credential_form)
credential_form.username = '_json_key' if credential_form.google?
credential_form.activate
UffizziCore::Account::CreateCredentialJob.perform_async(credential_form.id) if credential_form.save
respond_with credential_form
end
# Update existing credential of the given type
#
# @path [PUT] /api/cli/v1/account/credentials/{type}
#
# @parameter type(required,path) [string] Credential type
# @parameter credential(required,body) [object]
# @response [object] 200 OK
# @response [object] 422 Unprocessable entity
def update
credential = resource_account.credentials.find_by!(type: params[:type])
# Called every pipeline run from CLI with the --update-if-exists-option
return respond_with credential unless credential_changed?(credential, credential_params)
credential_form = credential.becomes(UffizziCore::Api::Cli::V1::Account::Credential::UpdateForm)
credential_form.assign_attributes(credential_params)
if credential_form.save
UffizziCore::Account::UpdateCredentialJob.perform_async(credential_form.id)
respond_with credential_form
else
respond_with credential_form, status: :unprocessable_entity
end
end
# Check if credential of the type already exists in the account
#
# @path [GET] /api/cli/v1/account/credentials/{type}/check_credential
#
# @parameter credential(required,body) [object]
# @response 422 Unprocessable entity
# @response 200 OK
def check_credential
credential_form = UffizziCore::Api::Cli::V1::Account::Credential::CheckCredentialForm.new
credential_form.type = params[:type]
credential_form.account = resource_account
if credential_form.valid?
respond_with credential_form
else
respond_with credential_form.errors, status: :unprocessable_entity
end
end
# Delete account credential
#
# @path [DELETE] /api/cli/v1/account/credentials/{type}
#
# @parameter type(required,path) [string] Type of the credential
# @response 204 No Content
# @response 401 Not authorized
# @response [object>] 404 Not found
def destroy
credential = resource_account.credentials.find_by!(type: params[:type])
credential.destroy
end
private
def credential_params
params.require(:credential)
end
def registry_url(credential_form)
if credential_form.docker_hub?
Settings.docker_hub.registry_url
elsif credential_form.google?
Settings.google.registry_url
elsif credential_form.github_container_registry?
Settings.github_container_registry.registry_url
else
credential_form.registry_url
end
end
def credential_changed?(credential, credential_params)
credential.password != credential_params[:password] ||
credential.username != credential_params[:username] ||
credential.registry_url != credential_params[:registry_url]
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/accounts/projects_controller.rb
================================================
# frozen_string_literal: true
# @resource Project
class UffizziCore::Api::Cli::V1::Accounts::ProjectsController < UffizziCore::Api::Cli::V1::Accounts::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_accounts_projects
def index
projects = resource_account.projects.active
respond_with projects, each_serializer: UffizziCore::Api::Cli::V1::ShortProjectSerializer
end
# Create a project
#
# @path [POST] /api/cli/v1/accounts/{account_id}/projects
# @parameter params(required,body) [object]
#
# @response > 200 OK
# @response 404 Not Found
# @response 401 Not authorized
# @response [object>] 422 Unprocessable entity
def create
project_form = UffizziCore::Api::Cli::V1::Project::CreateForm.new(project_params)
project_form.account = resource_account
UffizziCore::ProjectService.add_users_to_project!(project_form, project_form.account) if project_form.save
respond_with project_form
end
private
def project_params
params.require(:project)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/accounts_controller.rb
================================================
# frozen_string_literal: true
# @resource Project
class UffizziCore::Api::Cli::V1::AccountsController < UffizziCore::Api::Cli::V1::ApplicationController
include UffizziCore::Api::Cli::V1::AccountsControllerModule
before_action :authorize_uffizzi_core_api_cli_v1_accounts
# Get accounts of current user
#
# @path [GET] /api/cli/v1/accounts
#
# @response [object> >] 200 OK
# @response 401 Not authorized
def index
accounts = current_user.accounts.order(name: :desc)
respond_with accounts
end
# Get account by name
#
# @path [GET] /api/cli/v1/accounts/{name}
#
# @response [object>>> >] 200 OK
# @response 401 Not authorized
def show
respond_with resource_account
end
private
def policy_context
account = resource_account || current_user.default_account
UffizziCore::AccountContext.new(current_user, user_access_module, account, params)
end
def resource_account
@resource_account ||= if params[:name]
current_user.accounts.find_by!(name: params[:name])
else
current_user.default_account
end
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ApplicationController < UffizziCore::ApplicationController
before_action :authenticate_request!
def resource_project
@resource_project ||= current_user.projects.find_by!(slug: params[:slug])
end
def resource_account
@resource_account ||= resource_project.account
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/ci/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Ci::ApplicationController < UffizziCore::Api::Cli::V1::ApplicationController
before_action :authenticate_request!
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/ci/sessions_controller.rb
================================================
# frozen_string_literal: true
# @resource Uffizzi
class UffizziCore::Api::Cli::V1::Ci::SessionsController < UffizziCore::Api::Cli::V1::Ci::ApplicationController
skip_before_action :authenticate_request!, only: [:create]
# Create session
#
# @path [POST] /api/cli/v1/github/session
#
# @parameter user(required,body) [object]
# @response [object] 201 Created successfully
# @response [object>] 422 Unprocessable entity
def create
return render json: { errors: { title: [I18n.t('session.unsupported_login_type')] } }, status: :unprocessable_entity unless ci_session
session_data, errors = ci_session.session_data_from_ci(user_params)
return render json: { errors: errors }, status: :unprocessable_entity if errors.present?
sign_in(session_data[:user])
data = {
account_id: session_data[:account_id],
project_slug: session_data[:project_slug],
}
render json: data, status: :created
end
private
def user_params
params.require(:user).permit(:token, :github_access_token)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ApplicationController < UffizziCore::Api::Cli::V1::ApplicationController
def resource_project
@resource_project ||= current_user.projects.find_by!(slug: params[:project_slug])
end
def resource_account
@resource_account ||= resource_project.account
end
def policy_context
UffizziCore::ProjectContext.new(current_user, user_access_module, resource_project, resource_account, params)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/clusters/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Clusters::ApplicationController < UffizziCore::Api::Cli::V1::Projects::ApplicationController
def resource_cluster
@resource_cluster ||= if request_by_admin? || valid_request_from_ci_workflow?
active_project_clusters.find_by!(name: params[:cluster_name])
else
active_project_clusters.deployed_by_user(current_user).find_by!(name: params[:cluster_name])
end
end
private
def active_project_clusters
@active_project_clusters ||= resource_project.clusters.enabled
end
def request_by_admin?
current_user.admin_access_to_project?(resource_project)
end
def valid_request_from_ci_workflow?
ci_module.valid_request_from_ci_workflow?(params)
end
def policy_context
UffizziCore::Project::ClusterContext.new(current_user, resource_project, user_access_module, resource_cluster, params)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/clusters/ingresses_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Clusters::IngressesController <
UffizziCore::Api::Cli::V1::Projects::Clusters::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_projects_clusters_ingresses
def index
hosts = UffizziCore::ControllerService.ingress_hosts(resource_cluster)
user_hosts = UffizziCore::ClusterService.filter_user_ingress_host(resource_cluster, hosts)
data = {
ingresses: user_hosts,
}
respond_with data
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/clusters_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ClustersController < UffizziCore::Api::Cli::V1::Projects::ApplicationController
include UffizziCore::Api::Cli::V1::Projects::ClustersControllerModule
before_action :authorize_uffizzi_core_api_cli_v1_projects_clusters
before_action :check_account_quota, only: [:create]
before_action :check_current_plan
after_action :update_show_trial_quota_exceeded_warning, only: [:create, :destroy]
def index
clusters = resource_project.clusters.enabled
return respond_with clusters if request_by_admin? || valid_request_from_ci_workflow?
respond_with clusters.deployed_by_user(current_user), each_serializer: UffizziCore::Api::Cli::V1::Projects::ShortClusterSerializer
end
def create
version = cluster_params[:k8s_version]
kubernetes_distribution = find_kubernetes_distribution(version)
return render_distribution_version_error(version) if kubernetes_distribution.blank?
cluster_form = UffizziCore::Api::Cli::V1::Cluster::CreateForm.new(cluster_params)
cluster_form.project = resource_project
cluster_form.deployed_by = current_user
cluster_form.kubernetes_distribution = kubernetes_distribution
return respond_with cluster_form unless cluster_form.save
UffizziCore::ClusterService.start_deploy(cluster_form)
respond_with cluster_form
end
def scale_down
if resource_cluster.deployed?
UffizziCore::ClusterService.scale_down!(resource_cluster)
return respond_with resource_cluster
end
return render_scale_error(I18n.t('cluster.already_asleep', name: resource_cluster.name)) if resource_cluster.scaled_down?
if resource_cluster.deploying_namespace? || resource_cluster.deploying?
render_scale_error(I18n.t('cluster.deploy_in_process', name: resource_cluster.name))
end
rescue AASM::InvalidTransition, UffizziCore::ClusterScaleError => e
render_scale_error(e.message)
end
def scale_up
if resource_cluster.scaled_down?
UffizziCore::ClusterService.scale_up!(resource_cluster)
return respond_with resource_cluster
end
return render_scale_error(I18n.t('cluster.already_awake', name: resource_cluster.name)) if resource_cluster.deployed?
rescue AASM::InvalidTransition, UffizziCore::ClusterScaleError => e
render_scale_error(e.message)
end
def show
respond_with resource_cluster
end
def sync
cluster_form = resource_cluster.becomes(UffizziCore::Api::Cli::V1::Cluster::SyncForm)
cluster_form.sync_status
cluster_form.save
respond_with cluster_form
end
def destroy
resource_cluster.disable!
head(:no_content)
end
private
def resource_cluster
active_project_clusters = resource_project.clusters.enabled
@resource_cluster ||= if request_by_admin? || valid_request_from_ci_workflow?
active_project_clusters.find_by!(name: params[:name])
else
active_project_clusters.deployed_by_user(current_user).find_by!(name: params[:name])
end
end
def request_by_admin?
current_user.admin_access_to_project?(resource_project)
end
def valid_request_from_ci_workflow?
ci_module.valid_request_from_ci_workflow?(params)
end
def cluster_params
params.require(:cluster)
end
def render_scale_error(message)
render json: { errors: { state: [message] } }, status: :unprocessable_entity
end
def find_kubernetes_distribution(version)
return UffizziCore::KubernetesDistribution.default if version.blank?
UffizziCore::KubernetesDistribution.find_by(version: version)
end
def render_distribution_version_error(version)
available_versions = UffizziCore::KubernetesDistribution.pluck(:version).join(', ')
message = I18n.t('kubernetes_distribution.not_available', version: version, available_versions: available_versions)
render json: { errors: { kubernetes_distribution: [message] } }, status: :unprocessable_entity
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/compose_files_controller.rb
================================================
# frozen_string_literal: true
# @resource ComposeFile
class UffizziCore::Api::Cli::V1::Projects::ComposeFilesController < UffizziCore::Api::Cli::V1::Projects::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_projects_compose_files
# Get the compose file for the project
#
# @path [GET] /api/cli/v1/projects/{project_slug}/compose_file
#
# @parameter project_slug(required,path) [string] The project slug
#
# @response [ComposeFile] 200 OK
# @response 401 Not authorized
# @response [object>] 404 Not found
def show
respond_with compose_file
end
# Create a compose file for the project
#
# @path [POST] /api/cli/v1/projects/{project_slug}/compose_file
#
# @parameter project_slug(required,path) [string] The project slug
# @parameter params(required,body) [object <
# compose_file: object,
# dependencies: Array>>]
#
# @response [ComposeFile] 201 OK
# @response 422 A compose file already exists for this project
# @response [ComposeFile] 422 Invalid compose file
# @response 401 Not authorized
def create
params = {
project: resource_project,
user: current_user,
compose_file_params: compose_file_params,
dependencies: dependencies_params[:dependencies] || [],
}
compose_file_form, errors = create_or_update_compose_file(params)
return render_errors(errors) if errors.present?
respond_with compose_file_form
end
# Delete the compose file for the project
#
# @path [DELETE] /api/cli/v1/projects/{project_slug}/compose_file
#
# @parameter project_slug(required,path) [string] The project slug
#
# @response 204 No Content
# @response 401 Not authorized
def destroy
compose_file.destroy
head :no_content
end
private
def compose_file
compose_file = resource_project.compose_file
raise ActiveRecord::RecordNotFound.new("Couldn't find UffizziCore::ComposeFile", UffizziCore::ComposeFile) if compose_file.blank?
compose_file
end
def compose_file_params
params.require(:compose_file)
end
def dependencies_params
params.permit(dependencies: [:path, :source, :content, :use_kind, :is_file])
end
def create_or_update_compose_file(params)
existing_compose_file = resource_project.compose_file
if existing_compose_file.present?
UffizziCore::ComposeFileService.update(existing_compose_file, params)
else
kind = UffizziCore::ComposeFile.kind.main
UffizziCore::ComposeFileService.create(params, kind)
end
end
def render_errors(errors)
json = { errors: errors }
render json: json, status: :unprocessable_entity
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/activity_items_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemsController <
UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments_activity_items
# Get activity items for a deployment
#
# @path [GET] /api/cli/v1/projects/{project_slug}/deployment/{deployment_id}/actiivity_items
#
# @parameter project_slug(required,path) [string] The project slug
# @parameter deployment_d(required,path) [integer] The id of the deployment
#
# @response [ActivtyItem] 200 OK
# @response 401 Not authorized
# @response 404 Not found
def index
deployment = resource_project.deployments.enabled.find(params[:deployment_id])
unless deployment.active?
return render json: { errors: { title: [I18n.t('deployment.invalid_state', id: deployment.id, state: deployment.state)] } },
status: :unprocessable_entity
end
activity_items = deployment
.activity_items
.page(page)
.per(per_page)
.order(updated_at: :desc)
.ransack(q_param)
.result
meta = meta(activity_items)
activity_items = activity_items.map do |activity_item|
UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemSerializer.new(activity_item).as_json
end
render json: {
activity_items: activity_items,
meta: meta,
}
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController < UffizziCore::Api::Cli::V1::Projects::ApplicationController
def resource_deployment
@resource_deployment ||= resource_project.deployments.active.find(params[:deployment_id])
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::ApplicationController <
UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController
def resource_container
@resource_container ||= resource_deployment.active_containers.find_by!(service_name: params[:container_name])
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers/logs_controller.rb
================================================
# frozen_string_literal: true
# @resource Project/Deployment/Container/Log
class UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::LogsController <
UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::ApplicationController
# @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers/{container_name}/logs
#
# @parameter project_slug(required,path) [string] The slug of the project
# @parameter deployment_id(required,path) [integer] The id of the deployment
# @parameter container_name(required,path) [integer] The name of the container
#
# @response [object >>] 200 OK
# @response [object>] 404 Not found
# @response 401 Not authorized
def index
response = UffizziCore::LogsService.fetch_container_logs(resource_container, logs_params)
render json: response
end
private
def logs_params
params.permit(:limit, :previous)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers_controller.rb
================================================
# frozen_string_literal: true
# @resource Container
class UffizziCore::Api::Cli::V1::Projects::Deployments::ContainersController <
UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments_containers
# Get a list of container services for a deployment
#
# @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers
#
# @parameter project_slug(required,path) [string] The project slug
# @parameter deployment_id(required,path) [integer] The id of the deployment
#
# @response [Container] 200 OK
# @response 401 Not authorized
# @response 404 Not found
def index
containers = resource_deployment.containers.active
respond_with containers
end
def k8s_container_description
deployment ||= resource_project.deployments.enabled.find(params[:deployment_id])
container = deployment.containers.active.find_by!(service_name: params[:container_name])
last_state = UffizziCore::ContainerService.last_state(container)
render json: { last_state: last_state }
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/events_controller.rb
================================================
# frozen_string_literal: true
# @resource Event
class UffizziCore::Api::Cli::V1::Projects::Deployments::EventsController <
UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments_events
# Get the events associated with deployment
#
# @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/events
#
# @parameter project_slug(required,path) [string] The project_slug for the project
# @parameter deployment_id(required,path) [integer] The id of the deployment
#
# @response [object<
# events: Array
# > >] 200 OK
#
# @response 401 Not authorized
# @response 404 Not found
def index
response = UffizziCore::ControllerService.fetch_deployment_events(resource_deployment)
events = { events: response }
render json: events
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller.rb
================================================
# frozen_string_literal: true
# @resource Deployment
class UffizziCore::Api::Cli::V1::Projects::DeploymentsController < UffizziCore::Api::Cli::V1::Projects::ApplicationController
include UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerModule
before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments
before_action :check_account_quota, only: :create
before_action :check_current_plan
after_action :update_show_trial_quota_exceeded_warning, only: :destroy
# Get a list of active deployements for a project
#
# @path [GET] /api/cli/v1/projects/{project_slug}/deployments
#
# @parameter project_slug(required,path) [string] The project slug
#
# @response [Array] 200 OK
# @response 401 Not authorized
def index
search_labels = JSON.parse(q_param)
filtered_deployments = deployments.with_labels(search_labels)
respond_with filtered_deployments, each_serializer: UffizziCore::Api::Cli::V1::Projects::DeploymentsSerializer
end
# Get deployment information by id
#
# @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{id}
#
# @parameter project_slug(required,path) [string] The project slug
#
# @response [Deployment] 200 OK
# @response [object>] 404 Not found
# @response 401 Not authorized
def show
respond_with deployment
end
# Create a deployment from a compose file
#
# @path [POST] /api/cli/v1/projects/{project_slug}/deployments
#
# @parameter project_slug(required,path) [string] The project slug
# @parameter params(required,body) [object<
# compose_file: object,
# dependencies: Array>>]
#
# @response [Deployment] 201 OK
# @response [object>] 422 Unprocessable Entity
# @response [object>] 404 Not found
# @response 401 Not authorized
def create
return render_deployment_exists_error if deployments.with_metadata.with_labels(metadata_params).exists?
compose_file, errors = find_or_create_compose_file
return render_invalid_file if compose_file.invalid_file?
return render_errors(errors) if errors.present?
errors = check_credentials(compose_file)
return render_errors(errors) if errors.present?
params = {
metadata: metadata_params,
creation_source: creation_source_params,
}
deployment = UffizziCore::DeploymentService.create_from_compose(compose_file, resource_project, current_user, params)
respond_with deployment
end
# Update the deployment with new compose file
#
# @path [PUT] /api/cli/v1/projects/{project_slug}/deployments/{id}"
#
# @parameter project_slug(required,path) [string] The project slug
# @parameter params(required,body) [object<
# compose_file: object,
# dependencies: Array>>]
#
# @response [Deployment] 201 OK
# @response [object>] 422 Unprocessable Entity
# @response [object>] 404 Not found
# @response 401 Not authorized
def update
compose_file, errors = UffizziCore::ComposeFileService.create_temporary_compose(
resource_project,
current_user,
compose_file_params,
dependencies_params[:dependencies],
)
return render_invalid_file if compose_file.invalid_file?
return render_errors(errors) if errors.present?
errors = check_credentials(compose_file)
return render_errors(errors) if errors.present?
deployment = deployments.find(params[:id])
updated_deployment = UffizziCore::DeploymentService.update_from_compose(compose_file, resource_project, current_user, deployment,
metadata_params)
respond_with updated_deployment
end
# @path [POST] /api/cli/v1/projects/{project_slug}/deployments/{id}/deploy_containers
#
# @parameter project_slug(required,path) [string] The project slug
# @parameter id(required,path) [string] The id of the deployment
#
# @response 204 No Content
# @response [object>] 404 Not found
# @response 401 Not authorized
def deploy_containers
deployment = resource_project.deployments.active.find(params[:id])
deployment.update(deployed_by: current_user)
resource_project.config_files.by_deployment(deployment).each do |config_file|
UffizziCore::ConfigFile::ApplyJob.perform_async(deployment.id, config_file.id)
end
UffizziCore::Deployment::DeployContainersJob.perform_async(deployment.id)
end
# Disable deployment by id
#
# @path [DELETE] /api/cli/v1/projects/{project_slug}/deployments/{id}
#
# @parameter project_slug(required,path) [string] The project slug
#
# @response 204 No Content
# @response 401 Not authorized
def destroy
UffizziCore::DeploymentService.disable!(deployment)
deployment.deployment_events.create!(deployment_state: deployment.state, message: 'Destroyed by CLI')
head :no_content
end
private
def deployment
@deployment ||= deployments.find(params[:id])
end
def find_or_create_compose_file
existing_compose_file = resource_project.compose_file
if compose_file_params.present?
UffizziCore::ComposeFileService.create_temporary_compose(
resource_project,
current_user,
compose_file_params,
dependencies_params[:dependencies],
)
else
raise ActiveRecord::RecordNotFound if existing_compose_file.blank?
errors = []
[existing_compose_file, errors]
end
end
def check_credentials(compose_file)
credentials = resource_project.account.credentials
check_credentials_form = UffizziCore::Api::Cli::V1::ComposeFile::CheckCredentialsForm.new
check_credentials_form.compose_file = compose_file
check_credentials_form.credentials = credentials
return check_credentials_form.errors if check_credentials_form.invalid?
nil
end
def deployments
@deployments ||= resource_project.deployments.enabled
end
def deployment_params
params.required(:deployment)
end
def compose_file_params
params[:compose_file]
end
def dependencies_params
params.permit(dependencies: [:path, :source, :content, :use_kind, :is_file])
end
def metadata_params
params[:metadata]
end
def creation_source_params
params[:creation_source]
end
def render_invalid_file
render json: { errors: { state: [I18n.t('compose.invalid_compose')] } }, status: :unprocessable_entity
end
def render_deployment_exists_error
render json: { errors: { state: [I18n.t('deployment.already_exists')] } }, status: :unprocessable_entity
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects/secrets_controller.rb
================================================
# frozen_string_literal: true
# @resource Project/Secrets
class UffizziCore::Api::Cli::V1::Projects::SecretsController < UffizziCore::Api::Cli::V1::Projects::ApplicationController
before_action :authorize_uffizzi_core_api_cli_v1_projects_secrets
# Get secrets for the project
#
# @path [GET] /api/cli/v1/projects/{project_slug}/secrets
# @parameter project_slug(required,path) [string]
# @response [object>>] 200 OK
# @response 401 Not authorized
def index
respond_with resource_project.secrets, root: :secrets
end
# Add secret to project
#
# @path [POST] /api/cli/v1/projects/{project_slug}/secrets/bulk_create
# @parameter project_slug(required,path) [string]
# @parameter secrets(required,body) [object>>]
# @response [object>>] 201 Created
# @response 422 A compose file already exists for this project
# @response 401 Not authorized
def bulk_create
secrets_form = UffizziCore::Api::Cli::V1::Secret::BulkAssignForm.new
secrets_form.secrets = resource_project.secrets
secrets_form.assign_secrets(secrets_params)
return respond_with secrets_form unless secrets_form.valid?
resource_project.secrets.replace(secrets_form.secrets)
UffizziCore::ProjectService.update_compose_secrets(resource_project)
respond_with resource_project.secrets, root: :secrets
end
# Delete a secret from project by secret name
#
# @path [DELETE] /api/cli/v1/projects/{project_slug}/secrets/{secret_name}
# @parameter project_slug(required,path) [string]
# @response [Project] 200 OK
# @response 404
# @response 401 Not authorized
def destroy
secret_name = CGI.unescape(params[:id])
secret = resource_project.secrets.find_by!(name: secret_name)
UffizziCore::ProjectService.update_compose_secret_errors(resource_project, secret)
secret.destroy
head :no_content
end
private
def secrets_params
params.require(:secrets)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/projects_controller.rb
================================================
# frozen_string_literal: true
# @resource Project
class UffizziCore::Api::Cli::V1::ProjectsController < UffizziCore::Api::Cli::V1::ApplicationController
include UffizziCore::Api::Cli::V1::ProjectsControllerModule
before_action :authorize_uffizzi_core_api_cli_v1_projects
after_action :update_show_trial_quota_exceeded_warning, only: :destroy
# Get projects of current user
#
# @path [GET] /api/cli/v1/projects
#
# @response [object> >] 200 OK
# @response 401 Not authorized
def index
projects = current_user.projects.active.order(updated_at: :desc)
respond_with projects, each_serializer: UffizziCore::Api::Cli::V1::ShortProjectSerializer
end
# Get a project by slug
#
# @path [GET] /api/cli/v1/projects/{slug}
#
# @response > 200 OK
# @response 404 Not Found
# @response 401 Not authorized
def show
respond_with resource_project
end
# Delete a project
#
# @path [DELETE] /api/cli/v1/projects/{slug}
#
# @response 204 No content
# @response 404 Not Found
# @response 401 Not authorized
def destroy
resource_project.disable!
head :no_content
end
private
def project_params
params.require(:project)
end
def policy_context
current_project = current_user.projects.find_by(slug: params[:slug])
account = current_project&.account || current_user.default_account
UffizziCore::AccountContext.new(current_user, user_access_module, account, params)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/api/cli/v1/sessions_controller.rb
================================================
# frozen_string_literal: true
# @resource Uffizzi
class UffizziCore::Api::Cli::V1::SessionsController < UffizziCore::Api::Cli::V1::ApplicationController
skip_before_action :authenticate_request!, only: [:create]
# Create session
#
# @path [POST] /api/cli/v1/session
#
# @parameter user(required,body) [object]
# @response [object> >>] 201 Created successfully
# @response [object>] 422 Unprocessable entity
def create
session_form = UffizziCore::Api::Cli::V1::SessionCreateForm.new(session_params)
if session_form.valid?
sign_in(session_form.user)
return respond_with session_form.user
end
respond_with session_form
end
# Destroy session
#
# @path [DELETE] /api/cli/v1/session
#
# @response 204 No Content
def destroy
sign_out
head :no_content
end
private
def session_params
params.require(:user).permit(:email, :password)
end
end
================================================
FILE: core/app/controllers/uffizzi_core/application_controller.rb
================================================
# frozen_string_literal: true
class UffizziCore::ApplicationController < ActionController::Base
include Pundit::Authorization
include UffizziCore::ResponseService
include UffizziCore::AuthManagement
include UffizziCore::AuthorizationConcern
include UffizziCore::DependencyInjectionConcern
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 20
protect_from_forgery with: :exception
RESCUABLE_EXCEPTIONS = [RuntimeError, TypeError, NameError, ArgumentError, SyntaxError].freeze
unless Rails.env.test?
rescue_from *RESCUABLE_EXCEPTIONS do |exception|
render_server_error(exception)
end
end
rescue_from ActiveRecord::RecordNotFound do |exception|
render_not_found(exception)
end
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
before_action :authenticate_request!
skip_before_action :verify_authenticity_token
respond_to :json
def render_not_authorized
render json: { errors: { title: [I18n.t('session.unauthorized')] } }, status: :forbidden
end
def policy_context
UffizziCore::BaseContext.new(current_user, user_access_module, params)
end
def self.responder
UffizziCore::JsonResponder
end
def render_not_found(exception)
resource = exception.model || 'Resource'
render json: { errors: { title: ["#{resource} Not Found"] } }, status: :not_found
end
def render_server_error(error)
render json: { errors: { title: [error] } }, status: :internal_server_error
end
def render_errors(errors)
json = { errors: errors }
render json: json, status: :unprocessable_entity
end
def q_param
params[:q] || ActionController::Parameters.new
end
def page
params[:page] || DEFAULT_PAGE
end
def per_page
params[:per_page] || DEFAULT_PER_PAGE
end
end
================================================
FILE: core/app/errors/uffizzi_core/cluster_scale_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ClusterScaleError < StandardError
def initialize(action)
message = I18n.t('cluster.scaling_failed', action: action)
super(message)
end
end
================================================
FILE: core/app/errors/uffizzi_core/compose_file/build_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::BuildError < UffizziCore::ComposeFileError
def initialize(message, extra_errors = {})
error_key = UffizziCore::ComposeFile::ErrorsService::TEMPLATE_BUILD_ERROR_KEY
super(message, error_key, extra_errors)
end
end
================================================
FILE: core/app/errors/uffizzi_core/compose_file/credential_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::CredentialError < StandardError
end
================================================
FILE: core/app/errors/uffizzi_core/compose_file/parse_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::ParseError < StandardError
end
================================================
FILE: core/app/errors/uffizzi_core/compose_file/secrets_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::SecretsError < UffizziCore::ComposeFileError
def initialize(message, extra_errors = {})
error_key = UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY
super(message, error_key, extra_errors)
end
end
================================================
FILE: core/app/errors/uffizzi_core/compose_file_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFileError < StandardError
attr_reader :errors
def initialize(message, error_key = nil, extra_errors = {})
if [NilClass, String].exclude?(error_key.class)
raise StandardError.new("#{self.class} arg 'error_key' should be a #{String} or #{NilClass}")
end
unless extra_errors.is_a?(Hash)
raise StandardError.new("#{self.class} arg 'extra_errors' should be a #{Hash}")
end
@errors = error_key.nil? ? {} : { error_key => message.to_s }.merge(extra_errors)
super(message)
end
end
================================================
FILE: core/app/errors/uffizzi_core/container_registry_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistryError < StandardError
attr_reader :errors, :error_key
def initialize(response)
prepared_errors = prepare_errors(response[:body], response[:status])
@error_key = UffizziCore::ComposeFile::ErrorsService::DOCKER_REGISTRY_CONTAINER_ERROR_KEY
@errors = { @error_key => prepared_errors }
super(prepared_errors.to_json)
end
private
def prepare_errors(body, status)
parsed_body = JSON.parse!(body.to_s)
parsed_body.fetch('errors', parsed_body)
rescue JSON::ParserError, TypeError
msg = body.empty? ? I18n.t('registry.error', code: status) : body
{ message: msg }
end
end
================================================
FILE: core/app/errors/uffizzi_core/deployment/image_pull_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::ImagePullError < StandardError
attr_reader :deployment_id
def initialize(deployment_id)
super
@deployment_id = deployment_id
end
end
================================================
FILE: core/app/errors/uffizzi_core/deployment_not_found_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::DeploymentNotFoundError < StandardError
attr_reader :deployment_id
def initialize(deployment_id)
super
@deployment_id = deployment_id
end
end
================================================
FILE: core/app/errors/uffizzi_core/registry_not_supported_error.rb
================================================
# frozen_string_literal: true
class UffizziCore::RegistryNotSupportedError < StandardError
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/account/credential/check_credential_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Account::Credential::CheckCredentialForm
include UffizziCore::ApplicationFormWithoutActiveRecord
attribute :type
attribute :account
validate :credential_exists?
private
def credential_exists?
errors.add(:type, :exist) if account.credentials.by_type(type).exists?
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/account/credential/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Account::Credential::CreateForm < UffizziCore::Credential
include UffizziCore::ApplicationForm
permit :type, :registry_url, :username, :password
validates :password, presence: { message: :password_blank }
validate :check_registry_url, if: -> { errors[:password].empty? }
validate :check_credential_correctness, if: -> { errors[:password].empty? }
validate :credential_exists?, if: -> { errors[:password].empty? }
private
def check_registry_url
errors.add(:registry_url, :invalid_scheme) if URI.parse(registry_url).scheme.nil?
end
def check_credential_correctness
errors.add(:username, :incorrect, type: type.text) unless correct?
end
def credential_exists?
errors.add(:type, :exist) if account.credentials.by_type(type).exists?
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/account/credential/update_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Account::Credential::UpdateForm < UffizziCore::Credential
include UffizziCore::ApplicationForm
permit :registry_url, :username, :password
validates :password, presence: { message: :password_blank }
validate :check_registry_url, if: -> { errors[:password].empty? }
validate :check_credential_correctness, if: -> { errors[:password].empty? }
private
def check_registry_url
errors.add(:registry_url, :invalid_scheme) if URI.parse(registry_url).scheme.nil?
end
def check_credential_correctness
errors.add(:username, :incorrect) unless correct?
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/cluster/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Cluster::CreateForm < UffizziCore::Cluster
include UffizziCore::ApplicationForm
permit :name, :manifest, :creation_source
validate :check_manifest, if: -> { manifest.present? }
private
def check_manifest
YAML.load_stream(manifest)
rescue Psych::SyntaxError => e
err = [e.problem, e.context].compact.join(' ')
errors.add(:manifest, err)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/cluster/sync_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Cluster::SyncForm < UffizziCore::Cluster
include UffizziCore::ApplicationForm
permit :state
def sync_status
cluster_data = UffizziCore::ControllerService.show_cluster(self)
asleep_in_cluster = cluster_data.status.sleep
return unless deployed? || scaled_down?
return if actual_status?(asleep_in_cluster)
self.state = asleep_in_cluster ? UffizziCore::Cluster::STATE_SCALED_DOWN : UffizziCore::Cluster::STATE_DEPLOYED
self
end
private
def actual_status?(asleep_in_cluster)
(asleep_in_cluster && scaled_down?) || (!asleep_in_cluster && deployed?)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/compose_file/check_credentials_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ComposeFile::CheckCredentialsForm
include UffizziCore::ApplicationFormWithoutActiveRecord
attr_reader :type
attribute :compose_file
attribute :credentials
validate :check_containers_credentials
private
def check_containers_credentials
compose_content = Base64.decode64(compose_file.content)
compose_payload = { compose_file: compose_file }
compose_data = UffizziCore::ComposeFileService.parse(compose_content, compose_payload)
containers = compose_data[:containers]
containers.each do |container|
container_registry_service = UffizziCore::ContainerRegistryService.init_by_container(container, credentials)
@type = container_registry_service.type
next if container_registry_service.image_available?(credentials)
raise UffizziCore::ComposeFile::CredentialError.new(I18n.t('compose.unprocessable_image', value: type))
end
rescue UffizziCore::ContainerRegistryError => e
errors.add(:credentials, I18n.t('compose.unprocessable_image', value: type))
errors.add(e.error_key, e.message)
rescue UffizziCore::ComposeFile::CredentialError => e
errors.add(:credentials, e.message)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/compose_file/cli_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ComposeFile::CliForm
include UffizziCore::ApplicationFormWithoutActiveRecord
attribute :compose_content_data, Hash
attribute :compose_data, Hash
attribute :compose_dependencies, Array
attribute :compose_repositories, Array
attribute :content, String
attribute :source_kind, Symbol
attribute :compose_file, UffizziCore::ComposeFile
validates :content, presence: true
validates :compose_file, presence: true
validate :check_compose_parsed_data, if: -> { errors[:content].empty? }
def check_compose_parsed_data
compose_content = Base64.decode64(content)
compose_payload = { compose_file: compose_file }
self.compose_data = UffizziCore::ComposeFileService.parse(compose_content, compose_payload)
rescue UffizziCore::ComposeFile::ParseError => e
errors.add(:content, e.message)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/compose_file/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ComposeFile::CreateForm < UffizziCore::ComposeFile
include UffizziCore::ApplicationForm
permit :source, :path, :content
validates :source, presence: true
validates :path, presence: true
validates :content, presence: true
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/compose_file/template_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ComposeFile::TemplateForm
include UffizziCore::ApplicationFormWithoutActiveRecord
attribute :credentials
attribute :project, UffizziCore::Project
attribute :user, UffizziCore::User
attribute :compose_data, Hash
attribute :source, String
attribute :template_attributes, Hash
attribute :template_build_error, String
attribute :compose_dependencies, Array
attribute :compose_repositories, Array
validate :check_template_attributes
def assign_template_attributes!
self.template_attributes = UffizziCore::ComposeFileService.build_template_attributes(
compose_data,
source,
credentials,
project,
compose_dependencies,
compose_repositories,
)
rescue StandardError => e
self.template_build_error = e
end
private
def check_template_attributes
readable_errors = [
UffizziCore::ComposeFile::SecretsError,
UffizziCore::ComposeFile::BuildError,
UffizziCore::ContainerRegistryError,
]
if readable_errors.include?(template_build_error.class)
template_build_error.errors.each { |k, v| errors.add(k, v) }
return
end
raise template_build_error if template_build_error.is_a?(StandardError)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/compose_file/update_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ComposeFile::UpdateForm < UffizziCore::ComposeFile
include UffizziCore::ApplicationForm
permit :content, :source, :path
validates :content, presence: true
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/config_file/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ConfigFile::CreateForm < UffizziCore::ConfigFile
include UffizziCore::ApplicationForm
permit :filename, :kind, :payload
validates :filename, presence: true
validates :kind, presence: true
validates :payload, presence: true
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/deployment/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Deployment::CreateForm < UffizziCore::Deployment
include UffizziCore::ApplicationForm
include UffizziCore::DependencyInjectionConcern
permit :creation_source,
:metadata,
containers_attributes: [
:image,
:service_name,
:tag,
:full_image_name,
:port,
:public,
:memory_limit,
:memory_request,
:entrypoint,
:command,
:receive_incoming_requests,
:continuously_deploy,
{ variables: [:name, :value],
additional_subdomains: [],
secret_variables: [:name, :value],
volumes: [:source, :target, :type, :read_only],
healthcheck: [:test, :interval, :timeout, :retries, :start_period, :disable, { test: [] }],
repo_attributes: [
:namespace,
:name,
:slug,
:type,
:description,
:is_private,
:repository_id,
:branch,
:kind,
:dockerfile_path,
:dockerfile_context_path,
:deploy_preview_when_pull_request_is_opened,
:delete_preview_when_pull_request_is_closed,
:deploy_preview_when_image_tag_is_created,
:delete_preview_when_image_tag_is_updated,
:share_to_github,
:delete_preview_after,
{ args: [:name, :value] },
],
container_config_files_attributes: [
:config_file_id,
:mount_path,
],
container_host_volume_files_attributes: [
:host_volume_file_id,
:source_path,
] },
]
validate :check_all_containers_have_unique_ports
validate :check_exists_ingress_container
validate :check_secrets_exist_in_database
validate :check_max_memory_limit
def assign_dependences!(project, user)
self.project = project
self.containers = containers.map do |container|
container.repo.project = project if !container.repo.nil?
container
end
self.deployed_by = user
self
end
private
def check_all_containers_have_unique_ports
active_containers = containers.select(&:active?)
errors.add(:containers, :duplicate_ports) unless UffizziCore::DeploymentService.all_containers_have_unique_ports?(active_containers)
end
def check_exists_ingress_container
active_containers = containers.select(&:active?)
errors.add(:containers, :incorrect_ingress_container) unless UffizziCore::DeploymentService.ingress_container?(active_containers)
end
def check_secrets_exist_in_database
return if compose_file.nil?
existing_secrets = project.secrets.pluck('name')
compose_file.template.payload['containers_attributes']
.map { |container| container['secret_variables'].map { |secret| secret['name'] } }.flatten.uniq
.select { |secret| existing_secrets.exclude?(secret) }
.each do |secret|
error_message = I18n.t('compose.project_secret_not_found', secret: secret)
errors.add(:secret_variables, error_message)
end
end
def check_max_memory_limit
return if deployment_memory_module.valid_memory_limit?(self)
deployment_memory_module.memory_limit_error_message(self)
errors.add(:containers, message)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/deployment/update_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Deployment::UpdateForm < UffizziCore::Deployment
include UffizziCore::ApplicationForm
include UffizziCore::DependencyInjectionConcern
permit :metadata,
containers_attributes: [
:image,
:service_name,
:tag,
:port,
:full_image_name,
:public,
:memory_limit,
:memory_request,
:entrypoint,
:command,
:receive_incoming_requests,
:continuously_deploy,
{ variables: [:name, :value],
additional_subdomains: [],
secret_variables: [:name, :value],
volumes: [:source, :target, :type, :read_only],
healthcheck: [:test, :interval, :timeout, :retries, :start_period, :disable, { test: [] }],
repo_attributes: [
:namespace,
:name,
:slug,
:type,
:description,
:is_private,
:repository_id,
:branch,
:kind,
:dockerfile_path,
:dockerfile_context_path,
:deploy_preview_when_pull_request_is_opened,
:delete_preview_when_pull_request_is_closed,
:deploy_preview_when_image_tag_is_created,
:delete_preview_when_image_tag_is_updated,
:share_to_github,
:delete_preview_after,
{ args: [:name, :value] },
],
container_config_files_attributes: [
:config_file_id,
:mount_path,
],
container_host_volume_files_attributes: [
:host_volume_file_id,
:source_path,
] },
]
validate :check_all_containers_have_unique_ports
validate :check_exists_ingress_container
validate :check_max_memory_limit
def assign_dependences!(project, user)
self.project = project
self.containers = containers.map do |container|
container.repo.project = project if !container.repo.nil?
container
end
self.deployed_by = user
self
end
private
def check_all_containers_have_unique_ports
active_containers = containers.select(&:active?)
errors.add(:containers, :duplicate_ports) unless UffizziCore::DeploymentService.all_containers_have_unique_ports?(active_containers)
end
def check_exists_ingress_container
active_containers = containers.select(&:active?)
errors.add(:containers, :incorrect_ingress_container) unless UffizziCore::DeploymentService.ingress_container?(active_containers)
end
def check_max_memory_limit
return if deployment_memory_module.valid_memory_limit?(self)
deployment_memory_module.memory_limit_error_message(self)
errors.add(:containers, message)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/project/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Project::CreateForm < UffizziCore::Project
include UffizziCore::ApplicationForm
permit :name, :slug, :description
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/project/update_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Project::UpdateForm < UffizziCore::Project
include UffizziCore::ApplicationForm
permit :name, :slug, :description
validates :name, presence: true, uniqueness: { scope: :account }
validates :slug, presence: true, uniqueness: true
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/secret/bulk_assign_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Secret::BulkAssignForm
include UffizziCore::ApplicationFormWithoutActiveRecord
MAX_SECRET_KEY_LENGTH = 256
attribute :secrets, Array
validate :check_duplicates
validate :check_length
def assign_secrets(new_secrets)
return if new_secrets.blank?
new_secrets.each do |new_secret|
secret = UffizziCore::Secret.new(name: new_secret['name'], value: new_secret['value'])
secrets.append(secret)
end
end
private
def check_duplicates
duplicates = []
groupped_secrets = secrets.group_by { |secret| secret['name'] }
groupped_secrets.each_pair do |key, value|
duplicates << key if value.size > 1
end
error_message = I18n.t('secrets.duplicates_exists', secrets: duplicates.join(', '))
errors.add(:secrets, error_message) if duplicates.present?
end
def check_length
secrets_with_invalid_key_length = secrets.select { |secret| secret['name'].length > MAX_SECRET_KEY_LENGTH }
error_message = I18n.t('secrets.invalid_key_length')
errors.add(:secrets, error_message) if secrets_with_invalid_key_length.present?
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/session_create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::SessionCreateForm
include UffizziCore::ApplicationFormWithoutActiveRecord
attribute :email, String
attribute :password, String
validates :email, :password, presence: true
validate :check_authenticate, if: :email
def user
@user ||= UffizziCore::User.active.by_email(email).first
end
def check_authenticate
return unless wrong_email_or_password?
errors.add(:password, 'Email or password is incorrect.')
end
private
def wrong_email_or_password?
return true if user.nil?
!user.authenticate(password)
end
end
================================================
FILE: core/app/forms/uffizzi_core/api/cli/v1/template/create_form.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Template::CreateForm < UffizziCore::Template
include UffizziCore::DependencyInjectionConcern
validate :check_max_memory_limit
def check_max_memory_limit
return if template_memory_module.valid_memory_limit?(self)
message = template_memory_module.memory_limit_error_message(self)
errors.add(:payload, message)
end
end
================================================
FILE: core/app/forms/uffizzi_core/application_form.rb
================================================
# frozen_string_literal: true
module UffizziCore::ApplicationForm
extend ActiveSupport::Concern
include UffizziCore::MassAssignmentControlConcern
class_methods do
delegate :model_name, :name, to: :superclass
end
end
================================================
FILE: core/app/forms/uffizzi_core/application_form_without_active_record.rb
================================================
# frozen_string_literal: true
module UffizziCore::ApplicationFormWithoutActiveRecord
extend ActiveSupport::Concern
included do
include ActiveModel::Validations
include ActiveModel::Conversion
include ActiveModel::Serialization
include ActiveModel::Validations::Callbacks
include ::Virtus.model
end
def persisted?
false
end
end
================================================
FILE: core/app/forms/uffizzi_core/mass_assignment_control_concern.rb
================================================
# frozen_string_literal: true
module UffizziCore::MassAssignmentControlConcern
extend ActiveSupport::Concern
class_methods do
def permit(*args)
@_args = args
end
def _args
@_args
end
end
def assign_attributes(attrs = ActionController::Parameters.new)
attrs = ActionController::Parameters.new if attrs.nil?
new_attrs = attrs.permit(*self.class._args)
super(new_attrs)
end
end
================================================
FILE: core/app/helpers/uffizzi_core/application_helper.rb
================================================
# frozen_string_literal: true
module UffizziCore
module ApplicationHelper
end
end
================================================
FILE: core/app/jobs/uffizzi_core/account/create_credential_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Account::CreateCredentialJob < UffizziCore::ApplicationJob
sidekiq_options queue: :accounts, retry: 5
def perform(id)
credential = UffizziCore::Credential.find(id)
UffizziCore::AccountService.create_credential(credential)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/account/update_credential_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Account::UpdateCredentialJob < UffizziCore::ApplicationJob
sidekiq_options queue: :accounts, retry: 5
def perform(id)
credential = UffizziCore::Credential.find(id)
UffizziCore::AccountService.update_credential(credential)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/activity_item/docker/update_digest_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::ActivityItem::Docker::UpdateDigestJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: 5
def perform(id)
activity_item = UffizziCore::ActivityItem.find(id)
UffizziCore::ActivityItemService.update_docker_digest!(activity_item)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/application_job.rb
================================================
# frozen_string_literal: true
module UffizziCore
class ApplicationJob
include Sidekiq::Worker
end
end
================================================
FILE: core/app/jobs/uffizzi_core/cluster/delete_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Cluster::DeleteJob < UffizziCore::ApplicationJob
sidekiq_options queue: :clusters,
lock: :until_executed,
retry: Settings.default_job_retry_count
def perform(id)
Rails.logger.info("DEPLOYMENT_PROCESS cluster_id=#{id} DeleteJob")
cluster = UffizziCore::Cluster.find(id)
UffizziCore::ControllerService.delete_namespace(cluster)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/cluster/deploy_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Cluster::DeployJob < UffizziCore::ApplicationJob
sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count
def perform(id)
cluster = UffizziCore::Cluster.find(id)
UffizziCore::ClusterService.deploy_cluster(cluster)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/cluster/manage_deploying_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Cluster::ManageDeployingJob < UffizziCore::ApplicationJob
sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count
def perform(id, try = 1)
cluster = UffizziCore::Cluster.find(id)
UffizziCore::ClusterService.manage_deploying(cluster, try)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/cluster/manage_scaling_down_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Cluster::ManageScalingDownJob < UffizziCore::ApplicationJob
sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count
def perform(id)
cluster = UffizziCore::Cluster.find(id)
UffizziCore::ClusterService.manage_scale_down(cluster)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/cluster/manage_scaling_up_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Cluster::ManageScalingUpJob < UffizziCore::ApplicationJob
sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count
def perform(id, try = 1)
cluster = UffizziCore::Cluster.find(id)
UffizziCore::ClusterService.manage_scale_up(cluster, try)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/config_file/apply_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::ConfigFile::ApplyJob < UffizziCore::ApplicationJob
sidekiq_options queue: :config_files, retry: Settings.controller.resource_create_retry_count
sidekiq_retry_in do |count, exception|
case exception
when UffizziCore::DeploymentNotFoundError
Rails.logger.info("DEPLOYMENT_PROCESS ApplyJob retry deployment_id=#{exception.deployment_id} count=#{count}")
Settings.controller.resource_create_retry_time
else
if [Settings.default_job_retry_count, Settings.controller.resource_create_retry_count].include?(count)
Sentry.capture_exception(exception)
:kill
else
Settings.controller.resource_create_retry_time
end
end
end
def perform(deployment_id, config_file_id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment_id} Apply ConfigFile(config_file_id:#{config_file_id})")
deployment = UffizziCore::Deployment.find(deployment_id)
config_file = UffizziCore::ConfigFile.find(config_file_id)
if deployment.disabled?
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled stop config file applying")
return
end
unless UffizziCore::ControllerService.namespace_exists?(deployment)
raise UffizziCore::DeploymentNotFoundError,
deployment_id
end
UffizziCore::ControllerService.apply_config_file(deployment, config_file)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/create_credential_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::CreateCredentialJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: Settings.controller.resource_create_retry_count
sidekiq_retry_in do |count, exception|
case exception
when UffizziCore::DeploymentNotFoundError
Rails.logger.info("DEPLOYMENT_PROCESS CreateCredentialJob retry deployment_id=#{exception.deployment_id} count=#{count}")
Settings.controller.resource_create_retry_time
else
if [Settings.default_job_retry_count, Settings.controller.resource_create_retry_count].include?(count)
Sentry.capture_exception(exception)
:kill
else
Settings.controller.resource_create_retry_time
end
end
end
def perform(deployment_id, credential_id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment_id} CreateCredentialJob(cred_id:#{credential_id})")
deployment = UffizziCore::Deployment.find(deployment_id)
credential = UffizziCore::Credential.find(credential_id)
if deployment.disabled?
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled stop creating credential")
return
end
unless UffizziCore::ControllerService.namespace_exists?(deployment)
raise UffizziCore::DeploymentNotFoundError,
deployment_id
end
UffizziCore::ControllerService.apply_credential(deployment, credential)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/create_credentials_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::CreateCredentialsJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: 5
def perform(deployment_id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment_id} CreateCredentialsJob")
deployment = UffizziCore::Deployment.find(deployment_id)
credentials = deployment.project.account.credentials.deployable
credentials.each do |credential|
UffizziCore::Deployment::CreateCredentialJob.perform_async(deployment.id, credential.id)
end
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/create_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::CreateJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: 5
def perform(id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{id} CreateJob")
deployment = UffizziCore::Deployment.find(id)
UffizziCore::ControllerService.create_namespace(deployment)
UffizziCore::Deployment::CreateCredentialsJob.perform_async(deployment.id)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/delete_credential_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::DeleteCredentialJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: 5
def perform(deployment_id, credential_id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment_id} DeleteCredentialJob(cred_id:#{credential_id})")
deployment = UffizziCore::Deployment.find(deployment_id)
credential = UffizziCore::Credential.find(credential_id)
UffizziCore::ControllerService.delete_credential(deployment, credential)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/delete_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::DeleteJob < UffizziCore::ApplicationJob
sidekiq_options queue: :disable_deployments,
lock: :until_executed,
retry: Settings.default_job_retry_count
def perform(id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{id} DeleteJob")
deployment = UffizziCore::Deployment.find(id)
UffizziCore::ControllerService.delete_namespace(deployment)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/deploy_containers_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::DeployContainersJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: Settings.controller.resource_create_retry_count
sidekiq_retry_in do |count, exception|
case exception
when UffizziCore::DeploymentNotFoundError
Rails.logger.info("DEPLOYMENT_PROCESS DeployContainersJob retry deployment_id=#{exception.deployment_id} count=#{count}")
Settings.controller.resource_create_retry_time
else
if [Settings.default_job_retry_count, Settings.controller.resource_create_retry_count].include?(count)
Sentry.capture_exception(exception)
:kill
else
Settings.controller.resource_create_retry_time
end
end
end
def perform(id, repeated = false)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{id} DeployContainersJob")
deployment = UffizziCore::Deployment.find(id)
if deployment.disabled?
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled stop deploying")
return
end
raise UffizziCore::DeploymentNotFoundError, id unless UffizziCore::ControllerService.namespace_exists?(deployment)
UffizziCore::DeploymentService.deploy_containers(deployment, repeated)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/manage_deploy_activity_item_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::ManageDeployActivityItemJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: Settings.default_job_retry_count
sidekiq_retry_in do |count, exception|
case exception
when UffizziCore::Deployment::ImagePullError
Rails.logger.info("DEPLOYMENT_PROCESS ManageDeployActivityItemJob retry deployment_id=#{exception.deployment_id} count=#{count}")
Settings.controller.resource_create_retry_time
when ActiveRecord::RecordNotFound
:kill if exception.model.constantize == UffizziCore::ActivityItem
else
if count == Settings.default_job_retry_count
Sentry.capture_exception(exception)
:kill
else
Settings.controller.resource_create_retry_time
end
end
end
sidekiq_retries_exhausted do |msg, exception|
case exception
when UffizziCore::Deployment::ImagePullError
Rails.logger.info("DEPLOYMENT_PROCESS ManageDeployActivityItemJob exhausted #{msg.inspect} #{exception.inspect}")
activity_item_id = msg['args'].first
activity_item = UffizziCore::ActivityItem.find(activity_item_id)
UffizziCore::ActivityItemService.fail_deployment!(activity_item)
end
end
def perform(activity_item_id)
activity_item = UffizziCore::ActivityItem.find(activity_item_id)
container = activity_item.container
if container.disabled?
logger_message = "DEPLOYMENT_PROCESS deployment_id=#{container.deployment_id} activity_item_id=#{activity_item.id}
deployment was disabled stop monitoring"
Rails.logger.info(logger_message)
return
end
UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)
end
end
================================================
FILE: core/app/jobs/uffizzi_core/deployment/update_credential_job.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::UpdateCredentialJob < UffizziCore::ApplicationJob
sidekiq_options queue: :deployments, retry: Settings.controller.resource_update_retry_count
sidekiq_retry_in do |count, exception|
case exception
when UffizziCore::DeploymentNotFoundError
Rails.logger.info("DEPLOYMENT_PROCESS UpdateCredentialJob retry deployment_id=#{exception.deployment_id} count=#{count}")
Settings.controller.resource_update_retry_time
else
if [Settings.default_job_retry_count, Settings.controller.resource_update_retry_count].include?(count)
Sentry.capture_exception(exception)
:kill
else
Settings.controller.resource_create_retry_time
end
end
end
def perform(deployment_id, credential_id)
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment_id} UpdateCredentialJob(cred_id:#{credential_id})")
deployment = UffizziCore::Deployment.find(deployment_id)
credential = UffizziCore::Credential.find(credential_id)
if deployment.disabled?
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled. Stop updating credential")
return
end
unless UffizziCore::ControllerService.namespace_exists?(deployment)
raise UffizziCore::DeploymentNotFoundError,
deployment_id
end
UffizziCore::ControllerService.apply_credential(deployment, credential)
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/account.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Account
extend ActiveSupport::Concern
# rubocop:disable Metrics/BlockLength
included do
include AASM
include UffizziCore::AccountRepo
include UffizziCore::StateMachineConcern
extend Enumerize
self.table_name = UffizziCore.table_names[:accounts]
enumerize :kind, in: [:personal, :organizational], scope: true, predicates: true
validates :kind, presence: true
validates :domain, uniqueness: true, if: :domain
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :credentials, dependent: :destroy
has_many :projects, dependent: :destroy
has_many :deployments, through: :projects
has_many :payments, dependent: :destroy
has_many :clusters, through: :projects
aasm(:sso_state) do
state :connection_not_configured, initial: true
state :connection_disabled
state :connection_active
event :activate_connection do
transitions from: [:connection_not_configured, :connection_disabled], to: :connection_active
end
event :deactivate_connection do
transitions from: [:connection_active], to: :connection_disabled
end
event :reset_connection do
transitions from: [:connection_active, :connection_disabled], to: :connection_not_configured
end
end
def update_payment_issue_date
update(payment_issue_at: DateTime.current)
end
def active_projects
projects.active
end
def disable_projects
active_projects.each(&:disable_deployments)
end
# This method is deprecated. Don't use it.
def user
users.find_by(memberships: { role: UffizziCore::Membership.role.admin })
end
end
# rubocop:enable Metrics/BlockLength
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/activity_item.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::ActivityItem
extend ActiveSupport::Concern
included do
include UffizziCore::ActivityItemRepo
self.table_name = UffizziCore.table_names[:activity_items]
belongs_to :deployment
belongs_to :container
belongs_to :build, optional: true
has_many :events, dependent: :destroy
def docker?
type == UffizziCore::ActivityItem::Docker.name
end
def image
[namespace, name].compact.join('/')
end
def full_image
return "#{image}:#{tag}" if docker?
''
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/cluster.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Cluster
extend ActiveSupport::Concern
include UffizziCore::ClusterRepo
NAMESPACE_PREFIX = 'c'
# rubocop:disable Metrics/BlockLength
included do
include AASM
extend Enumerize
self.table_name = UffizziCore.table_names[:clusters]
belongs_to :project, class_name: UffizziCore::Project.name
belongs_to :deployed_by, class_name: UffizziCore::User.name, foreign_key: :deployed_by_id, optional: true
validates_uniqueness_of :name, conditions: -> { enabled }, scope: :project_id
validates :name, presence: true, format: { with: /\A[a-zA-Z0-9-]*\z/ }
enumerize :creation_source, in: UffizziCore.cluster_creation_sources, scope: true, predicates: true
attribute :creation_source, :string, default: :manual
validates :creation_source, presence: true
belongs_to :kubernetes_distribution, optional: true
aasm(:state) do
state :deploying_namespace, initial: true
state :failed_deploy_namespace
state :deploying
state :deployed
state :scaling_down
state :scaled_down
state :scaling_up
state :failed_scale_up
state :failed
state :disabled
event :start_deploying do
transitions from: [:deploying_namespace], to: :deploying
end
event :fail_deploy_namespace do
transitions from: [:deploying_namespace], to: :failed_deploy_namespace
end
event :finish_deploy do
transitions from: [:deploying], to: :deployed
end
event :start_scaling_down do
transitions from: [:deployed], to: :scaling_down
end
event :scale_down do
transitions from: [:scaling_down], to: :scaled_down
end
event :start_scaling_up do
transitions from: [:scaled_down, :failed_scale_up], to: :scaling_up
end
event :scale_up do
transitions from: [:scaling_up], to: :deployed
end
event :fail_scale_up do
transitions from: [:scaling_up], to: :failed_scale_up
end
event :fail do
transitions from: [:deploying], to: :failed
end
event :disable, after: :after_disable do
transitions from: [
:deploying_namespace,
:failed_deploy_namespace,
:deploying,
:deployed,
:scaling_down,
:scaling_up,
:scaled_down,
:failed,
], to: :disabled
end
end
def after_disable
UffizziCore::Cluster::DeleteJob.perform_async(id)
end
def namespace
[NAMESPACE_PREFIX, id].join
end
end
# rubocop:enable Metrics/BlockLength
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/comment.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Comment
extend ActiveSupport::Concern
included do
include UffizziCore::CommentRepo
self.table_name = UffizziCore.table_names[:comments]
has_ancestry(cache_depth: true)
const_set(:MAX_DEPTH_LEVEL, 1)
belongs_to :user
belongs_to :commentable, polymorphic: true
validates :content, length: { maximum: 1500 }, presence: true
validates :ancestry_depth, numericality: { less_than_or_equal_to: self::MAX_DEPTH_LEVEL, only_integer: true }
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/compose_file.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::ComposeFile
extend ActiveSupport::Concern
LOCAL_SOURCE = :local
included do
include UffizziCore::ComposeFileRepo
include AASM
extend Enumerize
self.table_name = UffizziCore.table_names[:compose_files]
belongs_to :project
belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id, optional: true
has_one :template, dependent: :destroy
has_many :config_files, dependent: :destroy
has_many :host_volume_files, dependent: :destroy
has_many :deployments, dependent: :nullify
enumerize :kind, in: UffizziCore.compose_file_kinds, predicates: true, scope: :shallow, default: :main
validates :source, presence: true
validate :main_compose_file_uniqueness, on: :create, if: -> { kind.main? }
aasm(:auto_deploy) do
state :disabled, initial: true
state :enabled
event :enable do
transitions from: [:disabled], to: :enabled
end
event :disable do
transitions from: [:enabled], to: :disabled
end
end
aasm(:state) do
state :valid_file, initial: true
state :invalid_file
event :set_valid do
transitions from: [:invalid_file], to: :valid_file
end
event :set_invalid do
transitions from: [:valid_file], to: :invalid_file
end
end
def local_source?
repository_id.nil? && branch.nil?
end
def source_kind
return LOCAL_SOURCE if local_source?
end
private
def main_compose_file_uniqueness
return unless project.compose_files.main.exists?
errors.add(:compose_file, 'A compose file already exist for this project')
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/config_file.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::ConfigFile
extend ActiveSupport::Concern
included do
include UffizziCore::ConfigFileRepo
extend Enumerize
self.table_name = UffizziCore.table_names[:config_files]
belongs_to :project
belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id, optional: true
belongs_to :compose_file, optional: true
has_many :container_config_files, dependent: :destroy
enumerize :kind, in: [:config_map, :secret], default: :config_map, predicates: true
enumerize :creation_source, in: [:manual, :compose_file, :system], predicates: true, scope: true
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/container.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Container
extend ActiveSupport::Concern
REQUEST_MEMORY_RATIO = 4
# rubocop:disable Metrics/BlockLength
included do
include UffizziCore::ContainerRepo
include AASM
include UffizziCore::StateMachineConcern
extend Enumerize
self.table_name = UffizziCore.table_names[:containers]
enumerize :kind, in: [:internal, :user], predicates: true
belongs_to :deployment, touch: true
belongs_to :repo, optional: true
has_many :activity_items, dependent: :destroy
has_many :container_config_files, dependent: :destroy
has_many :config_files, through: :container_config_files
has_many :container_host_volume_files, dependent: :destroy
has_many :host_volume_files, through: :container_host_volume_files
attribute :public, :boolean, default: false
attribute :port, :integer, default: nil
enumerize :source, in: [:github], skip_validations: true
validates :port,
presence: true,
inclusion: { in: 0..65_535 },
uniqueness: { scope: :deployment_id },
if: :should_check_port
after_commit :check_target_port, on: :create
before_save :update_target_port_value, if: :will_save_change_to_port?
before_create :set_defaults
accepts_nested_attributes_for :repo
accepts_nested_attributes_for :container_config_files, allow_destroy: true
accepts_nested_attributes_for :container_host_volume_files, allow_destroy: true
validates :variables, 'uffizzi_core/environment_variable_list': true, allow_nil: true
validates :secret_variables, 'uffizzi_core/environment_variable_list': true, allow_nil: true
validates :entrypoint, 'uffizzi_core/image_command_args': true, allow_nil: true
validates :command, 'uffizzi_core/image_command_args': true, allow_nil: true
validates :tag, presence: true
aasm :continuously_deploy, column: :continuously_deploy, namespace: :cd do
state :disabled, initial: true
state :enabled
end
aasm :state, column: :state do
state :active, initial: true
state :disabled
event :activate do
transitions from: [:disabled], to: :active
end
event :disable do
transitions from: [:active], to: :disabled
end
end
def image_name
"#{image}:#{tag}"
end
private
def should_check_port
public && active?
end
def set_defaults
update_target_port_value
end
def update_target_port_value
self.target_port = UffizziCore::ContainerService.target_port_value(self)
end
def check_target_port
if target_port && deployment.containers.where(target_port: target_port).size > 1
update(target_port: UffizziCore::DeploymentService.find_unused_port(deployment))
end
end
end
# rubocop:enable Metrics/BlockLength
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/container_config_file.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::ContainerConfigFile
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:container_config_files]
belongs_to :container
belongs_to :config_file
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/container_host_volume_file.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::ContainerHostVolumeFile
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:container_host_volume_files]
belongs_to :container
belongs_to :host_volume_file
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/coupon.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Coupon
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:coupons]
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/credential.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Credential
extend ActiveSupport::Concern
# rubocop:disable Metrics/BlockLength
included do
include AASM
include UffizziCore::CredentialRepo
extend Enumerize
self.table_name = UffizziCore.table_names[:credentials]
const_set(:CREDENTIAL_TYPES, [
'UffizziCore::Credential::Amazon',
'UffizziCore::Credential::Azure',
'UffizziCore::Credential::DockerHub',
'UffizziCore::Credential::DockerRegistry',
'UffizziCore::Credential::GithubContainerRegistry',
'UffizziCore::Credential::Google',
])
belongs_to :account
before_destroy :remove_token
validates :registry_url, presence: true
aasm :state, column: :state do
state :not_connected, initial: true
state :active
state :unauthorized
event :activate do
transitions from: [:not_connected, :unauthorized], to: :active
end
event :unauthorize do
transitions from: [:not_connected, :active], to: :unauthorized
end
event :disconnect do
transitions from: [:active, :unauthorized], to: :not_connected
end
end
UffizziCore::ContainerRegistryService.sources.each do |t|
define_method :"#{t}?" do
type == "UffizziCore::Credential::#{t.to_s.camelize}"
end
end
def correct?
credential = self
return false unless credential
container_registry_service = UffizziCore::ContainerRegistryService.init_by_subclass(credential.type)
status = container_registry_service.credential_correct?(credential)
if credential.persisted? && credential.active? && !status
Rails.logger.warn("Wrong credential: credential_correct? credential_id=#{credential.id}")
end
status
end
private
def remove_token
account.projects.find_each do |project|
project.deployments.find_each do |deployment|
containers = deployment.containers
attributes = { continuously_deploy: UffizziCore::Container::STATE_CD_DISABLED }
containers.with_docker_hub_repo.update_all(attributes) if docker_hub?
end
end
end
end
# rubocop:enable Metrics/BlockLength
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/deployment.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Deployment
extend ActiveSupport::Concern
# rubocop:disable Metrics/BlockLength
included do
include AASM
include UffizziCore::StateMachineConcern
include UffizziCore::DeploymentRepo
extend Enumerize
include UffizziCore::DependencyInjectionConcern
self.table_name = UffizziCore.table_names[:deployments]
enumerize :kind, in: [:standard], predicates: true, default: :standard
belongs_to :project, touch: true
belongs_to :deployed_by, class_name: UffizziCore::User.name, foreign_key: :deployed_by_id, optional: true
belongs_to :template, optional: true
belongs_to :compose_file, optional: true
has_many :credentials, through: :project
has_many :containers, dependent: :destroy, index_errors: true
has_many :activity_items, dependent: :destroy
has_many :deployment_events, dependent: :destroy
has_one :ingress_container, -> { where(receive_incoming_requests: true) }, class_name: UffizziCore::Container.name
validates :kind, presence: true
enumerize :creation_source, in: [:manual, :demo, :continuous_preview, :compose_file_manual, :compose_file_continuous_preview,
:github_actions, :gitlab_actions], predicates: true, scope: true, default: :manual
accepts_nested_attributes_for :containers, allow_destroy: true
after_destroy_commit :clean
def active_containers
containers.active
end
aasm(:state) do
state :active, initial: true
state :failed
state :disabled
event :activate, after: :after_activate do
transitions from: [:disabled, :failed], to: :active
end
event :fail do
transitions from: [:active], to: :failed
end
event :disable, after: :after_disable do
transitions from: [:active, :failed], to: :disabled
end
end
def after_activate
update!(disabled_at: nil)
end
def after_disable
clean
update!(disabled_at: Time.now)
end
def clean
active_containers.each(&:disable!)
UffizziCore::Deployment::DeleteJob.perform_async(id)
end
def preview_url
managed_dns_zone = controller_settings_service.deployment_settings_by_deployment(self).managed_dns_zone
"#{subdomain}.#{managed_dns_zone}"
end
def namespace
"deployment-#{id}"
end
end
# rubocop:enable Metrics/BlockLength
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/deployment_event.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::DeploymentEvent
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:deployment_events]
belongs_to :deployment
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/event.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Event
extend ActiveSupport::Concern
included do
include UffizziCore::EventRepo
extend Enumerize
self.table_name = UffizziCore.table_names[:events]
enumerize :state, in: UffizziCore.event_states, predicates: true, scope: true
belongs_to :activity_item, touch: true
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/host_volume_file.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::HostVolumeFile
extend ActiveSupport::Concern
included do
include UffizziCore::HostVolumeFileRepo
self.table_name = UffizziCore.table_names[:host_volume_files]
belongs_to :project
belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id, optional: true
belongs_to :compose_file, optional: true
has_many :container_host_volume_files, dependent: :destroy
validates :source, presence: true
validates :path, presence: true
validates :payload, presence: true
validates :is_file, inclusion: [true, false]
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/kubernetes_distribution.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::KubernetesDistribution
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:kubernetes_distributions]
has_many :clusters
def self.default
find_by(default: true)
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/membership.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Membership
extend ActiveSupport::Concern
included do
include AASM
include UffizziCore::StateMachineConcern
include UffizziCore::MembershipRepo
extend Enumerize
self.table_name = UffizziCore.table_names[:memberships]
enumerize :role, in: [:admin, :developer, :viewer], predicates: true
validates :role, presence: true
belongs_to :account
belongs_to :user
validates :role, presence: true
aasm(:state) do
state :active, initial: true
state :blocked
state :inactive
event :activate do
transitions from: [:blocked, :inactive], to: :active
end
event :deactivate do
transitions from: [:active], to: :inactive
end
event :block do
transitions from: [:active], to: :blocked
end
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/payment.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Payment
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:payments]
belongs_to :account
scope :succeeded, -> { where(status: :succeeded) }
scope :pending, -> { where(status: :pending) }
scope :failed, -> { where(status: :failed) }
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/price.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Price
extend ActiveSupport::Concern
included do
include UffizziCore::PriceRepo
self.table_name = UffizziCore.table_names[:prices]
belongs_to :product
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/product.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Product
extend ActiveSupport::Concern
included do
include UffizziCore::ProductRepo
self.table_name = UffizziCore.table_names[:products]
has_one :price, dependent: :destroy
UffizziCore::Product.inheritance_column = :sti
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/project.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Project
extend ActiveSupport::Concern
# rubocop:disable Metrics/BlockLength
included do
include AASM
include UffizziCore::StateMachineConcern
include UffizziCore::ProjectRepo
self.table_name = UffizziCore.table_names[:projects]
belongs_to :account
has_many :repos, dependent: :destroy
has_many :deployments, dependent: :destroy
has_many :user_projects, dependent: :destroy
has_many :users, through: :user_projects
has_many :config_files, dependent: :destroy
has_many :templates, dependent: :destroy
has_many :credentials, through: :account
has_many :compose_files, dependent: :destroy
has_many :secrets, dependent: :destroy, as: :resource
has_many :host_volume_files, dependent: :destroy
has_many :clusters, dependent: :destroy
validates :name, presence: true
validates_uniqueness_of :name, conditions: -> { where(state: :active) }, scope: :account_id
validates :slug, presence: true, uniqueness: true
aasm(:state) do
state :active, initial: true
state :disabled
event :activate do
transitions from: [:disabled], to: :active
end
event :disable, after: :after_disable do
transitions from: [:active], to: :disabled
end
end
def after_disable
disable_deployments
disable_clusters
end
def active_deployments
deployments.enabled
end
def active_clusters
clusters.enabled
end
def disable_deployments
active_deployments.each do |deployment|
UffizziCore::DeploymentService.disable!(deployment)
deployment.deployment_events.create!(deployment_state: deployment.state, message: 'Disabled after project was disabled')
end
end
def disable_clusters
active_clusters.each(&:disable!)
end
def compose_file
compose_files.main.first
end
end
# rubocop:enable Metrics/BlockLength
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/rating.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Rating
extend ActiveSupport::Concern
included do
include AASM
self.table_name = UffizziCore.table_names[:ratings]
aasm(:state) do
state :active, initial: true
state :disabled
event :activate do
transitions from: [:disabled], to: :active
end
event :disable do
transitions from: [:active], to: :disabled
end
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Repo
extend ActiveSupport::Concern
included do
extend Enumerize
include UffizziCore::RepoRepo
self.table_name = UffizziCore.table_names[:repos]
enumerize :kind, in: [:buildpacks18, :dockerfile, :dotnet, :gatsby, :barestatic], predicates: true
belongs_to :project
has_one :container, inverse_of: :repo, dependent: :destroy
validates :dockerfile_path, presence: true, if: :dockerfile?
validates :delete_preview_after, numericality: { greater_than: 0, only_integer: true }, allow_nil: true
def docker_hub?
type == UffizziCore::Repo::DockerHub.name
end
def azure?
type == UffizziCore::Repo::Azure.name
end
def google?
type == UffizziCore::Repo::Google.name
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/role.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Role
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:roles]
has_and_belongs_to_many :users, join_table: UffizziCore.table_names[:users_roles]
belongs_to :resource,
polymorphic: true,
optional: true
validates :resource_type,
inclusion: { in: Rolify.resource_types },
allow_nil: true
scopify
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/secret.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Secret
extend ActiveSupport::Concern
included do
self.table_name = UffizziCore.table_names[:secrets]
belongs_to :resource, polymorphic: true
validates :name, presence: true, uniqueness: { scope: :resource }
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/template.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::Template
extend ActiveSupport::Concern
included do
include UffizziCore::TemplateRepo
extend Enumerize
self.table_name = UffizziCore.table_names[:templates]
belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id
belongs_to :project, touch: true
belongs_to :compose_file, optional: true
has_many :deployments, dependent: :nullify
enumerize :creation_source, in: [:manual, :compose_file, :system], predicates: true, scope: true
validates :name, presence: true
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/user.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::User
extend ActiveSupport::Concern
included do
include AASM
include ActiveModel::Validations
include UffizziCore::StateMachineConcern
include UffizziCore::HashidConcern
include UffizziCore::UserRepo
extend Enumerize
self.table_name = UffizziCore.table_names[:users]
rolify({ role_cname: UffizziCore::Role.name, role_join_table_name: UffizziCore.table_names[:users_roles] })
has_many :memberships, dependent: :destroy
has_many :accounts, through: :memberships
has_many :user_projects, dependent: :destroy
has_many :projects, through: :user_projects
has_many :deployments, class_name: UffizziCore::Deployment.name, foreign_key: :deployed_by_id, dependent: :nullify
has_many :clusters, foreign_key: :deployed_by_id
has_one_attached :avatar
enumerize :creation_source, in: UffizziCore.user_creation_sources, predicates: true
def default_account
personal_account
end
def personal_account
accounts.personal.find_by(owner_id: id)
end
def full_name
"#{first_name} #{last_name}"
end
aasm(:state) do
state :initial, initial: true
state :active
state :disabled
event :activate do
transitions from: [:initial, :disabled], to: :active
end
event :disable do
transitions from: [:initial, :active], to: :disabled
end
end
def admin_access_to_project?(project)
project.user_projects.where(user_id: id, role: UffizziCore::UserProject.role.admin).exists?
end
end
end
================================================
FILE: core/app/lib/uffizzi_core/concerns/models/user_project.rb
================================================
# frozen_string_literal: true
module UffizziCore::Concerns::Models::UserProject
extend ActiveSupport::Concern
included do
extend Enumerize
self.table_name = UffizziCore.table_names[:user_projects]
enumerize :role, in: UffizziCore.user_project_roles, predicates: true, scope: true
validates :role, presence: true
belongs_to :user
belongs_to :project
belongs_to :invited_by, class_name: UffizziCore::User.name, foreign_key: :invited_by_id, optional: true
end
end
================================================
FILE: core/app/lib/uffizzi_core/rbac/user_access_service.rb
================================================
# frozen_string_literal: true
module UffizziCore::Rbac::UserAccessService
class << self
def admin_access_to_account?(_user, _account)
true
end
def any_access_to_account?(_user, _account)
true
end
def admin_or_developer_access_to_account?(_user, _account)
true
end
def admin_or_developer_access_to_project?(_user, _project)
true
end
def any_access_to_project?(_user, _project)
true
end
def admin_access_to_project?(_user, _project)
true
end
end
end
================================================
FILE: core/app/mailers/uffizzi_core/application_mailer.rb
================================================
# frozen_string_literal: true
module UffizziCore
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
end
end
================================================
FILE: core/app/models/concerns/uffizzi_core/hashid_concern.rb
================================================
# frozen_string_literal: true
module UffizziCore::HashidConcern
extend ActiveSupport::Concern
class_methods do
def hashid_service
@hashid_service ||= Hashids.new("#{Rails.application.secrets.secret_key_base}-#{name}")
end
def find_by_hashid(hashid)
id = hashid_service.decode(hashid.to_s).first
find_by(id: id)
end
def find_by_hashid!(hashid)
id = hashid_service.decode(hashid.to_s).first
find(id)
end
end
def hashid
self.class.hashid_service.encode(id)
end
end
================================================
FILE: core/app/models/concerns/uffizzi_core/state_machine_concern.rb
================================================
# frozen_string_literal: true
module UffizziCore::StateMachineConcern
extend ActiveSupport::Concern
class_methods do
def aasm(attribute, *args)
attr_accessor(:"#{attribute}_event")
define_method("#{attribute}_event=") do |value|
send(value)
end
super(attribute, *args)
end
end
end
================================================
FILE: core/app/models/uffizzi_core/account.rb
================================================
# frozen_string_literal: true
class UffizziCore::Account < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Account
belongs_to :owner, class_name: UffizziCore::User.name, foreign_key: :owner_id
aasm(:state) do
state :active, initial: true
state :payment_issue
state :disabled
state :draft
event :activate do
transitions from: [:payment_issue, :disabled, :draft], to: :active
end
event :raise_payment_issue, before_success: :update_payment_issue_date do
transitions from: [:active, :disabled], to: :payment_issue
end
event :disable, after: :disable_projects do
transitions from: [:active, :payment_issue], to: :disabled
end
end
end
================================================
FILE: core/app/models/uffizzi_core/activity_item/docker.rb
================================================
# frozen_string_literal: true
class UffizziCore::ActivityItem::Docker < UffizziCore::ActivityItem
end
================================================
FILE: core/app/models/uffizzi_core/activity_item/github.rb
================================================
# frozen_string_literal: true
class UffizziCore::ActivityItem::Github < UffizziCore::ActivityItem
end
================================================
FILE: core/app/models/uffizzi_core/activity_item/memory_limit.rb
================================================
# frozen_string_literal: true
class UffizziCore::ActivityItem::MemoryLimit < UffizziCore::ActivityItem
end
================================================
FILE: core/app/models/uffizzi_core/activity_item.rb
================================================
# frozen_string_literal: true
# @model
#
# @property id [integer]
# @property namespace [string]
# @property name(required) [string]
# @property tag [string]
# @property branch [string]
# @property type [string]
# @property container_id [string]
# @property commit [string]
# @property commit_message [string]
# @property build_id [integer]
# @property created_at [date]
# @property updated_at [date]
# @property data [object]
# @property digest [string]
# @property meta [object]
class UffizziCore::ActivityItem < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::ActivityItem
end
================================================
FILE: core/app/models/uffizzi_core/application_record.rb
================================================
# frozen_string_literal: true
module UffizziCore
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
end
================================================
FILE: core/app/models/uffizzi_core/cluster.rb
================================================
# frozen_string_literal: true
class UffizziCore::Cluster < ApplicationRecord
include UffizziCore::Concerns::Models::Cluster
end
================================================
FILE: core/app/models/uffizzi_core/comment.rb
================================================
# frozen_string_literal: true
class UffizziCore::Comment < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Comment
end
================================================
FILE: core/app/models/uffizzi_core/compose_file.rb
================================================
# frozen_string_literal: true
# @model ComposeFile
# @property id [integer]
# @property source [string]
# @property path [string]
# @property content(required) [string]
# @property auto_deploy [boolean]
# @property state [string]
# @property payload [string]
class UffizziCore::ComposeFile < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::ComposeFile
end
================================================
FILE: core/app/models/uffizzi_core/config_file.rb
================================================
# frozen_string_literal: true
# @model
#
# @property filename [string]
# @property kind [string]
# @property payload [string]
# @property source [string]
class UffizziCore::ConfigFile < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::ConfigFile
end
================================================
FILE: core/app/models/uffizzi_core/container.rb
================================================
# frozen_string_literal: true
# @model Container
#
# @property id [integer]
# @property name [string]
# @property memory_limit [integer]
# @property memory_request [integer]
# @property continuously_deploy [string]
# @property variables [object]
# @property secret_variables [object]
# @property container_config_files [ConfigFile]
class UffizziCore::Container < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Container
end
================================================
FILE: core/app/models/uffizzi_core/container_config_file.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerConfigFile < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::ContainerConfigFile
end
================================================
FILE: core/app/models/uffizzi_core/container_host_volume_file.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerHostVolumeFile < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::ContainerHostVolumeFile
end
================================================
FILE: core/app/models/uffizzi_core/continuous_preview.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContinuousPreview < UffizziCore::ApplicationRecord
end
================================================
FILE: core/app/models/uffizzi_core/coupon.rb
================================================
# frozen_string_literal: true
class UffizziCore::Coupon < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Coupon
end
================================================
FILE: core/app/models/uffizzi_core/credential/amazon.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential::Amazon < UffizziCore::Credential
end
================================================
FILE: core/app/models/uffizzi_core/credential/azure.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential::Azure < UffizziCore::Credential
end
================================================
FILE: core/app/models/uffizzi_core/credential/docker_hub.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential::DockerHub < UffizziCore::Credential
end
================================================
FILE: core/app/models/uffizzi_core/credential/docker_registry.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential::DockerRegistry < UffizziCore::Credential
end
================================================
FILE: core/app/models/uffizzi_core/credential/github_container_registry.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential::GithubContainerRegistry < UffizziCore::Credential
end
================================================
FILE: core/app/models/uffizzi_core/credential/google.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential::Google < UffizziCore::Credential
end
================================================
FILE: core/app/models/uffizzi_core/credential.rb
================================================
# frozen_string_literal: true
class UffizziCore::Credential < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Credential
enumerize :type,
in: self::CREDENTIAL_TYPES, i18n_scope: ['enumerize.credential.type']
end
================================================
FILE: core/app/models/uffizzi_core/database.rb
================================================
# frozen_string_literal: true
class UffizziCore::Database < UffizziCore::ApplicationRecord
end
================================================
FILE: core/app/models/uffizzi_core/database_offering.rb
================================================
# frozen_string_literal: true
class UffizziCore::DatabaseOffering < UffizziCore::ApplicationRecord
end
================================================
FILE: core/app/models/uffizzi_core/deployment.rb
================================================
# frozen_string_literal: true
# @model Deployment
#
# @property id [integer]
# @property project_id [integer]
# @property kind [string]
# @property state [string]
# @property preview_url [string]
# @property tag [string]
# @property branch [string]
# @property commit [string]
# @property image_id [string]
# @property created_at [date-time]
# @property updated_at [date-time]
# @property ingress_container_ready [boolean]
# @property ingress_container_state [string]
# @property creation_source [string]
# @property contaners [array]
# @property deployed_by [object]
class UffizziCore::Deployment < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Deployment
end
================================================
FILE: core/app/models/uffizzi_core/deployment_event.rb
================================================
# frozen_string_literal: true
class UffizziCore::DeploymentEvent < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::DeploymentEvent
end
================================================
FILE: core/app/models/uffizzi_core/event.rb
================================================
# frozen_string_literal: true
class UffizziCore::Event < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Event
end
================================================
FILE: core/app/models/uffizzi_core/host_volume_file.rb
================================================
# frozen_string_literal: true
class UffizziCore::HostVolumeFile < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::HostVolumeFile
end
================================================
FILE: core/app/models/uffizzi_core/kubernetes_distribution.rb
================================================
# frozen_string_literal: true
class UffizziCore::KubernetesDistribution < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::KubernetesDistribution
end
================================================
FILE: core/app/models/uffizzi_core/membership.rb
================================================
# frozen_string_literal: true
class UffizziCore::Membership < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Membership
end
================================================
FILE: core/app/models/uffizzi_core/payment.rb
================================================
# frozen_string_literal: true
class UffizziCore::Payment < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Payment
end
================================================
FILE: core/app/models/uffizzi_core/price.rb
================================================
# frozen_string_literal: true
class UffizziCore::Price < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Price
end
================================================
FILE: core/app/models/uffizzi_core/product.rb
================================================
# frozen_string_literal: true
class UffizziCore::Product < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Product
end
================================================
FILE: core/app/models/uffizzi_core/project.rb
================================================
# frozen_string_literal: true
# @model Project
# @property slug [string]
# @property name [string]
# @property description [string]
# @property created_at [date-time]
# @property secrets [string]
# @property default_compose [object]
# @property deployments [object]
# @property account [object]
class UffizziCore::Project < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Project
end
================================================
FILE: core/app/models/uffizzi_core/rating.rb
================================================
# frozen_string_literal: true
class UffizziCore::Rating < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Rating
end
================================================
FILE: core/app/models/uffizzi_core/repo/amazon.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo::Amazon < UffizziCore::Repo
end
================================================
FILE: core/app/models/uffizzi_core/repo/azure.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo::Azure < UffizziCore::Repo
end
================================================
FILE: core/app/models/uffizzi_core/repo/docker_hub.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo::DockerHub < UffizziCore::Repo
end
================================================
FILE: core/app/models/uffizzi_core/repo/docker_registry.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo::DockerRegistry < UffizziCore::Repo
end
================================================
FILE: core/app/models/uffizzi_core/repo/github_container_registry.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo::GithubContainerRegistry < UffizziCore::Repo
end
================================================
FILE: core/app/models/uffizzi_core/repo/google.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo::Google < UffizziCore::Repo
end
================================================
FILE: core/app/models/uffizzi_core/repo.rb
================================================
# frozen_string_literal: true
class UffizziCore::Repo < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Repo
end
================================================
FILE: core/app/models/uffizzi_core/role.rb
================================================
# frozen_string_literal: true
class UffizziCore::Role < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Role
end
================================================
FILE: core/app/models/uffizzi_core/secret.rb
================================================
# frozen_string_literal: true
class UffizziCore::Secret < ApplicationRecord
include UffizziCore::Concerns::Models::Secret
end
================================================
FILE: core/app/models/uffizzi_core/template.rb
================================================
# frozen_string_literal: true
class UffizziCore::Template < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::Template
end
================================================
FILE: core/app/models/uffizzi_core/user.rb
================================================
# frozen_string_literal: true
class UffizziCore::User < ActiveRecord::Base
include UffizziCore::Concerns::Models::User
has_secure_password
validates :email, presence: true, 'uffizzi_core/email': true, uniqueness: { case_sensitive: false }
validates :password, allow_nil: true, length: { minimum: 8 }, on: :update
end
================================================
FILE: core/app/models/uffizzi_core/user_project.rb
================================================
# frozen_string_literal: true
class UffizziCore::UserProject < UffizziCore::ApplicationRecord
include UffizziCore::Concerns::Models::UserProject
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/accounts/clusters_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ClustersPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_account?(context.user, context.account)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/accounts/credentials_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::CredentialsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.admin_access_to_account?(context.user, context.account)
end
def create?
context.user_access_module.admin_access_to_account?(context.user, context.account)
end
def update?
context.user_access_module.admin_access_to_account?(context.user, context.account)
end
def check_credential?
context.user_access_module.admin_access_to_account?(context.user, context.account)
end
def destroy?
context.user_access_module.admin_access_to_account?(context.user, context.account)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/accounts/projects_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ProjectsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_account?(context.user, context.account)
end
def create?
context.user_access_module.admin_or_developer_access_to_account?(context.user, context.account)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/accounts_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::AccountsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user.present?
end
def show?
context.user_access_module.any_access_to_account?(context.user, context.account)
end
def update?
false
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/clusters/ingresses_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Clusters::IngressesPolicy < UffizziCore::ApplicationPolicy
def index?
return true if context.user_access_module.admin_access_to_project?(context.user, context.project)
context.cluster.deployed_by_id == context.user.id
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/clusters_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ClustersPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def show?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def create?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def scale_down?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def scale_up?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def sync?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def destroy?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/compose_files_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ComposeFilesPolicy < UffizziCore::ApplicationPolicy
def show?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def create?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def destroy?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/deployments/activity_items_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/deployments/containers_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ContainersPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def k8s_container_description?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/deployments/events_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::EventsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/deployments_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::DeploymentsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def show?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def create?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def update?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def destroy?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def deploy_containers?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects/secrets_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::SecretsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_project?(context.user, context.project)
end
def bulk_create?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
def destroy?
context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)
end
end
================================================
FILE: core/app/policies/uffizzi_core/api/cli/v1/projects_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ProjectsPolicy < UffizziCore::ApplicationPolicy
def index?
context.user_access_module.any_access_to_account?(context.user, context.account)
end
def show?
context.user_access_module.any_access_to_account?(context.user, context.account)
end
def destroy?
context.user_access_module.admin_or_developer_access_to_account?(context.user, context.account)
end
end
================================================
FILE: core/app/policies/uffizzi_core/application_policy.rb
================================================
# frozen_string_literal: true
class UffizziCore::ApplicationPolicy
attr_reader :context, :record
def initialize(context, record)
raise Pundit::NotAuthorizedError, 'must be logged in' if !context.instance_of?(UffizziCore::WebhooksContext) && context.user.blank?
@record = record
@context = context
end
end
================================================
FILE: core/app/repositories/uffizzi_core/account_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::AccountRepo
extend ActiveSupport::Concern
included do
scope :by_kind, ->(kind) { where(kind: kind) }
scope :personal, -> { by_kind(UffizziCore::Account.kind.personal) }
scope :organizational, -> { by_kind(UffizziCore::Account.kind.organizational) }
end
end
================================================
FILE: core/app/repositories/uffizzi_core/activity_item_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ActivityItemRepo
extend ActiveSupport::Concern
included do
include UffizziCore::BasicOrderRepo
scope :docker, -> {
where(type: UffizziCore::ActivityItem::Docker.name)
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/basic_order_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::BasicOrderRepo
extend ActiveSupport::Concern
included do
scope :order_by_id, ->(order = :asc) {
order(id: order)
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/cluster_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ClusterRepo
extend ActiveSupport::Concern
included do
scope :deployed, -> { where(state: UffizziCore::Cluster::STATE_DEPLOYED) }
scope :enabled, -> { where.not(state: UffizziCore::Cluster::STATE_DISABLED) }
scope :deployed_by_user, ->(user) { where(deployed_by: user) }
scope :by_projects, ->(projects) { where(project_id: projects.select(:id)) }
end
end
================================================
FILE: core/app/repositories/uffizzi_core/comment_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::CommentRepo
extend ActiveSupport::Concern
included do
scope :by_user, ->(user) {
where(user: user)
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/compose_file_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ComposeFileRepo
extend ActiveSupport::Concern
included do
end
end
================================================
FILE: core/app/repositories/uffizzi_core/config_file_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ConfigFileRepo
extend ActiveSupport::Concern
included do
scope :by_deployment, ->(deployment) {
select(:id)
.joins(:container_config_files)
.where(container_config_files: { container: deployment.containers })
.distinct
}
scope :by_source, ->(source) {
where(source: source)
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/container_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ContainerRepo
extend ActiveSupport::Concern
included do
include UffizziCore::BasicOrderRepo
scope :with_amazon_repo, -> { includes(:repo).where(repo: { type: UffizziCore::Repo::Amazon.name }) }
scope :with_docker_hub_repo, -> { includes(:repo).where(repo: { type: UffizziCore::Repo::DockerHub.name }) }
scope :with_public_access, -> {
where(public: true)
}
scope :by_repo_type, ->(type) {
includes(:repo).where(repo: { type: type })
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/credential_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::CredentialRepo
extend ActiveSupport::Concern
included do
scope :by_type, ->(type) { where(type: type) }
scope :docker_hub, -> { by_type(UffizziCore::Credential::DockerHub.name) }
scope :docker_registry, -> { by_type(UffizziCore::Credential::DockerRegistry.name) }
scope :azure, -> { by_type(UffizziCore::Credential::Azure.name) }
scope :google, -> { by_type(UffizziCore::Credential::Google.name) }
scope :amazon, -> { by_type(UffizziCore::Credential::Amazon.name) }
scope :github_container_registry, -> { by_type(UffizziCore::Credential::GithubContainerRegistry.name) }
scope :deployable, -> {
by_type([
UffizziCore::Credential::DockerHub.name,
UffizziCore::Credential::DockerRegistry.name,
UffizziCore::Credential::Azure.name,
UffizziCore::Credential::Google.name,
UffizziCore::Credential::Amazon.name,
UffizziCore::Credential::GithubContainerRegistry.name,
])
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/deployment_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::DeploymentRepo
extend ActiveSupport::Concern
included do
scope :with_name, ->(name) {
where(name: name)
}
scope :with_amazon_repos, -> { includes(containers: [:repo]).where(containers: { repos: { type: UffizziCore::Repo::Amazon.name } }) }
scope :enabled, -> { where(state: [:active, :failed]) }
scope :active_for_credential_id, ->(credential_id) {
active.joins(project: :credentials).merge(UffizziCore::Project.active).where(credentials: { id: credential_id })
}
scope :with_metadata, -> { where.not(metadata: {}) }
scope :with_labels, ->(labels) {
where("#{UffizziCore::Deployment.table_name}.metadata @> ?", labels.to_json)
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/event_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::EventRepo
extend ActiveSupport::Concern
included do
include UffizziCore::BasicOrderRepo
end
end
================================================
FILE: core/app/repositories/uffizzi_core/host_volume_file_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::HostVolumeFileRepo
extend ActiveSupport::Concern
included do
scope :by_deployment, ->(deployment) {
joins(:container_host_volume_files)
.where(container_host_volume_files: { container: deployment.containers })
.distinct
}
scope :by_source, ->(source) {
where(source: source)
}
end
end
================================================
FILE: core/app/repositories/uffizzi_core/membership_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::MembershipRepo
extend ActiveSupport::Concern
included do
scope :by_role_admin, -> { by_role(UffizziCore::Membership.role.admin) }
scope :by_account, ->(account) { where(account_id: account.id) }
end
end
================================================
FILE: core/app/repositories/uffizzi_core/price_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::PriceRepo
extend ActiveSupport::Concern
class_methods do
def container_memory_for(env)
find_by(slug: env.kind)
end
end
end
================================================
FILE: core/app/repositories/uffizzi_core/product_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ProductRepo
extend ActiveSupport::Concern
class_methods do
def container_memory
find_by(slug: :container_memory)
end
end
end
================================================
FILE: core/app/repositories/uffizzi_core/project_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::ProjectRepo
extend ActiveSupport::Concern
included do
scope :by_ids, ->(ids) { where(id: ids) }
scope :by_accounts, ->(account_ids) { where(account_id: account_ids) }
end
end
================================================
FILE: core/app/repositories/uffizzi_core/repo_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::RepoRepo
extend ActiveSupport::Concern
included do
scope :docker_hub, -> { where(type: UffizziCore::Repo::DockerHub.name) }
end
end
================================================
FILE: core/app/repositories/uffizzi_core/template_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::TemplateRepo
extend ActiveSupport::Concern
included do
end
end
================================================
FILE: core/app/repositories/uffizzi_core/usage_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::UsageRepo
extend ActiveSupport::Concern
included do
scope :by_timestamp, ->(direction = :asc) { order("timestamp #{direction}") }
end
end
================================================
FILE: core/app/repositories/uffizzi_core/user_repo.rb
================================================
# frozen_string_literal: true
module UffizziCore::UserRepo
extend ActiveSupport::Concern
included do
scope :by_email, ->(email) {
where('lower(email) = ?', email.downcase)
}
end
end
================================================
FILE: core/app/responders/uffizzi_core/json_responder.rb
================================================
# frozen_string_literal: true
class UffizziCore::JsonResponder < ActionController::Responder
def api_behavior(*args, &block)
if post?
display(resource, status: :created)
elsif put? || patch?
display(resource, status: :ok)
else
super
end
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/account_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::AccountSerializer < UffizziCore::BaseSerializer
include UffizziCore::DependencyInjectionConcern
include_module_if_exists('UffizziCore::Api::Cli::V1::AccountSerializerModule')
type :account
has_many :projects
attributes :id, :name, :api_url, :vclusters_controller_url
def api_url
Settings.domain
end
def vclusters_controller_url
controller_settings_service.vcluster_settings_by_account(object).url
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/accounts/cluster_serializer/project_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ClusterSerializer::ProjectSerializer < UffizziCore::BaseSerializer
type :project
attributes :name
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/accounts/cluster_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ClusterSerializer < UffizziCore::BaseSerializer
type :cluster
belongs_to :project
attributes :name
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/accounts/credential_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::CredentialSerializer < UffizziCore::BaseSerializer
attributes :id, :username, :password, :type, :state
def password
anonymize(object.password)
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/accounts/project_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Accounts::ProjectSerializer < UffizziCore::BaseSerializer
type :project
attributes :name,
:slug,
:account_id,
:description,
:created_at
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/project_serializer/account_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ProjectSerializer::AccountSerializer < UffizziCore::BaseSerializer
attributes :id, :kind, :state, :name
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/project_serializer/compose_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ProjectSerializer::ComposeFileSerializer < UffizziCore::BaseSerializer
type :compose_file
attributes :source
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/project_serializer/deployment_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ProjectSerializer::DeploymentSerializer < UffizziCore::BaseSerializer
type :deployment
attributes :id,
:preview_url,
:state
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/project_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ProjectSerializer < UffizziCore::BaseSerializer
type :project
has_many :deployments
has_many :secrets
has_one :default_compose
belongs_to :account
attributes :name,
:slug,
:description,
:created_at
def default_compose
object.compose_files.main.first
end
def deployments
object.deployments.active.map do |deployment|
deployment.state = if UffizziCore::DeploymentService.failed?(deployment)
UffizziCore::Deployment::STATE_FAILED
else
UffizziCore::Deployment::STATE_ACTIVE
end
deployment
end
end
def secrets
return [] unless object.secrets
object.secrets.map(&:name)
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/cluster_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ClusterSerializer < UffizziCore::BaseSerializer
type :cluster
attributes :id, :name, :state, :kubeconfig, :created_at, :host, :k8s_version
def k8s_version
object.kubernetes_distribution&.version || UffizziCore::KubernetesDistribution.default.version
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/compose_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ComposeFileSerializer < UffizziCore::BaseSerializer
type :compose_file
attributes :id, :source, :path, :auto_deploy, :state, :payload, :content
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer/container_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::DeploymentSerializer::ContainerSerializer < UffizziCore::BaseSerializer
type :deployment
attributes :id,
:kind,
:image,
:tag,
:variables,
:secret_variables,
:created_at,
:updated_at,
:memory_limit,
:memory_request,
:entrypoint,
:command,
:port,
:public,
:repo_id,
:continuously_deploy,
:receive_incoming_requests,
:healthcheck,
:volumes,
:service_name
def secret_variables
return unless object.secret_variables.present?
object.secret_variables.map do |var|
{ name: var['name'], value: anonymize(var['value']) }
end
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer/user_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::DeploymentSerializer::UserSerializer < UffizziCore::BaseSerializer
type :user
attributes :id, :kind, :email
def kind
:internal
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::DeploymentSerializer < UffizziCore::BaseSerializer
include UffizziCore::DependencyInjectionConcern
include_module_if_exists('UffizziCore::Api::Cli::V1::Projects::DeploymentSerializerModule')
type :deployment
attributes :id,
:project_id,
:state,
:preview_url
has_many :containers
belongs_to :deployed_by
def containers
object.containers.active
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/activity_item_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemSerializer < UffizziCore::BaseSerializer
type :activity_item
attributes :id, :namespace, :name, :tag, :type, :branch, :commit, :commit_message, :state, :created_at,
:updated_at, :build_id, :data, :container_id, :digest
def type
return :github if object.type == UffizziCore::ActivityItem::Github.name
return :docker if object.type == UffizziCore::ActivityItem::Docker.name
return :memory_limit if object.type == UffizziCore::ActivityItem::MemoryLimit.name
nil
end
def commit
object.commit.to_s.slice(0..6)
end
def state
object.events.order_by_id.last&.state
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer/container_config_file_serializer/config_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ContainerSerializer::ContainerConfigFileSerializer::ConfigFileSerializer <
UffizziCore::BaseSerializer
attributes :id, :filename, :kind, :payload
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer/container_config_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ContainerSerializer::ContainerConfigFileSerializer < UffizziCore::BaseSerializer
attributes :id, :mount_path
belongs_to :config_file
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::Deployments::ContainerSerializer < UffizziCore::BaseSerializer
attributes :id, :name, :memory_limit, :memory_request, :continuously_deploy, :variables, :secret_variables, :healthcheck
has_many :container_config_files
type :container
def name
image_name = object.image.split('/').pop
"#{image_name}:#{object.tag}"
end
def secret_variables
return unless object.secret_variables.present?
object.secret_variables.map do |var|
{ name: var['name'], value: anonymize(var['value']) }
end
end
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments_serializer/user_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::DeploymentsSerializer::UserSerializer < UffizziCore::BaseSerializer
type :user
attributes :email
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::DeploymentsSerializer < UffizziCore::BaseSerializer
type :deployment
attributes :id,
:created_at,
:updated_at,
:state,
:preview_url,
:metadata
belongs_to :deployed_by
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/secret_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::SecretSerializer < UffizziCore::BaseSerializer
attributes :name, :created_at, :updated_at
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/projects/short_cluster_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::Projects::ShortClusterSerializer < UffizziCore::BaseSerializer
type :cluster
attributes :id, :name
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/short_project_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::ShortProjectSerializer < UffizziCore::BaseSerializer
type :project
belongs_to :account, serializer: UffizziCore::Api::Cli::V1::ProjectSerializer::AccountSerializer
# account_id supports CLI versions < 2.0.5
attributes :name, :slug, :account_id
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/user_serializer/account_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::UserSerializer::AccountSerializer < UffizziCore::BaseSerializer
attributes :id, :kind, :state, :name
end
================================================
FILE: core/app/serializers/uffizzi_core/api/cli/v1/user_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Api::Cli::V1::UserSerializer < UffizziCore::BaseSerializer
type :user
attributes :default_account
def default_account
UffizziCore::Api::Cli::V1::UserSerializer::AccountSerializer.new(object.default_account)
end
end
================================================
FILE: core/app/serializers/uffizzi_core/base_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::BaseSerializer < ActiveModel::Serializer
def anonymize(field)
'*' * field.length
end
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/apply_config_file/config_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::ApplyConfigFile::ConfigFileSerializer < UffizziCore::BaseSerializer
attributes :id, :filename, :kind, :payload
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/create_cluster/cluster_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::CreateCluster::ClusterSerializer < UffizziCore::BaseSerializer
include UffizziCore::DependencyInjectionConcern
include_module_if_exists('UffizziCore::Controller::CreateCluster::ClusterSerializerModule')
attributes :name, :manifest, :base_ingress_host, :distro, :image, :account_id
def base_ingress_host
managed_dns_zone = controller_settings_service.vcluster_settings_by_vcluster(object).managed_dns_zone
[object.namespace, managed_dns_zone].join('.')
end
def image
kubernetes_distribution.image
end
def distro
kubernetes_distribution.distro
end
def account_id
object.project.account_id
end
private
def kubernetes_distribution
@kubernetes_distribution ||= object.kubernetes_distribution
end
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/create_credential/credential_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::CreateCredential::CredentialSerializer < UffizziCore::BaseSerializer
attributes :id, :registry_url, :username, :password
def username
return 'AWS' if object.amazon?
object.username
end
def password
if object.amazon?
UffizziCore::ContainerRegistry::AmazonService.access_token(object)
else
object.password
end
end
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/create_deployment/deployment_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::CreateDeployment::DeploymentSerializer < UffizziCore::BaseSerializer
attributes :kind
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/compose_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::DeployContainers::ComposeFileSerializer < UffizziCore::BaseSerializer
attributes :source_kind
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer/container_config_file_serializer/config_file_serializer.rb
================================================
# frozen_string_literal: true
# rubocop:disable Layout/LineLength
class UffizziCore::Controller::DeployContainers::ContainerSerializer::ContainerConfigFileSerializer::ConfigFileSerializer < UffizziCore::BaseSerializer
# rubocop:enable Layout/LineLength
attributes :id, :filename, :kind, :payload
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer/container_config_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::DeployContainers::ContainerSerializer::ContainerConfigFileSerializer < UffizziCore::BaseSerializer
attributes :mount_path
belongs_to :config_file
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer/container_host_volume_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::DeployContainers::ContainerSerializer::ContainerHostVolumeFileSerializer < UffizziCore::BaseSerializer
attributes :host_volume_file_id, :source_path
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::DeployContainers::ContainerSerializer < UffizziCore::BaseSerializer
attributes :id,
:kind,
:full_image_name,
:variables,
:secret_variables,
:memory_limit,
:memory_request,
:entrypoint,
:command,
:port,
:target_port,
:public,
:controller_name,
:receive_incoming_requests,
:healthcheck,
:volumes,
:service_name,
:additional_subdomains
has_many :container_config_files
has_many :container_host_volume_files
def entrypoint
object.entrypoint.blank? ? nil : JSON.parse(object.entrypoint)
end
def command
object.command.blank? ? nil : JSON.parse(object.command)
end
def healthcheck
return {} if object.healthcheck.blank?
command = object.healthcheck['test']
new_command = if command.is_a?(Array)
items_to_remove = ['CMD', 'CMD-SHELL']
command.select { |item| items_to_remove.exclude?(item) }
elsif object.healthcheck['test'].is_a?(String)
command.split
end
object.healthcheck.merge('test' => new_command)
end
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/credential_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::DeployContainers::CredentialSerializer < UffizziCore::BaseSerializer
attributes :id
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/deploy_containers/host_volume_file_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::DeployContainers::HostVolumeFileSerializer < UffizziCore::BaseSerializer
attributes :id, :source, :path, :payload, :is_file
def payload
Base64.encode64(object.payload)
end
end
================================================
FILE: core/app/serializers/uffizzi_core/controller/update_cluster/cluster_serializer.rb
================================================
# frozen_string_literal: true
class UffizziCore::Controller::UpdateCluster::ClusterSerializer < UffizziCore::BaseSerializer
include UffizziCore::DependencyInjectionConcern
include_module_if_exists('UffizziCore::Controller::UpdateCluster::ClusterSerializerModule')
attributes :name, :manifest, :base_ingress_host
def base_ingress_host
managed_dns_zone = controller_settings_service.vcluster_settings_by_vcluster(object).managed_dns_zone
[object.namespace, managed_dns_zone].join('.')
end
end
================================================
FILE: core/app/services/uffizzi_core/account_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::AccountService
class << self
def create_credential(credential)
UffizziCore::Deployment.active_for_credential_id(credential.id).pluck(:id).each do |deployment_id|
UffizziCore::Deployment::CreateCredentialJob.perform_async(deployment_id, credential.id)
end
end
def update_credential(credential)
UffizziCore::Deployment.active_for_credential_id(credential.id).pluck(:id).each do |deployment_id|
UffizziCore::Deployment::UpdateCredentialJob.perform_async(deployment_id, credential.id)
end
end
def delete_credential(credential)
UffizziCore::Deployment.active_for_credential_id(credential.id).pluck(:id).each do |deployment_id|
UffizziCore::Deployment::DeleteCredentialJob.perform_async(deployment_id, credential.id)
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/activity_item_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ActivityItemService
COMPLETED_STATES = ['deployed', 'failed', 'cancelled'].freeze
class << self
include UffizziCore::DependencyInjectionConcern
def create_docker_item!(repo, container)
activity_item_attributes = {
namespace: repo.namespace,
name: repo.name,
container: container,
deployment_id: container.deployment_id,
type: UffizziCore::ActivityItem::Docker.name,
tag: container.tag,
}
create_item!(activity_item_attributes)
end
def fail_deployment!(activity_item)
deployment = activity_item.container.deployment
last_event = activity_item.events.order_by_id.last
activity_item.events.create(state: UffizziCore::Event.state.failed) unless last_event&.failed?
UffizziCore::DeploymentService.fail!(deployment)
end
def update_docker_digest!(activity_item)
container = activity_item.container
repo = container.repo
credential = UffizziCore::RepoService.credential(repo)
container_registry_service = UffizziCore::ContainerRegistryService.init_by_subclass(repo.type)
digest = container_registry_service.digest(credential, activity_item.image, activity_item.tag)
activity_item.update!(digest: digest)
activity_item
end
def manage_deploy_activity_item(activity_item)
container = activity_item.container
deployment = container.deployment
service = UffizziCore::ManageActivityItemsService.new(deployment)
container_status_item = service.container_status_item(container)
return if container_status_item.nil?
status = container_status_item[:status]
last_event = activity_item.events.order_by_id.last
activity_item.events.create(state: status) if last_event&.state != status
return handle_failed_status(activity_item, deployment) if failed?(status)
if deployed?(status) && UffizziCore::ContainerService.ingress_container?(container)
deployment.update!(last_deploy_at: last_event.created_at)
deployment.deployment_events.create!(deployment_state: status)
return
end
return unless [UffizziCore::Event.state.building, UffizziCore::Event.state.deploying].include?(status)
UffizziCore::Deployment::ManageDeployActivityItemJob.perform_in(5.seconds, activity_item.id)
end
private
def handle_failed_status(activity_item, deployment)
UffizziCore::ActivityItemService.fail_deployment!(activity_item)
notification_module.notify_about_failed_deployment(deployment) if notification_module.present?
end
def create_item!(activity_item_attributes)
activity_item = UffizziCore::ActivityItem.find_by(activity_item_attributes)
return activity_item unless completed?(activity_item)
UffizziCore::ActivityItem.create!(activity_item_attributes)
end
def completed?(activity_item)
return true if activity_item.nil?
last_event = activity_item.events.order_by_id.last
COMPLETED_STATES.include?(last_event.state)
end
def failed?(status)
status == UffizziCore::Event.state.failed
end
def deployed?(status)
status == UffizziCore::Event.state.deployed
end
end
end
================================================
FILE: core/app/services/uffizzi_core/ci_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::CiService
class << self
def valid_request_from_ci_workflow?(_params)
false
end
end
end
================================================
FILE: core/app/services/uffizzi_core/cluster_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ClusterService
class << self
def start_deploy(cluster)
UffizziCore::Cluster::DeployJob.perform_async(cluster.id)
end
def deploy_cluster(cluster)
begin
UffizziCore::ControllerService.create_namespace(cluster)
rescue UffizziCore::ControllerClient::ConnectionError
return cluster.fail_deploy_namespace!
end
cluster.start_deploying!
begin
UffizziCore::ControllerService.create_cluster(cluster)
rescue UffizziCore::ControllerClient::ConnectionError
return cluster.fail!
end
UffizziCore::Cluster::ManageDeployingJob.perform_in(5.seconds, cluster.id)
end
def scale_up!(cluster)
cluster.start_scaling_up!
UffizziCore::ControllerService.patch_cluster(cluster, sleep: false)
UffizziCore::Cluster::ManageScalingUpJob.perform_in(5.seconds, cluster.id)
end
def manage_scale_up(cluster, try)
return cluster.fail_scale_up! if try > Settings.vcluster.max_scale_up_retry_count
return cluster.scale_up! if ready?(cluster)
UffizziCore::Cluster::ManageScalingUpJob.perform_in(5.seconds, cluster.id, ++try)
end
def scale_down!(cluster)
cluster.start_scaling_down!
UffizziCore::ControllerService.patch_cluster(cluster, sleep: true)
UffizziCore::Cluster::ManageScalingDownJob.perform_in(5.seconds, cluster.id)
end
def manage_scale_down(cluster)
return cluster.scale_down! unless awake?(cluster)
UffizziCore::Cluster::ManageScalingDownJob.perform_in(5.seconds, cluster.id)
end
def manage_deploying(cluster, try)
return if cluster.disabled?
return cluster.fail! if try > Settings.vcluster.max_creation_retry_count
deployed_cluster = UffizziCore::ControllerService.show_cluster(cluster)
if deployed_cluster.status.ready && deployed_cluster.status.kube_config.present?
cluster.finish_deploy
cluster.kubeconfig = deployed_cluster.status.kube_config
cluster.host = deployed_cluster.status.host
cluster.save!
return
end
UffizziCore::Cluster::ManageDeployingJob.perform_in(5.seconds, cluster.id, ++try)
end
def filter_user_ingress_host(cluster, ingress_hosts)
ingress_hosts.reject { |h| h == cluster.host }
end
private
def awake?(cluster)
data = UffizziCore::ControllerService.show_cluster(cluster)
!data.status.sleep
end
def ready?(cluster)
data = UffizziCore::ControllerService.show_cluster(cluster)
data.status.ready
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/builders/container_builder_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Builders::ContainerBuilderService
attr_accessor :credentials, :project, :repositories
def initialize(credentials, project, repositories = [])
@credentials = credentials
@project = project
@repositories = repositories
end
# rubocop:disable Metrics/PerceivedComplexity
def build_attributes(container_data, ingress_data, continuous_preview_global_data, compose_dependencies)
image_data = container_data[:image] || {}
build_data = container_data[:build] || {}
environment = container_data[:environment] || []
deploy_data = container_data[:deploy] || {}
configs_data = container_data[:configs] || []
secrets = container_data[:secrets] || []
container_name = container_data[:container_name]
healthcheck_data = container_data[:healthcheck] || {}
volumes_data = container_data[:volumes] || []
github_deps_service = UffizziCore::ComposeFile::GithubDependenciesService
env_file_dependencies = github_deps_service.env_file_dependencies_for_container(compose_dependencies, container_name)
configs_dependencies = github_deps_service.configs_dependencies_for_container(compose_dependencies, container_name)
host_volumes_dependencies = github_deps_service.host_volumes_dependencies_for_container(compose_dependencies, container_name)
is_ingress = ingress_container?(container_name, ingress_data)
repo_attributes = repo_attributes(container_data, continuous_preview_global_data)
additional_subdomains = is_ingress ? ingress_data.fetch(:additional_subdomains, []) : []
memory_limit = memory_limit(deploy_data)
{
tag: tag(image_data, repo_attributes),
port: port(container_name, ingress_data),
full_image_name: full_image_name(image_data, build_data, repo_attributes),
image: image(container_data, image_data, build_data, credentials),
public: is_ingress,
entrypoint: entrypoint(container_data),
command: command(container_data),
variables: variables(environment, env_file_dependencies),
secret_variables: secret_variables(secrets),
memory_limit: memory_limit,
memory_request: memory_request(memory_limit),
repo_attributes: repo_attributes,
continuously_deploy: continuously_deploy(deploy_data),
receive_incoming_requests: is_ingress,
container_config_files_attributes: container_config_files_attributes(configs_data, configs_dependencies),
service_name: container_name,
name: container_name,
healthcheck: healthcheck_data,
volumes: volumes_data,
additional_subdomains: additional_subdomains,
container_host_volume_files_attributes: container_host_volume_files_attributes(volumes_data, host_volumes_dependencies),
}
end
# rubocop:enable Metrics/PerceivedComplexity
private
def container_registry(container_data)
@container_registry ||= UffizziCore::ContainerRegistryService.init_by_container(container_data, @credentials)
end
def repo_attributes(container_data, continuous_preview_global_data)
repo_attributes = build_repo_attributes(container_data)
continuous_preview_container_data = container_data[:'x-uffizzi-continuous-preview'] || container_data[:'x-uffizzi-continuous-previews']
set_continuous_preview_attributes_to_repo(repo_attributes, continuous_preview_global_data.to_h, continuous_preview_container_data.to_h)
end
def build_repo_attributes(container_data)
container_registry = container_registry(container_data)
repo_type = container_registry.repo_type.name
raise UffizziCore::ComposeFile::BuildError, I18n.t('compose.invalid_repo_type') if repo_type.blank?
image_data = container_registry.image_data
if container_registry.image_available?(credentials)
docker_repo_builder = UffizziCore::ComposeFile::Builders::DockerRepoBuilderService.new(repo_type)
return docker_repo_builder.build_attributes(image_data)
end
UffizziCore::ComposeFile::ErrorsService.raise_build_error!(container_registry.type)
rescue UffizziCore::ContainerRegistryError => e
UffizziCore::ComposeFile::ErrorsService.raise_build_error!(container_registry.type, e.errors)
end
def set_continuous_preview_attributes_to_repo(repo_attributes, global_data, container_data)
condition_attributes = [
:deploy_preview_when_pull_request_is_opened,
:delete_preview_when_pull_request_is_closed,
:deploy_preview_when_image_tag_is_created,
:delete_preview_when_image_tag_is_updated,
:share_to_github,
]
condition_attributes.each do |attribute|
repo_attributes[attribute] = select_continuous_preview_attribute(global_data[attribute], container_data[attribute], false)
end
global = global_data.dig(:delete_preview_after, :value)
local = container_data.dig(:delete_preview_after, :value)
repo_attributes[:delete_preview_after] = select_continuous_preview_attribute(global, local, nil)
repo_attributes
end
def select_continuous_preview_attribute(global_attribute, local_attribute, default_attribute)
return local_attribute if local_attribute.present?
return global_attribute if global_attribute.present?
default_attribute
end
def tag(image_data, repo_attributes)
image_data[:tag] || repo_attributes[:branch]
end
def port(container_name, ingress)
return nil unless ingress_container?(container_name, ingress)
ingress[:port]
end
def image(container_data, image_data, build_data, credentials)
if image_data.present?
container_registry(container_data).image_name(credentials)
else
"#{build_data[:account_name]}/#{build_data[:repository_name]}"
end
end
def image_name(container_data)
container_registry = container_registry(container_data)
container_registry.image_name(credentials)
end
def full_image_name(image_data, build_data, repo_attributes)
return image_data[:full_image_name] if image_data.present?
"#{build_data[:account_name]}/#{build_data[:repository_name]}:#{repo_attributes[:branch]}"
end
def ingress_container?(container_name, ingress)
ingress[:container_name] == container_name
end
def entrypoint(container_data)
entrypoint = container_data[:entrypoint]
entrypoint.present? ? entrypoint.to_s : nil
end
def command(container_data)
command = container_data[:command]
command.present? ? command.to_s : nil
end
def command_args(command_data)
return nil if command_data[:command_args].blank?
command_data[:command_args].to_s
end
def memory_limit(deploy_data)
memory = deploy_data[:memory]
return Settings.compose.default_memory if memory.nil?
memory_value = case memory[:postfix]
when 'b'
memory[:value] / 1_000_000
when 'k'
memory[:value] / 1000
when 'm'
memory[:value]
when 'g'
memory[:value] * 1000
end
unless Settings.compose.memory_values.include?(memory_value)
raise UffizziCore::ComposeFile::BuildError,
I18n.t('compose.invalid_memory')
end
memory_value
end
def memory_request(memory)
memory / UffizziCore::Container::REQUEST_MEMORY_RATIO
end
def continuously_deploy(deploy_data)
return :disabled if deploy_data[:auto] == false
:enabled
end
def variables(variables_data, dependencies)
variables_builder.build_attributes(variables_data, dependencies)
end
def secret_variables(secrets)
variables_builder.build_secret_attributes(secrets)
end
def container_config_files_attributes(config_files_data, dependencies)
UffizziCore::ComposeFile::Builders::ContainerConfigFilesBuilderService.build_attributes(config_files_data, dependencies, project)
end
def container_host_volume_files_attributes(volumes_data, host_volumes_dependencies)
host_volumes_data = volumes_data.select do |v|
v[:type] == UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::HOST_VOLUME_TYPE
end
UffizziCore::ComposeFile::Builders::ContainerHostVolumeFilesBuilderService
.build_attributes(host_volumes_data, host_volumes_dependencies, project)
end
def variables_builder
@variables_builder ||= UffizziCore::ComposeFile::Builders::VariablesBuilderService.new(project)
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/builders/container_config_files_builder_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Builders::ContainerConfigFilesBuilderService
class << self
def build_attributes(config_files_data, dependencies, project)
return [] if config_files_data.empty?
config_file_sources = dependencies.pluck(:source)
config_files = project.config_files.with_creation_source(UffizziCore::ConfigFile.creation_source.compose_file)
.by_source(config_file_sources)
config_files_data.map do |config_file_data|
detected_dependency = dependencies.detect { |dependency| dependency[:path] == config_file_data[:source] }
detected_config_file = config_files.detect { |config_file| config_file.source == detected_dependency[:source] }
if detected_config_file.nil?
raise UffizziCore::ComposeFile::BuildError, I18n.t('compose.config_file_not_found', name: config_file_data[:source])
end
{
mount_path: config_file_data[:target],
config_file_id: detected_config_file.id,
}
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/builders/container_host_volume_files_builder_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Builders::ContainerHostVolumeFilesBuilderService
class << self
def build_attributes(container_host_volumes_data, host_volumes_dependencies, project)
return [] if container_host_volumes_data.empty?
host_volume_files = project
.host_volume_files
.by_source(host_volumes_dependencies.pluck(:source))
container_host_volumes_data.map do |container_host_volume_data|
detected_dependency = host_volumes_dependencies.detect do |dependency|
dependency[:raw_source] == container_host_volume_data[:source]
end
detected_host_volume_file = host_volume_files.detect { |host_volume_file| host_volume_file.source == detected_dependency[:source] }
if detected_host_volume_file.nil?
raise UffizziCore::ComposeFile::BuildError,
I18n.t('compose.host_volume_file_not_found', name: container_host_volume_data[:source])
end
{
source_path: container_host_volume_data[:source],
host_volume_file_id: detected_host_volume_file.id,
}
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/builders/docker_repo_builder_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Builders::DockerRepoBuilderService
attr_accessor :type
def initialize(type)
@type = type
end
def build_attributes(image_data)
{
kind: nil,
name: image_data[:name],
slug: image_data[:name],
type: type,
branch: nil,
namespace: image_data[:namespace],
is_private: nil, # TODO: detect
description: '',
repository_id: nil,
dockerfile_path: '',
dockerfile_context_path: nil,
}
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/builders/template_builder_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Builders::TemplateBuilderService
attr_accessor :credentials, :project, :repositories
def initialize(credentials, project, repositories = [])
@credentials = credentials
@project = project
@repositories = repositories
end
def build_attributes(compose_data, compose_dependencies, source)
containers_data = compose_data[:containers]
ingress_data = compose_data[:ingress]
continuous_preview_global_data = compose_data[:continuous_preview]
containers_attributes = build_containers_attributes(
containers_data,
ingress_data,
continuous_preview_global_data,
compose_dependencies,
)
{
name: source,
payload: {
containers_attributes: containers_attributes,
},
}
end
private
def build_containers_attributes(containers_data, ingress_data, continuous_preview_global_data, compose_dependencies)
containers_data.map do |container_data|
builder = UffizziCore::ComposeFile::Builders::ContainerBuilderService.new(credentials, project, repositories)
builder.build_attributes(container_data, ingress_data, continuous_preview_global_data, compose_dependencies)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/builders/variables_builder_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Builders::VariablesBuilderService
attr_accessor :project
require 'dotenv'
def initialize(project)
@project = project
end
def build_attributes(variables_data, dependencies)
variables = variables_data
variables_from_dependencies = variables_from_dependencies(dependencies)
variables + variables_from_dependencies
end
def build_secret_attributes(secrets)
project_secrets = project.secrets || []
secrets.uniq.map do |secret|
detected_secret = project_secrets.detect { |project_secret| project_secret['name'] == secret }
error_message = I18n.t('compose.project_secret_not_found', secret: secret)
raise UffizziCore::ComposeFile::SecretsError, error_message if detected_secret.nil?
build_variable(detected_secret['name'], detected_secret['value'])
end
end
private
def variables_from_dependencies(dependencies)
variables = dependencies.map do |dependency|
variables_data = parse_variables_from_dependency(dependency)
variables_data.map { |variable_data| build_variable(variable_data.first, variable_data.last) }
end
variables.flatten
end
def parse_variables_from_dependency(dependency)
content = dependency[:content]
return [] if content.blank?
variables_content = UffizziCore::ComposeFile::GithubDependenciesService.content(dependency)
parser = Dotenv::Parser.new(variables_content)
parser.call.to_a
end
def build_variable(name, value)
{
name: name,
value: value,
}
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/config_files_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::ConfigFilesService
def initialize(compose_file_form)
@compose_file_form = compose_file_form
@repository_id = compose_file_form.repository_id
@branch = compose_file_form.branch
@path = compose_file_form.path
@user = compose_file_form.added_by
@project = compose_file_form.project
end
def create_config_files(compose_dependencies)
configs_dependencies = UffizziCore::ComposeFile::GithubDependenciesService.configs_dependencies(compose_dependencies)
errors = []
configs_dependencies.each do |config_dependency|
errors = create_config_file(config_dependency)
errors << errors if errors
end
errors
end
private
def create_config_file(config_dependency)
source = UffizziCore::ComposeFile::GithubDependenciesService.build_source_path(@path, config_dependency[:path], @repository_id, @branch)
config_file = @project.config_files.find_or_initialize_by(source: source)
attributes = {
filename: UffizziCore::ComposeFile::GithubDependenciesService.filename(config_dependency),
payload: UffizziCore::ComposeFile::GithubDependenciesService.content(config_dependency),
}
config_file.assign_attributes(attributes)
config_file_form = build_config_file_form(config_file)
return config_file_form.errors if config_file_form.invalid?
config_file_form.save
nil
end
def build_config_file_form(config_file)
config_file_form = config_file.becomes(UffizziCore::Api::Cli::V1::ConfigFile::CreateForm)
config_file_form.project = @project
config_file_form.added_by = @user
config_file_form.compose_file = @compose_file_form
config_file_form.creation_source = UffizziCore::ConfigFile.creation_source.compose_file
config_file_form
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/config_option_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::ConfigOptionService
class << self
def valid_option_format?(option)
if option.is_a?(TrueClass) || option.is_a?(FalseClass)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.boolean_option', value: option)
end
option.match(/^[\w.-]+$/).present?
end
def config_options(compose_data)
compose_data.each_with_object([]) do |(key, value), keys|
if compose_data.equal?(value)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.infinite_recursion', key: key)
end
keys << key
keys.concat(config_options(value)) if value.is_a?(Hash)
end
end
def prepare_file_path_value(file_path)
pathname = Pathname.new(file_path)
pathname.cleanpath.to_s.strip.delete_prefix('/')
end
def ingress_option(compose_data)
compose_data.dig('x-uffizzi', 'ingress').presence || compose_data['x-uffizzi-ingress'].presence
end
def continuous_preview_option(compose_data)
compose_data.dig('x-uffizzi', 'continuous_preview').presence ||
compose_data.dig('x-uffizzi', 'continuous_previews').presence ||
compose_data['x-uffizzi-continuous-preview'].presence ||
compose_data['x-uffizzi-continuous-previews'].presence
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/container_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::ContainerService
class << self
def has_secret?(container, secret)
container['secret_variables'].any? { |container_secret| container_secret['name'] == secret['name'] }
end
def update_secret(container, secret)
secret_index = container['secret_variables'].find_index { |container_secret| container_secret['name'] == secret['name'] }
container['secret_variables'][secret_index] = secret
container
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/dependencies_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::DependenciesService
ENV_FILE_TYPE = 'env_file'
CONFIG_TYPE = 'config'
VOLUME_TYPE = 'volume'
DEPENDENCY_CONFIG_USE_KIND = 'config_map'
DEPENDENCY_VOLUME_USE_KIND = 'volume'
class << self
def build_dependencies(compose_data, compose_path, dependencies_params)
config_dependencies_params = dependencies_params.select { |d| d[:use_kind] == DEPENDENCY_CONFIG_USE_KIND }
volume_dependencies_params = dependencies_params.select { |d| d[:use_kind] == DEPENDENCY_VOLUME_USE_KIND }
dependencies = compose_data[:containers].map do |container|
env_file_dependencies = build_env_files_dependencies(container, compose_path, config_dependencies_params)
configs_dependencies = build_configs_dependencies(container, compose_path, config_dependencies_params)
volumes_dependencies = build_volumes_dependencies(container, compose_path, volume_dependencies_params)
env_file_dependencies + configs_dependencies + volumes_dependencies
end
dependencies.compact.flatten
end
def build_env_files_dependencies(container, compose_path, dependencies_params)
env_files = container[:env_file]
return [] unless env_files.present?
env_files.map do |path|
dependency = dependencies_params.detect { |item| item[:path] == path }
source = build_source_path(compose_path, path)
base_file_params(dependency, container).merge(source: source, type: ENV_FILE_TYPE)
end
end
def build_configs_dependencies(container, compose_path, dependencies_params)
configs = container[:configs]
return [] unless configs.present?
configs.map do |config|
dependency = dependencies_params.detect { |item| item[:path] == config[:source] }
source = build_source_path(compose_path, dependency[:path])
base_file_params(dependency, container).merge(source: source, type: CONFIG_TYPE)
end
end
def build_volumes_dependencies(container, compose_path, raw_dependencies)
container_volumes = container[:volumes]
return [] unless container_volumes.present?
container_volumes
.select { |c| c[:type] == UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::HOST_VOLUME_TYPE }
.map do |container_volume|
detected_raw_dependency = raw_dependencies.detect { |raw_dependency| raw_dependency[:source] == container_volume[:source] }
builded_source = build_source_path(compose_path, detected_raw_dependency[:source])
{
content: detected_raw_dependency[:content],
path: detected_raw_dependency[:path],
container_name: container[:container_name],
source: builded_source,
raw_source: detected_raw_dependency[:source],
type: VOLUME_TYPE,
is_file: detected_raw_dependency[:is_file],
}
end
end
def base_file_params(dependency, container)
{
content: dependency[:content],
path: dependency[:path],
container_name: container[:container_name],
}
end
def build_source_path(compose_path, dependency_path)
prepared_compose_path = Pathname.new(compose_path).basename.to_s
"#{prepared_compose_path}/#{dependency_path}"
end
def host_volume_binary_content(dependency)
Base64.decode64(dependency[:content])
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/errors_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::ErrorsService
SECRETS_ERROR_KEY = 'secret_variables'
TEMPLATE_BUILD_ERROR_KEY = 'template_build_error'
DOCKER_REGISTRY_CONTAINER_ERROR_KEY = 'docker_registry_container_error'
class << self
def has_error?(compose_file, error_code)
error = compose_file.payload.dig('errors', error_code)
error.present?
end
def has_errors?(compose_file)
compose_file.payload['errors'].present?
end
def update_compose_errors!(compose_file, errors, invalid_content)
compose_file.payload['errors'] = errors
compose_file.set_invalid if compose_file.valid_file?
compose_file.content = invalid_content
compose_file.save!
compose_file
end
def reset_compose_errors!(compose_file)
compose_file.payload['errors'] = nil
compose_file.set_valid
compose_file.save!
compose_file
end
def reset_error!(compose_file, error_code)
errors = compose_file.payload['errors']
return if errors.nil?
new_errors = errors.except(error_code)
compose_file.payload['errors'] = new_errors
compose_file.save!
compose_file
end
def raise_build_error!(type, extra_errors = {})
msg = I18n.t('compose.unprocessable_image', value: type)
raise UffizziCore::ComposeFile::BuildError.new(msg, extra_errors)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/github_dependencies_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::GithubDependenciesService
ENV_FILE_TYPE = 'env_file'
CONFIG_TYPE = 'config'
VOLUME_TYPE = 'volume'
class << self
def filename(dependency)
pathname = Pathname.new(dependency[:path])
pathname.basename.to_s
end
def content(dependency)
Base64.decode64(dependency[:content])
end
def env_file_dependencies_for_container(dependencies, container_name)
dependencies.select { |dependency| dependency[:type] == ENV_FILE_TYPE && dependency[:container_name] == container_name }
end
def configs_dependencies_for_container(dependencies, container_name)
configs_dependencies(dependencies).select { |dependency| dependency[:container_name] == container_name }
end
def host_volumes_dependencies_for_container(dependencies, container_name)
dependencies.select { |dependency| dependency[:type] == VOLUME_TYPE && dependency[:container_name] == container_name }
end
def configs_dependencies(dependencies)
dependencies.select { |dependency| dependency[:type] == CONFIG_TYPE }
end
def select_dependencies_by_type(dependencies, type)
dependencies.select { |dependency| dependency[:type].to_s == type.to_s }
end
def build_source_path(compose_path, dependency_path, repository_id, branch)
prepared_compose_path = Pathname.new(compose_path).basename.to_s
base_source = "#{prepared_compose_path}/#{dependency_path}"
return base_source if repository_id.blank?
"#{repository_id}/#{branch}/#{base_source}"
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/host_volume_files_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::HostVolumeFilesService
class << self
def bulk_create(compose_file_form, compose_dependencies)
volumes_dependencies = UffizziCore::ComposeFile::GithubDependenciesService
.select_dependencies_by_type(compose_dependencies, UffizziCore::ComposeFile::DependenciesService::VOLUME_TYPE)
errors = []
volumes_dependencies.each do |volume_dependency|
new_errors = create(compose_file_form, volume_dependency)
errors << new_errors if new_errors
end
errors
end
def create(compose_file_form, volume_dependency)
source = volume_dependency[:source]
host_volume_file = compose_file_form.project.host_volume_files.find_or_initialize_by(source: source)
attributes = {
payload: UffizziCore::ComposeFile::DependenciesService.host_volume_binary_content(volume_dependency),
source: source,
path: volume_dependency[:path],
is_file: volume_dependency[:is_file],
}
host_volume_file.assign_attributes(attributes)
host_volume_file.project = compose_file_form.project
host_volume_file.added_by = compose_file_form.added_by
host_volume_file.compose_file = compose_file_form
return host_volume_file.errors if host_volume_file.invalid?
host_volume_file.save
nil
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/configs_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::ConfigsParserService
class << self
def parse(configs_data)
return [] if configs_data.nil?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :configs) unless configs_data.is_a?(Hash)
configs = []
configs_data.each_pair do |config_name, config_data|
if config_data['file'].blank?
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.config_file_option_empty', config_name: config_name)
end
configs << {
config_name: config_name,
config_file: config_data['file'],
}
end
configs
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/continuous_preview_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::ContinuousPreviewParserService
class << self
def parse(continuous_preview_data)
return {} if continuous_preview_data.nil?
{
deploy_preview_when_pull_request_is_opened: trigger_value(continuous_preview_data, 'deploy_preview_when_pull_request_is_opened'),
delete_preview_when_pull_request_is_closed: trigger_value(continuous_preview_data, 'delete_preview_when_pull_request_is_closed'),
deploy_preview_when_image_tag_is_created: trigger_value(continuous_preview_data, 'deploy_preview_when_image_tag_is_created'),
delete_preview_when_image_tag_is_updated: trigger_value(continuous_preview_data, 'delete_preview_when_image_tag_is_updated'),
delete_preview_after: delete_preview_after_value(continuous_preview_data['delete_preview_after']),
share_to_github: trigger_value(continuous_preview_data, 'share_to_github'),
}
end
private
def trigger_value(continuous_preview_data, field)
value = continuous_preview_data[field]
return nil if value.nil?
return value if value.in?([true, false])
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_bool_value', field: field, value: value)
end
def delete_preview_after_value(value)
return {} if value.blank?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_string', option: :delete_preview_after) unless value.is_a?(String)
hours, postfix = value.scan(/^([0-9]+)([a-zA-Z])$/).flatten
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_integer', option: :delete_preview_after) if hours.nil?
formatted_hours = hours.to_i
if formatted_hours < Settings.compose.delete_after_min_value
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.invalid_delete_after_min', value: Settings.compose.delete_after_min_value)
end
if formatted_hours > Settings.compose.delete_after_max_value
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.invalid_delete_after_max', value: Settings.compose.delete_after_max_value)
end
if postfix.nil? || !Settings.compose.delete_after_postfixes.include?(postfix.downcase)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_delete_after_postfix')
end
{
value: formatted_hours,
postfix: postfix.downcase,
}
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/ingress_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::IngressParserService
class << self
def parse(ingress_data, services_data)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.no_ingress') if ingress_data.nil?
container_name = container_name(ingress_data, services_data)
port = port(ingress_data)
additional_attributes = build_additional_attributes(ingress_data, services_data)
{
container_name: container_name,
port: port,
}.merge(additional_attributes)
end
private
def container_name(ingress_data, services_data)
container_name = ingress_data['service']
if container_name.nil?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.ingress_service_not_found')
end
unless services_data.keys.include?(container_name)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_ingress_service', value: container_name)
end
container_name
end
def port(ingress_data)
port = ingress_data['port']
if port.nil?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.ingress_port_not_specified')
end
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_integer', option: :port) unless port.is_a?(Integer)
port_min = Settings.compose.port_min_value
port_max = Settings.compose.port_max_value
if port < port_min || port > port_max
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.port_out_of_range', port_min: port_min, port_max: port_max)
end
port
end
def build_additional_attributes(*)
{}
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/named_volumes_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::NamedVolumesParserService
VALID_VOLUME_NAME_REGEX = /^[a-zA-Z0-9._-]+$/.freeze
class << self
def parse(volumes_data)
return [] if volumes_data.nil?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :volumes) unless volumes_data.is_a?(Hash)
volume_names = volumes_data.keys
volume_names.each do |volume_name|
unless volume_name.match?(VALID_VOLUME_NAME_REGEX)
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.volume_invalid_name', name: volume_name)
end
end
volume_names
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/secrets_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::SecretsParserService
class << self
def parse(secrets_data)
return [] if secrets_data.nil?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :secrets) unless secrets_data.is_a?(Hash)
secrets = []
secrets_data.each_pair do |secret_name, secret_data|
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.secret_name_blank', option: secret_name) if secret_data['name'].blank?
if secret_data['external'] != true
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.secret_external', secret: secret_name)
end
secrets << {
secret_name: secret_name,
secret_variable: secret_data['name'],
}
end
secrets
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/command_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::CommandParserService
class << self
def parse(command_data)
return nil if command_data.blank?
case command_data
when String
Shellwords.split(command_data)
when Array
command_data
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :command)
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/configs_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::ConfigsParserService
class << self
def parse(configs, global_configs_data)
return [] if configs.nil?
configs.map do |config|
config_data = case config
when String
process_short_syntax(config, global_configs_data)
when Hash
process_long_syntax(config, global_configs_data)
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :configs)
end
config_data
end
end
private
def process_short_syntax(config_name, global_configs_data)
global_config = find_global_config!(config_name, global_configs_data)
{
source: UffizziCore::ComposeFile::ConfigOptionService.prepare_file_path_value(global_config[:config_file]),
target: global_config[:config_file],
}
end
def process_long_syntax(config_data, global_configs_data)
global_config = find_global_config!(config_data['source'], global_configs_data)
target = config_data['target'].blank? ? global_config[:config_file] : config_data['target']
{
source: UffizziCore::ComposeFile::ConfigOptionService.prepare_file_path_value(global_config[:config_file]),
target: target,
}
end
def find_global_config!(config_name, global_configs_data)
detected_config = global_configs_data.detect { |global_config_data| global_config_data[:config_name] == config_name }
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.global_config_not_found', config: config_name) if detected_config.nil?
detected_config
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/deploy_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::DeployParserService
class << self
def parse(deploy_data)
return {} if deploy_data.blank?
auto = prepare_deploy_auto(deploy_data)
memory = prepare_memory(deploy_data)
{
auto: auto,
memory: memory,
}
end
private
def prepare_deploy_auto(deploy_data)
auto = deploy_data['x-uffizzi-auto-deploy-updates']
return auto if auto.nil? || auto.in?([true, false])
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_bool_value', field: :auto, value: auto)
end
def prepare_memory(deploy_data)
memory = deploy_data.dig('resources', 'limits', 'memory')
return nil if memory.blank?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_memory_type', value: memory) unless memory.is_a?(String)
value, postfix = memory.scan(/^([0-9]+)([a-zA-Z]+)$/).flatten
if postfix.nil? || !Settings.compose.memory_postfixes.include?(postfix.downcase)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_memory_postfix', value: memory)
end
{
value: value.to_i,
postfix: postfix.downcase,
}
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/entrypoint_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::EntrypointParserService
class << self
def parse(entrypoint_data)
return nil if entrypoint_data.blank?
case entrypoint_data
when String
[entrypoint_data]
when Array
entrypoint_data
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :entrypoint)
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/env_file_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::EnvFileParserService
class << self
def parse(env_file)
env_files = case env_file
when String
[prepare_file_path(env_file)]
when Array
env_file.map { |env_file_path| prepare_file_path(env_file_path) }
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :env_file)
end
check_duplicates(env_files)
env_files
end
private
def prepare_file_path(env_file_path)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.empty_env_file') if env_file_path.blank?
UffizziCore::ComposeFile::ConfigOptionService.prepare_file_path_value(env_file_path)
end
def check_duplicates(env_files)
return if env_files.uniq.length == env_files.length
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.env_file_duplicates', values: env_files)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/environment_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::EnvironmentParserService
extend UffizziCore::ComposeFile::Parsers::VariablesParserService
class << self
def parse(environment)
return [] if environment.blank?
case environment
when Array
environment.map { |variable| parse_variable_from_string(variable) }
when Hash
environment.to_a.map { |variable| parse_variable_from_array(variable) }
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :environment)
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/healthcheck_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::HealthcheckParserService
REQUIRED_START_COMMANDS = ['NONE', 'CMD', 'CMD-SHELL'].freeze
REQUIRED_OPTIONS = ['test', 'disable'].freeze
class << self
def parse(healthcheck_data)
return {} if healthcheck_data.blank?
unless required_options_any?(healthcheck_data)
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.healthcheck_missing_required_option',
required_options: REQUIRED_OPTIONS.join(', '))
end
command = parse_command(healthcheck_data) if healthcheck_data['test'].present?
{
test: command,
interval: parse_time(:interval, healthcheck_data['interval']),
timeout: parse_time(:timeout, healthcheck_data['timeout']),
retries: parse_retries(healthcheck_data['retries']),
start_period: parse_time(:start_period, healthcheck_data['start_period']),
disable: parse_disable_option(healthcheck_data['disable'], command),
}
end
private
def parse_command(healthcheck_data)
command = healthcheck_data['test']
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.string_or_array_error', option: :test) if command.nil?
case command
when Array
start_command = command.first
unless REQUIRED_START_COMMANDS.include?(start_command)
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.required_start_commands', available_commands: REQUIRED_START_COMMANDS.join(', '))
end
command
when String
command
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :test)
end
end
def parse_retries(value)
return if value.nil?
unless value.is_a?(Integer)
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.invalid_retries', value: value)
end
value
end
def parse_time(key, value)
return if value.nil?
error_message = I18n.t('compose.invalid_time_interval', key: key, value: value)
raise UffizziCore::ComposeFile::ParseError, error_message if value.is_a?(Integer)
tokens = {
's' => 1,
'm' => 60,
'h' => (60 * 60),
'd' => (60 * 60 * 24),
}
time_parts = value.scan(/(\d+)(\w)/).compact
time_parts.reduce(0) do |acc, part|
amount, measure = part
acc += amount.to_i * tokens[measure]
acc
rescue StandardError
raise UffizziCore::ComposeFile::ParseError, error_message
end
end
def parse_disable_option(value, command)
return true if command.is_a?(Array) && command.first == 'NONE'
return false if value.nil?
return value if value.in?([true, false])
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_bool_value', field: 'disable', value: value)
end
def required_options_any?(healthcheck_data)
(REQUIRED_OPTIONS & healthcheck_data.keys).present?
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/image_parser_service.rb
================================================
# frozen_string_literal: true
require 'docker_distribution'
class UffizziCore::ComposeFile::Parsers::Services::ImageParserService
DEFAULT_TAG = Settings.compose.default_tag
class << self
def parse(value)
return {} if value.blank?
parsed_image = DockerDistribution::Normalize.parse_docker_ref(value)
image_path = parsed_image.path
namespace, name = get_namespace_and_name(image_path)
full_image_name = "#{[parsed_image.domain, parsed_image.path].compact.join('/')}:#{parsed_image.tag}"
{
registry_url: parsed_image.domain,
namespace: namespace,
name: name,
tag: parsed_image.tag,
full_image_name: full_image_name,
}
rescue DockerDistribution::NameContainsUppercase
raise_parse_error(I18n.t('compose.image_name_contains_uppercase_value', value: value))
rescue DockerDistribution::ReferenceInvalidFormat, DockerDistribution::ParseNormalizedNamedError
raise_parse_error(I18n.t('compose.invalid_image_value', value: value))
end
private
def raise_parse_error(message)
raise UffizziCore::ComposeFile::ParseError, message
end
def get_namespace_and_name(image_path)
return [nil, image_path] unless image_path.index('/').present?
image_path.split('/')
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/secrets_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::SecretsParserService
class << self
def parse(secrets, global_secrets_data)
return [] if secrets.nil?
secrets.map do |secret|
variable_name = if secret.is_a?(String)
process_short_syntax(secret, global_secrets_data)
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :secrets)
end
variable_name
end
end
private
def process_short_syntax(secret_name, global_secrets_data)
global_secret = find_global_secret!(secret_name, global_secrets_data)
global_secret[:secret_variable]
end
def find_global_secret!(secret_name, global_secrets_data)
detected_secret = global_secrets_data.detect { |global_secret_data| global_secret_data[:secret_name] == secret_name }
error_message = I18n.t('compose.global_secret_not_found', secret: secret_name)
raise UffizziCore::ComposeFile::ParseError, error_message if detected_secret.nil?
detected_secret
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services/volumes_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::Services::VolumesParserService
HOST_VOLUME_TYPE = :host
NAMED_VOLUME_TYPE = :named
ANONYMOUS_VOLUME_TYPE = :anonymous
READONLY_OPTION = 'ro'
READ_WRITE_OPTION = 'rw'
class << self
def parse(volumes, additional_data)
return [] if volumes.blank?
if volumes.is_a?(String)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volumes_should_be_array', volumes: volumes)
end
volumes.map do |volume|
volume_data = case volume
when String
process_short_syntax(volume, additional_data)
when Hash
process_long_syntax(volume, additional_data)
else
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :volumes)
end
volume_data
end.uniq
end
private
def process_short_syntax(volume_data, additional_data)
volume_parts = volume_data.split(':').map(&:strip)
read_only = volume_parts.last.to_s.downcase == READONLY_OPTION
part1, part2 = volume_parts
source_path = part1
target_path = [READONLY_OPTION, READ_WRITE_OPTION].include?(part2.to_s.downcase) ? nil : part2
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_prop_is_required', prop_name: 'source') if source_path.blank?
build_volume_attributes(source_path, target_path, read_only, additional_data)
end
def process_long_syntax(volume_data, additional_data)
source_path = volume_data['source'].to_s.strip
target_path = volume_data['target'].to_s.strip
read_only = volume_data['read_only'].present?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_prop_is_required', prop_name: 'source') if source_path.blank?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_prop_is_required', prop_name: 'target') if target_path.blank?
build_volume_attributes(source_path, target_path, read_only, additional_data)
end
def build_volume_attributes(source_path, target_path, read_only, params = {})
volume_type = build_volume_type(source_path, target_path)
if volume_type == NAMED_VOLUME_TYPE
validate_named_volume(source_path, target_path, params[:named_volumes_names], params[:service_name])
end
if volume_type == ANONYMOUS_VOLUME_TYPE
validate_anonymous_volume(source_path)
end
{
source: source_path,
target: target_path,
type: volume_type,
read_only: read_only,
}
end
def build_volume_type(source_path, target_path)
return HOST_VOLUME_TYPE if path?(source_path) && path?(target_path)
return ANONYMOUS_VOLUME_TYPE if path?(source_path) && target_path.blank?
return NAMED_VOLUME_TYPE if source_path.present? && !path?(source_path) && path?(target_path)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_path_is_invalid', path: [source_path, target_path].join(':'))
end
def path?(path)
path.to_s.start_with?('/', './', '../')
end
def validate_named_volume(source_path, target_path, named_volumes_names, service_name)
if path_has_only_root?(target_path)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_volume_destination', spec: "#{source_path}:#{target_path}")
end
return if named_volumes_names.include?(source_path)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.named_volume_not_exists', source_path: source_path,
target_path: target_path,
service_name: service_name)
end
def validate_anonymous_volume(path)
if path_has_only_root?(path)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_volume_destination', spec: path)
end
end
def path_has_only_root?(path)
path.size == 1 && path.include?('/')
end
end
end
================================================
FILE: core/app/services/uffizzi_core/compose_file/parsers/services_parser_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ComposeFile::Parsers::ServicesParserService
class << self
include UffizziCore::DependencyInjectionConcern
def parse(services, global_configs_data, global_secrets_data, compose_payload, global_named_volume_names)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.no_services') if services.nil? || services.keys.empty?
services.keys.map do |service|
unless valid_service_name?(service)
raise UffizziCore::ComposeFile::ParseError,
I18n.t('compose.invalid_service_name', value: service)
end
service_data = prepare_service_data(service, services.fetch(service), global_configs_data,
global_secrets_data, compose_payload, global_named_volume_names)
if service_data[:image].blank? && service_data[:build].blank?
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.image_build_no_specified', value: service)
end
service_data
end
end
private
def prepare_service_data(service_name, service_data, global_configs_data,
global_secrets_data, compose_payload, global_named_volume_names)
options_data = {}
service_data.each_pair do |key, value|
service_key = key.to_sym
options_data[service_key] = case service_key
when :image
UffizziCore::ComposeFile::Parsers::Services::ImageParserService.parse(value)
when :build
check_and_parse_build_option(value, compose_payload)
when :env_file
UffizziCore::ComposeFile::Parsers::Services::EnvFileParserService.parse(value)
when :environment
UffizziCore::ComposeFile::Parsers::Services::EnvironmentParserService.parse(value)
when :configs
UffizziCore::ComposeFile::Parsers::Services::ConfigsParserService.parse(value, global_configs_data)
when :secrets
UffizziCore::ComposeFile::Parsers::Services::SecretsParserService.parse(value, global_secrets_data)
when :deploy
UffizziCore::ComposeFile::Parsers::Services::DeployParserService.parse(value)
when :entrypoint
UffizziCore::ComposeFile::Parsers::Services::EntrypointParserService.parse(value)
when :command
UffizziCore::ComposeFile::Parsers::Services::CommandParserService.parse(value)
when :healthcheck
UffizziCore::ComposeFile::Parsers::Services::HealthcheckParserService.parse(value)
when :'x-uffizzi-continuous-preview', :'x-uffizzi-continuous-previews'
UffizziCore::ComposeFile::Parsers::ContinuousPreviewParserService.parse(value)
when :volumes
parse_volumes(value, named_volumes_names: global_named_volume_names,
service_name: service_name,
compose_payload: compose_payload)
end
end
options_data[:container_name] = service_name
options_data
end
def check_and_parse_build_option(value, compose_payload)
build_parser_module = find_build_parser_module
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.build_not_implemented') unless build_parser_module
build_parser_module.parse(value, compose_payload)
end
def parse_volumes(volumes, additional_data)
volume_parser_module = find_volume_parser_module
return UffizziCore::ComposeFile::Parsers::Services::VolumesParserService.parse(volumes, additional_data) unless volume_parser_module
volume_parser_module.parse(volumes, additional_data)
end
# All services are added as Additional Hosts in Kubernetes and must be RFC 123 compliant
def valid_service_name?(name)
rfc_123_pattern = /\A(?!-)[a-z0-9-]{1,63}(? new_secrets_errors }
UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, compose_file_errors.merge(new_errors),
compose_file.content)
next
end
compose_file_errors.delete(['secret_variables'])
next UffizziCore::ComposeFile::ErrorsService.reset_compose_errors!(compose_file) if compose_file_errors.empty?
UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, compose_file_errors, compose_file.content)
end
compose_file.template.save!
compose_file
end
def secrets_valid?(compose_file, secrets)
secret_names = secrets.pluck('name')
compose_file.template.payload['containers_attributes'].all? do |container|
container['secret_variables'].all? { |secret| secret_names.include?(secret['name']) }
end
end
def create_temporary_compose(resource_project, current_user, compose_file_params, dependencies)
create_params = { project: resource_project, user: current_user, compose_file_params: compose_file_params,
dependencies: dependencies || [] }
kind = UffizziCore::ComposeFile.kind.temporary
UffizziCore::ComposeFileService.create(create_params, kind)
end
private
def process_compose_file(compose_file_form, params)
cli_form = UffizziCore::Api::Cli::V1::ComposeFile::CliForm.new
cli_form.content = compose_file_form.content
cli_form.compose_file = compose_file_form.becomes(UffizziCore::ComposeFile)
return [compose_file_form, cli_form.errors] if cli_form.invalid?
dependencies = params[:dependencies].to_a
compose_data = cli_form.compose_data
compose_dependencies = build_compose_dependecies(compose_data, compose_file_form.path, dependencies)
cli_form.compose_dependencies = compose_dependencies
persist!(compose_file_form, cli_form)
end
def create_compose_form(params, kind)
compose_file_params = params[:compose_file_params]
compose_file_form = UffizziCore::Api::Cli::V1::ComposeFile::CreateForm.new(compose_file_params)
compose_file_form.project = params[:project]
compose_file_form.added_by = params[:user]
compose_file_form.content = compose_file_params[:content]
compose_file_form.kind = kind
payload_dependencies = prepare_compose_file_dependencies(params[:dependencies])
compose_file_form.payload['dependencies'] = payload_dependencies
compose_file_form
end
def create_update_compose_form(compose_file, params)
compose_file_form = compose_file.becomes(UffizziCore::Api::Cli::V1::ComposeFile::UpdateForm)
compose_file_params = params[:compose_file_params]
compose_file_form.assign_attributes(compose_file_params)
payload_dependencies = prepare_compose_file_dependencies(params[:dependencies])
compose_file_form.payload['dependencies'] = payload_dependencies
compose_file_form
end
def build_compose_dependecies(compose_data, compose_path, dependencies)
return [] if dependencies.empty?
UffizziCore::ComposeFile::DependenciesService.build_dependencies(compose_data, compose_path, dependencies)
end
def prepare_compose_file_dependencies(compose_dependencies)
compose_dependencies.map { |dependency| { path: dependency[:path] } }
end
def persist!(compose_file_form, cli_form)
errors = []
ActiveRecord::Base.transaction do
if !compose_file_form.save
errors = compose_file_form.errors
raise ActiveRecord::Rollback
end
config_files_service = UffizziCore::ComposeFile::ConfigFilesService.new(compose_file_form)
config_file_errors = config_files_service.create_config_files(cli_form.compose_dependencies)
host_volume_file_errors = UffizziCore::ComposeFile::HostVolumeFilesService
.bulk_create(compose_file_form, cli_form.compose_dependencies)
raise ActiveRecord::Rollback if config_file_errors.present? || host_volume_file_errors.present?
project = compose_file_form.project
user = compose_file_form.added_by
template_service = UffizziCore::ComposeFile::TemplateService.new(cli_form, project, user)
errors = template_service.create_template(compose_file_form)
raise ActiveRecord::Rollback if errors.present?
end
[compose_file_form, errors]
end
def load_compose_data(compose_content)
begin
compose_data = YAML.safe_load(compose_content, aliases: true)
rescue Psych::SyntaxError => e
err = [e.problem, e.context].compact.join(' ')
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_file', err: err, line: e.line, column: e.column)
end
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.unsupported_file') if compose_data.nil? || compose_data.is_a?(String)
compose_data
end
def check_config_options_format(compose_data)
options = UffizziCore::ComposeFile::ConfigOptionService.config_options(compose_data)
options.each do |option|
next if UffizziCore::ComposeFile::ConfigOptionService.valid_option_format?(option)
raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_config_option', value: option)
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry/amazon_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistry::AmazonService
class << self
def digest(credential, image, tag)
response = client(credential).batch_get_image(image: image, tag: tag)
response.images[0].image_id.image_digest
rescue StandardError
nil
end
def get_region_from_registry_url(url)
parsed_url = URI.parse(url)
host = parsed_url.host
parsed_host = host.split('.')
parsed_host[3]
end
def image_available?(credential, _image_data)
credential.present?
end
def credential_correct?(credential)
access_token(credential).present?
end
def access_token(credential)
response = client(credential).authorization_token
base64_data = response.authorization_data[0].authorization_token
token_string = Base64.decode64(base64_data)
token_items = token_string.split(':')
token_items.pop
rescue Aws::ECR::Errors::UnrecognizedClientException, Aws::ECR::Errors::InvalidSignatureException
''
end
private
def client(credential)
region = get_region_from_registry_url(credential.registry_url)
UffizziCore::AmazonRegistryClient.new(
region: region,
access_key_id: credential.username,
secret_access_key: credential.password,
)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry/azure_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistry::AzureService
class << self
def image_available?(credential, _image_data)
credential.present?
end
def credential_correct?(credential)
client(credential).authenticated?
end
def digest(*); end
private
def client(c)
UffizziCore::AzureRegistryClient.new(registry_url: c.registry_url, username: c.username, password: c.password)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry/docker_hub_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistry::DockerHubService
class << self
def accounts(credential)
client = user_client(credential)
response = client.accounts
Rails.logger.info("DockerHubService accounts response=#{response.inspect} credential_id=#{credential.id}")
accounts_response = response.result
accounts_response.nil? ? [] : accounts_response.namespaces
end
def image_available?(credential, image_data)
namespace = image_data[:namespace]
repo_name = image_data[:name]
client(credential).repository(namespace: namespace, image: repo_name)
true
end
def user_client(credential)
return @client if @client&.credential&.username == credential.username
@client = client(credential)
unless @client.authenticated?
Rails.logger.warn("broken credentials, DockerHubService credential_id=#{credential.id}")
credential.unauthorize! unless credential.unauthorized?
end
@client
end
def digest(credential, image, tag)
docker_hub_client = client(credential)
token = docker_hub_client.get_token(image).result.token
response = docker_hub_client.digest(image: image, tag: tag, token: token)
response.headers['docker-content-digest']
# FIXME: can't get digest for private image: {"code":"UNAUTHORIZED","message":"authentication required"...}
rescue UffizziCore::ContainerRegistryError
'unknown'
end
def credential_correct?(credential)
client(credential).authenticated?
end
private
def client(credential)
UffizziCore::DockerHubClient.new(credential)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry/docker_registry_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistry::DockerRegistryService
class << self
def image_available?(credential, image_data)
client_params = build_client_params(credential, image_data)
client = UffizziCore::DockerRegistryClient.new(**client_params)
client.manifests(namespace: image_data[:namespace], image: image_data[:name], tag: image_data[:tag])
true
end
def credential_correct?(credential)
client(credential).authenticated?
end
def digest(*); end
private
def client(credential)
params = {
registry_url: credential.registry_url,
username: credential.username,
password: credential.password,
}
UffizziCore::DockerRegistryClient.new(params)
end
def build_client_params(credential, image_data)
registry_url = credential&.registry_url || image_data[:registry_url]
new_registry_url = registry_url.start_with?('https://', 'http://') ? registry_url : "https://#{registry_url}"
{
registry_url: new_registry_url,
username: credential&.username,
password: credential&.password,
}
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry/github_container_registry_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistry::GithubContainerRegistryService
class << self
def image_available?(credential, _image_data)
credential.present?
end
def credential_correct?(credential)
client(credential).authenticated?
end
def digest(*); end
private
def client(c)
UffizziCore::GithubContainerRegistryClient.new(registry_url: c.registry_url, username: c.username, password: c.password)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry/google_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistry::GoogleService
class << self
def digest(credential, image, tag)
response = client(credential).manifests(image: image, tag: tag)
response.headers['docker-content-digest']
end
def image_available?(credential, _image_data)
credential.present?
end
def credential_correct?(credential)
client(credential).authenticated?
end
private
def client(c)
UffizziCore::GoogleRegistryClient.new(registry_url: c.registry_url, username: c.username, password: c.password)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/container_registry_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerRegistryService
attr_accessor :type, :container_data
class << self
def init_by_subclass(credential_type)
type = credential_type.demodulize.underscore
new(type.to_sym)
end
def init_by_container(container, credentials)
registry_url = container.dig(:image, :registry_url)
return new(:docker_hub, container) if registry_url.include?('docker.io')
return new(:azure, container) if registry_url.include?('azurecr.io')
return new(:google, container) if registry_url.include?('gcr.io')
return init_by_credentials(container, credentials) if registry_url.include?('amazonaws.com')
return new(:github_container_registry, container) if registry_url.include?('ghcr.io')
return new(:docker_registry, container) if docker_registry?(container)
end
def init_by_credentials(container, credentials)
return new(:docker_registry, container) if credentials && credentials.docker_registry.where(username: 'AWS').exists?
new(:amazon, container)
end
def docker_registry?(container)
registry_url = container.dig(:image, :registry_url)
return false if registry_url.nil?
registry_domain_regexp = /(\w+\.\w{2,})(?::\d+)?\z/
registry_domain = registry_url.match(registry_domain_regexp)&.to_a&.last
return false if registry_domain.nil?
['amazonaws.com', 'azurecr.io', 'gcr.io', 'ghcr.io'].exclude?(registry_domain)
end
def sources
[:azure, :google, :amazon, :github_container_registry, :docker_registry, :docker_hub, *additional_sources]
end
def additional_sources
[]
end
end
def initialize(type, container_data = {})
@type = type
@container_data = container_data
raise ::UffizziCore::RegistryNotSupportedError unless self.class.sources.include?(type)
end
def digest(credential, image, tag)
service.digest(credential, image, tag)
end
def service
@service ||= "UffizziCore::ContainerRegistry::#{type.to_s.camelize}Service".safe_constantize
end
def repo_type
@repo_type ||= "UffizziCore::Repo::#{type.to_s.camelize}".safe_constantize
end
def credential_correct?(credential)
service.credential_correct?(credential)
rescue URI::InvalidURIError, Faraday::ConnectionFailed, UffizziCore::ContainerRegistryError
false
end
def image_data
@image_data ||= container_data[:image]
end
def image_name(credentials)
if image_data[:registry_url].present? && [:google, :github_container_registry, :docker_registry, :docker_hub].exclude?(type)
return image_data[:name]
end
if type == :docker_registry && credential(credentials).nil?
return [image_data[:registry_url], image_data[:namespace], image_data[:name]].compact.join('/')
end
"#{image_data[:namespace]}/#{image_data[:name]}"
end
def credential(credentials_scope)
credentials_scope.send(type).first
end
def image_available?(credentials_scope)
credential = credential(credentials_scope)
service.image_available?(credential, image_data)
end
end
================================================
FILE: core/app/services/uffizzi_core/container_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ContainerService
class << self
def pod_name(container)
return container.controller_name if container.controller_name.present?
formatted_name = container.image_name
formatted_name.parameterize.gsub('_', '-')
end
def target_port_value(container)
container.port
end
def defines_env?(container, name)
return false if container.variables.nil?
container.variables.select { |v| v['name'].downcase.to_sym == name.to_sym }.any?
end
def continuously_deploy_enabled?(container)
container.aasm(:continuously_deploy).current_state == UffizziCore::Container::STATE_CD_ENABLED
end
def last_state(container)
pods = pods_by_container(container)
container_status = container_status(container, pods)
return {} if container_status.blank? || container_status&.dig('last_state')&.blank?
container_status['last_state'].map do |code, state|
{
code: code,
reason: state.reason,
exit_code: state.exit_code,
started_at: state.started_at,
finished_at: state.finished_at,
}
end.first
end
def ingress_container?(container)
container.receive_incoming_requests?
end
private
def container_status(container, pods)
pods
.flat_map { |pod| pod&.status&.container_statuses }
.compact
.detect { |cs| cs.name.include?(container.controller_name) }
end
def pods_by_container(container)
UffizziCore::ControllerService.fetch_pods(container.deployment)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/controller_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ControllerService
class InvalidPublicPort < StandardError
def initialize(containers)
msg = "Deployment with ID #{containers.first.deployment.id} does not have any public port"
super(msg)
end
end
class << self
include UffizziCore::DependencyInjectionConcern
def apply_config_file(deployment, config_file)
body = {
config_file: UffizziCore::Controller::ApplyConfigFile::ConfigFileSerializer.new(config_file).as_json,
}
controller_client(deployment).apply_config_file(deployment_id: deployment.id, config_file_id: config_file.id, body: body)
end
def apply_credential(deployment, credential)
image = if credential.github_container_registry?
deployment.containers.by_repo_type(UffizziCore::Repo::GithubContainerRegistry.name).first&.image
end
options = { image: image }
body = UffizziCore::Controller::CreateCredential::CredentialSerializer.new(credential, options).as_json
controller_client(deployment).apply_credential(deployment_id: deployment.id, body: body)
end
def delete_credential(deployment, credential)
controller_client(deployment).delete_credential(deployment_id: deployment.id, credential_id: credential.id)
end
def deploy_containers(deployment, containers)
check_any_container_has_public_port(containers) do |exists|
UffizziCore::DeploymentService.disable!(deployment) unless exists
end
containers = containers.map do |container|
UffizziCore::Controller::DeployContainers::ContainerSerializer.new(container).as_json(include: '**')
end
credentials = deployment.credentials.deployable.map do |credential|
UffizziCore::Controller::DeployContainers::CredentialSerializer.new(credential).as_json
end
host_volume_files = UffizziCore::HostVolumeFile.by_deployment(deployment).map do |host_volume_file|
UffizziCore::Controller::DeployContainers::HostVolumeFileSerializer.new(host_volume_file).as_json
end
compose_file = if deployment.compose_file.present?
UffizziCore::Controller::DeployContainers::ComposeFileSerializer.new(deployment.compose_file).as_json
end
body = {
containers: containers,
credentials: credentials,
deployment_url: deployment.preview_url,
compose_file: compose_file,
host_volume_files: host_volume_files,
}
if password_protection_module.present?
body = password_protection_module.add_password_configuration(body, deployment.project_id)
end
controller_client(deployment).deploy_containers(deployment_id: deployment.id, body: body)
end
def namespace_exists?(deployable)
controller_client(deployable).namespace(namespace: deployable.namespace).code == 200
end
def fetch_deployment_events(deployment)
request_events(deployment) || []
end
def fetch_pods(deployment)
pods = controller_client(deployment).deployment_containers(deployment_id: deployment.id).result || []
pods.filter { |pod| pod.metadata.name.start_with?(Settings.controller.namespace_prefix) }
end
def fetch_namespace(deployable)
controller_client(deployable).namespace(namespace: deployable.namespace).result || nil
end
def create_namespace(deployable)
body = { namespace: deployable.namespace }
controller_client(deployable).create_namespace(body: body).result || nil
end
def delete_namespace(deployable)
controller_client(deployable).delete_namespace(namespace: deployable.namespace)
end
def create_cluster(cluster)
body = UffizziCore::Controller::CreateCluster::ClusterSerializer.new(cluster).as_json
controller_client(cluster).create_cluster(namespace: cluster.namespace, body: body).result
end
def show_cluster(cluster)
controller_client(cluster).show_cluster(namespace: cluster.namespace, name: cluster.name).result
end
def delete_cluster(cluster)
controller_client(cluster).delete_cluster(namespace: cluster.namespace)
end
def patch_cluster(cluster, sleep:)
body = UffizziCore::Controller::UpdateCluster::ClusterSerializer.new(cluster).as_json
body[:sleep] = sleep
controller_client(cluster).patch_cluster(name: cluster.name, namespace: cluster.namespace, body: body)
end
def ingress_hosts(cluster)
namespace = cluster.namespace
ingresses = controller_client(cluster).ingresses(namespace: namespace).result.items
ingresses.map do |ingress|
ingress.spec.rules.map(&:host)
end.flatten
end
private
def check_any_container_has_public_port(containers)
exists = containers.any? { |c| c.port.present? && c.public }
yield(exists) if block_given?
return if exists
raise InvalidPublicPort.new(containers)
end
def request_events(deployment)
controller_client(deployment).deployment_containers_events(deployment_id: deployment.id)
end
def controller_client(deployable)
settings = case deployable
when UffizziCore::Deployment
controller_settings_service.deployment_settings_by_deployment(deployable)
when UffizziCore::Cluster
controller_settings_service.vcluster_settings_by_vcluster(deployable)
else
raise StandardError, "Deployable #{deployable.class.name} undefined"
end
UffizziCore::ControllerClient.new(settings)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/controller_settings_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ControllerSettingsService
class << self
def vcluster_settings_by_vcluster(_cluster)
Settings.vcluster_controller
end
def vcluster_settings_by_account(_account)
Settings.vcluster_controller
end
def deployment_settings_by_deployment(_deployment)
Settings.controller.deep_dup.tap do |s|
s.managed_dns_zone = Settings.app.managed_dns_zone
end
end
end
end
================================================
FILE: core/app/services/uffizzi_core/deployment/domain_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::DomainService
class << self
include UffizziCore::DependencyInjectionConcern
def build_subdomain(deployment)
return domain_module.build_subdomain(deployment) if domain_module.present?
return build_docker_continuous_preview_subdomain(deployment) if deployment&.continuous_preview_payload&.fetch('docker', nil).present?
build_default_subdomain(deployment)
end
def update_subdomain!(deployment)
deployment.subdomain = build_subdomain(deployment)
deployment.save!
end
private
def build_docker_continuous_preview_subdomain(deployment)
project = deployment.project
continuous_preview_payload = deployment.continuous_preview_payload
docker_payload = continuous_preview_payload['docker']
repo_name = docker_payload['image'].split('/').last
image_tag = docker_payload['tag']
deployment_name = name(deployment)
subdomain = "#{image_tag}-#{deployment_name}-#{repo_name}-#{project.slug}"
format_subdomain(subdomain)
end
def build_default_subdomain(deployment)
deployment_name = name(deployment)
slug = deployment.project.slug.to_s
subdomain = "#{deployment_name}-#{slug}"
format_subdomain(subdomain)
end
def name(deployment)
"deployment-#{deployment.id}"
end
def format_subdomain(full_subdomain_name)
# Replace _ to - because RFC 1123 subdomain must consist of lower case alphanumeric characters,
# '-' or '.', and must start and end with an alphanumeric character
rfc_subdomain = full_subdomain_name.gsub('_', '-')
subdomain_length_limit = Settings.deployment.subdomain.length_limit
return rfc_subdomain if rfc_subdomain.length <= subdomain_length_limit
sliced_subdomain = rfc_subdomain.slice(0, subdomain_length_limit)
return sliced_subdomain.chop if sliced_subdomain.end_with?('-')
sliced_subdomain
end
def format_url_safe(name)
name.gsub(/ /, '-').gsub(/[^\w-]+/, '-')
end
end
end
================================================
FILE: core/app/services/uffizzi_core/deployment/memory_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::Deployment::MemoryService
class << self
def valid_memory_limit?(_deployment)
true
end
def memory_limit_error_message(_deployment); end
end
end
================================================
FILE: core/app/services/uffizzi_core/deployment_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::DeploymentService
include UffizziCore::DependencyInjectionConcern
prepend_module_if_exists('UffizziCore::DeploymentServiceModule')
MIN_TARGET_PORT_RANGE = 37_000
MAX_TARGET_PORT_RANGE = 39_999
DEPLOYMENT_PROCESS_STATUSES = {
building: :building,
deploying: :deploying,
failed: :failed,
queued: :queued,
}.freeze
class << self
include UffizziCore::DependencyInjectionConcern
def create_from_compose(compose_file, project, user, params)
deployment_attributes = ActionController::Parameters.new(compose_file.template.payload)
deployment_form = UffizziCore::Api::Cli::V1::Deployment::CreateForm.new(deployment_attributes)
deployment_form.assign_dependences!(project, user)
deployment_form.compose_file = compose_file
deployment_form.creation_source = params[:creation_source] || UffizziCore::Deployment.creation_source.compose_file_manual
deployment_form.metadata = params[:metadata] || {}
run_deployment_creation_tasks(deployment_form) if deployment_form.save
deployment_form
end
def update_from_compose(compose_file, project, user, deployment, metadata)
deployment_attributes = ActionController::Parameters.new(compose_file.template.payload)
deployment_form = UffizziCore::Api::Cli::V1::Deployment::UpdateForm.new(deployment_attributes)
deployment_form.assign_dependences!(project, user)
deployment_form.compose_file = compose_file
deployment_form.metadata = metadata || {}
ActiveRecord::Base.transaction do
deployment.containers.destroy_all
deployment.compose_file.destroy! if deployment.compose_file&.kind&.temporary?
deployment.activate unless deployment.active?
new_params = params_for_update_deployment(deployment_form, compose_file)
deployment.update!(new_params)
end
deployment
end
def deploy_containers(deployment, repeated = false)
if !repeated
create_activity_items(deployment)
update_controller_container_names(deployment)
end
status = deployment_process_status(deployment)
case status
when DEPLOYMENT_PROCESS_STATUSES[:building]
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} repeat deploy_containers")
UffizziCore::Deployment::DeployContainersJob.perform_in(1.minute, deployment.id, true)
unless repeated
deployment.deployment_events.create!(deployment_state: status)
end
when DEPLOYMENT_PROCESS_STATUSES[:deploying]
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} start deploying into controller")
containers = deployment.active_containers
containers_with_variables = add_default_deployment_variables!(containers, deployment)
UffizziCore::ControllerService.deploy_containers(deployment, containers_with_variables)
deployment.deployment_events.create!(deployment_state: status)
else
Rails.logger.info("DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment has builds errors, stopping")
end
end
def disable!(deployment)
deployment.disable!
compose_file = deployment.compose_file || deployment.template&.compose_file
return unless compose_file&.kind&.temporary?
compose_file.destroy!
end
def fail!(deployment)
return if deployment.failed?
deployment.fail!
deployment.deployment_events.create!(deployment_state: deployment.state)
compose_file = deployment.compose_file || deployment.template&.compose_file
return unless compose_file&.kind&.temporary?
compose_file.destroy!
end
def all_containers_have_unique_ports?(containers)
ports = containers.map(&:port).compact
containers.empty? || ports.size == ports.uniq.size
end
def ingress_container?(containers)
containers.empty? || containers.map(&:receive_incoming_requests).count(true) == 1
end
def find_unused_port(deployment)
selected_port = nil
Timeout.timeout(20) do
loop do
selected_port = rand(MIN_TARGET_PORT_RANGE..MAX_TARGET_PORT_RANGE)
break if !deployment.containers.exists?(target_port: selected_port)
end
end
selected_port
end
def setup_ingress_container(deployment, ingress_container, port)
old_deployment_subdomain = deployment.subdomain
containers = deployment.containers.active
UffizziCore::Container.transaction do
containers.update_all(receive_incoming_requests: false, port: nil, public: false)
containers.find(ingress_container.id).update!(port: port, public: true, receive_incoming_requests: true)
end
deployment.reload
new_deployment_subdomain = DomainService.build_subdomain(deployment)
if new_deployment_subdomain != old_deployment_subdomain
deployment.update(subdomain: new_deployment_subdomain)
end
UffizziCore::Deployment::DeployContainersJob.perform_async(deployment.id)
end
def pull_request_payload_present?(deployment)
deployment.continuous_preview_payload.present? && deployment.continuous_preview_payload['pull_request'].present?
end
def failed?(deployment)
deployment_status = deployment_process_status(deployment)
deployment_status == DEPLOYMENT_PROCESS_STATUSES[:failed]
end
private
def run_deployment_creation_tasks(deployment)
UffizziCore::Deployment::DomainService.update_subdomain!(deployment)
UffizziCore::Deployment::CreateJob.perform_async(deployment.id)
end
def deployment_process_status(deployment)
containers = deployment.active_containers
activity_items = containers.map { |container| container.activity_items.order_by_id.last }.compact
events = activity_items.map { |activity_item| activity_item.events.order_by_id.last&.state }
events = events.flatten.uniq
return DEPLOYMENT_PROCESS_STATUSES[:queued] if events.empty?
return DEPLOYMENT_PROCESS_STATUSES[:failed] if events.include?(UffizziCore::Event.state.failed)
return DEPLOYMENT_PROCESS_STATUSES[:building] if events.include?(UffizziCore::Event.state.building)
DEPLOYMENT_PROCESS_STATUSES[:deploying]
end
def create_activity_items(deployment)
deployment.active_containers.each do |container|
repo = container.repo
activity_item = UffizziCore::ActivityItemService.create_docker_item!(repo, container)
create_default_activity_item_event(activity_item)
if UffizziCore::RepoService.credential(repo).present? && activity_item.docker?
UffizziCore::ActivityItem::Docker::UpdateDigestJob.perform_async(activity_item.id)
end
UffizziCore::Deployment::ManageDeployActivityItemJob.perform_in(5.seconds, activity_item.id)
end
end
def create_default_activity_item_event(activity_item)
activity_item.events.create(state: UffizziCore::Event.state.deploying) if activity_item.docker?
end
def update_controller_container_names(deployment)
deployment.active_containers.each do |container|
next if container.controller_name.present?
controller_name = generate_controller_container_name(container)
container.update!(controller_name: controller_name)
end
end
def generate_controller_container_name(container)
Digest::SHA256.hexdigest("#{container.id}:#{container.image}")[0, 10]
end
def add_default_deployment_variables!(containers, deployment)
containers.each do |container|
envs = []
if container.port.present? && !UffizziCore::ContainerService.defines_env?(container, 'PORT')
envs.push('name' => 'PORT', 'value' => container.target_port.to_s)
end
envs.push('name' => 'UFFIZZI_URL', 'value' => "https://#{deployment.preview_url}")
envs.push('name' => 'UFFIZZI_DOMAIN', 'value' => deployment.preview_url)
preview_url = "https://#{domain_module.build_preview_url(deployment)}" if domain_module.present?
envs.push('name' => 'UFFIZZI_PREDICTABLE_URL', 'value' => preview_url || '')
container.variables = [] if container.variables.nil?
container.variables.push(*envs)
end
end
def params_for_update_deployment(deployment_form, compose_file)
{
containers: deployment_form.containers,
compose_file_id: compose_file.id,
metadata: deployment_form.metadata,
}
end
end
end
================================================
FILE: core/app/services/uffizzi_core/logs_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::LogsService
NOT_ALLOWED_SYMBOLS_IN_NAME_REGEX = /[^a-zA-Z0-9-]/.freeze
DEFAULT_LOGS_LIMIT = 1000
class << self
def fetch_container_logs(container, query = {})
response = request_logs(container, query).result || {}
response = Hashie::Mash.new(response)
logs = response.logs || []
{
logs: format_logs(logs),
}
end
private
def request_logs(container, query)
deployment = container.deployment
controller_client.deployment_container_logs(
deployment_id: deployment.id,
container_name: UffizziCore::ContainerService.pod_name(container),
limit: query[:limit] || DEFAULT_LOGS_LIMIT,
previous: query[:previous] || false,
)
end
def format_logs(logs)
logs.map do |item|
timestamp, *payload = item.split
formatted_timestamp = timestamp.present? ? timestamp.to_time(:utc).strftime('%Y-%m-%d %H:%M:%S.%L %Z') : nil
{ timestamp: formatted_timestamp, payload: payload.join(' ') }
end
end
def controller_client
UffizziCore::ControllerClient.new(Settings.controller)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/manage_activity_items_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ManageActivityItemsService
attr_accessor :deployment, :containers, :pods, :namespace_data
def initialize(deployment)
@deployment = deployment
@containers = deployment.active_containers
@namespace_data = UffizziCore::ControllerService.fetch_namespace(deployment)
@pods = UffizziCore::ControllerService.fetch_pods(deployment)
end
def container_status_item(container)
container_status_items.detect { |container_statuses| container_statuses[:id] == container.id }
end
def container_status_items
build_container_status_items(build_network_connectivities, build_containers_replicas)
end
private
def build_network_connectivities
containers.with_public_access.map do |container|
{ id: container.id, items: build_container_network_connectivity_items(container) }
end
end
def build_container_network_connectivity_items(container)
network_connectivities = container_network_connectivities(container)
return [] if network_connectivities.nil?
network_connectivities.map do |network_connectivity|
type, value = network_connectivity
{ type: type, status: value.status }
end
end
def build_containers_replicas
containers.map do |container|
items = pods.map do |pod|
{
name: item_name(pod, container),
status: get_status(pod, container),
}
end
{ id: container.id, items: items }
end
end
def build_container_status_items(network_connectivities, containers_replicas)
containers.map do |container|
network_connectivity = network_connectivities.detect { |item| item[:id] == container.id }
container_replicas = containers_replicas.detect { |item| item[:id] == container.id }
{
id: container.id,
status: build_container_status(container, network_connectivity, container_replicas),
}
end
end
def replicas_contains_status?(replicas, status)
replicas.any? { |replica| replica[:status] == status }
end
def build_container_status(container, network_connectivity, container_replicas)
error = replicas_contains_status?(container_replicas[:items], UffizziCore::Event.state.failed)
container_is_running = replicas_contains_status?(container_replicas[:items], UffizziCore::Event.state.deployed)
deployed = !error && container_is_running
return container_status(error, deployed) unless container.public?
network_connectivity[:items].each do |item|
status = item[:status].to_sym
error ||= status == :failed
deployed &&= status == :success
end
container_status(error, deployed)
end
def container_status(error, deployed)
return UffizziCore::Event.state.failed if error
return UffizziCore::Event.state.deployed if deployed
UffizziCore::Event.state.deploying
end
def item_name(pod, container)
hash = pod.metadata.name.split('-').last
"#{container.image_name}-#{hash}"
end
def get_status(pod, container)
pod_container = pod_container(pod, container)
return UffizziCore::Event.state.deploying if pod_container.nil? || pod_container[:state].to_h.empty?
pod_container_status = pod_container[:state].keys.first
state = pod_container[:state][pod_container_status]
reason = state&.reason
case pod_container_status.to_sym
when :running
UffizziCore::Event.state.deployed
when :waiting
raise UffizziCore::Deployment::ImagePullError, @deployment.id if ['ErrImagePull', 'ImagePullBackOff'].include?(reason)
UffizziCore::Event.state.deploying
else
UffizziCore::Event.state.deploying
end
end
def pod_container(pod, container)
pod_name = UffizziCore::ContainerService.pod_name(container)
pod&.status&.container_statuses&.detect { |cs| cs.name.include?(pod_name) }
end
def container_network_connectivities(container)
network_connectivity = Hashie::Mash.new(JSON.parse(deployment_network_connectivity))
containers_network_connectivity = network_connectivity&.containers
containers_network_connectivity[container.id.to_s] unless containers_network_connectivity.nil?
end
def deployment_network_connectivity
namespace_data&.metadata&.annotations&.network_connectivity.presence || '{}'
end
end
================================================
FILE: core/app/services/uffizzi_core/project_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::ProjectService
class << self
def update_compose_secrets(project)
compose_file = project.compose_file
return if compose_file&.template.nil?
project.secrets.each do |secret|
if UffizziCore::ComposeFileService.has_secret?(compose_file, secret)
UffizziCore::ComposeFileService.update_secret!(compose_file, secret)
end
end
return unless UffizziCore::ComposeFileService.secrets_valid?(compose_file, project.secrets)
secrets_error_key = UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY
return unless UffizziCore::ComposeFile::ErrorsService.has_error?(compose_file, secrets_error_key)
UffizziCore::ComposeFile::ErrorsService.reset_error!(compose_file, secrets_error_key)
compose_file.set_valid! unless UffizziCore::ComposeFile::ErrorsService.has_errors?(compose_file)
end
def update_compose_secret_errors(project, secret)
compose_file = project.compose_file
return if compose_file.nil?
return unless UffizziCore::ComposeFileService.has_secret?(compose_file, secret)
error_message = I18n.t('compose.project_secret_not_found', secret: secret['name'])
compose_file_errors = compose_file.payload['errors'] || {}
secrets_errors = compose_file_errors[UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY].presence || []
new_secrets_errors = secrets_errors.append(error_message).uniq
error = { UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY => new_secrets_errors }
new_errors = compose_file_errors.merge(error)
UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, new_errors, compose_file.content)
end
def add_users_to_project!(project, account)
user_projects = account.memberships.map do |membership|
{ project: project, user: membership.user, role: membership.role }
end
UffizziCore::UserProject.create!(user_projects)
end
end
end
================================================
FILE: core/app/services/uffizzi_core/repo_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::RepoService
class << self
def needs_target_port?(repo)
return false if repo.nil?
!repo.dockerfile?
end
def credential(repo)
container_registry_service = UffizziCore::ContainerRegistryService.init_by_subclass(repo.type)
credentials = repo.project.account.credentials
container_registry_service.credential(credentials)
end
def image_name(repo)
"e#{repo.container.deployment_id}r#{repo.id}-#{Digest::SHA256.hexdigest("#{self.class}:#{repo.branch}:
#{repo.project_id}:#{repo.id}")[0, 10]}"
end
def image(repo)
repo_credential = credential(repo)
"#{repo_credential.registry_url}/#{image_name(repo)}"
end
end
end
================================================
FILE: core/app/services/uffizzi_core/response_service.rb
================================================
# frozen_string_literal: true
module UffizziCore::ResponseService
def meta(collection)
{
total_count: collection.total_count,
current_page: collection.current_page,
per_page: collection.limit_value,
count: collection.to_a.count,
total_pages: collection.total_pages,
}
end
end
================================================
FILE: core/app/services/uffizzi_core/template/memory_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::Template::MemoryService
class << self
def valid_memory_limit?(_template)
true
end
def memory_limit_error_message(_template); end
end
end
================================================
FILE: core/app/services/uffizzi_core/token_service.rb
================================================
# frozen_string_literal: true
module UffizziCore::TokenService
class << self
def encode(payload)
JWT.encode(payload, Settings.rails.secret_key_base, 'HS256')
end
def decode(token)
JWT.decode(token, Settings.rails.secret_key_base, true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
def generate
SecureRandom.hex
end
end
end
================================================
FILE: core/app/services/uffizzi_core/user_access_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::UserAccessService
attr_accessor :user_access_module
delegate :admin_access_to_account?, :developer_access_to_account?, :viewer_access_to_account?,
:admin_or_developer_access_to_account?, :any_access_to_account?, :admin_access_to_project?,
:developer_access_to_project?, :viewer_access_to_project?, :admin_or_developer_access_to_project?,
:any_access_to_project?, :global_admin?, :valid_token?, to: :@user_access_module
def initialize(user_access_module)
@user_access_module = user_access_module
end
end
================================================
FILE: core/app/services/uffizzi_core/user_generator_service.rb
================================================
# frozen_string_literal: true
class UffizziCore::UserGeneratorService
DEFAULT_USER_EMAIL = 'user@example.com'
DEFAULT_PROJECT_NAME = 'default'
DEFAULT_ACCOUNT_NAME = 'default'
class << self
def safe_generate(email, password, project_name)
generate(email, password, project_name)
rescue ActiveRecord::RecordInvalid => e
puts e.message
end
def generate(email, password, project_name)
user_attributes = build_user_attributes(email, password)
project_attributes = build_project_attributes(project_name)
ActiveRecord::Base.transaction do
user = UffizziCore::User.create!(user_attributes)
account_attributes = build_account_attributes(user)
account = UffizziCore::Account.create!(account_attributes)
user.memberships.create!(account: account, role: UffizziCore::Membership.role.admin)
project = account.projects.create!(project_attributes)
project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)
end
end
private
def build_user_attributes(email, password)
user_attributes = {
state: UffizziCore::User::STATE_ACTIVE,
creation_source: UffizziCore::User.creation_source.system,
}
if email.present?
user_attributes[:email] = email
elsif IO::console.present?
IO::console.write("Enter User Email (default: #{DEFAULT_USER_EMAIL}): ")
user_attributes[:email] = IO::console.gets.strip.presence || DEFAULT_USER_EMAIL
end
user_attributes[:password] = if password.present?
password
elsif IO::console.present?
IO::console.getpass('Enter Password: ')
end
user_attributes
end
def build_project_attributes(project_name)
project_attributes = {
state: UffizziCore::Project::STATE_ACTIVE,
}
if project_name.present?
project_attributes[:name] = project_name
elsif IO::console.present?
IO::console.write("Enter Project Name (default: #{DEFAULT_PROJECT_NAME}): ")
project_attributes[:name] = IO::console.gets.strip.presence || DEFAULT_PROJECT_NAME
else
project_attributes[:name] = DEFAULT_PROJECT_NAME
end
project_attributes[:slug] = prepare_project_slug(project_attributes[:name])
project_attributes
end
def build_account_attributes(user)
{
owner: user,
name: DEFAULT_ACCOUNT_NAME,
state: UffizziCore::Account::STATE_ACTIVE,
kind: UffizziCore::Account.kind.personal,
}
end
def prepare_project_slug(project_name)
project_name.downcase.gsub(/[^A-Za-z0-9]/, '-')
end
end
end
================================================
FILE: core/app/utils/uffizzi_core/converters.rb
================================================
# frozen_string_literal: true
module UffizziCore::Converters
class << self
include ActionView::Helpers::NumberHelper
def deep_lower_camelize_keys(object)
case object
when Array
object.map do |element|
element.deep_transform_keys { |key| key.to_s.camelize(:lower) }
end
when Hash
object.deep_transform_keys { |key| key.to_s.camelize(:lower) }
else
object
end
end
def deep_underscore_keys(object)
case object
when Array
object.map do |element|
element.deep_transform_keys { |key| key.to_s.underscore }
end
when Hash
object.deep_transform_keys { |key| key.to_s.underscore }
else
object
end
end
end
end
================================================
FILE: core/app/validators/uffizzi_core/email_validator.rb
================================================
# frozen_string_literal: true
class UffizziCore::EmailValidator < ActiveModel::EachValidator
EMAIL_REGEXP = /\A([^@\s<>]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.freeze
def validate_each(record, attribute, value)
record.errors.add(attribute, (options[:message] || :invalid)) unless value&.match?(EMAIL_REGEXP)
end
end
================================================
FILE: core/app/validators/uffizzi_core/environment_variable_list_validator.rb
================================================
# frozen_string_literal: true
class UffizziCore::EnvironmentVariableListValidator < ActiveModel::EachValidator
def validate_each(record, attribute, variables)
record.errors.add(attribute, :invalid) if variables.class != Array || !valid_variables?(variables)
end
def valid_variables?(variables)
variables.each do |variable|
return false if variable.class != Hash || !variable.key?('name') || variable['name'].nil? || !variable.key?('value')
end
true
end
end
================================================
FILE: core/app/validators/uffizzi_core/image_command_args_validator.rb
================================================
# frozen_string_literal: true
class UffizziCore::ImageCommandArgsValidator < ActiveModel::EachValidator
def validate_each(record, attribute, command_args)
record.errors.add(attribute, :invalid) if !command_args.nil? && !valid_command_args?(command_args)
end
def valid_command_args?(raw_command_args)
return true if raw_command_args.empty?
begin
command_args = JSON.parse(raw_command_args)
rescue JSON::ParserError
return false
end
return false if command_args.class != Array || command_args.empty?
command_args.all? { |item| item.instance_of?(String) }
end
end
================================================
FILE: core/bin/rails
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
ENGINE_ROOT = File.expand_path('..', __dir__)
ENGINE_PATH = File.expand_path('../lib/uffizzi_core/engine', __dir__)
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
require 'rails/engine/commands'
================================================
FILE: core/config/initializers/rswag_api.rb
================================================
# frozen_string_literal: true
Rswag::Api.configure do |c|
# Specify a root folder where Swagger JSON files are located
# This is used by the Swagger middleware to serve requests for API descriptions
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
# that it's configured to generate files in the same folder
c.swagger_root = "#{UffizziCore::Engine.root}/swagger"
# Inject a lamda function to alter the returned Swagger prior to serialization
# The function will have access to the rack env for the current request
# For example, you could leverage this to dynamically assign the "host" property
#
# c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
end
================================================
FILE: core/config/initializers/rswag_ui.rb
================================================
# frozen_string_literal: true
Rswag::Ui.configure do |c|
# List the Swagger endpoints that you want to be documented through the swagger-ui
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
# endpoint and the second is a title that will be displayed in the document selector
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON or YAML endpoints,
# then the list below should correspond to the relative paths for those endpoints
c.swagger_endpoint('/api-docs/v1/swagger.json', 'API V1 Docs')
# Add Basic Auth in case your API is private
# c.basic_auth_enabled = true
# c.basic_auth_credentials 'username', 'password'
end
================================================
FILE: core/config/initializers/swagger_yard.rb
================================================
# frozen_string_literal: true
require 'swagger_yard'
SwaggerYard.configure do |config|
config.api_version = '1.0'
config.title = 'Uffizzi docs'
config.description = 'Your API does this'
config.api_base_path = 'http://lvh.me:7000'
config.controller_path = File.expand_path('app/controllers/uffizzi_core/api/**/*', UffizziCore::Engine.root)
config.model_path = File.expand_path('app/models/uffizzi_core/**/*', UffizziCore::Engine.root)
config.include_private = false
end
================================================
FILE: core/config/locales/en.activerecord.yml
================================================
en:
activerecord:
errors:
models:
uffizzi_core/project:
attributes:
secret:
not_found: There is no secret with name %{name}
name:
taken: "A project with the name '%{value}' already exists."
slug:
taken: "A project slug '%{value}' already taken."
uffizzi_core/credential:
attributes:
registry_url:
invalid_scheme: The protocol for the registry url is not specified
password:
password_blank: Password/Access Token can't be blank
username:
incorrect: "We were unable to log you into %{type}. Please verify that your username and password are correct."
type:
exist: Credential of that type already exist.
uffizzi_core/deployment:
attributes:
containers:
max_memory_limit_error: "Total memory limit of containers must be less than %{max}"
uffizzi_core/user:
attributes:
email:
invalid: Invalid email address
================================================
FILE: core/config/locales/en.yml
================================================
en:
github:
preview_url_message: |
**This branch has been deployed using Uffizzi.**
Preview URL:
https://%{preview_url}
View deployment details here:
https://%{deployment_url}
This is an automated comment. To turn off commenting, visit uffizzi.com.
invalid_compose_message: |
:exclamation: **Preview failed: invalid compose**
Uffizzi was unable to deploy a preview of this pull request because the compose file in this branch is invalid.
compose:
unsupported_file: Unsupported compose file
invalid_file: 'Syntax error: %{err} at line %{line} column %{column}'
invalid_compose: Invalid compose file
invalid_config_option: Invalid config option '%{value}' - only a-zA-Z0-9._- characters are allowed
no_services: Service services has neither an image nor a build context specified. At least one must be provided.
invalid_service_name: "Invalid service name '%{value}': a service name must consist of lower case alphanumeric characters, ''-'', and must start and end with an alphanumeric character"
no_ingress: Service ingress has not been defined.
invalid_image_value: Invalid image value '%{value}'
image_name_contains_uppercase_value: Image name '%{value}' contains uppercase characters
unprocessable_image: Invalid credential '%{value} or image does not exist'
invalid_repo_type: Unsupported repo type
repo_not_found: The specified repository doesn't exist '%{name}'
ingress_port_not_specified: Ingress port not specified
ingress_service_not_found: Ingress service not found
invalid_ingress_service: Invalid ingress service '%{value}'
no_variable_name: Invalid environment variable 'name=%{name}' 'value=%{value}'
invalid_config: Invalid config value 'source=%{source}' 'target=%{target}'
port_out_of_range: Port should be specified between %{port_min} - %{port_max}
invalid_memory_postfix: The specified value for memory '%{value}' should specify the units. The postfix should be one of the `b` `k` `m` `g` characters
invalid_memory_type: Memory '%{value}' contains an invalid type, it should be a string
invalid_memory_value: Invalid memory value '%{value}'
invalid_memory: The memory should be one of the `125m` `250m` `500m` `1000m` `2000m` `4000m` values
image_build_no_specified: Service '%{value}' has neither an image nor a build context specified. At least one must be provided.
build_context_no_specified: The context option should be specified
config_file_not_found: Config file not found '%{name}'
host_volume_file_not_found: Host volume file not found '%{name}'
invalid_context: Invalid context value '%{value}'
invalid_bool_value: Invalid %{field} value '%{value}'. The value should be `true` or `false`
invalid_delete_after_postfix: The postfix of the delete_preview_after value should be `h`
invalid_integer: The specified value for %{option} should be an Integer type
invalid_string: The specified value for %{option} should be a String type
invalid_delete_after_min: Minimum delete_preview_after allowed is %{value}h
invalid_delete_after_max: Maximum delete_preview_after allowed is %{value}h
invalid_type: Unsupported type of '%{option}' option
empty_env_file: env_file contains an empty value
file_not_found: Couldn't find '%{path}' file
env_file_duplicates: env_file contains non-unique items '%{values}'
boolean_option: The service name %{value} must be a quoted string, i.e. '%{value}'.
config_file_option_empty: "'%{config_name}' has an empty file"
global_config_not_found: undefined config '%{config}'
invalid_branch: Branch '%{branch}' doesn't exist for repository '%{repository_url}'
repository_not_found: The specified repository doesn't exist '%{repository_url}'
build_context_unknown_repository: Compose repository is undefined
secret_name_blank: The specified name for '%{option}' secret can not be blank
secret_external: The specified secret '%{secret}' should be external
global_secret_not_found: undefined secret '%{secret}'
project_secret_not_found: Project secret '%{secret}' not found
continuous_preview_in_service_level: The option '%{option}' is not supported for service-level. Use 'x-uffizzi-continuous-preview' instead
file_already_exists: A compose file already exists for this project. Run 'uffizzi compose update' to update this file or 'uffizzi compose rm' to remove it. For more options, see 'uffizzi compose --help'
invalid_time_interval: "Invalid time interval: '%{key}:%{value}'. The time interval should be in the following format '{hours}h{minutes}m{seconds}s'. At least one value must be present."
invalid_retries: "Invalid retries value: 'retries:%{value}'. The value should be an integer."
string_or_array_error: "'%{option}' contains an invalid type, it should be a string, or an array"
not_implemented: "'%{option}' option is not implemented"
infinite_recursion: "Found infinite recursion for key '%{key}'"
volume_path_is_invalid: The path '%{path}' is invalid
volume_prop_is_required: The '%{prop_name}' is a required property
volume_invalid_name: "Volumes value '%{name}' does not match any of the regexes: '^[a-zA-Z0-9._-]+$'"
named_volume_not_exists: Named volume '%{source_path}:%{target_path}' is used in service '%{service_name}' but no declaration was found in the volumes section.
invalid_volume_destination: Invalid volume specification '%{spec}' destination can't be '/'
required_start_commands: "When 'test' is a list the first item must be one of: '%{available_commands}'"
volumes_should_be_array: Volumes '%{volumes}' should be an arra
healthcheck_missing_required_option: "One of these options is required: %{required_options}"
build_not_implemented: "The 'build' directive is not supported by the uffizzi command-line tool. Use 'image' instead, or configure Uffizzi CI if you want Uffizzi Cloud to build your application from source. See https://docs.uffizzi.com/references/compose-spec/"
secrets:
duplicates_exists: Secret with key %{secrets} already exist.
invalid_key_length: A secret key must be no longer than 256 characters.
deployment:
invalid_state: Preview with ID deployment-%{id} %{state}
already_exists: An active deployment already exists
cluster:
already_asleep: The cluster %{name} is already asleep.
already_awake: The cluster %{name} is already awake.
scaling_failed: Failed to %{action} cluster.
deploy_in_process: Please wait until the cluster %{name} is deployed.
session:
unsupported_login_type: This type of login is not supported
unauthorized: Unauthorized
kubernetes_distribution:
not_available: "The k8s version %{version} is not supported. Available version are: %{available_versions}."
enumerize:
credential:
type:
"UffizziCore::Credential::GithubContainerRegistry": "Github Container Registry"
"UffizziCore::Credential::DockerHub": "DockerHub"
"UffizziCore::Credential::DockerRegistry": "Docker Registry"
"UffizziCore::Credential::Azure": "Azure"
"UffizziCore::Credential::Google": "Google"
"UffizziCore::Credential::Amazon": "Amazon"
registry:
error: Container registry returned a %{code} error
================================================
FILE: core/config/routes.rb
================================================
# frozen_string_literal: true
UffizziCore::Engine.routes.draw do
mount Rswag::Api::Engine => '/api-docs'
mount Rswag::Ui::Engine => '/api-docs'
namespace :api, defaults: { format: :json } do
namespace :cli do
namespace :v1 do
resources :projects, only: ['index', 'show', 'destroy'], param: :slug do
scope module: :projects do
resource :compose_file, only: ['show', 'create', 'destroy']
resources :clusters, only: [:index, :create, :show, :destroy], param: :name do
member do
put :scale_down
put :scale_up
put :sync
end
scope module: :clusters do
resources :ingresses, only: ['index']
end
end
resources :deployments, only: ['index', 'show', 'create', 'destroy', 'update'] do
post :deploy_containers, on: :member
scope module: :deployments do
resources :activity_items, only: ['index']
resources :events, only: ['index']
resources :containers, only: ['index'], param: :name do
get :k8s_container_description
scope module: :containers do
resources :logs, only: ['index']
resources :builds, only: [] do
collection do
get :logs
end
end
end
end
end
end
resources :secrets, only: ['index', 'destroy'] do
collection do
post :bulk_create
end
end
end
end
resource :session, only: ['create', 'destroy']
namespace :ci do
resource :session, only: ['create']
end
resources :accounts, only: ['show', 'update'], param: :name
resources :accounts, only: ['index'] do
scope module: :accounts do
resources :projects, only: ['index', 'create']
resources :clusters, only: ['index']
resources :credentials, only: ['index', 'create', 'update', 'destroy'], param: :type do
member do
get :check_credential
end
end
end
end
end
end
end
end
================================================
FILE: core/db/migrate/20220218121438_create_uffizzi_core_tables.rb
================================================
# frozen_string_literal: true
class CreateUffizziCoreTables < ActiveRecord::Migration[6.1]
def change
create_table('uffizzi_core_accounts', force: :cascade) do |t|
t.text('name')
t.text('kind', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('customer_token')
t.string('state')
t.string('subscription_token')
t.datetime('payment_issue_at')
t.string('domain')
t.boolean('sso_enabled', default: false)
t.bigint('owner_id')
t.integer('container_memory_limit')
t.string('workos_organization_id')
t.string('sso_state')
t.index(['customer_token'], name: 'index_accounts_on_customer_token', unique: true)
t.index(['domain'], name: 'index_accounts_on_domain', unique: true)
t.index(['subscription_token'], name: 'index_accounts_on_subscription_token', unique: true)
end
create_table('uffizzi_core_activity_items', force: :cascade) do |t|
t.bigint('deployment_id', null: false)
t.string('namespace')
t.string('name')
t.string('tag')
t.string('branch')
t.string('type')
t.bigint('container_id', null: false)
t.string('commit')
t.string('commit_message')
t.bigint('build_id')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.jsonb('data', default: {}, null: false)
t.string('digest')
t.index(['container_id'], name: 'index_activity_items_on_container_id')
t.index(['deployment_id'], name: 'index_activity_items_on_deployment_id')
end
create_table('uffizzi_core_builds', force: :cascade) do |t|
t.bigint('repo_id', null: false)
t.string('build_id')
t.string('repository')
t.string('branch')
t.string('commit')
t.string('committer')
t.string('message')
t.string('log_url')
t.integer('status')
t.datetime('started_at')
t.datetime('ended_at')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.boolean('deployed')
t.index(['build_id'], name: 'index_builds_on_build_id', unique: true)
t.index(['repo_id'], name: 'index_builds_on_repo_id')
end
create_table('uffizzi_core_comments', force: :cascade) do |t|
t.string('commentable_type')
t.bigint('commentable_id')
t.text('content')
t.bigint('user_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('ancestry')
t.integer('ancestry_depth', default: 0)
t.index(['ancestry'], name: 'index_comments_on_ancestry')
t.index(['commentable_type', 'commentable_id'], name: 'index_comments_on_commentable')
t.index(['user_id'], name: 'index_comments_on_user_id')
end
create_table('uffizzi_core_compose_files', force: :cascade) do |t|
t.string('source')
t.bigint('repository_id')
t.string('branch')
t.string('path')
t.string('auto_deploy')
t.bigint('added_by_id')
t.bigint('project_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('state')
t.jsonb('payload', default: {}, null: false)
t.text('content')
t.string('kind', default: 'main')
t.index(['project_id'], name: 'index_compose_files_on_project_id')
end
create_table('uffizzi_core_config_files', force: :cascade) do |t|
t.string('filename')
t.string('kind')
t.bigint('added_by_id')
t.text('payload')
t.bigint('project_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.bigint('compose_file_id')
t.string('creation_source')
t.string('source')
t.index(['compose_file_id'], name: 'index_config_files_on_compose_file_id')
t.index(['project_id'], name: 'index_config_files_on_project_id')
end
create_table('uffizzi_core_container_config_files', force: :cascade) do |t|
t.string('mount_path')
t.bigint('container_id', null: false)
t.bigint('config_file_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['config_file_id'], name: 'index_container_config_files_on_config_file_id')
t.index(['container_id'], name: 'index_container_config_files_on_container_id')
end
create_table('uffizzi_core_containers', force: :cascade) do |t|
t.string('image')
t.string('tag')
t.jsonb('variables')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.bigint('deployment_id')
t.boolean('public', default: false, null: false)
t.integer('port')
t.bigint('repo_id')
t.string('state')
t.string('continuously_deploy', null: false)
t.string('kind', default: 'user')
t.integer('target_port')
t.string('controller_name')
t.boolean('receive_incoming_requests')
t.integer('memory_request')
t.integer('memory_limit')
t.jsonb('secret_variables')
t.string('entrypoint')
t.string('command')
t.index(['deployment_id'], name: 'index_containers_on_deployment_id')
t.index(['repo_id'], name: 'index_containers_on_repo_id')
end
create_table('uffizzi_core_coupons', force: :cascade) do |t|
t.string('token')
t.string('name', null: false)
t.string('currency', null: false)
t.bigint('amount_off', null: false)
t.string('duration', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
end
create_table('uffizzi_core_credentials', force: :cascade) do |t|
t.string('type')
t.string('username')
t.string('password')
t.bigint('project_id')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('provider_ref')
t.bigint('account_id')
t.string('state')
t.string('registry_url')
t.index(['account_id'], name: 'index_credentials_on_account_id')
t.index(['project_id'], name: 'index_credentials_on_project_id')
t.index(['provider_ref'], name: 'index_credentials_on_provider_ref')
end
create_table('uffizzi_core_deployments', force: :cascade) do |t|
t.bigint('project_id', null: false)
t.text('kind', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('creator_name')
t.string('subdomain')
t.string('state')
t.float('memory_limit')
t.bigint('deployed_by_id')
t.bigint('continuous_preview_id_deprecated')
t.jsonb('continuous_preview_payload')
t.string('creation_source')
t.bigint('compose_file_id')
t.bigint('template_id')
t.index(['compose_file_id'], name: 'index_deployments_on_compose_file_id')
t.index(['project_id'], name: 'index_deployments_on_project_id')
t.index(['template_id'], name: 'index_deployments_on_template_id')
end
create_table('uffizzi_core_events', force: :cascade) do |t|
t.bigint('activity_item_id', null: false)
t.string('state')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['activity_item_id'], name: 'index_events_on_activity_item_id')
end
create_table('uffizzi_core_invitations', force: :cascade) do |t|
t.text('email', null: false)
t.text('token', null: false)
t.string('status', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.bigint('invited_by_id', null: false)
t.bigint('entityable_id', null: false)
t.string('entityable_type', null: false)
t.string('role', null: false)
t.bigint('invitee_id')
t.index(['token'], name: 'index_invitations_on_token', unique: true)
end
create_table('uffizzi_core_memberships', force: :cascade) do |t|
t.bigint('user_id', null: false)
t.bigint('account_id', null: false)
t.text('role', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['account_id'], name: 'index_memberships_on_account_id')
t.index(['user_id', 'account_id'], name: 'index_memberships_on_user_id_and_account_id')
t.index(['user_id'], name: 'index_memberships_on_user_id')
end
create_table('uffizzi_core_payments', force: :cascade) do |t|
t.bigint('account_id', null: false)
t.string('charge_id')
t.string('status')
t.float('amount')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['account_id'], name: 'index_payments_on_account_id')
end
create_table('uffizzi_core_prices', force: :cascade) do |t|
t.string('token')
t.string('slug', null: false)
t.string('name', null: false)
t.float('units_price', null: false)
t.bigint('units_amount', null: false)
t.bigint('product_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['product_id'], name: 'index_prices_on_product_id')
end
create_table('uffizzi_core_products', force: :cascade) do |t|
t.string('token')
t.string('slug', null: false)
t.string('name', null: false)
t.string('type', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('kind')
end
create_table('uffizzi_core_projects', force: :cascade) do |t|
t.text('name', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.bigint('account_id', null: false)
t.string('state')
t.string('slug')
t.string('description')
t.jsonb('secrets')
t.index(['account_id', 'name'], name: 'index_projects_on_account_id_and_name', unique: true)
t.index(['account_id'], name: 'index_projects_on_account_id')
end
create_table('uffizzi_core_ratings', force: :cascade) do |t|
t.string('name')
t.string('state')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
end
create_table('uffizzi_core_repos', force: :cascade) do |t|
t.string('namespace')
t.string('name')
t.string('tag')
t.string('type')
t.string('branch')
t.bigint('project_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('description')
t.boolean('is_private')
t.string('slug')
t.bigint('repository_id')
t.string('kind')
t.string('dockerfile_path')
t.jsonb('args')
t.string('dockerfile_context_path')
t.boolean('deploy_preview_when_pull_request_is_opened')
t.boolean('delete_preview_when_pull_request_is_closed')
t.boolean('deploy_preview_when_image_tag_is_created')
t.boolean('delete_preview_when_image_tag_is_updated')
t.boolean('share_to_github')
t.integer('delete_preview_after')
t.string('tag_pattern_deprecated')
t.index(['project_id'], name: 'index_repos_on_project_id')
end
create_table('uffizzi_core_roles', force: :cascade) do |t|
t.string('name')
t.string('resource_type')
t.bigint('resource_id')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['name', 'resource_type', 'resource_id'], name: 'index_roles_on_name_and_resource_type_and_resource_id')
t.index(['resource_type', 'resource_id'], name: 'index_roles_on_resource_type_and_resource_id')
end
create_table('uffizzi_core_templates', force: :cascade) do |t|
t.string('name')
t.bigint('added_by_id')
t.jsonb('payload', null: false)
t.bigint('project_id', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.bigint('compose_file_id')
t.string('creation_source')
t.index(['project_id'], name: 'index_templates_on_project_id')
end
create_table('uffizzi_core_user_projects', force: :cascade) do |t|
t.bigint('user_id', null: false)
t.bigint('project_id', null: false)
t.text('role', null: false)
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.bigint('invited_by_id')
t.index(['project_id'], name: 'index_user_projects_on_project_id')
t.index(['user_id', 'project_id'], name: 'index_user_projects_on_user_id_and_project_id')
t.index(['user_id'], name: 'index_user_projects_on_user_id')
end
create_table('uffizzi_core_users', force: :cascade) do |t|
t.string('first_name')
t.string('last_name')
t.string('email', default: '', null: false)
t.string('password_digest', default: '', null: false)
t.string('confirmation_token')
t.string('state')
t.string('phone')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.string('github')
t.string('website')
t.string('twitter')
t.string('linkedin')
t.string('devto')
t.string('facebook')
t.string('blog')
t.text('bio')
t.string('status')
t.string('availability')
t.string('primary_skills')
t.string('learning')
t.string('coding_for')
t.string('education')
t.string('title')
t.string('work')
t.string('primary_location')
t.string('creation_source')
t.index('lower((email)::text)', name: 'index_email_on_lower_email', unique: true)
end
create_table('uffizzi_core_users_roles', id: false, force: :cascade) do |t|
t.bigint('user_id')
t.bigint('role_id')
t.index(['role_id'], name: 'index_users_roles_on_role_id')
t.index(['user_id', 'role_id'], name: 'index_users_roles_on_user_id_and_role_id')
t.index(['user_id'], name: 'index_users_roles_on_user_id')
end
end
end
================================================
FILE: core/db/migrate/20220309110201_remove_secrets_from_projects.rb
================================================
# frozen_string_literal: true
class RemoveSecretsFromProjects < ActiveRecord::Migration[6.1]
def change
remove_column('uffizzi_core_projects', 'secrets')
end
end
================================================
FILE: core/db/migrate/20220310110150_create_project_secrets.rb
================================================
# frozen_string_literal: true
class CreateProjectSecrets < ActiveRecord::Migration[6.1]
def change
create_table('uffizzi_core_project_secrets', force: :cascade) do |t|
t.bigint('project_id', null: false)
t.string('name')
t.string('value')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['project_id'], name: 'index_project_secrets_on_project_id')
end
end
end
================================================
FILE: core/db/migrate/20220325113342_add_name_to_uffizzi_containers.rb
================================================
# frozen_string_literal: true
class AddNameToUffizziContainers < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :name, :string)
end
end
================================================
FILE: core/db/migrate/20220329123323_rename_project_secrets_to_secrets.rb
================================================
# frozen_string_literal: true
class RenameProjectSecretsToSecrets < ActiveRecord::Migration[6.1]
def change
rename_table(:uffizzi_core_project_secrets, :uffizzi_core_secrets)
end
end
================================================
FILE: core/db/migrate/20220329124542_add_resource_to_secrets.rb
================================================
# frozen_string_literal: true
class AddResourceToSecrets < ActiveRecord::Migration[6.1]
def change
add_belongs_to(:uffizzi_core_secrets, :resource, polymorphic: true)
end
end
================================================
FILE: core/db/migrate/20220329143241_remove_project_ref_from_secrets.rb
================================================
# frozen_string_literal: true
class RemoveProjectRefFromSecrets < ActiveRecord::Migration[6.1]
def change
remove_reference(:uffizzi_core_secrets, :project)
end
end
================================================
FILE: core/db/migrate/20220419074956_add_health_check_to_containers.rb
================================================
# frozen_string_literal: true
class AddHealthCheckToContainers < ActiveRecord::Migration[6.1]
def change
add_column :uffizzi_core_containers, :healthcheck, :jsonb
end
end
================================================
FILE: core/db/migrate/20220422151523_add_volumes_to_uffizzi_core_containers.rb
================================================
# frozen_string_literal: true
class AddVolumesToUffizziCoreContainers < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :volumes, :jsonb)
end
end
================================================
FILE: core/db/migrate/20220525113412_rename_name_to_uffizzi_containers.rb
================================================
# frozen_string_literal: true
class RenameNameToUffizziContainers < ActiveRecord::Migration[6.1]
def change
rename_column(:uffizzi_core_containers, :name, :service_name)
end
end
================================================
FILE: core/db/migrate/20220704135629_add_disabled_at_to_deployments.rb
================================================
# frozen_string_literal: true
class AddDisabledAtToDeployments < ActiveRecord::Migration[6.1]
def up
change_table :uffizzi_core_deployments do |t|
t.datetime :disabled_at
end
UffizziCore::Deployment.disabled.update_all('disabled_at = updated_at')
end
def down
change_table :uffizzi_core_deployments do |t|
t.remove :disabled_at
end
end
end
================================================
FILE: core/db/migrate/20220805164628_add_metadata_to_deployment.rb
================================================
# frozen_string_literal: true
class AddMetadataToDeployment < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_deployments, :metadata, :jsonb, default: {})
end
end
================================================
FILE: core/db/migrate/20220901110752_create_host_volume_files.rb
================================================
# frozen_string_literal: true
class CreateHostVolumeFiles < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_host_volume_files do |t|
t.string :source
t.string :path
t.boolean :is_file
t.binary :payload
t.bigint :added_by_id
t.timestamps
t.references :project, null: false,
foreign_key: true,
index: { name: :index_host_volume_file_on_project_id },
foreign_key: { to_table: :uffizzi_core_projects }
t.references :compose_file, null: false,
foreign_key: true,
index: { name: :index_host_volume_file_on_compose_file_id },
foreign_key: { to_table: :uffizzi_core_compose_files }
end
end
end
================================================
FILE: core/db/migrate/20220901165313_create_container_host_volume_files.rb
================================================
# frozen_string_literal: true
class CreateContainerHostVolumeFiles < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_container_host_volume_files do |t|
t.string :source_path
t.timestamps
t.references :container, null: false,
foreign_key: true,
index: { name: :uf_core_cont_h_v_on_cont },
foreign_key: { to_table: :uffizzi_core_containers }
t.references :host_volume_file, null: false,
foreign_key: true,
index: { name: :uf_core_cont_h_v_on_h_v_file },
foreign_key: { to_table: :uffizzi_core_host_volume_files }
end
end
end
================================================
FILE: core/db/migrate/20220927113647_add_additional_subdomains_to_containers.rb
================================================
# frozen_string_literal: true
class AddAdditionalSubdomainsToContainers < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :additional_subdomains, :string, array: true, default: [])
end
end
================================================
FILE: core/db/migrate/20230111000000_add_state_to_memberships.rb
================================================
# frozen_string_literal: true
class AddStateToMemberships < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_memberships, :state, :string)
end
end
================================================
FILE: core/db/migrate/20230306142513_add_last_deploy_at_to_deployments.rb
================================================
# frozen_string_literal: true
class AddLastDeployAtToDeployments < ActiveRecord::Migration[6.1]
def up
add_column :uffizzi_core_deployments, :last_deploy_at, :datetime
UffizziCore::Deployment.where(last_deploy_at: nil).update_all('last_deploy_at = updated_at')
end
def down
remove_column :uffizzi_core_deployments, :last_deploy_at
end
end
================================================
FILE: core/db/migrate/20230406154451_add_full_image_name_to_container.rb
================================================
# frozen_string_literal: true
class AddFullImageNameToContainer < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :full_image_name, :string)
end
end
================================================
FILE: core/db/migrate/20230531135739_create_deployment_events.rb
================================================
# frozen_string_literal: true
class CreateDeploymentEvents < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_deployment_events do |t|
t.string :deployment_state
t.string :message
t.timestamps
t.references :deployment, null: false,
index: { name: :uf_core_dep_events_on_dep },
foreign_key: { to_table: :uffizzi_core_deployments }
end
end
end
================================================
FILE: core/db/migrate/20230613101901_create_clusters.rb
================================================
# frozen_string_literal: true
class CreateClusters < ActiveRecord::Migration[6.1]
def change
create_table('uffizzi_core_clusters', force: :cascade) do |t|
t.references :project, null: false,
foreign_key: true,
index: { name: :index_cluster_on_project_id },
foreign_key: { to_table: :uffizzi_core_projects }
t.bigint 'deployed_by_id', foreign_key: true
t.string 'state'
t.string 'name'
t.text 'manifest'
t.text 'kubeconfig'
t.timestamps
end
end
end
================================================
FILE: core/db/migrate/20230711101901_add_host_to_clusters.rb
================================================
# frozen_string_literal: true
class AddHostToClusters < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :host, :string)
end
end
================================================
FILE: core/db/migrate/20230810140316_add_source_to_uffizzi_core_clusters.rb
================================================
# frozen_string_literal: true
class AddSourceToUffizziCoreClusters < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :creation_source, :string)
end
end
================================================
FILE: core/db/migrate/20230824150022_update_name_constraint_to_projects.rb
================================================
# frozen_string_literal: true
class UpdateNameConstraintToProjects < ActiveRecord::Migration[6.1]
def change
remove_index(:uffizzi_core_projects, [:account_id, :name])
add_index(:uffizzi_core_projects, [:account_id, :name], unique: true,
where: "state = 'active'",
name: 'proj_uniq_name')
end
end
================================================
FILE: core/db/migrate/20231009162719_create_uffizzi_core_kubernetes_distributions.rb
================================================
# frozen_string_literal: true
class CreateUffizziCoreKubernetesDistributions < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_kubernetes_distributions do |t|
t.string :version
t.string :distro
t.string :image
t.boolean :default, default: false
t.timestamps
end
end
end
================================================
FILE: core/db/migrate/20231009182412_add_kubernetes_distribution_id_to_uffizzi_core_clusters.rb
================================================
# frozen_string_literal: true
class AddKubernetesDistributionIdToUffizziCoreClusters < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :kubernetes_distribution_id, :integer, foreign_key: true)
end
end
================================================
FILE: core/db/migrate/20240301200235_add_node_selector_to_cluster.rb
================================================
# frozen_string_literal: true
class AddNodeSelectorToCluster < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :node_selector, :string)
end
end
================================================
FILE: core/db/migrate/20240314170113_delete_node_selector_from_cluster.rb
================================================
# frozen_string_literal: true
class DeleteNodeSelectorFromCluster < ActiveRecord::Migration[6.1]
def change
remove_column(:uffizzi_core_clusters, :node_selector, :string)
end
end
================================================
FILE: core/db/seeds.rb
================================================
# frozen_string_literal: true
user = UffizziCore::User.create!(
email: 'admin@uffizzi.com',
password: 'password',
state: UffizziCore::User::STATE_ACTIVE,
creation_source: UffizziCore::User.creation_source.system,
)
account = UffizziCore::Account.create!(
owner: user, name: 'default',
state: UffizziCore::Account::STATE_ACTIVE,
kind: UffizziCore::Account.kind.personal
)
user.memberships.create!(account: account, role: UffizziCore::Membership.role.admin)
project = account.projects.create!(name: 'default', slug: 'default', state: UffizziCore::Project::STATE_ACTIVE)
project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)
================================================
FILE: core/lib/tasks/uffizzi_core_tasks.rake
================================================
# frozen_string_literal: true
namespace :uffizzi_core do
Rake::Task['install:migrations'].clear_comments
desc 'Copy over the migration needed to the application'
task install: :environment do
if Rake::Task.task_defined?('uffizzi_core:install:migrations')
Rake::Task['uffizzi_core:install:migrations'].invoke
else
Rake::Task['app:uffizzi_core:install:migrations'].invoke
end
end
desc 'Create a new user'
task create_user: :environment do
UffizziCore::UserGeneratorService.safe_generate(ENV['UFFIZZI_USER_EMAIL'], ENV['UFFIZZI_USER_PASSWORD'], ENV['UFFIZZI_PROJECT_NAME'])
end
end
================================================
FILE: core/lib/uffizzi_core/engine.rb
================================================
# frozen_string_literal: true
require 'active_model_serializers'
module UffizziCore
class Engine < ::Rails::Engine
isolate_namespace UffizziCore
config.before_initialize do
config.i18n.load_path += Dir["#{config.root}/config/locales/**/*.yml"]
end
ActiveModelSerializers.config.adapter = :json
end
end
================================================
FILE: core/lib/uffizzi_core/version.rb
================================================
# frozen_string_literal: true
module UffizziCore
VERSION = '2.4.10'
end
================================================
FILE: core/lib/uffizzi_core.rb
================================================
# frozen_string_literal: true
require 'uffizzi_core/version'
require 'uffizzi_core/engine'
require 'aasm'
require 'active_model_serializers'
require 'ancestry'
require 'aws-sdk-ecr'
require 'aws-sdk-eventbridge'
require 'aws-sdk-iam'
require 'config'
require 'hashie'
require 'faraday'
require 'enumerize'
require 'jwt'
require 'kaminari'
require 'octokit'
require 'pg'
require 'pundit'
require 'ransack'
require 'responders'
require 'rolify'
require 'rswag/api'
require 'rswag/ui'
require 'sidekiq'
require 'virtus'
require 'faraday/follow_redirects'
module UffizziCore
mattr_accessor :dependencies, default: {
rbac: 'UffizziCore::Rbac::UserAccessService',
deployment_memory_module: 'UffizziCore::Deployment::MemoryService',
template_memory_module: 'UffizziCore::Template::MemoryService',
controller_settings: 'UffizziCore::ControllerSettingsService',
ci_module: 'UffizziCore::CiService',
}
mattr_accessor :table_names, default: {
accounts: :uffizzi_core_accounts,
activity_items: :uffizzi_core_activity_items,
builds: :uffizzi_core_builds,
clusters: :uffizzi_core_clusters,
comments: :uffizzi_core_comments,
compose_files: :uffizzi_core_compose_files,
config_files: :uffizzi_core_config_files,
container_config_files: :uffizzi_core_container_config_files,
containers: :uffizzi_core_containers,
coupons: :uffizzi_core_coupons,
credentials: :uffizzi_core_credentials,
deployments: :uffizzi_core_deployments,
events: :uffizzi_core_events,
invitations: :uffizzi_core_invitations,
memberships: :uffizzi_core_memberships,
payments: :uffizzi_core_payments,
prices: :uffizzi_core_prices,
products: :uffizzi_core_products,
projects: :uffizzi_core_projects,
ratings: :uffizzi_core_ratings,
repos: :uffizzi_core_repos,
roles: :uffizzi_core_roles,
secrets: :uffizzi_core_secrets,
templates: :uffizzi_core_templates,
user_projects: :uffizzi_core_user_projects,
users: :uffizzi_core_users,
users_roles: :uffizzi_core_users_roles,
host_volume_files: :uffizzi_core_host_volume_files,
container_host_volume_files: :uffizzi_core_container_host_volume_files,
deployment_events: :uffizzi_core_deployment_events,
kubernetes_distributions: :uffizzi_core_kubernetes_distributions,
}
mattr_accessor :user_creation_sources, default: [:system, :online_registration, :google, :sso]
mattr_accessor :user_project_roles, default: [:admin, :developer, :viewer]
mattr_accessor :account_sources, default: [:manual]
mattr_accessor :compose_file_kinds, default: [:main, :temporary]
mattr_accessor :event_states, default: [:waiting, :queued, :successful, :deployed, :failed, :building, :timeout, :cancelled, :deploying]
mattr_accessor :cluster_creation_sources, default: [:manual]
end
================================================
FILE: core/swagger/v1/swagger.json
================================================
{
"swagger": "2.0",
"info": {
"title": "Uffizzi docs",
"description": "Your API does this",
"version": "1.0"
},
"host": "lvh.me:7000",
"basePath": "/",
"schemes": [
"http"
],
"paths": {
"/api/cli/v1/account/credentials": {
"get": {
"tags": [
"Account/Credential"
],
"operationId": "Account/Credential-index",
"parameters": [
{
"name": "credential",
"description": "credential",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
}
],
"responses": {
"default": {
"description": "Get a list of accounts credential"
}
},
"description": "Get a list of accounts credential",
"summary": "Get a list of accounts credential",
"x-controller": "uffizzi_core/api/cli/v1/account/credentials",
"x-action": "index"
},
"post": {
"tags": [
"Account/Credential"
],
"operationId": "Account/Credential-create",
"parameters": [
{
"name": "credential",
"description": "credential",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
}
],
"responses": {
"default": {
"description": "Create account credential",
"examples": {
"application/json": "Possible types:\nUffizziCore::Credential::Amazon, UffizziCore::Credential::Azure, UffizziCore::Credential::DockerHub,\nUffizziCore::Credential::DockerRegistry, UffizziCore::Credential::Google, UffizziCore::Credential::GithubContainerRegistry"
}
},
"201": {
"description": "Created successfully",
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"type": {
"type": "string"
},
"state": {
"type": "string"
}
}
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"type": "object",
"additionalProperties": {
"type": "errors"
}
}
}
},
"description": "Create account credential",
"summary": "Create account credential",
"x-controller": "uffizzi_core/api/cli/v1/account/credentials",
"x-action": "create"
}
},
"/api/cli/v1/account/credentials/{type}": {
"put": {
"tags": [
"Account/Credential"
],
"operationId": "Account/Credential-update",
"parameters": [
{
"name": "credential",
"description": "credential",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
}
},
{
"name": "type",
"description": "Credential type",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"registry_url": {
"type": "string"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
}
}
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"type": "object",
"additionalProperties": {
"type": "errors"
}
}
}
},
"description": "Update existing credential of the given type",
"summary": "Update existing credential of the given type",
"x-controller": "uffizzi_core/api/cli/v1/account/credentials",
"x-action": "update"
},
"delete": {
"tags": [
"Account/Credential"
],
"operationId": "Account/Credential-destroy",
"parameters": [
{
"name": "type",
"description": "Type of the credential",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Not authorized"
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
}
},
"description": "Delete account credential",
"summary": "Delete account credential",
"x-controller": "uffizzi_core/api/cli/v1/account/credentials",
"x-action": "destroy"
}
},
"/api/cli/v1/account/credentials/{type}/check_credential": {
"get": {
"tags": [
"Account/Credential"
],
"operationId": "Account/Credential-check_credential",
"parameters": [
{
"name": "credential",
"description": "credential",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
}
},
{
"name": "type",
"description": "Scope response to type",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"422": {
"description": "Unprocessable entity"
},
"200": {
"description": "OK"
}
},
"description": "Check if credential of the type already exists in the account",
"summary": "Check if credential of the type already exists in the account",
"x-controller": "uffizzi_core/api/cli/v1/account/credentials",
"x-action": "check_credential"
}
},
"/api/cli/v1/projects/{project_slug}/compose_file": {
"get": {
"tags": [
"ComposeFile"
],
"operationId": "ComposeFile-show",
"parameters": [
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/ComposeFile"
}
},
"401": {
"description": "Not authorized"
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
}
},
"description": "Get the compose file for the project",
"summary": "Get the compose file for the project",
"x-controller": "uffizzi_core/api/cli/v1/projects/compose_files",
"x-action": "show"
},
"post": {
"tags": [
"ComposeFile"
],
"operationId": "ComposeFile-create",
"parameters": [
{
"name": "params",
"description": "params",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"compose_file": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"source": {
"type": "string"
},
"content": {
"type": "string"
}
}
},
"dependencies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"source": {
"type": "string"
},
"content": {
"type": "string"
}
}
}
}
}
}
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"201": {
"description": "OK",
"schema": {
"$ref": "#/definitions/ComposeFile"
}
},
"422": {
"description": "Invalid compose file",
"schema": {
"$ref": "#/definitions/ComposeFile"
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Create a compose file for the project",
"summary": "Create a compose file for the project",
"x-controller": "uffizzi_core/api/cli/v1/projects/compose_files",
"x-action": "create"
},
"delete": {
"tags": [
"ComposeFile"
],
"operationId": "ComposeFile-destroy",
"parameters": [
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Not authorized"
}
},
"description": "Delete the compose file for the project",
"summary": "Delete the compose file for the project",
"x-controller": "uffizzi_core/api/cli/v1/projects/compose_files",
"x-action": "destroy"
}
},
"/api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers/{container_name}/logs": {
"get": {
"tags": [
"Project/Deployment/Container/Log"
],
"operationId": "Project/Deployment/Container/Log-index",
"parameters": [
{
"name": "container_name",
"description": "The name of the container",
"required": true,
"in": "path",
"type": "integer"
},
{
"name": "deployment_id",
"description": "The id of the deployment",
"required": true,
"in": "path",
"type": "integer"
},
{
"name": "project_slug",
"description": "The slug of the project",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"logs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"insert_id": {
"type": "string"
},
"payload": {
"type": "string"
}
}
}
}
}
}
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments/containers/logs",
"x-action": "index"
}
},
"/api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers": {
"get": {
"tags": [
"Container"
],
"operationId": "Container-index",
"parameters": [
{
"name": "deployment_id",
"description": "The id of the deployment",
"required": true,
"in": "path",
"type": "integer"
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Container"
}
},
"401": {
"description": "Not authorized"
},
"404": {
"description": "Not found"
}
},
"description": "Get a list of container services for a deployment",
"summary": "Get a list of container services for a deployment",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments/containers",
"x-action": "index"
}
},
"/api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/events": {
"get": {
"tags": [
"Event"
],
"operationId": "Event-index",
"parameters": [
{
"name": "deployment_id",
"description": "The id of the deployment",
"required": true,
"in": "path",
"type": "integer"
},
{
"name": "project_slug",
"description": "The project_slug for the project",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"first_timestamp": {
"type": "string"
},
"last_timestamp": {
"type": "string"
},
"reason": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
},
"401": {
"description": "Not authorized"
},
"404": {
"description": "Not found"
}
},
"description": "Get the events associated with deployment",
"summary": "Get the events associated with deployment",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments/events",
"x-action": "index"
}
},
"/api/cli/v1/projects/{project_slug}/deployments": {
"get": {
"tags": [
"Deployment"
],
"operationId": "Deployment-index",
"parameters": [
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Deployment"
}
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Get a list of active deployements for a project",
"summary": "Get a list of active deployements for a project",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments",
"x-action": "index"
},
"post": {
"tags": [
"Deployment"
],
"operationId": "Deployment-create",
"parameters": [
{
"name": "params",
"description": "params",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"compose_file": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"source": {
"type": "string"
},
"content": {
"type": "string"
}
}
},
"dependencies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"source": {
"type": "string"
},
"content": {
"type": "string"
}
}
}
}
}
}
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"201": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Deployment"
}
},
"422": {
"description": "Unprocessable Entity",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"state": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Create a deployment from a compose file",
"summary": "Create a deployment from a compose file",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments",
"x-action": "create"
}
},
"/api/cli/v1/projects/{project_slug}/deployments/{id}": {
"get": {
"tags": [
"Deployment"
],
"operationId": "Deployment-show",
"parameters": [
{
"name": "id",
"description": "Scope response to id",
"required": true,
"in": "path",
"type": "string"
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Deployment"
}
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Get deployment information by id",
"summary": "Get deployment information by id",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments",
"x-action": "show"
},
"delete": {
"tags": [
"Deployment"
],
"operationId": "Deployment-destroy",
"parameters": [
{
"name": "id",
"description": "Scope response to id",
"required": true,
"in": "path",
"type": "string"
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Not authorized"
}
},
"description": "Disable deployment by id",
"summary": "Disable deployment by id",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments",
"x-action": "destroy"
}
},
"/api/cli/v1/projects/{project_slug}/deployments/{id}\"": {
"put": {
"tags": [
"Deployment"
],
"operationId": "Deployment-update",
"parameters": [
{
"name": "id",
"description": "Scope response to id",
"required": true,
"in": "path",
"type": "string"
},
{
"name": "params",
"description": "params",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"compose_file": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"source": {
"type": "string"
},
"content": {
"type": "string"
}
}
},
"dependencies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"source": {
"type": "string"
},
"content": {
"type": "string"
}
}
}
}
}
}
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"201": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Deployment"
}
},
"422": {
"description": "Unprocessable Entity",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"state": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Update the deployment with new compose file",
"summary": "Update the deployment with new compose file",
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments",
"x-action": "update"
}
},
"/api/cli/v1/projects/{project_slug}/deployments/{id}/deploy_containers": {
"post": {
"tags": [
"Deployment"
],
"operationId": "Deployment-deploy_containers",
"parameters": [
{
"name": "id",
"description": "The id of the deployment",
"required": true,
"in": "path",
"type": "string"
},
{
"name": "project_slug",
"description": "The project slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"204": {
"description": "No Content"
},
"404": {
"description": "Not found",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"x-controller": "uffizzi_core/api/cli/v1/projects/deployments",
"x-action": "deploy_containers"
}
},
"/api/cli/v1/projects/{project_slug}/secrets": {
"get": {
"tags": [
"Project/Secrets"
],
"operationId": "Project/Secrets-index",
"parameters": [
{
"name": "project_slug",
"description": "project_slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"secrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date"
},
"updated_at": {
"type": "string",
"format": "date"
}
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Get secrets for the project",
"summary": "Get secrets for the project",
"x-controller": "uffizzi_core/api/cli/v1/projects/secrets",
"x-action": "index"
}
},
"/api/cli/v1/projects/{project_slug}/secrets/bulk_create": {
"post": {
"tags": [
"Project/Secrets"
],
"operationId": "Project/Secrets-bulk_create",
"parameters": [
{
"name": "project_slug",
"description": "project_slug",
"required": true,
"in": "path",
"type": "string"
},
{
"name": "secrets",
"description": "secrets",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"secrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
}
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"type": "object",
"properties": {
"secrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date"
},
"updated_at": {
"type": "string",
"format": "date"
}
}
}
}
}
}
},
"422": {
"description": "A compose file already exists for this project"
},
"401": {
"description": "Not authorized"
}
},
"description": "Add secret to project",
"summary": "Add secret to project",
"x-controller": "uffizzi_core/api/cli/v1/projects/secrets",
"x-action": "bulk_create"
}
},
"/api/cli/v1/projects/{project_slug}/secrets/{secret_name}": {
"delete": {
"tags": [
"Project/Secrets"
],
"operationId": "Project/Secrets-destroy",
"parameters": [
{
"name": "project_slug",
"description": "project_slug",
"required": true,
"in": "path",
"type": "string"
},
{
"name": "secret_name",
"description": "Scope response to secret_name",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Project"
}
},
"404": {
"description": "Delete a secret from project by secret name"
},
"401": {
"description": "Not authorized"
}
},
"description": "Delete a secret from project by secret name",
"summary": "Delete a secret from project by secret name",
"x-controller": "uffizzi_core/api/cli/v1/projects/secrets",
"x-action": "destroy"
}
},
"/api/cli/v1/projects": {
"get": {
"tags": [
"Project"
],
"operationId": "Project-index",
"parameters": [
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"projects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}
}
},
"401": {
"description": "Not authorized"
}
},
"description": "Get projects of current user",
"summary": "Get projects of current user",
"x-controller": "uffizzi_core/api/cli/v1/projects",
"x-action": "index"
},
"post": {
"tags": [
"Project"
],
"operationId": "Project-create",
"parameters": [
{
"name": "params",
"description": "params",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"slug": {
"type": "string"
},
"description": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"project": {
"$ref": "#/definitions/Project"
}
}
}
},
"404": {
"description": "Not Found"
},
"401": {
"description": "Not authorized"
},
"422": {
"description": "Unprocessable entity",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
}
}
}
}
},
"description": "Create a project",
"summary": "Create a project",
"x-controller": "uffizzi_core/api/cli/v1/projects",
"x-action": "create"
}
},
"/api/cli/v1/projects/{slug}": {
"get": {
"tags": [
"Project"
],
"operationId": "Project-show",
"parameters": [
{
"name": "slug",
"description": "Scope response to slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"204": {
"description": "No content"
},
"404": {
"description": "Not Found"
},
"401": {
"description": "Not authorized"
}
},
"description": "Get a project by slug",
"summary": "Get a project by slug",
"x-controller": "uffizzi_core/api/cli/v1/projects",
"x-action": "show"
},
"delete": {
"tags": [
"Project"
],
"operationId": "Project-destroy",
"parameters": [
{
"name": "slug",
"description": "Scope response to slug",
"required": true,
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"properties": {
"project": {
"$ref": "#/definitions/Project"
}
}
}
},
"404": {
"description": "Not Found"
},
"401": {
"description": "Not authorized"
}
},
"description": "Delete a project",
"summary": "Delete a project",
"x-controller": "uffizzi_core/api/cli/v1/projects",
"x-action": "destroy"
}
},
"/api/cli/v1/session": {
"post": {
"tags": [
"Uffizzi"
],
"operationId": "Uffizzi-create",
"parameters": [
{
"name": "user",
"description": "user",
"required": true,
"in": "body",
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
}
}
],
"responses": {
"201": {
"description": "Created successfully",
"schema": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"accounts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"state": {
"type": "string"
}
}
}
}
}
}
}
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
}
}
}
}
},
"description": "Create session",
"summary": "Create session",
"x-controller": "uffizzi_core/api/cli/v1/sessions",
"x-action": "create"
},
"delete": {
"tags": [
"Uffizzi"
],
"operationId": "Uffizzi-destroy",
"parameters": [
],
"responses": {
"204": {
"description": "No Content"
}
},
"description": "Destroy session",
"summary": "Destroy session",
"x-controller": "uffizzi_core/api/cli/v1/sessions",
"x-action": "destroy"
}
}
},
"tags": [
{
"name": "Account/Credential",
"description": ""
},
{
"name": "ComposeFile",
"description": ""
},
{
"name": "Container",
"description": ""
},
{
"name": "Deployment",
"description": ""
},
{
"name": "Event",
"description": ""
},
{
"name": "Project",
"description": ""
},
{
"name": "Project/Deployment/Container/Log",
"description": ""
},
{
"name": "Project/Secrets",
"description": ""
},
{
"name": "Uffizzi",
"description": ""
}
],
"securityDefinitions": {
},
"definitions": {
"UffizziCore_ActivityItem": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"namespace": {
"type": "string"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
},
"branch": {
"type": "string"
},
"type": {
"type": "string"
},
"container_id": {
"type": "string"
},
"commit": {
"type": "string"
},
"commit_message": {
"type": "string"
},
"build_id": {
"type": "integer"
},
"created_at": {
"type": "string",
"format": "date"
},
"updated_at": {
"type": "string",
"format": "date"
},
"data": {
"type": "object"
},
"digest": {
"type": "string"
},
"meta": {
"type": "object"
}
},
"required": [
"name"
]
},
"ComposeFile": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"source": {
"type": "string"
},
"path": {
"type": "string"
},
"content": {
"type": "string"
},
"auto_deploy": {
"type": "boolean"
},
"state": {
"type": "string"
},
"payload": {
"type": "string"
}
},
"required": [
"content"
]
},
"UffizziCore_ConfigFile": {
"type": "object",
"properties": {
"filename": {
"type": "string"
},
"kind": {
"type": "string"
},
"payload": {
"type": "string"
},
"source": {
"type": "string"
}
}
},
"Container": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"memory_limit": {
"type": "integer"
},
"memory_request": {
"type": "integer"
},
"continuously_deploy": {
"type": "string"
},
"variables": {
"type": "object"
},
"secret_variables": {
"type": "object"
},
"container_config_files": {
"$ref": "#/definitions/ConfigFile"
}
}
},
"Deployment": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"project_id": {
"type": "integer"
},
"kind": {
"type": "string"
},
"state": {
"type": "string"
},
"preview_url": {
"type": "string"
},
"tag": {
"type": "string"
},
"branch": {
"type": "string"
},
"commit": {
"type": "string"
},
"image_id": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"ingress_container_ready": {
"type": "boolean"
},
"ingress_container_state": {
"type": "string"
},
"creation_source": {
"type": "string"
},
"contaners": {
"type": "array",
"items": {
"type": "string"
}
},
"deployed_by": {
"type": "object"
}
}
},
"Project": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"secrets": {
"type": "string"
},
"default_compose": {
"type": "object",
"properties": {
"source": {
"type": "string"
}
}
},
"deployments": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"domain": {
"type": "string"
}
}
}
}
}
}
}
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/accounts/clusters_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Accounts::ClustersControllerTest < ActionController::TestCase
setup do
@developer = create(:user, :with_organizational_account)
sign_in(@developer)
end
test '#index' do
admin_user = create(:user, :with_organizational_account)
admin_user_org_account = admin_user.accounts.organizational.first
create(:membership, :developer, user: @developer, account: admin_user_org_account)
admin_project = create(:project, :with_members, account: admin_user_org_account, members: [admin_user, @developer])
create(:cluster, :deployed, project: admin_project, deployed_by_id: admin_user.id)
showable_cluster1 = create(:cluster, :deployed, project: admin_project, deployed_by_id: @developer.id)
admin_project2 = create(:project, :with_members, account: admin_user_org_account, members: [admin_user])
create(:user_project, :admin, user: @developer, project: admin_project2)
showable_cluster2 = create(:cluster, :deployed, project: admin_project2, deployed_by_id: admin_user.id)
other_user = create(:user, :with_organizational_account)
other_user_account = other_user.accounts.organizational.first
create(:membership, :developer, user: @developer, account: other_user_account)
other_project = create(:project, :with_members, account: other_user_account, members: [other_user, @developer])
create(:cluster, :deployed, project: other_project, deployed_by_id: other_user.id)
create(:cluster, :deployed, project: other_project, deployed_by_id: @developer.id)
get :index, params: { account_id: admin_user_org_account.id }, format: :json
assert_response(:success)
clusters_data = JSON.parse(response.body)['clusters']
assert_equal(2, clusters_data.count)
assert_equal([showable_cluster1.name, showable_cluster2.name], clusters_data.pluck('name').sort)
actual_project_name1 = clusters_data.min_by { |c| c['name'] }.dig('project', 'name')
actual_project_name2 = clusters_data.max_by { |c| c['name'] }.dig('project', 'name')
assert_equal(showable_cluster1.project.name, actual_project_name1)
assert_equal(showable_cluster2.project.name, actual_project_name2)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/accounts/credentials_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Accounts::CredentialsControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
@account = @user.personal_account
@project = create(:project, :with_members, account: @account, members: [@user])
sign_in @user
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
end
teardown do
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#index returns a list of credentials' do
create(:credential, :docker_hub, account: @account)
create(:credential, :amazon, account: @account)
params = { account_id: @account.id }
get :index, params: params, format: :json
assert_response :success
data = JSON.parse(response.body)
assert_equal(['UffizziCore::Credential::DockerHub', 'UffizziCore::Credential::Amazon'], data['credentials'])
end
test '#create docker hub credential' do
stubbed_dockerhub_login = stub_dockerhub_login
attributes = attributes_for(:credential, :docker_hub)
params = { account_id: @account.id, credential: attributes }
differences = {
-> { UffizziCore::Credential.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
assert_requested(stubbed_dockerhub_login)
end
test '#create if docker hub auth is failed' do
data = json_fixture('files/dockerhub/login_fail.json')
stubbed_dockerhub_login = stub_dockerhub_login_fail(data)
attributes = attributes_for(:credential, :docker_hub)
params = { account_id: @account.id, credential: attributes }
differences = {
-> { UffizziCore::Credential.count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :unprocessable_entity
assert_requested(stubbed_dockerhub_login)
end
test '#create azure credential' do
registry_url = generate(:url)
oauth2_token_response = { access_token: generate(:string) }
stubbed_azure_registry_oauth2_token = stub_azure_registry_oauth2_token(registry_url, oauth2_token_response)
attributes = attributes_for(:credential, :azure, registry_url: registry_url)
params = { account_id: @account.id, credential: attributes }
differences = {
-> { UffizziCore::Credential.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_requested stubbed_azure_registry_oauth2_token
assert_response :success
end
test '#create google credential' do
attributes = attributes_for(:credential, :google)
params = { account_id: @account.id, credential: attributes }
token_response = { token: generate(:string) }
stubbed_google_registry_token = stub_google_registry_token(token_response)
differences = {
-> { UffizziCore::Credential.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_requested stubbed_google_registry_token
assert_response :success
end
test '#create amazon credential' do
attributes = attributes_for(:credential, :amazon)
params = { account_id: @account.id, credential: attributes }
UffizziCore::ContainerRegistry::AmazonService.expects(:credential_correct?).at_least(1).returns(true)
differences = {
-> { UffizziCore::Credential.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create github_container_registry credential' do
attributes = attributes_for(:credential, :github_container_registry)
params = { account_id: @account.id, credential: attributes }
registry_url = attributes[:registry_url]
token_response = { token: generate(:string) }
stub_github_container_registry_access_token(registry_url, token_response)
differences = {
-> { UffizziCore::Credential.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create docker registry credential to ecr' do
attributes = attributes_for(:credential, :docker_registry)
attributes[:registry_url] = attributes_for(:credential, :amazon)[:registry_url]
params = { account_id: @account.id, credential: attributes }
UffizziCore::ContainerRegistry::DockerRegistryService.expects(:credential_correct?).at_least(1).returns(true)
differences = {
-> { UffizziCore::Credential.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#update when data changed' do
stubbed_dockerhub_login = stub_dockerhub_login
credential = create(:credential, :docker_hub, account: @account)
new_username = 'new username'
credential_attributes = attributes_for(:credential, :docker_hub, account: @account).merge(username: new_username)
params = { account_id: @account.id, credential: credential_attributes, type: credential_attributes[:type] }
differences = {
-> { UffizziCore::Credential.count } => 0,
}
assert_difference(differences) do
put :update, params: params, format: :json
end
assert_response(:success)
assert_equal(credential.reload.username, new_username)
assert_requested(stubbed_dockerhub_login)
end
test '#update when credential is the same' do
stubbed_dockerhub_login = stub_dockerhub_login
credential = create(:credential, :docker_hub, account: @account)
params = {
account_id: @account.id,
credential: {
password: credential.password,
username: credential.username,
registry_url: credential.registry_url,
},
type: credential.type,
}
put :update, params: params, format: :json
assert_response(:success)
refute_requested(stubbed_dockerhub_login)
end
test '#create duplicate credential' do
stub_dockerhub_login
stub_controller
credential = create(:credential, :docker_hub, account: @account)
differences = { -> { UffizziCore::Credential.count } => 0 }
attributes = attributes_for(:credential, :docker_hub, account: @account)
params = { account_id: @account.id, credential: attributes }
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :unprocessable_entity
assert_equal UffizziCore::Credential.last.id, credential.id
end
test '#check_credential valid credential' do
stub_dockerhub_login
stub_controller
attributes = attributes_for(:credential, :docker_hub, account: @account)
params = { account_id: @account.id, type: attributes[:type] }
post :check_credential, params: params, format: :json
assert_response :success
end
test '#check_credential duplicate credential' do
stub_dockerhub_login
stub_controller
credential = create(:credential, :docker_hub, account: @account)
params = { account_id: @account.id, type: credential.type }
post :check_credential, params: params, format: :json
assert_response :unprocessable_entity
end
test '#destroy docker hub credential' do
credential = create(:credential, :docker_hub, account: @account)
params = { account_id: @account.id, type: credential.type }
differences = {
-> { UffizziCore::Credential.count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
end
test '#destroy azure credential' do
credential = create(:credential, :azure, account: @account)
params = { account_id: @account.id, type: credential.type }
differences = {
-> { UffizziCore::Credential.count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
end
test '#destroy google credential' do
credential = create(:credential, :google, account: @account)
params = { account_id: @account.id, type: credential.type }
differences = {
-> { UffizziCore::Credential.count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
end
test '#destroy amazon credential' do
credential = create(:credential, :amazon, account: @account)
params = { account_id: @account.id, type: credential.type }
differences = {
-> { UffizziCore::Credential.count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
end
test '#destroy github_container_registry credential' do
credential = create(:credential, :github_container_registry, account: @account)
params = { account_id: @account.id, type: credential.type }
differences = {
-> { UffizziCore::Credential.count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
end
test '#destroy unexisted credential' do
params = { account_id: @account.id, type: UffizziCore::Credential::DockerHub.name }
differences = {
-> { UffizziCore::Credential.count } => 0,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :not_found
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/accounts/projects_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Accounts::ProjectsControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_organizational_account)
@account = @admin.accounts.organizational.first
sign_in(@admin)
end
test '#index' do
create(:project, :with_members, account: @account, members: [@admin])
get :index, params: { account_id: @account.id }, format: :json
data = JSON.parse(response.body)
assert(data['projects'].first['account'].present?)
assert_response(:success)
end
test '#create' do
attributes = attributes_for(:project)
create(:user, :developer_in_organization, organization: @account)
differences = {
-> { UffizziCore::Project.count } => 1,
-> { UffizziCore::UserProject.count } => 2,
}
assert_difference differences do
post :create, params: { account_id: @account.id, project: attributes }, format: :json
end
assert_response(:success)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/accounts_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::AccountsControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
sign_in(@user)
end
test '#index' do
get :index, format: :json
assert_response(:success)
end
test '#show' do
get :show, params: { name: 'wrong' }, format: :json
assert_response(:not_found)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/clusters/ingresses_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::Clusters::IngressesControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
@project = create(:project, :with_members, account: @user.personal_account, members: [@user])
@cluster = create(:cluster, project: @project, deployed_by: @user)
sign_in @user
end
test '#index' do
data = json_fixture('files/controller/ingresses.json')
stubbed_get_ingresses_request = stub_get_ingresses(data)
params = { project_slug: @project.slug, cluster_name: @cluster.name }
get :index, params: params, format: :json
assert_response(:success)
assert_requested(stubbed_get_ingresses_request)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/clusters_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::ClustersControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_organizational_account)
@account = @admin.accounts.organizational.first
@project = create(:project, :with_members, members: [@admin], account: @account)
@developer = create(:user)
create(:membership, :developer, account: @account, user: @developer)
create(:user_project, :developer, project: @project, user: @developer)
create(:kubernetes_distribution, :default)
end
teardown do
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#index lists all clusters to admins' do
sign_in(@admin)
create(:cluster, project: @project, deployed_by: @developer)
params = {
project_slug: @project.slug,
}
get :index, params: params, format: :json
assert_response(:success)
data = JSON.parse(response.body)
assert_equal(1, data['clusters'].count)
end
test '#index only shows clusters deployed by the same user for non-adminss' do
create(:cluster, project: @project, deployed_by: @admin)
create(:cluster, project: @project, deployed_by: @developer)
sign_in(@developer)
params = {
project_slug: @project.slug,
}
get :index, params: params, format: :json
assert_response(:success)
data = JSON.parse(response.body)
assert_equal(1, data['clusters'].count)
end
test '#create' do
sign_in(@admin)
cluster_creation_data = json_fixture('files/controller/cluster_not_ready.json')
params = {
project_slug: @project.slug,
cluster: {
name: cluster_creation_data[:name],
},
}
expected_request = {
name: cluster_creation_data[:name],
manifest: nil,
base_ingress_host: /#{UffizziCore::Cluster::NAMESPACE_PREFIX}\d/,
}
stubbed_create_cluster_request = stub_create_cluster_request_with_expected(cluster_creation_data, expected_request)
stubbed_create_namespace_request = stub_create_namespace_request
cluster_show_data = json_fixture('files/controller/cluster_ready.json')
stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)
differences = {
-> { UffizziCore::Cluster.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response(:success)
assert(UffizziCore::Cluster.find_by(name: cluster_creation_data[:name]).creation_source.manual?)
assert_requested(stubbed_create_cluster_request)
assert_requested(stubbed_create_namespace_request)
assert_requested(stubbed_cluster_request)
end
test '#create with wrong k8s version' do
sign_in(@admin)
params = {
project_slug: @project.slug,
cluster: {
name: 'my cluster',
k8s_version: 'wrong.version',
},
}
differences = {
-> { UffizziCore::Cluster.count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response(:unprocessable_entity)
end
test '#create when enabled cluster with the same name exists' do
sign_in(@admin)
name = 'test'
create(:cluster, project: @project, deployed_by: @admin, name: name)
params = {
project_slug: @project.slug,
cluster: {
name: name,
},
}
differences = {
-> { UffizziCore::Cluster.count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response(:unprocessable_entity)
end
test '#create with manifest' do
sign_in(@admin)
manifest = File.read('test/fixtures/files/cluster/manifest.yml')
cluster_creation_data = json_fixture('files/controller/cluster_not_ready.json')
cluster_show_data = json_fixture('files/controller/cluster_ready.json')
params = {
project_slug: @project.slug,
cluster: {
name: cluster_creation_data[:name],
manifest: manifest,
},
}
stubbed_create_namespace_request = stub_create_namespace_request
expected_request = {
name: cluster_creation_data[:name],
manifest: manifest,
base_ingress_host: /#{UffizziCore::Cluster::NAMESPACE_PREFIX}\d/,
}
stubbed_create_cluster_request = stub_create_cluster_request_with_expected(cluster_creation_data, expected_request)
stubbed_get_cluster_request = stub_get_cluster_request(cluster_show_data)
differences = {
-> { UffizziCore::Cluster.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response(:success)
assert_requested(stubbed_create_cluster_request)
assert_requested(stubbed_create_namespace_request)
assert_requested(stubbed_get_cluster_request)
end
test '#show shows cluster created by the same developer' do
cluster = create(:cluster, project: @project, deployed_by: @developer, name: 'test')
sign_in(@developer)
params = {
project_slug: @project.slug,
name: cluster.name,
}
get :show, params: params, format: :json
assert_response(:success)
end
test '#show does not show cluster created by a different user to developer' do
sign_in(@developer)
cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test')
params = {
project_slug: @project.slug,
name: cluster.name,
}
get :show, params: params, format: :json
assert_response(:not_found)
end
test '#show shows clusters created by a different user to admin' do
sign_in(@admin)
cluster = create(:cluster, project: @project, deployed_by: @developer, name: 'test')
params = {
project_slug: @project.slug,
name: cluster.name,
}
get :show, params: params, format: :json
assert_response(:success)
end
test '#scale_down' do
sign_in(@admin)
cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_DEPLOYED)
stubbed_scale_request = stub_scale_cluster_request
cluster_show_data = json_fixture('files/controller/cluster_asleep.json')
stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)
params = {
project_slug: @project.slug,
name: cluster.name,
}
put :scale_down, params: params, format: :json
assert_response(:success)
assert(cluster.reload.scaled_down?)
assert_requested(stubbed_scale_request)
assert_requested(stubbed_cluster_request)
end
test '#scale_up' do
sign_in(@admin)
cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_SCALED_DOWN)
stubbed_scale_request = stub_scale_cluster_request
cluster_show_data = json_fixture('files/controller/cluster_awake.json')
stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)
params = {
project_slug: @project.slug,
name: cluster.name,
}
put :scale_up, params: params, format: :json
assert_response(:success)
assert(cluster.reload.deployed?)
assert_requested(stubbed_scale_request)
assert_requested(stubbed_cluster_request)
end
test '#sync when the data is actual' do
sign_in(@admin)
cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_DEPLOYED)
cluster_show_data = json_fixture('files/controller/cluster_awake.json')
stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)
params = {
project_slug: @project.slug,
name: cluster.name,
}
put :sync, params: params, format: :json
assert_requested(stubbed_cluster_request)
assert(cluster.reload.deployed?)
end
test '#sync when the data needs to be updated' do
sign_in(@admin)
cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_DEPLOYED)
cluster_show_data = json_fixture('files/controller/cluster_asleep.json')
stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)
params = {
project_slug: @project.slug,
name: cluster.name,
}
put :sync, params: params, format: :json
assert_requested(stubbed_cluster_request)
assert(cluster.reload.scaled_down?)
end
test '#destroy developer can destroy a cluster created by him' do
sign_in(@developer)
cluster = create(:cluster, :deployed, project: @project, deployed_by: @developer, name: 'test')
stubbed_delete_namespace_request = stub_delete_namespace_request(cluster)
params = {
project_slug: @project.slug,
name: cluster.name,
}
delete :destroy, params: params, format: :json
assert_response(:success)
assert(cluster.reload.disabled?)
assert_requested(stubbed_delete_namespace_request)
end
test '#destroy developer cannot destroy a cluster created by other user' do
sign_in(@developer)
cluster = create(:cluster, :deployed, project: @project, deployed_by: @admin, name: 'test')
stubbed_delete_namespace_request = stub_delete_namespace_request(cluster)
params = {
project_slug: @project.slug,
name: cluster.name,
}
delete :destroy, params: params, format: :json
assert_response(:not_found)
refute_requested(stubbed_delete_namespace_request)
end
test '#destroy admin can destroy a cluster created by other user' do
sign_in(@admin)
cluster = create(:cluster, :deployed, project: @project, deployed_by: @developer, name: 'test')
stubbed_delete_namespace_request = stub_delete_namespace_request(cluster)
params = {
project_slug: @project.slug,
name: cluster.name,
}
delete :destroy, params: params, format: :json
assert_response(:success)
assert(cluster.reload.disabled?)
assert_requested(stubbed_delete_namespace_request)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/compose_files_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::ComposeFilesControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_personal_account)
@account = @admin.personal_account
@project = create(:project, :with_members, account: @account, members: [@admin])
@compose_file = create(:compose_file, project: @project, added_by: @admin)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin, payload: template_payload)
end
test '#show - admin gets compose file' do
sign_in @admin
params = { project_slug: @project.slug }
get :show, params: params, format: :json
assert_response :success
end
test '#show - returns 404 if compose file does not exist' do
sign_in @admin
@compose_file.destroy!
params = { project_slug: @project.slug }
get :show, params: params, format: :json
error_message = JSON.parse(response.body, symbolize_names: true)[:errors][:title].first
assert_response :not_found
assert_equal('UffizziCore::ComposeFile Not Found', error_message)
end
test '#create - docker compose file' do
sign_in @admin
@compose_file.destroy!
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-vote-app-docker.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
dependency = {
path: 'configs/vote.conf',
source: 'vote.conf',
content: json_fixture('files/compose_dependencies/configs/vote_conf.json')[:content],
use_kind: UffizziCore::ComposeFile::DependenciesService::DEPENDENCY_CONFIG_USE_KIND,
}
stub_dockerhub_repository_any
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [dependency],
}
differences = {
-> { UffizziCore::ComposeFile.main.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create - main compose file when already exists' do
sign_in @admin
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/test-compose-success-without-dependencies.yml')
encoded_content = Base64.encode64(file_content)
stub_dockerhub_repository_any
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
differences = {
-> { UffizziCore::ComposeFile.main.count } => 0,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create - check amazon ecr creation' do
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :amazon, account: @account)
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-amazon.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
differences = {
-> { UffizziCore::Template.count } => 1,
-> { UffizziCore::ComposeFile.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create - check github registry creation' do
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :github_container_registry, account: @account)
create(:credential, :docker_hub, account: @account)
@compose_file.destroy!
base_attributes = attributes_for(:compose_file).slice(:source, :path)
content = json_fixture('files/github/compose_files/hello_world_compose_github_container_registry.json')[:content]
compose_file_attributes = base_attributes.merge(content: content)
differences = {
-> { UffizziCore::Template.count } => 1,
-> { UffizziCore::ComposeFile.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test 'create - ECR via Docker Registry' do
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
registry_url = 'https://323707565364.dkr.ecr.us-east-1.amazonaws.com'
credential = create(:credential, :docker_registry, username: 'AWS', registry_url: registry_url, account: @account)
@compose_file.destroy!
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-amazon.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content)
stubbed_docker_registry_manifests = stub_docker_registry_manifests(credential.registry_url, 'test-compose', 'latest')
differences = {
-> { UffizziCore::Template.count } => 1,
-> { UffizziCore::ComposeFile.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
assert_requested(stubbed_docker_registry_manifests)
end
test '#create - compose file with volumes' do
sign_in @admin
@compose_file.destroy!
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/compose_files/compose_with_volumes.yml')
stub_dockerhub_repository_any
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
differences = {
-> { UffizziCore::ComposeFile.main.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create - with yaml aliases' do
sign_in @admin
@compose_file.destroy!
stub_dockerhub_repository('library', 'nginx')
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-with_alias.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
dependency = {
path: 'configs/vote.conf',
source: 'vote.conf',
content: json_fixture('files/compose_dependencies/configs/vote_conf.json')[:content],
use_kind: UffizziCore::ComposeFile::DependenciesService::DEPENDENCY_CONFIG_USE_KIND,
}
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [dependency],
}
differences = {
-> { UffizziCore::ComposeFile.main.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
end
test '#create - string command' do
sign_in @admin
@compose_file.destroy!
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-vote-app-with-command-as-string.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
stub_dockerhub_repository_any
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
differences = {
-> { UffizziCore::ComposeFile.main.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
template = UffizziCore::Template.last
assert_equal('["postgres", "-c", "jit=off"]', template.payload['containers_attributes'].first['command'])
end
test '#create - service name does not match RFC 123' do
sign_in @admin
@compose_file.destroy!
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-invalid-service-name.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [],
}
differences = {
-> { UffizziCore::ComposeFile.main.count } => 0,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response(:unprocessable_entity)
end
test '#destroy - deletes compose file' do
sign_in @admin
params = { project_slug: @project.slug }
differences = {
-> { UffizziCore::ComposeFile.count } => -1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :no_content
end
test '#destroy - when compose_file not found' do
sign_in @admin
@compose_file.destroy!
params = { project_slug: @project.slug }
delete :destroy, params: params, format: :json
error_message = JSON.parse(response.body, symbolize_names: true)[:errors][:title].first
assert_response :not_found
assert_equal('UffizziCore::ComposeFile Not Found', error_message)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/activity_items_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemsControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
@project = create(:project, :with_members, account: @user.personal_account, members: [@user])
@deployment = create(:deployment, project: @project)
sign_in @user
end
test '#index' do
container = create(:container, :with_public_port, deployment: @deployment)
create(:activity_item, :with_building_event, tag: container.tag, container: container, deployment: @deployment)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
}
get :index, params: params, format: :json
assert_response :success
end
test '#index - with failed deployment' do
@deployment.fail!
container = create(:container, :with_public_port, deployment: @deployment)
create(:activity_item, :with_building_event, tag: container.tag, container: container, deployment: @deployment)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
}
get :index, params: params, format: :json
assert_response :unprocessable_entity
response_body = JSON.parse(response.body)
assert_equal("Preview with ID deployment-#{@deployment.id} failed", response_body['errors']['title'].first)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers/logs_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::LogsControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
@project = create(:project, :with_members, account: @user.personal_account, members: [@user])
@deployment = create(:deployment, project: @project)
@container = create(:container, deployment: @deployment)
@pod_name = UffizziCore::ContainerService.pod_name(@container)
@limit = 30
@previous = false
sign_in @user
end
test '#index' do
controller_response = json_fixture('files/controller/logs.json')
pod_name = UffizziCore::ContainerService.pod_name(@container)
stubbed_request = stub_container_log_request(@deployment.id, pod_name, @limit, @previous, controller_response)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
container_name: @container.service_name,
limit: @limit,
previous: @previous,
}
get :index, params: params, format: :json
assert_requested stubbed_request
assert_response :success
data = JSON.parse(response.body)
expected_result = {
'logs' => [
{
'timestamp' => '2022-11-14 11:46:55.474 UTC',
'payload' => '/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration',
},
],
}
assert_equal(expected_result, data)
end
test '#index with empty logs info' do
controller_response = UffizziCore::Converters.deep_lower_camelize_keys(
{
logs: [],
},
)
stubbed_request = stub_container_log_request(@deployment.id, @pod_name, @limit, @previous, controller_response)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
container_name: @container.service_name,
limit: @limit,
previous: @previous,
}
get :index, params: params, format: :json
assert_requested stubbed_request
assert_response :success
end
test '#index with controller error' do
controller_response = UffizziCore::Converters.deep_lower_camelize_keys(
{
logs: [],
},
)
stubbed_request = stub_container_log_request(@deployment.id, @pod_name, @limit, @previous, controller_response)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
container_name: @container.service_name,
limit: @limit,
previous: @previous,
}
get :index, params: params, format: :json
assert_requested stubbed_request
assert_response :success
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::Deployments::ContainersControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
sign_in @user
@account = @user.personal_account
@project = create(:project, :with_members, account: @user.personal_account, members: [@user])
@deployment = create(:deployment, project: @project)
end
test '#index' do
repo = create(:repo, :docker_hub, project: @project)
create(
:container,
deployment: @deployment,
repo: repo,
image: 'uffizzitest/webhooks-test-app',
tag: 'latest',
secret_variables: [{ 'name' => 'test', 'value' => 'test' }],
)
params = { project_slug: @project.slug, deployment_id: @deployment.id }
get :index, params: params, format: :json
assert_response :success
end
test '#k8s_container_description - with last state' do
container = create(:container, deployment: @deployment, controller_name: 'f03d008a48')
controller_response = json_fixture('files/controller/deployment_containers_with_error.json')
stubbed_request = stub_controller_containers_request(@deployment, controller_response)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
container_name: container.service_name,
}
get :k8s_container_description, params: params, format: :json
assert_requested stubbed_request
assert_response :success
data = JSON.parse(response.body)
expected_result = {
'last_state' => {
'code' => 'terminated',
'reason' => 'Error',
'exit_code' => 127,
'started_at' => '2022-12-05T18:11:46Z',
'finished_at' => '2022-12-05T18:11:46Z',
},
}
assert_equal(expected_result, data)
end
test '#k8s_container_description - with empty last state' do
container = create(:container, deployment: @deployment, controller_name: 'f03d008a48')
controller_response = json_fixture('files/controller/deployment_containers.json')
stubbed_request = stub_controller_containers_request(@deployment, controller_response)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
container_name: container.service_name,
}
get :k8s_container_description, params: params, format: :json
assert_requested stubbed_request
assert_response :success
data = JSON.parse(response.body)
expected_result = { 'last_state' => {} }
assert_equal(expected_result, data)
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/events_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::Deployments::EventsControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
@project = create(:project, :with_members, account: @user.personal_account, members: [@user])
@deployment = create(:deployment, project: @project)
sign_in @user
end
test '#index' do
first_timestamp = generate(:string)
last_timestamp = generate(:string)
reason = generate(:string)
message = generate(:string)
events = UffizziCore::Converters.deep_lower_camelize_keys(
{
items: [
{
involved_object: { kind: 'Pod' },
first_timestamp: first_timestamp,
last_timestamp: last_timestamp,
reason: reason,
message: message,
},
],
},
)
stubbed_controller_get_deployment_events = stub_controller_get_deployment_events(@deployment, events)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
}
get :index, params: params, format: :json
assert_requested(stubbed_controller_get_deployment_events)
assert_response :success
collected_result = {
events: [
{
first_timestamp: first_timestamp,
last_timestamp: last_timestamp,
reason: reason,
message: message,
},
],
}.to_json
assert_equal(collected_result, response.body)
end
test '#index with empty logs info' do
events = UffizziCore::Converters.deep_lower_camelize_keys(
{
items: [],
},
)
stubbed_controller_get_deployment_events = stub_controller_get_deployment_events(@deployment, events)
params = {
project_slug: @project.slug,
deployment_id: @deployment.id,
}
get :index, params: params, format: :json
assert_requested(stubbed_controller_get_deployment_events)
assert_response :success
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/create_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_personal_account)
@account = @admin.personal_account
@project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])
@deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)
@metadata = {
'labels' => {
'github' => {
'repository' => 'feature/#24_My_awesome_feature',
'pull_request' => {
'number' => '24',
},
},
},
}
@deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
full_image_name: "#{image}:#{target_branch}",
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
@template = create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin,
payload: template_payload)
sign_in @admin
end
test '#create - from the existing compose file' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
file_content = File.read('test/fixtures/files/test-compose-success.yml')
encoded_content = Base64.encode64(file_content)
compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
:with_named_volume,
image: image,
tag: target_branch,
full_image_name: "#{image}:#{target_branch}",
healthcheck: { test: ['CMD', 'curl', '-f', 'https://localhost'] },
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
stub_dockerhub_repository('library', 'redis')
params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: {} }
differences = {
-> { UffizziCore::Deployment.active.count } => 1,
-> { UffizziCore::Repo::DockerHub.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
subdomains = UffizziCore::Deployment.active.map(&:subdomain)
assert_nil(subdomains.detect { |s| s.include?('_') })
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from the existing compose file with metadata' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
file_content = File.read('test/fixtures/files/test-compose-success.yml')
encoded_content = Base64.encode64(file_content)
compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
:with_named_volume,
image: image,
tag: target_branch,
full_image_name: "#{image}:#{target_branch}",
healthcheck: { test: ['CMD', 'curl', '-f', 'https://localhost'] },
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
stub_dockerhub_repository('library', 'redis')
params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: @metadata }
post :create, params: params, format: :json
assert_response :success
deployment = compose_file.deployments.first
assert_equal(@metadata, deployment.metadata)
assert_equal(deployment.subdomain.downcase, deployment.subdomain)
assert_equal(deployment.creation_source, UffizziCore::Deployment.creation_source.compose_file_manual)
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from the existing compose file with metadata when an active deployment exists - should return an eror' do
create(:deployment, metadata: @metadata, project: @project)
params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: @metadata }
post :create, params: params, format: :json
assert_response :unprocessable_entity
end
test '#create - from the existing compose file with github_actions creation source (for self-hosted version)' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
file_content = File.read('test/fixtures/files/test-compose-success.yml')
encoded_content = Base64.encode64(file_content)
compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
image: image,
tag: target_branch,
full_image_name: "#{image}:#{target_branch}",
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
stub_dockerhub_repository('library', 'redis')
creation_source = UffizziCore::Deployment.creation_source.github_actions
params = { project_slug: @project.slug, compose_file: {}, dependencies: [], creation_source: creation_source }
post :create, params: params, format: :json
assert_response :success
deployment = compose_file.deployments.first
assert_equal(creation_source, deployment.creation_source)
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from the existing compose file when credentials are removed' do
create_namespace_request = stub_create_namespace_request
file_content = File.read('test/fixtures/files/uffizzi-compose-vote-app-docker.yml')
encoded_content = Base64.encode64(file_content)
compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
full_image_name: "#{image}:#{target_branch}",
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
stub_dockerhub_private_repository('library', 'redis')
params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: {} }
differences = {
-> { UffizziCore::Deployment.active.count } => 0,
-> { UffizziCore::Repo::DockerHub.count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :unprocessable_entity
assert_not_requested(create_namespace_request)
end
test '#create - from the existing compose file - when the file is invalid' do
create(:credential, :github_container_registry, account: @admin.personal_account)
compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
full_image_name: "#{image}:#{target_branch}",
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: {} }
differences = {
-> { UffizziCore::Deployment.active.count } => 0,
-> { UffizziCore::Repo::DockerHub.count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :unprocessable_entity
end
test '#create - when compose file does not exist and no params given' do
params = {
project_slug: @project.slug,
compose_file: {},
dependencies: [],
metadata: {},
}
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 0,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :not_found
end
test '#create - when compose file does not exist and use docker registry without auth' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
stub_docker_registry_manifests('https://ttl.sh', 'abc', '1h')
compose_file_content = File.read('test/fixtures/files/uffizzi-compose-docker-registry-anonymous.yml')
encoded_compose_file_content = Base64.encode64(compose_file_content)
compose_file = {
source: '/gem/tmp/docker-compose.uffizzi.yaml',
path: '/gem/tmp/docker-compose.uffizzi.yaml',
content: encoded_compose_file_content,
}
params = {
project_slug: @project.slug,
compose_file: compose_file,
dependencies: [],
metadata: {},
}
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - with content when compose file does not exist' do
deployment_data = json_fixture('files/controller/deployments.json')
stubbed_namespace_request = stub_controller_get_namespace_request_any(deployment_data)
stubbed_controller_create_name_request = stub_create_namespace_request
stub_controller_apply_credential
compose_file_name = 'test-compose-full.yml'
file_content = File.read("test/fixtures/files/#{compose_file_name}")
encoded_content = Base64.encode64(file_content)
stub_dockerhub_repository_any
# rubocop:disable Layout/LineLength
params = {
project_slug: @project.slug,
compose_file: {
source: "/gem/tmp/#{compose_file_name}",
path: "/gem/tmp/#{compose_file_name}",
content: encoded_content,
},
dependencies: [
{
content: "ZGF0YQ1==\n",
is_file: false,
path: '/gem/tmp/some_app_dir',
source: './some_app_dir',
use_kind: 'volume',
},
{
content: "ZGF0YQ2==\n",
is_file: true,
path: '/gem/tmp/files/some_app_file',
source: './files/some_app_file',
use_kind: 'volume',
},
{
content: "ZGF0YQ3==\n",
is_file: false,
path: '/gem/tmp/some_db_dir',
source: './some_db_dir',
use_kind: 'volume',
},
{
content: "ZGF0YQ4==\n",
is_file: true,
path: '/gem/tmp/some_db_file',
source: './some_db_file',
use_kind: 'volume',
},
{
content: "ZGF0YQ==\n",
is_file: false,
path: '/gem/tmp',
source: './',
use_kind: 'volume',
},
{
content: "S0VZPXZhbHVl\n",
path: 'env_files/env_file.env',
source: 'env_files/env_file.env',
use_kind: 'config_map',
},
{
content: "UE9TVEdSRVNfVVNFUj1wb3N0Z3JlcyBQT1NUR1JFU19QQVNTV09SRD1wb3N0\nZ3Jlcw==\n",
path: 'local.env',
source: 'local.env',
use_kind: 'config_map',
},
{
content: "c2VydmVyIHsgbGlzdGVuICAgICAgIDg4ODg7IHNlcnZlcl9uYW1lICBsb2Nh\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\n",
path: 'config_files/config_file.conf',
source: 'config_files/config_file.conf',
use_kind: 'config_map',
},
{
content: "c2VydmVyIHsgbGlzdGVuICAgICAgIDgwODA7IHNlcnZlcl9uYW1lICBsb2Nh\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\n",
path: 'app.conf',
source: 'app.conf',
use_kind: 'config_map',
},
],
metadata: {},
}
# rubocop:enable Layout/LineLength
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Deployment.count } => 1,
-> { UffizziCore::Container.count } => 3,
-> { UffizziCore::HostVolumeFile.count } => 5,
-> { UffizziCore::ConfigFile.count } => 1,
-> { UffizziCore::Repo.count } => 3,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
assert_requested(stubbed_controller_create_name_request)
assert_requested(stubbed_namespace_request)
default_container_attributes = {
image: nil,
tag: nil,
service_name: nil,
variables: [],
public: false,
port: nil,
state: 'active',
continuously_deploy: 'enabled',
kind: 'user',
target_port: nil,
controller_name: nil,
receive_incoming_requests: false,
memory_request: 125 / UffizziCore::Container::REQUEST_MEMORY_RATIO,
memory_limit: 125,
secret_variables: [],
entrypoint: nil,
command: nil,
healthcheck: {},
volumes: [],
additional_subdomains: [],
source: nil,
}
app_container_attributes = {
image: 'uffizzicloud/app',
tag: 'latest',
service_name: 'app',
volumes: [
{
type: 'host',
source: './some_app_dir',
target: '/var/app/some_dir',
read_only: false,
},
{
type: 'host',
source: './files/some_app_file',
target: '/var/app/some_app_files',
read_only: false,
},
{
type: 'host',
source: './',
target: '/var/entire_app',
read_only: false,
},
{
type: 'named',
source: 'app_share',
target: '/some_app_share',
read_only: true,
},
{
type: 'anonymous',
source: '/some_anonymous_dir',
target: nil,
read_only: false,
},
],
variables: [
{
name: 'POSTGRES_USER',
value: 'postgres POSTGRES_PASSWORD=postgres',
},
{
name: 'KEY',
value: 'value',
},
],
}
db_container_attributes = {
image: 'library/postgres',
tag: 'latest',
service_name: 'db',
volumes: [
{
type: 'host',
source: './some_app_dir',
target: '/var/db/some_dir_2',
read_only: false,
},
{
type: 'host',
source: './some_db_dir',
target: '/var/db/some_dir_3',
read_only: false,
},
{
type: 'host',
source: './some_db_file',
target: '/var/db/some_db_files',
read_only: false,
},
{
type: 'named',
source: 'db_share',
target: '/some_db_share',
read_only: true,
},
],
}
nginx_container_attributes = {
image: 'library/nginx',
tag: '1.32',
service_name: 'nginx',
port: 80,
target_port: 80,
public: true,
receive_incoming_requests: true,
}
expected_app_container_attributes = default_container_attributes.merge(app_container_attributes)
expected_db_container_attributes = default_container_attributes.merge(db_container_attributes)
expected_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes)
exclude_params = [:state, :source, :kind, :target_port, :controller_name]
expected_template_app_container_attributes = default_container_attributes.merge(app_container_attributes).without(exclude_params)
expected_template_db_container_attributes = default_container_attributes.merge(db_container_attributes).without(exclude_params)
expected_template_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes).without(exclude_params)
container_keys = default_container_attributes.keys
deployment = UffizziCore::Deployment.last
actual_containers_attributes = deployment.containers.map { |c| c.attributes.deep_symbolize_keys.slice(*container_keys) }
actual_template_containers_attributes = deployment.compose_file.template.payload
.deep_symbolize_keys[:containers_attributes]
.map { |c| c.slice(*container_keys) }
actual_app_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'app' }
actual_db_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'db' }
actual_nginx_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'nginx' }
actual_template_app_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'app' }
actual_template_db_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'db' }
actual_template_nginx_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'nginx' }
assert_equal expected_app_container_attributes, actual_app_container_attributes
assert_equal expected_template_app_container_attributes, actual_template_app_container_attributes
assert_equal expected_db_container_attributes, actual_db_container_attributes
assert_equal expected_template_db_container_attributes, actual_template_db_container_attributes
assert_equal expected_nginx_container_attributes, actual_nginx_container_attributes
assert_equal expected_template_nginx_container_attributes, actual_template_nginx_container_attributes
actual_host_volume_file_paths = UffizziCore::HostVolumeFile.pluck(:path)
expected_host_volume_file_paths = params[:dependencies].select { |d| d[:use_kind] == 'volume' }.pluck(:path)
assert_equal expected_host_volume_file_paths.sort, actual_host_volume_file_paths.sort
actual_host_volume_file_sources = UffizziCore::HostVolumeFile.pluck(:source)
expected_host_volume_file_sources = params[:dependencies]
.select { |d| d[:use_kind] == 'volume' }
.pluck(:source)
.map { |s| "#{compose_file_name}/#{s}" }
assert_equal expected_host_volume_file_sources.sort, actual_host_volume_file_sources.sort
actual_host_volume_file_count_which_is_file = UffizziCore::HostVolumeFile.where(is_file: true).count
assert_equal(2, actual_host_volume_file_count_which_is_file)
end
test '#create - file with local host volume when same host volume file exists' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
stub_dockerhub_login
stub_dockerhub_repository('library', 'nginx')
create(:credential, :docker_hub, account: @admin.personal_account)
compose_file_content = File.read('test/fixtures/files/uffizzi-compose-with-host-volumes.yml')
encoded_compose_file_content = Base64.encode64(compose_file_content)
host_volume_content = Base64.encode64(File.binread('test/fixtures/files/file.tar.gz'))
compose_file_params = {
source: '/gem/tmp/dc.uffizzi-nginx.yaml',
path: '/gem/tmp/dc.uffizzi-nginx.yaml',
content: encoded_compose_file_content,
}
dependency = {
path: '/gem/tmp/share_dir',
source: './share_dir',
content: host_volume_content,
use_kind: UffizziCore::ComposeFile::DependenciesService::DEPENDENCY_VOLUME_USE_KIND,
is_file: false,
}
compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_compose_file_content)
create(:host_volume_file, path: dependency[:path],
source: 'dc.uffizzi-nginx.yaml/./share_dir',
payload: Base64.decode64(dependency[:content]),
is_file: false,
project: @project,
compose_file: compose_file)
params = { project_slug: @project.slug, compose_file: compose_file_params, dependencies: [dependency] }
differences = {
-> { UffizziCore::Container.count } => 1,
-> { UffizziCore::ContainerHostVolumeFile.count } => 1,
-> { UffizziCore::HostVolumeFile.count } => 0,
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from amazon image with credentials' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
registry_url = 'https://323707565364.dkr.ecr.us-east-1.amazonaws.com'
stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :amazon, :active, account: @account, registry_url: registry_url)
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-amazon.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
metadata: {},
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - compose file with jfrog docker registry with auth' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
stub_docker_registry_manifests('https://elnealo.jfrog.io', 'uffizzi-test-docker/webhook-test-app', 'latest')
compose_file_content = File.read('test/fixtures/files/test-compose-success-jfrog.yml')
encoded_compose_file_content = Base64.encode64(compose_file_content)
compose_file = {
source: '/gem/tmp/docker-compose.uffizzi.yaml',
path: '/gem/tmp/docker-compose.uffizzi.yaml',
content: encoded_compose_file_content,
}
params = {
project_slug: @project.slug,
compose_file: compose_file,
dependencies: [],
metadata: {},
}
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
assert_difference differences do
post :create, params: params, format: :json
end
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from azure image with credentials' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
registry_url = 'account.azurecr.io/nginx:latest'
stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :azure, :active, account: @account, registry_url: registry_url)
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-azure.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
metadata: {},
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from google(gcr) image with credentials' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
registry_url = 'gcr.io/project1/test-compose:latest'
stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :google, :active, account: @account, registry_url: registry_url)
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-google.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
metadata: {},
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from ghcr image with credentials' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
registry_url = 'ghcr.io/project1/test-compose:latest'
stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :github_container_registry, :active, account: @account, registry_url: registry_url)
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-ghcr.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
metadata: {},
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#create - from dockerhub image with credentials' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
registry_url = 'project1/test-compose:latest'
stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')
stubbed_dockerhub_login = stub_dockerhub_login
stub_dockerhub_repository('project1', 'test-compose')
sign_in @admin
project = create(:project, :with_members, account: @account, members: [@admin])
create(:credential, :docker_hub, :active, account: @account, registry_url: registry_url)
base_attributes = attributes_for(:compose_file).slice(:source, :path)
file_content = File.read('test/fixtures/files/uffizzi-compose-dockerhub.yml')
encoded_content = Base64.encode64(file_content)
compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { UffizziCore::Container.count } => 1,
}
params = {
project_slug: project.slug,
compose_file: compose_file_attributes,
dependencies: [],
metadata: {},
}
assert_difference differences do
post :create, params: params, format: :json
end
assert_response :success
assert_requested stubbed_dockerhub_login, times: 2
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/deploy_containers_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_personal_account)
@account = @admin.personal_account
@project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])
@deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)
@deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))
@credential = create(:credential, :github_container_registry, account: @account)
sign_in @admin
end
test '#deploy_containers' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.fake!
stub_dockerhub_login
repo1 = create(:repo, :docker_hub, project: @project)
repo2 = create(:repo, :docker_hub, project: @project)
create(:container, :with_public_port, deployment: @deployment, repo: repo1)
create(:container, :with_public_port, deployment: @deployment, repo: repo2)
params = { project_slug: @project.slug, id: @deployment.id }
post :deploy_containers, params: params, format: :json
assert_response :success
assert { UffizziCore::Deployment::DeployContainersJob.jobs.size == 1 }
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
end
test '#deploy_containers if deployment has not created yet' do
UffizziCore::ControllerService.expects(:namespace_exists?).returns(false)
params = { project_slug: @project.slug, id: @deployment.id }
assert_raises UffizziCore::DeploymentNotFoundError do
post :deploy_containers, params: params, format: :json
end
end
test '#deploy_containers create a new docker hub activity item' do
UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)
webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')
digest_data = json_fixture('files/dockerhub/digest.json')
deployment_containers_data = json_fixture('files/controller/deployment_containers.json')
deployment_data = json_fixture('files/controller/deployments.json')
stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)
stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)
stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)
stubbed_dockerhub_login = stub_dockerhub_login
params = { project_slug: @project.slug, id: @deployment.id }
namespace, name = webhooks_data[:repository][:repo_name].split('/')
repo = create(:repo,
:docker_hub,
project: @project,
namespace: namespace,
name: name)
container = create(
:container,
:with_public_port,
:continuously_deploy_enabled,
deployment: @deployment,
repo: repo,
image: webhooks_data[:repository][:repo_name],
tag: webhooks_data[:push_data][:tag],
controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],
)
create(:credential, :docker_hub, account: @account)
stubbed_digest_auth = stub_dockerhub_auth_for_digest(container.image)
stubbed_digest = stub_dockerhub_get_digest(container.image, container.tag, digest_data)
post :deploy_containers, params: params, format: :json
assert_requested stubbed_digest
assert_requested stubbed_digest_auth
assert_requested stubbed_dockerhub_login
assert_requested stubbed_deploy_containers_request
assert_requested stubbed_containers_request
assert_requested stubbed_namespace_request
assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }
assert { UffizziCore::ActivityItem::Docker.count == 1 }
end
test '#deploy_containers skip activity item creation if existing has not finished yet' do
UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)
webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')
digest_data = json_fixture('files/dockerhub/digest.json')
deployment_containers_data = json_fixture('files/controller/deployment_containers.json')
deployment_data = json_fixture('files/controller/deployments.json')
namespace, name = webhooks_data[:repository][:repo_name].split('/')
repo = create(:repo,
:docker_hub,
project: @project,
namespace: namespace,
name: name)
container = create(
:container,
:with_public_port,
:continuously_deploy_enabled,
deployment: @deployment,
repo: repo,
image: webhooks_data[:repository][:repo_name],
tag: webhooks_data[:push_data][:tag],
controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],
)
create(:activity_item,
:docker,
:with_building_event,
namespace: repo.namespace,
name: repo.name,
tag: container.tag,
container: container,
deployment: @deployment)
stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)
stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)
stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)
stubbed_dockerhub_login = stub_dockerhub_login
params = { project_slug: @project.slug, id: @deployment.id }
create(:credential, :docker_hub, account: @account)
stubbed_digest_auth = stub_dockerhub_auth_for_digest(container.image)
stubbed_digest = stub_dockerhub_get_digest(container.image, container.tag, digest_data)
post :deploy_containers, params: params, format: :json
assert_requested stubbed_digest
assert_requested stubbed_digest_auth
assert_requested stubbed_dockerhub_login
assert_requested stubbed_deploy_containers_request
assert_requested stubbed_containers_request
assert_requested stubbed_namespace_request
assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }
assert { UffizziCore::ActivityItem::Docker.count == 1 }
end
test '#deploy_containers create a new activity item creation if existing has finished' do
UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)
webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')
digest_data = json_fixture('files/dockerhub/digest.json')
deployment_containers_data = json_fixture('files/controller/deployment_containers.json')
deployment_data = json_fixture('files/controller/deployments.json')
namespace, name = webhooks_data[:repository][:repo_name].split('/')
repo = create(:repo,
:docker_hub,
project: @project,
namespace: namespace,
name: name)
container = create(
:container,
:with_public_port,
:continuously_deploy_enabled,
deployment: @deployment,
repo: repo,
image: webhooks_data[:repository][:repo_name],
tag: webhooks_data[:push_data][:tag],
controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],
)
create(:activity_item,
:docker,
:with_deployed_event,
namespace: repo.namespace,
name: repo.name,
tag: container.tag,
container: container,
deployment: @deployment)
stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)
stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)
stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)
stubbed_dockerhub_login = stub_dockerhub_login
params = { project_slug: @project.slug, id: @deployment.id }
create(:credential, :docker_hub, account: @account)
stubbed_digest_auth = stub_dockerhub_auth_for_digest(container.image)
stubbed_digest = stub_dockerhub_get_digest(container.image, container.tag, digest_data)
post :deploy_containers, params: params, format: :json
assert_requested stubbed_digest
assert_requested stubbed_digest_auth
assert_requested stubbed_dockerhub_login
assert_requested stubbed_deploy_containers_request
assert_requested stubbed_containers_request
assert_requested stubbed_namespace_request
assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }
assert { UffizziCore::ActivityItem::Docker.count == 2 }
end
test '#deploy_containers create a new activity item creation without credential' do
UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)
webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')
deployment_containers_data = json_fixture('files/controller/deployment_containers.json')
deployment_data = json_fixture('files/controller/deployments.json')
namespace, name = webhooks_data[:repository][:repo_name].split('/')
repo = create(:repo,
:docker_hub,
project: @project,
namespace: namespace,
name: name)
container = create(
:container,
:continuously_deploy_enabled,
:with_public_port,
:with_named_volume,
deployment: @deployment,
repo: repo,
image: webhooks_data[:repository][:repo_name],
tag: webhooks_data[:push_data][:tag],
controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],
)
create(:activity_item,
:docker,
:with_deployed_event,
namespace: repo.namespace,
name: repo.name,
tag: container.tag,
container: container,
deployment: @deployment)
stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)
stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)
stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)
params = { project_slug: @project.slug, id: @deployment.id }
post :deploy_containers, params: params, format: :json
assert { UffizziCore::ActivityItem.last.digest.nil? }
assert_requested stubbed_deploy_containers_request
assert_requested stubbed_containers_request
assert_requested stubbed_namespace_request
assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }
assert { UffizziCore::ActivityItem::Docker.count == 2 }
end
test '#deploy_containers create a new activity items' do
metadata = {
'labels' => {
'github' => {
'repository' => 'feature/#24_my_awesome_feature',
'event' => {
'number' => '24',
},
},
},
}
@deployment.update!(metadata: metadata)
UffizziCore::ControllerService.expects(:namespace_exists?).at_least(1).returns(true)
digest_data = json_fixture('files/dockerhub/digest.json')
deployment_containers_data = json_fixture('files/controller/deployment_containers.json')
deployment_data = json_fixture('files/controller/deployments.json')
stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)
stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)
stubbed_dockerhub_login = stub_dockerhub_login
compose_file_name = 'test-compose-full.yml'
file_content = File.read("test/fixtures/files/#{compose_file_name}")
encoded_content = Base64.encode64(file_content)
compose_file = create(:compose_file, content: encoded_content, repository_id: nil, branch: nil, project: @project)
@deployment.update!(compose_file: compose_file)
controller_name = deployment_containers_data.first[:spec][:containers].first[:controllerName]
app_name = 'app'
app_namespace = 'uffizzicloud'
nginx_name = 'nginx'
nginx_namespace = 'library'
host_volume_file_attrs_app_dir = {
type: 'host',
source: './some_app_dir',
target: '/var/app/some_dir',
read_only: false,
}
host_volume_file_attrs_app_dir2 = {
type: 'host',
source: './',
target: '/var/entire_app',
read_only: false,
}
host_volume_file_attrs_app_file = {
type: 'host',
source: './files/some_app_file',
target: '/var/app/some_app_files',
read_only: false,
}
container_app_attrs = {
controller_name: controller_name,
service_name: app_name,
image: "#{app_namespace}/#{app_name}",
tag: 'latest',
full_image_name: "#{app_namespace}/#{app_name}:latest",
volumes: [
host_volume_file_attrs_app_dir,
host_volume_file_attrs_app_file,
host_volume_file_attrs_app_dir2,
{
type: 'named',
source: 'app_share',
target: '/some_app_share',
read_only: true,
},
{
type: 'anonymous',
source: '/some_anonymous_dir',
target: nil,
read_only: false,
},
],
}
container_nginx_attrs = {
controller_name: controller_name,
service_name: nginx_name,
image: "#{nginx_namespace}/#{nginx_name}",
tag: '1.32',
full_image_name: "#{nginx_namespace}/#{nginx_name}:1.32",
public: true,
port: 80,
target_port: 80,
}
host_volume_file_app_dir_params = {
path: host_volume_file_attrs_app_dir[:source],
source: "#{compose_file_name}/#{host_volume_file_attrs_app_dir[:source]}",
payload: 'some_app_dir_data',
is_file: false,
project: @project,
compose_file: compose_file,
}
host_volume_file_app_dir2_params = {
path: host_volume_file_attrs_app_dir2[:source],
source: "#{compose_file_name}/#{host_volume_file_attrs_app_dir2[:source]}",
payload: 'some_app_dir_data',
is_file: false,
project: @project,
compose_file: compose_file,
}
host_volume_file_app_file_params = {
path: host_volume_file_attrs_app_file[:source],
source: "#{compose_file_name}/#{host_volume_file_attrs_app_file[:source]}",
payload: 'some_app_file_data',
is_file: true,
project: @project,
compose_file: compose_file,
}
docker_hub_credential = create(:credential, :docker_hub, account: @account)
app_repo = create(:repo, :docker_hub, project: @project, namespace: app_namespace, name: app_name)
nginx_repo = create(:repo, :docker_hub, project: @project, namespace: nginx_namespace, name: nginx_name)
host_volume_file_app_dir = create(:host_volume_file, **host_volume_file_app_dir_params)
host_volume_file_app_dir2 = create(:host_volume_file, **host_volume_file_app_dir2_params)
host_volume_file_app_file = create(:host_volume_file, **host_volume_file_app_file_params)
app_container = create(:container, :continuously_deploy_enabled,
**{ deployment: @deployment, repo: app_repo }.merge(container_app_attrs))
nginx_container = create(:container, :with_public_port, :continuously_deploy_enabled,
**{ deployment: @deployment, repo: nginx_repo }.merge(container_nginx_attrs))
container_host_volume_files_app_dir = create(:container_host_volume_file, container: app_container,
host_volume_file: host_volume_file_app_dir,
source_path: host_volume_file_attrs_app_dir[:source])
container_host_volume_files_app_dir2 = create(:container_host_volume_file, container: app_container,
host_volume_file: host_volume_file_app_dir2,
source_path: host_volume_file_attrs_app_dir2[:source])
container_host_volume_files_app_file = create(:container_host_volume_file, container: app_container,
host_volume_file: host_volume_file_app_file,
source_path: host_volume_file_attrs_app_file[:source])
app_config_file = create(:config_file, :compose_file_source, project: @project, payload: 'data', filename: 'config_file.conf')
app_container_config_file = create(:container_config_file, container: app_container, config_file: app_config_file)
stubbed_digest_auth_app = stub_dockerhub_auth_for_digest(app_container.image)
stubbed_digest_auth_nginx = stub_dockerhub_auth_for_digest(nginx_container.image)
stubbed_digest_app = stub_dockerhub_get_digest(app_container.image, app_container.tag, digest_data)
stubbed_digest_nginx = stub_dockerhub_get_digest(nginx_container.image, nginx_container.tag, digest_data)
expected_default_container_params = {
secret_variables: [],
memory_limit: Settings.compose.default_memory,
memory_request: Settings.compose.default_memory,
entrypoint: nil,
command: nil,
port: nil,
target_port: nil,
public: false,
receive_incoming_requests: false,
healthcheck: {},
volumes: nil,
container_config_files: [],
additional_subdomains: [],
container_host_volume_files: [],
}
expected_app_container_attrs = {
id: app_container.id,
kind: app_container.kind,
variables: [
{
name: 'UFFIZZI_URL',
value: "https://#{@deployment.preview_url}",
},
{
name: 'UFFIZZI_DOMAIN',
value: @deployment.preview_url,
},
{
name: 'UFFIZZI_PREDICTABLE_URL',
value: '',
},
],
container_host_volume_files: [
{
host_volume_file_id: host_volume_file_app_dir.id,
source_path: container_host_volume_files_app_dir.source_path,
},
{
host_volume_file_id: host_volume_file_app_dir2.id,
source_path: container_host_volume_files_app_dir2.source_path,
},
{
host_volume_file_id: host_volume_file_app_file.id,
source_path: container_host_volume_files_app_file.source_path,
},
],
container_config_files: [
{
mount_path: app_container_config_file.mount_path,
config_file: {
id: app_config_file.id,
filename: app_config_file.filename,
kind: app_config_file.kind,
payload: app_config_file.payload,
},
},
],
}
expected_nginx_container_attrs = {
id: nginx_container.id,
kind: nginx_container.kind,
variables: [
{
name: 'UFFIZZI_URL',
value: "https://#{@deployment.preview_url}",
},
{
name: 'UFFIZZI_DOMAIN',
value: @deployment.preview_url,
},
{
name: 'PORT',
value: '80',
},
{
name: 'UFFIZZI_PREDICTABLE_URL',
value: '',
},
],
}
expected_app_container = expected_default_container_params
.merge(expected_app_container_attrs)
.merge(container_app_attrs.except(:image, :tag))
expected_nginx_container = expected_default_container_params
.merge(expected_nginx_container_attrs)
.merge(container_nginx_attrs.except(:image, :tag))
expected_request_container_to_controller = {
containers: [
expected_app_container,
expected_nginx_container,
],
credentials: [{ id: docker_hub_credential.id }, { id: @credential.id }],
deployment_url: @deployment.preview_url,
compose_file: { source_kind: 'local' },
host_volume_files: [
{
id: host_volume_file_app_dir.id,
source: host_volume_file_app_dir_params[:source],
path: host_volume_file_app_dir_params[:path],
payload: Base64.encode64(host_volume_file_app_dir_params[:payload]),
is_file: host_volume_file_app_dir_params[:is_file],
},
{
id: host_volume_file_app_dir2.id,
source: host_volume_file_app_dir2_params[:source],
path: host_volume_file_app_dir2_params[:path],
payload: Base64.encode64(host_volume_file_app_dir2_params[:payload]),
is_file: host_volume_file_app_dir2_params[:is_file],
},
{
id: host_volume_file_app_file.id,
source: host_volume_file_app_file_params[:source],
path: host_volume_file_app_file_params[:path],
payload: Base64.encode64(host_volume_file_app_file_params[:payload]),
is_file: host_volume_file_app_file_params[:is_file],
},
],
}
expected_request_config_file_to_controller = {
config_file: {
id: app_config_file.id,
filename: app_config_file.filename,
kind: app_config_file.kind,
payload: app_config_file.payload,
},
}
stub_apply_config_file_request_with_expected(@deployment, app_config_file, expected_request_config_file_to_controller)
stubbed_deploy_containers_request = stub_deploy_containers_request_with_expected(@deployment, expected_request_container_to_controller)
params = { project_slug: @project.slug, id: @deployment.id }
post :deploy_containers, params: params, format: :json
assert_requested stubbed_deploy_containers_request
assert_requested stubbed_digest_app
assert_requested stubbed_digest_nginx
assert_requested stubbed_digest_auth_app
assert_requested stubbed_digest_auth_nginx
assert_requested stubbed_dockerhub_login, times: 2
assert_requested stubbed_containers_request, times: 2
assert_requested stubbed_namespace_request, times: 2
assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }
assert { UffizziCore::ActivityItem::Docker.count == 2 }
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/destroy_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_personal_account)
@account = @admin.personal_account
@project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])
@deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)
@metadata = {
'labels' => {
'github' => {
'repository' => 'feature/#24_my_awesome_feature',
'pull_request' => {
'number' => '24',
},
},
},
}
@deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))
@credential = create(:credential, :github_container_registry, account: @account)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
@template = create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin,
payload: template_payload)
sign_in @admin
end
test '#destroy' do
Sidekiq::Worker.clear_all
Sidekiq::Testing.inline!
stubbed_request = stub_delete_namespace_request(@deployment)
container = create(:container, :with_public_port, deployment: @deployment)
differences = {
-> { UffizziCore::Deployment.active.count } => -1,
}
params = {
project_slug: @project.slug,
id: @deployment.id,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_requested(stubbed_request)
assert { container.reload.disabled? }
assert_response :success
Sidekiq::Worker.clear_all
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/update_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_personal_account)
@account = @admin.personal_account
@project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])
@deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)
@metadata = {
'labels' => {
'github' => {
'repository' => 'feature/#24_my_awesome_feature',
'pull_request' => {
'number' => '24',
},
},
},
}
@deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))
@credential = create(:credential, :github_container_registry, account: @account)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
@template = create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin,
payload: template_payload)
sign_in @admin
end
test '#update - update deployment created from main compose file' do
file_content = File.read('test/fixtures/files/test-compose-success-without-dependencies.yml')
compose_file = create(:compose_file, project: @project, added_by: @admin)
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: @template.payload)
@deployment.update!(compose_file: compose_file)
encoded_content = Base64.encode64(file_content)
compose_file_attributes = attributes_for(:compose_file, :temporary, project: @project, added_by: @admin, content: encoded_content)
stub_dockerhub_repository_any
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [],
id: @deployment[:id],
metadata: {},
}
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 1,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,
-> { @deployment.containers.count } => 3,
}
assert_difference differences do
put :update, params: params, format: :json
end
assert_response :success
container_keys = [:image, :tag, :service_name, :port, :public]
actual_containers_attributes = UffizziCore::Container.all.map { |c| c.attributes.deep_symbolize_keys.slice(*container_keys) }
actual_template_containers_attributes = UffizziCore::Template.last
.payload
.deep_symbolize_keys[:containers_attributes]
.map { |c| c.slice(*container_keys) }
actual_app_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'app' }
actual_db_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'db' }
actual_nginx_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'nginx' }
actual_template_app_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'app' }
actual_template_db_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'db' }
actual_template_nginx_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'nginx' }
expected_app_container_attributes = {
image: 'uffizzicloud/app',
tag: 'latest',
service_name: 'app',
port: nil,
public: false,
}
expected_db_container_attributes = {
image: 'library/postgres',
tag: 'latest',
service_name: 'db',
port: nil,
public: false,
}
expected_nginx_container_attributes = {
image: 'library/nginx',
tag: '1.32',
service_name: 'nginx',
port: 80,
public: true,
}
assert_equal expected_app_container_attributes, actual_app_container_attributes
assert_equal expected_app_container_attributes, actual_template_app_container_attributes
assert_equal expected_db_container_attributes, actual_db_container_attributes
assert_equal expected_db_container_attributes, actual_template_db_container_attributes
assert_equal expected_nginx_container_attributes, actual_nginx_container_attributes
assert_equal expected_nginx_container_attributes, actual_template_nginx_container_attributes
end
test '#update - update deployment with metadata' do
file_content = File.read('test/fixtures/files/test-compose-success-without-dependencies.yml')
compose_file = create(:compose_file, project: @project, added_by: @admin)
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: @template.payload)
@deployment.update!(compose_file: compose_file)
encoded_content = Base64.encode64(file_content)
compose_file_attributes = attributes_for(:compose_file, :temporary, project: @project, added_by: @admin, content: encoded_content)
2.times { stub_dockerhub_repository_any }
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [],
id: @deployment[:id],
metadata: @metadata,
}
put :update, params: params, format: :json
assert_response :success
assert_equal(@metadata, @deployment.reload.metadata)
end
test '#update - update deployment created from temporary compose file' do
file_content = File.read('test/fixtures/files/test-compose-full.yml')
compose_file = create(:compose_file, :temporary, project: @project, added_by: @admin)
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: @template.payload)
@deployment.update!(compose_file: compose_file)
encoded_content = Base64.encode64(file_content)
compose_file_attributes = attributes_for(:compose_file, :temporary, project: @project, added_by: @admin, content: encoded_content)
stub_dockerhub_repository_any
# rubocop:disable Layout/LineLength
params = {
project_slug: @project.slug,
compose_file: compose_file_attributes,
dependencies: [
{
content: "ZGF0YQ1==\n",
is_file: false,
path: '/gem/tmp/some_app_dir',
source: './some_app_dir',
use_kind: 'volume',
},
{
content: "ZGF0YQ2==\n",
is_file: true,
path: '/gem/tmp/files/some_app_file',
source: './files/some_app_file',
use_kind: 'volume',
},
{
content: "ZGF0YQ3==\n",
is_file: false,
path: '/gem/tmp/some_db_dir',
source: './some_db_dir',
use_kind: 'volume',
},
{
content: "ZGF0YQ4==\n",
is_file: true,
path: '/gem/tmp/some_db_file',
source: './some_db_file',
use_kind: 'volume',
},
{
content: "ZGF0YQ==\n",
is_file: false,
path: '/gem/tmp',
source: './',
use_kind: 'volume',
},
{
content: "S0VZPXZhbHVl\n",
path: 'env_files/env_file.env',
source: 'env_files/env_file.env',
use_kind: 'config_map',
},
{
content: "UE9TVEdSRVNfVVNFUj1wb3N0Z3JlcyBQT1NUR1JFU19QQVNTV09SRD1wb3N0\nZ3Jlcw==\n",
path: 'local.env',
source: 'local.env',
use_kind: 'config_map',
},
{
content: "c2VydmVyIHsgbGlzdGVuICAgICAgIDg4ODg7IHNlcnZlcl9uYW1lICBsb2Nh\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\n",
path: 'config_files/config_file.conf',
source: 'config_files/config_file.conf',
use_kind: 'config_map',
},
{
content: "c2VydmVyIHsgbGlzdGVuICAgICAgIDgwODA7IHNlcnZlcl9uYW1lICBsb2Nh\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\n",
path: 'app.conf',
source: 'app.conf',
use_kind: 'config_map',
},
],
id: @deployment[:id],
metadata: {},
}
# rubocop:enable Layout/LineLength
differences = {
-> { UffizziCore::ComposeFile.temporary.count } => 0,
-> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,
-> { @deployment.containers.count } => 3,
-> { UffizziCore::HostVolumeFile.count } => 5,
-> { UffizziCore::ConfigFile.count } => 1,
}
assert_difference differences do
put :update, params: params, format: :json
end
assert_response :success
default_container_attributes = {
image: nil,
tag: nil,
service_name: nil,
variables: [],
public: false,
port: nil,
state: 'active',
continuously_deploy: 'enabled',
kind: 'user',
target_port: nil,
controller_name: nil,
receive_incoming_requests: false,
memory_request: 125 / UffizziCore::Container::REQUEST_MEMORY_RATIO,
memory_limit: 125,
secret_variables: [],
entrypoint: nil,
command: nil,
healthcheck: {},
volumes: [],
additional_subdomains: [],
source: nil,
}
app_container_attributes = {
image: 'uffizzicloud/app',
tag: 'latest',
service_name: 'app',
volumes: [
{
type: 'host',
source: './some_app_dir',
target: '/var/app/some_dir',
read_only: false,
},
{
type: 'host',
source: './files/some_app_file',
target: '/var/app/some_app_files',
read_only: false,
},
{
type: 'host',
source: './',
target: '/var/entire_app',
read_only: false,
},
{
type: 'named',
source: 'app_share',
target: '/some_app_share',
read_only: true,
},
{
type: 'anonymous',
source: '/some_anonymous_dir',
target: nil,
read_only: false,
},
],
variables: [
{
name: 'POSTGRES_USER',
value: 'postgres POSTGRES_PASSWORD=postgres',
},
{
name: 'KEY',
value: 'value',
},
],
}
db_container_attributes = {
image: 'library/postgres',
tag: 'latest',
service_name: 'db',
volumes: [
{
type: 'host',
source: './some_app_dir',
target: '/var/db/some_dir_2',
read_only: false,
},
{
type: 'host',
source: './some_db_dir',
target: '/var/db/some_dir_3',
read_only: false,
},
{
type: 'host',
source: './some_db_file',
target: '/var/db/some_db_files',
read_only: false,
},
{
type: 'named',
source: 'db_share',
target: '/some_db_share',
read_only: true,
},
],
}
nginx_container_attributes = {
image: 'library/nginx',
tag: '1.32',
service_name: 'nginx',
port: 80,
target_port: 80,
public: true,
receive_incoming_requests: true,
}
expected_app_container_attributes = default_container_attributes.merge(app_container_attributes)
expected_db_container_attributes = default_container_attributes.merge(db_container_attributes)
expected_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes)
exclude_params = [:state, :source, :kind, :target_port, :controller_name]
expected_template_app_container_attributes = default_container_attributes.merge(app_container_attributes).without(exclude_params)
expected_template_db_container_attributes = default_container_attributes.merge(db_container_attributes).without(exclude_params)
expected_template_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes).without(exclude_params)
container_keys = default_container_attributes.keys
deployment = UffizziCore::Deployment.last
actual_containers_attributes = deployment.containers.map { |c| c.attributes.deep_symbolize_keys.slice(*container_keys) }
actual_template_containers_attributes = deployment.compose_file.template.payload
.deep_symbolize_keys[:containers_attributes]
.map { |c| c.slice(*container_keys) }
actual_app_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'app' }
actual_db_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'db' }
actual_nginx_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'nginx' }
actual_template_app_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'app' }
actual_template_db_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'db' }
actual_template_nginx_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'nginx' }
assert_equal expected_app_container_attributes, actual_app_container_attributes
assert_equal expected_template_app_container_attributes, actual_template_app_container_attributes
assert_equal expected_db_container_attributes, actual_db_container_attributes
assert_equal expected_template_db_container_attributes, actual_template_db_container_attributes
assert_equal expected_nginx_container_attributes, actual_nginx_container_attributes
assert_equal expected_template_nginx_container_attributes, actual_template_nginx_container_attributes
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects/secrets_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::Projects::SecretsControllerTest < ActionController::TestCase
setup do
@admin = create(:user, :with_personal_account)
@account = @admin.personal_account
@developer = create(:user, :developer_in_organization, organization: @account)
@viewer = create(:user, :viewer_in_organization, organization: @account)
secrets = [build(:secret, name: generate(:string), value: generate(:string))]
@project = create(:project, :with_members, account: @account, members: [@admin, @developer, @viewer], secrets: secrets)
sign_in @admin
end
test '#index admin gets list of secrets' do
params = {
project_slug: @project.slug,
}
get :index, params: params, format: :json
assert_response :success
end
test '#bulk_create admin creates secrets' do
new_secrets = [
{ name: generate(:string), value: generate(:string) },
{ name: generate(:string), value: generate(:string) },
]
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
differences = {
-> { UffizziCore::Project.find(@project.id).secrets.count } => new_secrets.count,
}
assert_difference differences do
post :bulk_create, params: params, format: :json
end
assert_response :success
end
test '#bulk_create check if a secret has already been added' do
new_secrets = [
{ name: @project.secrets.first['name'], value: generate(:string) },
]
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
post :bulk_create, params: params, format: :json
assert_response :unprocessable_entity
response_body = JSON.parse(response.body)
assert { response_body['errors']['secrets'].present? }
end
test '#bulk_create check if a secret name too long' do
length = UffizziCore::Api::Cli::V1::Secret::BulkAssignForm::MAX_SECRET_KEY_LENGTH + 1
long_name = SecureRandom.alphanumeric(length)
new_secrets = [
{ name: long_name, value: generate(:string) },
]
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
post :bulk_create, params: params, format: :json
assert_response :unprocessable_entity
response_body = JSON.parse(response.body)
assert { response_body['errors']['secrets'].present? }
end
test '#bulk_create check update secret in a compose' do
new_secrets = [
{ name: generate(:string), value: generate(:string) },
]
compose_file = create(:compose_file, project: @project, added_by: @admin)
container_attributes = attributes_for(
:container,
:with_public_port,
receive_incoming_requests: true,
secret_variables: [{ name: new_secrets.first[:name], value: '' }],
)
template_payload = { containers_attributes: [container_attributes] }
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
post :bulk_create, params: params, format: :json
assert_response :success
compose_file.reload
@project.reload
new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }
container = compose_file.template.payload['containers_attributes'].first
compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }
assert_equal(new_project_secret['value'], compose_secret['value'])
end
test '#bulk_create check update secret in a compose if it has invalid secrets' do
new_secrets = [
{ name: generate(:string), value: generate(:string) },
]
compose_additional_secrets = [
{ name: generate(:string), value: generate(:string) },
]
compose_payload = { errors: { secret_variables: [generate(:string)] } }
compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin, payload: compose_payload)
container_attributes = attributes_for(
:container,
:with_public_port,
receive_incoming_requests: true,
secret_variables: [{ name: new_secrets.first[:name], value: '' }] + compose_additional_secrets,
)
template_payload = { containers_attributes: [container_attributes] }
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
post :bulk_create, params: params, format: :json
assert_response :success
compose_file.reload
@project.reload
new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }
container = compose_file.template.payload['containers_attributes'].first
compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }
assert_equal(new_project_secret['value'], compose_secret['value'])
assert(compose_file.invalid_file?)
refute_empty(compose_file.payload['errors'])
end
test '#bulk_create check update secret in a compose if it has errors with secrets' do
@project.secrets.destroy_all
new_secrets = [
{ name: generate(:string), value: generate(:string) },
]
compose_payload = { errors: { secret_variables: [generate(:string)] } }
compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin, payload: compose_payload)
container_attributes = attributes_for(
:container,
:with_public_port,
receive_incoming_requests: true,
secret_variables: [{ name: new_secrets.first[:name], value: '' }],
)
template_payload = { containers_attributes: [container_attributes] }
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
post :bulk_create, params: params, format: :json
assert_response :success
compose_file.reload
@project.reload
new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }
container = compose_file.template.payload['containers_attributes'].first
compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }
assert { compose_file.valid_file? }
assert_equal(new_project_secret['value'], compose_secret['value'])
assert_empty(compose_file.payload['errors'])
end
test '#bulk_create check update secret in a compose if it has errors not related to secrets' do
@project.secrets.destroy_all
new_secrets = [
{ name: generate(:string), value: generate(:string) },
]
compose_payload = { errors: { secret_variables: [generate(:string)], error_key: [generate(:string)] } }
compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin, payload: compose_payload)
container_attributes = attributes_for(
:container,
:with_public_port,
receive_incoming_requests: true,
secret_variables: [{ name: new_secrets.first[:name], value: '' }],
)
template_payload = { containers_attributes: [container_attributes] }
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
params = {
project_slug: @project.slug,
secrets: new_secrets,
}
post :bulk_create, params: params, format: :json
assert_response :success
compose_file.reload
@project.reload
new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }
container = compose_file.template.payload['containers_attributes'].first
compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }
assert { compose_file.invalid_file? }
assert_equal(new_project_secret['value'], compose_secret['value'])
refute_empty(compose_file.payload['errors'])
end
test '#destroy admin deletes a secret' do
deletable_secret = UffizziCore::Project.find(@project.id).secrets.last
params = {
project_slug: @project.slug,
id: deletable_secret['name'],
}
differences = {
-> { UffizziCore::Project.last.secrets.count } => -1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
end
test '#destroy if a project has a compose with deleted secret' do
deletable_secret = UffizziCore::Project.find(@project.id).secrets.last
compose_file = create(:compose_file, project: @project, added_by: @admin)
container_attributes = attributes_for(
:container,
:with_public_port,
receive_incoming_requests: true,
secret_variables: [deletable_secret],
)
template_payload = { containers_attributes: [container_attributes] }
create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)
params = {
project_slug: @project.slug,
id: deletable_secret['name'],
}
differences = {
-> { UffizziCore::Project.last.secrets.count } => -1,
-> { UffizziCore::ComposeFile.where(state: UffizziCore::ComposeFile::STATE_INVALID_FILE).count } => 1,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :success
compose_file.reload
assert { compose_file.invalid_file? }
refute_empty(compose_file.payload['errors'])
end
test '#destroy if a secret doesn\'t exist' do
params = {
project_slug: @project.slug,
id: generate(:string),
}
differences = {
-> { UffizziCore::Project.last.secrets.count } => 0,
}
assert_difference differences do
delete :destroy, params: params, format: :json
end
assert_response :not_found
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/projects_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::ProjectsControllerTest < ActionController::TestCase
setup do
@user = create(:user, :with_personal_account)
sign_in @user
@project = create(:project, :with_members, account: @user.personal_account, members: [@user])
end
test '#index' do
get :index, format: :json
assert_response :success
end
test '#show' do
create(:compose_file, project: @project, added_by: @user)
create(:secret, resource: @project)
create(:deployment, project: @project)
get :show, params: { slug: @project.slug }, format: :json
assert_response :success
assert_equal(@project.name, JSON.parse(response.body)['project']['name'])
end
test '#destroy' do
differences = {
-> { UffizziCore::Project.active.count } => -1,
}
assert_difference differences do
delete :destroy, params: { slug: @project.slug }, format: :json
end
assert_response :success
end
end
================================================
FILE: core/test/controllers/uffizzi_core/api/cli/v1/sessions_controller_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::Api::Cli::V1::SessionsControllerTest < ActionController::TestCase
test '#create - successful' do
password = generate(:password)
user = create(:user, :with_personal_account, :active, password: password)
params = { email: user.email, password: password }
post :create, params: { user: params }, format: :json
assert_response :success
assert { signed_in? }
end
test '#create - failed with wrong password' do
password = generate(:password)
user = create(:user, :active, password: password)
wrong_password = generate(:password)
params = { email: user.email, password: wrong_password }
post :create, params: { user: params }, format: :json
assert_response :unprocessable_entity
assert_not_empty JSON.parse(response.body)['errors']
assert { !signed_in? }
end
end
================================================
FILE: core/test/dummy/Rakefile
================================================
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks
================================================
FILE: core/test/dummy/app/assets/config/manifest.js
================================================
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link uffizzi_core_manifest.js
================================================
FILE: core/test/dummy/app/assets/images/.keep
================================================
================================================
FILE: core/test/dummy/app/assets/stylesheets/application.css
================================================
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
================================================
FILE: core/test/dummy/app/channels/application_cable/channel.rb
================================================
# frozen_string_literal: true
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
================================================
FILE: core/test/dummy/app/channels/application_cable/connection.rb
================================================
# frozen_string_literal: true
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
================================================
FILE: core/test/dummy/app/controllers/application_controller.rb
================================================
# frozen_string_literal: true
class ApplicationController < ActionController::Base
end
================================================
FILE: core/test/dummy/app/controllers/concerns/.keep
================================================
================================================
FILE: core/test/dummy/app/helpers/application_helper.rb
================================================
# frozen_string_literal: true
module ApplicationHelper
end
================================================
FILE: core/test/dummy/app/javascript/packs/application.js
================================================
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require activestorage
//= require_tree .
================================================
FILE: core/test/dummy/app/jobs/application_job.rb
================================================
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end
================================================
FILE: core/test/dummy/app/mailers/application_mailer.rb
================================================
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
end
================================================
FILE: core/test/dummy/app/models/application_record.rb
================================================
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
================================================
FILE: core/test/dummy/app/models/concerns/.keep
================================================
================================================
FILE: core/test/dummy/app/views/layouts/application.html.erb
================================================
Dummy
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= yield %>
================================================
FILE: core/test/dummy/app/views/layouts/mailer.html.erb
================================================
<%= yield %>
================================================
FILE: core/test/dummy/app/views/layouts/mailer.text.erb
================================================
<%= yield %>
================================================
FILE: core/test/dummy/bin/rails
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
load File.expand_path('spring', __dir__)
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
================================================
FILE: core/test/dummy/bin/rake
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
load File.expand_path('spring', __dir__)
require_relative '../config/boot'
require 'rake'
Rake.application.run
================================================
FILE: core/test/dummy/bin/setup
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils'
# path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
FileUtils.chdir(APP_ROOT) do
# This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
# end
puts "\n== Preparing database =="
system! 'bin/rails db:prepare'
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
system! 'bin/rails restart'
end
================================================
FILE: core/test/dummy/bin/spring
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
if !defined?(Spring) && [nil, 'development', 'test'].include?(ENV['RAILS_ENV'])
# Load Spring without loading other gems in the Gemfile, for speed.
require 'bundler'
Bundler.locked_gems.specs.detect { |spec| spec.name == 'spring' }&.tap do |spring|
Gem.use_paths(Gem.dir, Bundler.bundle_path.to_s, *Gem.path)
gem 'spring', spring.version
require 'spring/binstub'
end
end
================================================
FILE: core/test/dummy/config/application.rb
================================================
# frozen_string_literal: true
require_relative 'boot'
require 'byebug'
require 'rack/cors'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
require 'uffizzi_core'
module Dummy
class Application < Rails::Application
config.load_defaults(Rails::VERSION::STRING.to_f)
config.hosts = Settings.allowed_hosts
config.middleware.insert_before(0, Rack::Cors) do
allow do
origins do |source|
uri = URI.parse(source)
Settings.allowed_hosts.any? { |host| uri.host == host || uri.host.ends_with?(host) }
end
resource '*', headers: :any, methods: [:get, :post, :options, :put, :patch, :delete], credentials: true
end
end
end
end
================================================
FILE: core/test/dummy/config/boot.rb
================================================
# frozen_string_literal: true
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
$LOAD_PATH.unshift(File.expand_path('../../../lib', __dir__))
================================================
FILE: core/test/dummy/config/cable.yml
================================================
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: dummy_production
================================================
FILE: core/test/dummy/config/database.yml
================================================
default: &default
adapter: postgresql
encoding: unicode
host: <%= ENV.fetch("DATABASE_HOST") {"127.0.0.1"} %>
port: <%= ENV.fetch("DATABASE_PORT") {5432} %>
pool: <%= ENV.fetch("DATABASE_POOL") {5} %>
username: <%= ENV.fetch("DATABASE_USER") {"postgres"} %>
password: <%= ENV.fetch("DATABASE_PASSWORD") {""} %>
development:
<<: *default
database: uffizzi_core_development
test:
<<: *default
database: uffizzi_core_test
================================================
FILE: core/test/dummy/config/environment.rb
================================================
# frozen_string_literal: true
# Load the Rails application.
require_relative 'application'
# Initialize the Rails application.
Rails.application.initialize!
================================================
FILE: core/test/dummy/config/environments/development.rb
================================================
# frozen_string_literal: true
require 'active_support/core_ext/integer/time'
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded any time
# it changes. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports.
config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.to_i}",
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large
# number of complex assets.
config.assets.debug = true
# Suppress logger output for asset requests.
config.assets.quiet = true
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
# config.file_watcher = ActiveSupport::EventedFileUpdateChecker
# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
end
================================================
FILE: core/test/dummy/config/environments/production.rb
================================================
# frozen_string_literal: true
require 'active_support/core_ext/integer/time'
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress CSS using a preprocessor.
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = 'http://assets.example.com'
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Include generic and useful information about system operation, but avoid logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII).
config.log_level = :info
# Prepend all log lines with the following tags.
config.log_tags = [:request_id]
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "dummy_production"
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Log disallowed deprecations.
config.active_support.disallowed_deprecation = :log
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require "syslog/logger"
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV['RAILS_LOG_TO_STDOUT'].present?
logger = ActiveSupport::Logger.new($stdout)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Inserts middleware to perform automatic connection switching.
# The `database_selector` hash is used to pass options to the DatabaseSelector
# middleware. The `delay` is used to determine how long to wait after a write
# to send a subsequent read to the primary.
#
# The `database_resolver` class is used by the middleware to determine which
# database is appropriate to use based on the time delay.
#
# The `database_resolver_context` class is used by the middleware to set
# timestamps for the last write to the primary. The resolver uses the context
# class timestamps to determine how long to wait before reading from the
# replica.
#
# By default Rails will store a last write timestamp in the session. The
# DatabaseSelector middleware is designed as such you can define your own
# strategy for connection switching and pass that into the middleware through
# these configuration options.
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end
================================================
FILE: core/test/dummy/config/environments/test.rb
================================================
# frozen_string_literal: true
require 'active_support/core_ext/integer/time'
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
config.cache_classes = false
config.action_view.cache_template_loading = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}",
}
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
end
================================================
FILE: core/test/dummy/config/initializers/application_controller_renderer.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# ActiveSupport::Reloader.to_prepare do
# ApplicationController.renderer.defaults.merge!(
# http_host: 'example.org',
# https: false
# )
# end
================================================
FILE: core/test/dummy/config/initializers/assets.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
================================================
FILE: core/test/dummy/config/initializers/backtrace_silencers.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
# by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE']
================================================
FILE: core/test/dummy/config/initializers/config.rb
================================================
# frozen_string_literal: true
Config.setup do |config|
# Name of the constant exposing loaded settings
config.const_name = 'Settings'
# Ability to remove elements of the array set in earlier loaded settings file. For example value: '--'.
#
# config.knockout_prefix = nil
# Overwrite an existing value when merging a `nil` value.
# When set to `false`, the existing value is retained after merge.
#
# config.merge_nil_values = true
# Overwrite arrays found in previously loaded settings file. When set to `false`, arrays will be merged.
#
# config.overwrite_arrays = true
# Load environment variables from the `ENV` object and override any settings defined in files.
#
# config.use_env = false
# Define ENV variable prefix deciding which variables to load into config.
#
# Reading variables from ENV is case-sensitive. If you define lowercase value below, ensure your ENV variables are
# prefixed in the same way.
#
# When not set it defaults to `config.const_name`.
#
config.env_prefix = 'SETTINGS'
# What string to use as level separator for settings loaded from ENV variables. Default value of '.' works well
# with Heroku, but you might want to change it for example for '__' to easy override settings from command line, where
# using dots in variable names might not be allowed (eg. Bash).
#
# config.env_separator = '.'
# Ability to process variables names:
# * nil - no change
# * :downcase - convert to lower case
#
# config.env_converter = :downcase
# Parse numeric values as integers instead of strings.
#
# config.env_parse_values = true
# Validate presence and type of specific config values. Check https://github.com/dry-rb/dry-validation for details.
#
# config.schema do
# required(:name).filled
# required(:age).maybe(:int?)
# required(:email).filled(format?: EMAIL_REGEX)
# end
# Evaluate ERB in YAML config files at load time.
#
# config.evaluate_erb_in_yaml = true
end
================================================
FILE: core/test/dummy/config/initializers/content_security_policy.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
# If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
# Set the nonce only to specific directives
# Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
# Report CSP violations to a specified URI
# For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true
================================================
FILE: core/test/dummy/config/initializers/cookies_serializer.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Specify a serializer for the signed and encrypted cookie jars.
# Valid options are :json, :marshal, and :hybrid.
Rails.application.config.action_dispatch.cookies_serializer = :json
================================================
FILE: core/test/dummy/config/initializers/filter_parameter_logging.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
]
================================================
FILE: core/test/dummy/config/initializers/inflections.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end
================================================
FILE: core/test/dummy/config/initializers/mime_types.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
================================================
FILE: core/test/dummy/config/initializers/octokit.rb
================================================
# frozen_string_literal: true
module Octokit
class Client
module Contents
def contents?(repo, options = {})
options = options.dup
repo_path = options.delete(:path)
url = "#{Repository.path(repo)}/contents/#{repo_path}"
head(url, options).nil?
rescue Octokit::NotFound
false
end
end
end
end
================================================
FILE: core/test/dummy/config/initializers/permissions_policy.rb
================================================
# frozen_string_literal: true
# Define an application-wide HTTP permissions policy. For further
# information see https://developers.google.com/web/updates/2018/06/feature-policy
#
# Rails.application.config.permissions_policy do |f|
# f.camera :none
# f.gyroscope :none
# f.microphone :none
# f.usb :none
# f.fullscreen :self
# f.payment :self, "https://secure.example.com"
# end
================================================
FILE: core/test/dummy/config/initializers/wrap_parameters.rb
================================================
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true
# end
================================================
FILE: core/test/dummy/config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
# <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# The following keys must be escaped otherwise they will not be retrieved by
# the default I18n backend:
#
# true, false, on, off, yes, no
#
# Instead, surround them with single quotes.
#
# en:
# 'true': 'foo'
#
# To learn more, please read the Rails Internationalization guide
# available at https://guides.rubyonrails.org/i18n.html.
en:
hello: "Hello world"
================================================
FILE: core/test/dummy/config/puma.rb
================================================
# frozen_string_literal: true
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
threads min_threads_count, max_threads_count
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
#
worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port ENV.fetch('PORT', 3000)
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch('RAILS_ENV', 'development')
# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid')
# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
================================================
FILE: core/test/dummy/config/routes.rb
================================================
# frozen_string_literal: true
Rails.application.routes.draw do
mount UffizziCore::Engine => '/'
end
================================================
FILE: core/test/dummy/config/settings.yml
================================================
app:
ttl_reset_password_token: 900
host: http://lvh.me
login: ''
password: ''
managed_dns_zone: example.com
github:
app_id: 123
app_slug: test
client_id: test
client_secret: test
private_key: "-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKUZo1+jM7j760xsPlgt/5WzbRHL62kohW9hy8JpAauglOdgjRbY
URXle+6+VNzGBU0kUXYjzUNJLBgC+JeubvUCAwEAAQJAEzQrO6mZD5BF60q/6bPY
AcqwChzlEgNDmhQPBlr+db71EuLtmui7moCOTNLnZGIryd0uCGjEhXCwS653ivHW
NQIhANcWtxH2xHviNafPAK+5mIABOCL+MXXCI5EnjUdARMc7AiEAxIDWD+bOzavo
OGwiYrOQYcxxtZXGUUBKuQzpgvM5H48CIDcwbOj/GItxD7NvOg3c4XR226Ce+LHu
jpHARE/z/bHhAiA+hxiWmsU3oPoV6iLO8YCB/kI4m94tQJ4GYFt1tdt5dQIhALQg
cNfHZndOE7mwn6pm/JI8bXCseL7V2t+cXyCexlru
-----END RSA PRIVATE KEY-----
"
webhook_secret: test
registry_url: gcr.io/test
docker_hub:
registry_url: 'https://index.docker.io/v1/'
public_namespace: 'library'
controller:
url: http://controller:8080
login: ''
password: ''
connection:
retires_count: 1
next_retry_timeout_seconds: 1
timeout: 7
open_timeout: 5
limits:
cpu: '200m'
namespace_prefix: 'app-'
resource_create_retry_time: 15.seconds
resource_create_retry_count: 60
vcluster_controller:
url: http://controller:8080
login: ''
password: ''
managed_dns_zone: ''
connection:
retires_count: 1
next_retry_timeout_seconds: 1
timeout: 7
open_timeout: 5
allowed_hosts: []
domain: http://lvh.me
features:
email_delivery_enabled: true
stripe_enabled: false
platform_cluster:
project_id: test
google:
registry_url: 'https://gcr.io/'
github_container_registry:
registry_url: 'https://ghcr.io/'
compose:
default_memory: 125
memory_postfixes: <%= ['b', 'k', 'm', 'g'] %>
memory_values: <%= [125, 250, 500, 1000, 2000, 4000] %>
port_min_value: 1
port_max_value: 65535
delete_after_postfixes: <%= ['h'] %>
delete_after_min_value: 1
delete_after_max_value: 720
default_tag: latest
dockerfile_default_path: Dockerfile
default_branch: 'master'
continuous_preview:
default_delete_preview_after: 72
deployment:
max_memory_limit: 8000
subdomain:
length_limit: 63
vcluster:
max_creation_retry_count: 5
max_scale_up_retry_count: 5
================================================
FILE: core/test/dummy/config/spring.rb
================================================
# frozen_string_literal: true
Spring.watch(
'.ruby-version',
'.rbenv-vars',
'tmp/restart.txt',
'tmp/caching-dev.txt',
)
================================================
FILE: core/test/dummy/config/storage.yml
================================================
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
================================================
FILE: core/test/dummy/config.ru
================================================
# frozen_string_literal: true
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run Rails.application
Rails.application.load_server
================================================
FILE: core/test/dummy/db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2024_03_14_170113) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "uffizzi_core_accounts", force: :cascade do |t|
t.text "name"
t.text "kind", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "customer_token"
t.string "state"
t.string "subscription_token"
t.datetime "payment_issue_at"
t.string "domain"
t.boolean "sso_enabled", default: false
t.bigint "owner_id"
t.integer "container_memory_limit"
t.string "workos_organization_id"
t.string "sso_state"
t.index ["customer_token"], name: "index_accounts_on_customer_token", unique: true
t.index ["domain"], name: "index_accounts_on_domain", unique: true
t.index ["subscription_token"], name: "index_accounts_on_subscription_token", unique: true
end
create_table "uffizzi_core_activity_items", force: :cascade do |t|
t.bigint "deployment_id", null: false
t.string "namespace"
t.string "name"
t.string "tag"
t.string "branch"
t.string "type"
t.bigint "container_id", null: false
t.string "commit"
t.string "commit_message"
t.bigint "build_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.jsonb "data", default: {}, null: false
t.string "digest"
t.index ["container_id"], name: "index_activity_items_on_container_id"
t.index ["deployment_id"], name: "index_activity_items_on_deployment_id"
end
create_table "uffizzi_core_builds", force: :cascade do |t|
t.bigint "repo_id", null: false
t.string "build_id"
t.string "repository"
t.string "branch"
t.string "commit"
t.string "committer"
t.string "message"
t.string "log_url"
t.integer "status"
t.datetime "started_at"
t.datetime "ended_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "deployed"
t.index ["build_id"], name: "index_builds_on_build_id", unique: true
t.index ["repo_id"], name: "index_builds_on_repo_id"
end
create_table "uffizzi_core_clusters", force: :cascade do |t|
t.bigint "project_id", null: false
t.bigint "deployed_by_id"
t.string "state"
t.string "name"
t.text "manifest"
t.text "kubeconfig"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "host"
t.string "creation_source"
t.integer "kubernetes_distribution_id"
t.index ["project_id"], name: "index_cluster_on_project_id"
end
create_table "uffizzi_core_comments", force: :cascade do |t|
t.string "commentable_type"
t.bigint "commentable_id"
t.text "content"
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "ancestry"
t.integer "ancestry_depth", default: 0
t.index ["ancestry"], name: "index_comments_on_ancestry"
t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable"
t.index ["user_id"], name: "index_comments_on_user_id"
end
create_table "uffizzi_core_compose_files", force: :cascade do |t|
t.string "source"
t.bigint "repository_id"
t.string "branch"
t.string "path"
t.string "auto_deploy"
t.bigint "added_by_id"
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "state"
t.jsonb "payload", default: {}, null: false
t.text "content"
t.string "kind", default: "main"
t.index ["project_id"], name: "index_compose_files_on_project_id"
end
create_table "uffizzi_core_config_files", force: :cascade do |t|
t.string "filename"
t.string "kind"
t.bigint "added_by_id"
t.text "payload"
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "compose_file_id"
t.string "creation_source"
t.string "source"
t.index ["compose_file_id"], name: "index_config_files_on_compose_file_id"
t.index ["project_id"], name: "index_config_files_on_project_id"
end
create_table "uffizzi_core_container_config_files", force: :cascade do |t|
t.string "mount_path"
t.bigint "container_id", null: false
t.bigint "config_file_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["config_file_id"], name: "index_container_config_files_on_config_file_id"
t.index ["container_id"], name: "index_container_config_files_on_container_id"
end
create_table "uffizzi_core_container_host_volume_files", force: :cascade do |t|
t.string "source_path"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "container_id", null: false
t.bigint "host_volume_file_id", null: false
t.index ["container_id"], name: "uf_core_cont_h_v_on_cont"
t.index ["host_volume_file_id"], name: "uf_core_cont_h_v_on_h_v_file"
end
create_table "uffizzi_core_containers", force: :cascade do |t|
t.string "image"
t.string "tag"
t.jsonb "variables"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "deployment_id"
t.boolean "public", default: false, null: false
t.integer "port"
t.bigint "repo_id"
t.string "state"
t.string "continuously_deploy", null: false
t.string "kind", default: "user"
t.integer "target_port"
t.string "controller_name"
t.boolean "receive_incoming_requests"
t.integer "memory_request"
t.integer "memory_limit"
t.jsonb "secret_variables"
t.string "entrypoint"
t.string "command"
t.string "service_name"
t.jsonb "healthcheck"
t.jsonb "volumes"
t.string "additional_subdomains", default: [], array: true
t.string "full_image_name"
t.index ["deployment_id"], name: "index_containers_on_deployment_id"
t.index ["repo_id"], name: "index_containers_on_repo_id"
end
create_table "uffizzi_core_coupons", force: :cascade do |t|
t.string "token"
t.string "name", null: false
t.string "currency", null: false
t.bigint "amount_off", null: false
t.string "duration", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "uffizzi_core_credentials", force: :cascade do |t|
t.string "type"
t.string "username"
t.string "password"
t.bigint "project_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "provider_ref"
t.bigint "account_id"
t.string "state"
t.string "registry_url"
t.index ["account_id"], name: "index_credentials_on_account_id"
t.index ["project_id"], name: "index_credentials_on_project_id"
t.index ["provider_ref"], name: "index_credentials_on_provider_ref"
end
create_table "uffizzi_core_deployment_events", force: :cascade do |t|
t.string "deployment_state"
t.string "message"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "deployment_id", null: false
t.index ["deployment_id"], name: "uf_core_dep_events_on_dep"
end
create_table "uffizzi_core_deployments", force: :cascade do |t|
t.bigint "project_id", null: false
t.text "kind", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "creator_name"
t.string "subdomain"
t.string "state"
t.float "memory_limit"
t.bigint "deployed_by_id"
t.bigint "continuous_preview_id_deprecated"
t.jsonb "continuous_preview_payload"
t.string "creation_source"
t.bigint "compose_file_id"
t.bigint "template_id"
t.datetime "disabled_at"
t.jsonb "metadata", default: {}
t.datetime "last_deploy_at"
t.index ["compose_file_id"], name: "index_deployments_on_compose_file_id"
t.index ["project_id"], name: "index_deployments_on_project_id"
t.index ["template_id"], name: "index_deployments_on_template_id"
end
create_table "uffizzi_core_events", force: :cascade do |t|
t.bigint "activity_item_id", null: false
t.string "state"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["activity_item_id"], name: "index_events_on_activity_item_id"
end
create_table "uffizzi_core_host_volume_files", force: :cascade do |t|
t.string "source"
t.string "path"
t.boolean "is_file"
t.binary "payload"
t.bigint "added_by_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "project_id", null: false
t.bigint "compose_file_id", null: false
t.index ["compose_file_id"], name: "index_host_volume_file_on_compose_file_id"
t.index ["project_id"], name: "index_host_volume_file_on_project_id"
end
create_table "uffizzi_core_invitations", force: :cascade do |t|
t.text "email", null: false
t.text "token", null: false
t.string "status", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "invited_by_id", null: false
t.bigint "entityable_id", null: false
t.string "entityable_type", null: false
t.string "role", null: false
t.bigint "invitee_id"
t.index ["token"], name: "index_invitations_on_token", unique: true
end
create_table "uffizzi_core_kubernetes_distributions", force: :cascade do |t|
t.string "version"
t.string "distro"
t.string "image"
t.boolean "default", default: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "uffizzi_core_memberships", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "account_id", null: false
t.text "role", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "state"
t.index ["account_id"], name: "index_memberships_on_account_id"
t.index ["user_id", "account_id"], name: "index_memberships_on_user_id_and_account_id"
t.index ["user_id"], name: "index_memberships_on_user_id"
end
create_table "uffizzi_core_payments", force: :cascade do |t|
t.bigint "account_id", null: false
t.string "charge_id"
t.string "status"
t.float "amount"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_payments_on_account_id"
end
create_table "uffizzi_core_prices", force: :cascade do |t|
t.string "token"
t.string "slug", null: false
t.string "name", null: false
t.float "units_price", null: false
t.bigint "units_amount", null: false
t.bigint "product_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["product_id"], name: "index_prices_on_product_id"
end
create_table "uffizzi_core_products", force: :cascade do |t|
t.string "token"
t.string "slug", null: false
t.string "name", null: false
t.string "type", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "kind"
end
create_table "uffizzi_core_projects", force: :cascade do |t|
t.text "name", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "account_id", null: false
t.string "state"
t.string "slug"
t.string "description"
t.index ["account_id", "name"], name: "proj_uniq_name", unique: true, where: "((state)::text = 'active'::text)"
t.index ["account_id"], name: "index_projects_on_account_id"
end
create_table "uffizzi_core_ratings", force: :cascade do |t|
t.string "name"
t.string "state"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "uffizzi_core_repos", force: :cascade do |t|
t.string "namespace"
t.string "name"
t.string "tag"
t.string "type"
t.string "branch"
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "description"
t.boolean "is_private"
t.string "slug"
t.bigint "repository_id"
t.string "kind"
t.string "dockerfile_path"
t.jsonb "args"
t.string "dockerfile_context_path"
t.boolean "deploy_preview_when_pull_request_is_opened"
t.boolean "delete_preview_when_pull_request_is_closed"
t.boolean "deploy_preview_when_image_tag_is_created"
t.boolean "delete_preview_when_image_tag_is_updated"
t.boolean "share_to_github"
t.integer "delete_preview_after"
t.string "tag_pattern_deprecated"
t.index ["project_id"], name: "index_repos_on_project_id"
end
create_table "uffizzi_core_roles", force: :cascade do |t|
t.string "name"
t.string "resource_type"
t.bigint "resource_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
end
create_table "uffizzi_core_secrets", force: :cascade do |t|
t.string "name"
t.string "value"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "resource_type"
t.bigint "resource_id"
t.index ["resource_type", "resource_id"], name: "index_uffizzi_core_secrets_on_resource"
end
create_table "uffizzi_core_templates", force: :cascade do |t|
t.string "name"
t.bigint "added_by_id"
t.jsonb "payload", null: false
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "compose_file_id"
t.string "creation_source"
t.index ["project_id"], name: "index_templates_on_project_id"
end
create_table "uffizzi_core_user_projects", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "project_id", null: false
t.text "role", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "invited_by_id"
t.index ["project_id"], name: "index_user_projects_on_project_id"
t.index ["user_id", "project_id"], name: "index_user_projects_on_user_id_and_project_id"
t.index ["user_id"], name: "index_user_projects_on_user_id"
end
create_table "uffizzi_core_users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email", default: "", null: false
t.string "password_digest", default: "", null: false
t.string "confirmation_token"
t.string "state"
t.string "phone"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "github"
t.string "website"
t.string "twitter"
t.string "linkedin"
t.string "devto"
t.string "facebook"
t.string "blog"
t.text "bio"
t.string "status"
t.string "availability"
t.string "primary_skills"
t.string "learning"
t.string "coding_for"
t.string "education"
t.string "title"
t.string "work"
t.string "primary_location"
t.string "creation_source"
t.index "lower((email)::text)", name: "index_email_on_lower_email", unique: true
end
create_table "uffizzi_core_users_roles", id: false, force: :cascade do |t|
t.bigint "user_id"
t.bigint "role_id"
t.index ["role_id"], name: "index_users_roles_on_role_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
t.index ["user_id"], name: "index_users_roles_on_user_id"
end
add_foreign_key "uffizzi_core_clusters", "uffizzi_core_projects", column: "project_id"
add_foreign_key "uffizzi_core_container_host_volume_files", "uffizzi_core_containers", column: "container_id"
add_foreign_key "uffizzi_core_container_host_volume_files", "uffizzi_core_host_volume_files", column: "host_volume_file_id"
add_foreign_key "uffizzi_core_deployment_events", "uffizzi_core_deployments", column: "deployment_id"
add_foreign_key "uffizzi_core_host_volume_files", "uffizzi_core_compose_files", column: "compose_file_id"
add_foreign_key "uffizzi_core_host_volume_files", "uffizzi_core_projects", column: "project_id"
end
================================================
FILE: core/test/dummy/lib/assets/.keep
================================================
================================================
FILE: core/test/dummy/log/.keep
================================================
================================================
FILE: core/test/dummy/public/404.html
================================================
The page you were looking for doesn't exist (404)
The page you were looking for doesn't exist.
You may have mistyped the address or the page may have moved.
If you are the application owner check the logs for more information.
================================================
FILE: core/test/dummy/public/422.html
================================================
The change you wanted was rejected (422)
The change you wanted was rejected.
Maybe you tried to change something you didn't have access to.
If you are the application owner check the logs for more information.
================================================
FILE: core/test/dummy/public/500.html
================================================
We're sorry, but something went wrong (500)
We're sorry, but something went wrong.
If you are the application owner check the logs for more information.
================================================
FILE: core/test/factories/account.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :account, class: UffizziCore::Account do
name
kind { UffizziCore::Account.kind.personal }
customer_token { nil }
subscription_token { nil }
state { nil }
payment_issue_at { nil }
owner { nil }
created_at
domain
trait :with_admin do
transient do
admin { nil }
end
before(:create) do |account, evaluator|
account.owner = evaluator.admin
end
after(:create) do |account, evaluator|
if evaluator.admin
user = evaluator.admin
user.memberships.create(user: user, account: account, role: UffizziCore::Membership.role.admin)
account.projects.each do |project|
user.user_projects.create!(user: user, project: project, role: UffizziCore::UserProject.role.admin)
end
end
end
end
trait :personal_account do
kind { UffizziCore::Account.kind.personal }
end
trait :with_stripe_enitities do
after(:create) do |account, _evaluator|
if account
UffizziCore::StripeService.create_customer(account)
UffizziCore::StripeService.create_subscription(account)
end
end
end
trait :disabled do
state { :disabled }
end
trait :payment_issue do
state { :payment_issue }
end
trait :draft do
state { UffizziCore::Account::STATE_DRAFT }
end
trait :sso_connection_active do
sso_state { UffizziCore::Account::STATE_CONNECTION_ACTIVE }
end
trait :sso_connection_disabled do
sso_state { UffizziCore::Account::STATE_CONNECTION_DISABLED }
end
end
end
================================================
FILE: core/test/factories/activity_item.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :activity_item, class: UffizziCore::ActivityItem do
deployment { nil }
namespace
name
tag { '' }
branch { '' }
container { nil }
commit
commit_message
build_id { nil }
trait :with_building_event do
after(:create) do |activity_item, _evaluator|
activity_item.events.create(state: UffizziCore::Event.state.building)
end
end
trait :with_deploying_event do
after(:create) do |activity_item, _evaluator|
activity_item.events.create(state: UffizziCore::Event.state.deploying)
end
end
trait :with_deployed_event do
after(:create) do |activity_item, _evaluator|
activity_item.events.create(state: UffizziCore::Event.state.deployed)
end
end
trait :with_failed_event do
after(:create) do |activity_item, _evaluator|
activity_item.events.create(state: UffizziCore::Event.state.failed)
end
end
trait :docker do
type { UffizziCore::ActivityItem::Docker.name }
end
trait :github do
type { UffizziCore::ActivityItem::Github.name }
end
trait :memory_limit do
type { UffizziCore::ActivityItem::MemoryLimit.name }
end
end
end
================================================
FILE: core/test/factories/cluster.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :cluster, class: UffizziCore::Cluster do
name { generate(:cluster_name) }
project { nil }
deployed_by { nil }
trait :deployed do
state { :deployed }
end
end
end
================================================
FILE: core/test/factories/comments.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :comment, class: UffizziCore::Comment do
commentable { nil }
content
parent { nil }
user { nil }
end
end
================================================
FILE: core/test/factories/compose_files.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :compose_file, class: UffizziCore::ComposeFile do
source { generate(:name) }
repository_id { generate(:number) }
branch
path
auto_deploy { UffizziCore::ComposeFile::STATE_DISABLED }
kind { UffizziCore::ComposeFile.kind.main }
trait :auto_deploy do
auto_deploy { UffizziCore::ComposeFile::STATE_ENABLED }
end
trait :invalid_file do
state { :invalid_file }
end
trait :temporary do
kind { UffizziCore::ComposeFile.kind.temporary }
end
end
end
================================================
FILE: core/test/factories/config_files.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :config_file, class: UffizziCore::ConfigFile do
filename { generate(:name) }
added_by { nil }
kind { UffizziCore::ConfigFile.kind.config_map }
payload { generate(:string) }
project { nil }
creation_source { UffizziCore::ConfigFile.creation_source.manual }
end
trait :compose_file_source do
creation_source { UffizziCore::ConfigFile.creation_source.compose_file }
end
end
================================================
FILE: core/test/factories/container_config_files.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :container_config_file, class: UffizziCore::ContainerConfigFile do
mount_path { generate(:path) }
container { nil }
config_file { nil }
end
end
================================================
FILE: core/test/factories/container_host_volume_files.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :container_host_volume_file, class: UffizziCore::ContainerHostVolumeFile do
source_path { generate(:relative_path) }
container { nil }
host_volume_file { nil }
end
end
================================================
FILE: core/test/factories/containers.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :container, class: UffizziCore::Container do
image
full_image_name
tag
service_name
variables { [] }
secret_variables { [] }
deployment
repo { nil }
receive_incoming_requests { false }
entrypoint { nil }
command { nil }
continuously_deploy { UffizziCore::Container::STATE_CD_DISABLED }
healthcheck { nil }
volumes { nil }
memory_limit { Settings.compose.default_memory }
memory_request { Settings.compose.default_memory }
trait :with_public_port do
public { true }
port
end
trait :continuously_deploy_enabled do
continuously_deploy { UffizziCore::Container::STATE_CD_ENABLED }
end
trait :continuously_deploy_disabled do
continuously_deploy { UffizziCore::Container::STATE_CD_DISABLED }
end
initialize_with { new }
trait :active do
state { UffizziCore::Container::STATE_ACTIVE }
end
trait :with_named_volume do
volumes do
[
{
source: generate(:string),
target: generate(:path),
type: UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::NAMED_VOLUME_TYPE,
},
]
end
end
end
end
================================================
FILE: core/test/factories/credentials.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :credential, class: UffizziCore::Credential do
username { generate(:login) }
password
account { nil }
registry_url { generate(:url) }
trait :docker_hub do
type { UffizziCore::Credential::DockerHub.name }
end
trait :azure do
type { UffizziCore::Credential::Azure.name }
end
trait :google do
type { UffizziCore::Credential::Google.name }
registry_url { 'https://gcr.io/' }
end
trait :github_container_registry do
type { UffizziCore::Credential::GithubContainerRegistry.name }
registry_url { 'https://ghcr.io' }
end
trait :docker_registry do
type { UffizziCore::Credential::DockerRegistry.name }
registry_url { 'https://example.com/registry' }
end
trait :amazon do
type { UffizziCore::Credential::Amazon.name }
registry_url { 'https://123456789876.dkr.ecr.us-east-1.amazonaws.com' }
end
trait :unauthorized do
state { :unauthorized }
end
trait :active do
state { :active }
end
end
end
================================================
FILE: core/test/factories/deployment.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :deployment, class: UffizziCore::Deployment do
kind { nil }
subdomain { generate :subdomain_name }
created_at { DateTime.current }
memory_limit { 1 }
continuous_preview_payload { nil }
creation_source { nil }
metadata { {} }
trait :active do
state { :active }
end
trait :disabled do
state { :disabled }
end
end
end
================================================
FILE: core/test/factories/host_volume_files.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :host_volume_file, class: UffizziCore::HostVolumeFile do
path { generate(:path) }
source { generate(:relative_path) }
payload { generate(:string) }
added_by { nil }
project { nil }
compose_file { nil }
end
end
================================================
FILE: core/test/factories/kubernetes_distribution.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :kubernetes_distribution, class: UffizziCore::KubernetesDistribution do
version { generate(:version) }
image { generate(:string) }
distro { generate(:string) }
trait :default do
default { true }
end
end
end
================================================
FILE: core/test/factories/membership.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :membership, class: UffizziCore::Membership do
user { nil }
account { nil }
role { UffizziCore::Membership.role.developer }
trait :admin do
role { UffizziCore::Membership.role.admin }
end
trait :developer do
role { UffizziCore::Membership.role.developer }
end
trait :viewer do
role { UffizziCore::Membership.role.viewer }
end
end
end
================================================
FILE: core/test/factories/payments.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :payment, class: UffizziCore::Payment do
account { nil }
end
end
================================================
FILE: core/test/factories/project.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :project, class: UffizziCore::Project do
name { generate :name }
slug { generate :slug }
description { generate :description }
account
trait :with_members do
transient do
members { [] }
end
after(:create) do |project, evaluator|
evaluator.members.each do |member|
account = project.account
role = account.memberships.find_by(user_id: member.id).role
project.user_projects.create(
user: member,
invited_by: account.owner,
role: role,
)
end
end
end
end
end
================================================
FILE: core/test/factories/ratings.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :rating, class: UffizziCore::Rating do
name
end
end
================================================
FILE: core/test/factories/repos.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :repo, class: UffizziCore::Repo do
namespace
name { generate(:string) }
tag
slug
branch
description
repository_id { generate(:number) }
deploy_preview_when_pull_request_is_opened { false }
delete_preview_when_pull_request_is_closed { false }
deploy_preview_when_image_tag_is_created { false }
delete_preview_when_image_tag_is_updated { false }
delete_preview_after { nil }
share_to_github { false }
trait :docker_hub do
type { UffizziCore::Repo::DockerHub.name }
end
trait :azure do
type { UffizziCore::Repo::Azure.name }
end
trait :google do
type { UffizziCore::Repo::Google.name }
end
trait :amazon do
type { UffizziCore::Repo::Amazon.name }
end
trait :kind_barestatic do
kind { UffizziCore::Repo.kind.barestatic }
end
trait :kind_buildpacks18 do
kind { UffizziCore::Repo.kind.buildpacks18 }
end
trait :kind_dockerfile do
kind { UffizziCore::Repo.kind.dockerfile }
end
end
end
================================================
FILE: core/test/factories/secrets.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :secret, class: UffizziCore::Secret do
resource { nil }
name { generate(:string) }
value { generate(:string) }
end
end
================================================
FILE: core/test/factories/sequences.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
sequence :time do
Faker::Time.between(from: DateTime.now - 1, to: DateTime.now)
end
sequence :string,
aliases: [
:tag, :namespace, :branch, :phone, :website, :twitter, :github,
:linkedin, :devto, :facebook, :blog, :bio, :status, :availability,
:primary_skills, :learning, :coding_for, :education, :title, :work,
:primary_location, :content, :login
] do |n|
"string_#{n}"
end
sequence :image do |n|
"namespace/name-#{n}"
end
sequence :full_image_name do |n|
"namespace/name-#{n}:latest"
end
sequence :kubernetes_name do |n|
"kubernetes-name-#{n}"
end
sequence :commit do |n|
"commit-hash-#{n}"
end
sequence :commit_message do |n|
"commit-message-#{n}"
end
sequence :slug do |n|
"slug_#{n}"
end
sequence :name, aliases: [:first_name, :last_name, :username, :description, :service_name] do |n|
"Name #{n}"
end
sequence :token, aliases: [:confirmation_token, :customer_token, :subscription_token] do |_n|
UffizziCore::TokenService.generate
end
sequence :url, aliases: [:image_url] do |n|
"http://url#{n}.com"
end
sequence :password do |n|
"Password1String-#{n}"
end
sequence :email do |n|
"user#{n}@example.com"
end
sequence :instance_name do |n|
"project-name-#{Time.now.to_i}-#{n}"
end
sequence :path do |n|
"/volumes/directory-#{n}"
end
sequence :relative_path do |n|
"./directory-#{n}"
end
sequence :number, aliases: [
:integer,
:mileage,
:ram,
:port,
:selected_storage_size,
:storage_initial,
:storage_capacity,
:max_connections,
:base_price_per_month,
:extra_gigabyte_price_per_hour,
:memory_limit,
:claps,
] do |n|
n
end
sequence :boolean, aliases: [
:has_public_ip,
:has_storage_autoscaling,
:has_high_availability,
:backups_mandatory,
:has_shared_memory,
] do
Faker::Boolean.boolean
end
sequence :vcpu do |n|
['shared', n].sample
end
sequence :domain_name, aliases: [:domain] do
Faker::Internet.domain_name(subdomain: true)
end
sequence :subdomain_name do |_n|
Faker::Lorem
.words(number: 2, supplemental: true)
.join('-')
.delete(',. ')
.downcase
end
sequence :created_at, aliases: ['updated_at'] do
DateTime.now
end
sequence :cluster_name do |n|
"cluster-name-#{n}"
end
sequence :version do |n|
"#{n}.#{n}#{n}"
end
end
================================================
FILE: core/test/factories/templates.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :template, class: UffizziCore::Template do
name
project { nil }
added_by_id { nil }
creation_source { UffizziCore::Template.creation_source.manual }
trait :compose_file_source do
creation_source { UffizziCore::Template.creation_source.compose_file }
end
end
end
================================================
FILE: core/test/factories/user.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :user, class: UffizziCore::User do
first_name
last_name
email
password
phone
confirmation_token
created_at
github
website
twitter
linkedin
devto
facebook
blog
bio
status
availability
primary_skills
learning
coding_for
education
title
work
primary_location
trait :with_personal_account do
after(:create) do |user, _evaluator|
create(:account, :with_admin, kind: UffizziCore::Account.kind.personal, admin: user, created_at: user.created_at)
end
end
trait :with_organizational_account do
after(:create) do |user, _evaluator|
create(:account, :with_admin, kind: UffizziCore::Account.kind.organizational, admin: user, created_at: user.created_at)
end
end
trait :active do
state { :active }
end
trait :disabled do
state { :disabled }
end
trait :global_admin do
after(:create) do |user, _evaluator|
user.add_role(:admin)
end
end
trait :user_google do
creation_source { :google }
end
trait :user_sso do
creation_source { :sso }
end
trait :admin_in_organization do
transient do
organization { nil }
end
after(:create) do |user, evaluator|
create(:membership, :admin, user: user, account: evaluator.organization)
end
end
trait :developer_in_organization do
transient do
organization { nil }
end
after(:create) do |user, evaluator|
create(:membership, :developer, user: user, account: evaluator.organization)
end
end
trait :viewer_in_organization do
transient do
organization { nil }
end
after(:create) do |user, evaluator|
create(:membership, :viewer, user: user, account: evaluator.organization)
end
end
end
end
================================================
FILE: core/test/factories/user_project.rb
================================================
# frozen_string_literal: true
FactoryBot.define do
factory :user_project, class: UffizziCore::UserProject do
user { nil }
project { nil }
role { nil }
trait :admin do
role { UffizziCore::UserProject.role.admin }
end
trait :developer do
role { UffizziCore::UserProject.role.developer }
end
trait :viewer do
role { UffizziCore::UserProject.role.viewer }
end
end
end
================================================
FILE: core/test/fixtures/files/cluster/manifest.yml
================================================
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app.kubernetes.io/name: web-app
ports:
- protocol: TCP
port: 8080
targetPort: 80
================================================
FILE: core/test/fixtures/files/compose_dependencies/configs/vote_conf.json
================================================
{
"content": "c2VydmVyIHsKICBsaXN0ZW4gICAgICAgODA4MDsKICBzZXJ2ZXJfbmFtZSAg\nZXhhbXBsZS5jb207CiAgbG9jYXRpb24gLyB7CiAgICBwcm94eV9wYXNzICAg\nICAgaHR0cDovLzEyNy4wLjAuMTo4MC87CiAgfQogIGxvY2F0aW9uIC92b3Rl\nLyB7CiAgICBwcm94eV9wYXNzICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4\nLzsKICB9Cn0K\n"
}
================================================
FILE: core/test/fixtures/files/compose_files/azure_services/nginx.yml
================================================
services:
nginx:
image: account.azurecr.io/nginx:latest
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/boolean_option.yml
================================================
services:
on:
image: nginx:latest
================================================
FILE: core/test/fixtures/files/compose_files/compose_empty.yml
================================================
================================================
FILE: core/test/fixtures/files/compose_files/compose_memory.yml
================================================
services:
nginx:
image: nginx:latest
deploy:
resources:
limits:
memory: 1000m
redis:
image: redis:latest
deploy:
resources:
limits:
memory: 250000000b
ubuntu:
image: ubuntu:latest
deploy:
resources:
limits:
memory: 4g
postgres:
image: postgres:latest
deploy:
resources:
limits:
memory: 125000k
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/compose_vote_app_github.yml
================================================
services:
redis:
image: redis:latest
postgres:
image: postgres:9.6
environment:
POSTGRES_USER: USER
POSTGRES_PASSWORD: PASSWORD
nginx:
image: nginx:latest
configs:
- source: vote_conf
target: /etc/nginx/conf.d
worker:
build:
context: https://github.com/ACCOUNT/example-voting-worker
dockerfile: Dockerfile
deploy:
resources:
limits:
memory: 250M
vote:
build:
context: https://github.com/ACCOUNT/example-voting-vote
dockerfile: Dockerfile
result:
build:
context: https://github.com/ACCOUNT/example-voting-result
dockerfile: Dockerfile
configs:
vote_conf:
file: ./vote.conf
continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
x-uffizzi-ingress:
service: nginx
port: 8080
================================================
FILE: core/test/fixtures/files/compose_files/compose_with_continuous_preview.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: 12h
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/compose_with_only_line.yml
================================================
services
================================================
FILE: core/test/fixtures/files/compose_files/compose_with_syntax_error.yml
================================================
services:
redis:
image: redis:latest
hello-world
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: 12h
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/compose_with_volumes.yml
================================================
services:
web:
image: web_service:latest
volumes:
- share_data:/var/web/logs
- share_data_2:/var/web/logs:ro
nginx:
image: nginx:latest
volumes:
- source: share_data
target: /some_share_data
read_only: true
- source: share_data_2
target: /some_share_data_2
- source: share_data_2
target: /some_share_data_22
volumes:
share_data:
share_data_2:
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/compose_without_image.yml
================================================
services:
nginx:
environment:
TEST: 123
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/compose_without_services.yml
================================================
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/config_file_dependencies.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- source: vote.conf
target: /etc/nginx/conf.d
x-uffizzi-ingress:
service: nginx
port: 8080
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/account_custom_image.yml
================================================
services:
nginx:
image: account/custom_image:v1.0
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_auto_deploy_off.yml
================================================
services:
hello-world:
image: nginx
deploy:
x-uffizzi-auto-deploy-updates: false
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_config_files.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- source: vote.conf
target: /etc/nginx
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_config_invalid_ingress_service.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi-ingress:
service: test
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_long_syntax.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- source: vote_conf
target: /etc/nginx
configs:
vote_conf:
file: ./configs/vote.conf
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_long_syntax_without_target.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- source: vote_conf
configs:
vote_conf:
file: ./etc/nginx/vote.conf
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_short_syntax.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- vote_conf
configs:
vote_conf:
file: ./vote.conf
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_short_syntax_invalid_path.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- vote_conf
configs:
vote_conf:
file: ''
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_short_syntax_unknown_config.yml
================================================
services:
nginx:
image: nginx:latest
configs:
- unknown_config
configs:
vote_conf:
file: ./etc/nginx/vote.conf
defaulf_conf:
file: ./etc/nginx/default.conf
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_delete_after_integer.yml
================================================
services:
hello-world:
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: 99
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_hours.yml
================================================
services:
hello-world:
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_max.yml
================================================
services:
hello-world:
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: 99999999h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_min.yml
================================================
services:
hello-world:
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: 0h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_postfix.yml
================================================
services:
hello-world:
image: nginx
x-uffizzi-continuous-preview:
delete_preview_after: 24m
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file.yml
================================================
services:
hello-world:
image: nginx
env_file: .env
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file_duplicates.yml
================================================
services:
hello-world:
image: nginx
env_file:
- ./.env
- .env
- .env/
- .env
- infra/secrets.env
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file_empty.yml
================================================
services:
hello-world:
image: nginx
env_file:
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file_empty_in_array.yml
================================================
services:
hello-world:
image: nginx
env_file:
- .env
-
- .local.env
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_envs.yml
================================================
services:
hello-world:
image: nginx
environment:
MULTILINE_KEY: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvt9uoL0Ke7He/EmdxhfBn3hwg0WLASk6zoxLlTcvNpCMUEED
QkUUQX9S1PCUDuW8Urr87ZRCnzurnj7EGjFSeKS7qt4kesIfaAsyBC8Sf8D7IZ7d
QuOS/f1R5Qt1ofSSmgmUvg6rROJs7CqtI0YLYysr2Dn2fuTZf4Jc
-----END RSA PRIVATE KEY-----
SECRET: 'secret'
RAILS_WORKERS_COUNT: 0
APP_URL: 'http://lvh.me:7000'
APP_PASSWORD: ''
RUBYOPT: -W:no-deprecated -W:no-experimental
CDN_HOST: https://cdn.cloud?k=1
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_ingress_port_non_integer.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi-ingress:
service: nginx
port: '2345'
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_deploy_auto.yml
================================================
services:
hello-world:
image: nginx
deploy:
x-uffizzi-auto-deploy-updates: TEST
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory.yml
================================================
services:
nginx:
image: nginx:latest
deploy:
resources:
limits:
memory: 3g
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory_min_value.yml
================================================
services:
nginx:
image: nginx:latest
deploy:
resources:
limits:
memory: 5M
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory_postfix.yml
================================================
services:
nginx:
image: nginx:latest
deploy:
resources:
limits:
memory: 5TEST
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory_type.yml
================================================
services:
nginx:
image: nginx:latest
deploy:
resources:
limits:
memory: 100
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_port.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi-ingress:
service: nginx
port: 99999
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_uffizzi_ingress.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi:
ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_with_continuous_preview_without_x-uffizzi.yml
================================================
services:
nginx:
image: nginx:latest
continuous_preview:
delete_preview_after: 10h
share_to_github: false
x-uffizzi-ingress:
service: nginx
port: 80
continuous_preview:
delete_preview_after: 10h
share_to_github: false
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_with_ingress_without_x-uffizzi.yml
================================================
services:
nginx:
image: nginx:latest
ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_ingress.yml
================================================
services:
nginx:
image: nginx:latest
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_ingress_port.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi-ingress:
service: nginx
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_ingress_service.yml
================================================
services:
nginx:
image: nginx:latest
x-uffizzi-ingress:
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_tag.yml
================================================
services:
nginx:
image: nginx
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets.yml
================================================
services:
postgres:
image: postgres
secrets:
- postgres_user
- postgres_password
secrets:
postgres_user:
external: true
name: POSTGRES_USER
postgres_password:
external: true
name: POSTGRES_PASSWORD
x-uffizzi-ingress:
service: postgres
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_duplicates.yml
================================================
services:
postgres:
image: postgres
secrets:
- postgres_user
- postgres_password
secrets:
postgres_user:
external: true
name: POSTGRES_USER
postgres_password:
external: true
name: POSTGRES_USER
x-uffizzi-ingress:
service: postgres
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_unknown.yml
================================================
services:
postgres:
image: postgres:latest
secrets:
- postgres
secrets:
postgres_user:
external: true
name: POSTGRES_USER
postgres_password:
external: true
name: POSTGRES_PASSWORD
x-uffizzi-ingress:
service: postgres
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_without_external.yml
================================================
services:
postgres:
image: postgres
secrets:
- postgres_user
- postgres_password
secrets:
postgres_user:
external: true
name: POSTGRES_USER
postgres_password:
external: false
name: POSTGRES_PASSWORD
x-uffizzi-ingress:
service: postgres
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_without_name.yml
================================================
services:
postgres:
image: postgres
secrets:
- postgres_user
- postgres_password
secrets:
postgres_user:
external: true
name: POSTGRES_USER
postgres_password:
external: true
x-uffizzi-ingress:
service: postgres
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/volumes_anonymous.yml
================================================
services:
nginx:
image: nginx:latest
volumes:
- /var/web/logs_1
- /var/web/logs_2
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/dockerhub_services/volumes_named.yml
================================================
services:
nginx:
image: nginx:latest
volumes:
- share_data:/var/web/logs_1
volumes:
share_data:
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/github_services/hello_world.yml
================================================
services:
hello-world:
build:
context: https://github.com/ACCOUNT/hello-world
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/google_services/cloudsql.yml
================================================
services:
nginx:
image: gcr.io/project-name/nginx:latest
sql-proxy:
image: gcr.io/cloudsql-docker/gce-proxy:1.19.1
command:
- '/cloud_sql_proxy'
- '-dir=/cloudsql'
- '-instances=instance=tcp:5432'
- '-credential_file=/sql-proxy.json'
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/google_services/cloudsql_entrypoint.yml
================================================
services:
nginx:
image: gcr.io/project-name/nginx:latest
sql-proxy:
image: gcr.io/cloudsql-docker/gce-proxy:1.19.1
entrypoint: /cloud_sql_proxy -dir=/cloudsql -instances=instance=tcp:5432 -credential_file=/sql-proxy.json
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/google_services/nginx.yml
================================================
services:
nginx:
image: gcr.io/project-name/nginx:latest
x-uffizzi-ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/array_command_success.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/disabled_healthcheck.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
test: ["NONE"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/healthcheck_with_disable.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
disable: true
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/invalid_command.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
test: 1
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/invalid_interval.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1p30w
timeout: 10s
retries: 3
start_period: 40s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/invalid_options.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
interval: 1m30s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/invalid_retries.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: '3'
start_period: 40s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/healthcheck/string_command_success.yml
================================================
services:
redis:
image: redis:latest
hello-world:
image: nginx
healthcheck:
test: curl -f http://localhost
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
x-uffizzi:
continuous_preview:
delete_preview_after: 10h
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/compose_files/invalid_service.yml
================================================
services:
hello-^&*world:
image: nginx
x-uffizzi-ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/controller/cluster_asleep.json
================================================
{
"name": "uffizzi-test-cluster-vcluster",
"namespace": "uffizzi-test-cluster",
"uid": "90cffab2-812b-418f-a84b-5dcdded15bfd",
"status": {
"ready": true,
"sleep": true,
"kubeConfig": "test-kubeconfig"
}
}
================================================
FILE: core/test/fixtures/files/controller/cluster_awake.json
================================================
{
"name": "uffizzi-test-cluster-vcluster",
"namespace": "uffizzi-test-cluster",
"uid": "90cffab2-812b-418f-a84b-5dcdded15bfd",
"status": {
"ready": true,
"sleep": false,
"kubeConfig": "test-kubeconfig"
}
}
================================================
FILE: core/test/fixtures/files/controller/cluster_not_ready.json
================================================
{
"name": "uffizzi-test-cluster-vcluster",
"namespace": "uffizzi-test-cluster",
"uid": "90cffab2-812b-418f-a84b-5dcdded15bfd",
"status": {
"ready": false,
"kubeConfig": ""
}
}
================================================
FILE: core/test/fixtures/files/controller/cluster_ready.json
================================================
{
"name": "uffizzi-test-cluster-vcluster",
"namespace": "uffizzi-test-cluster",
"uid": "90cffab2-812b-418f-a84b-5dcdded15bfd",
"status": {
"ready": true,
"kubeConfig": "test-kubeconfig"
}
}
================================================
FILE: core/test/fixtures/files/controller/deployment_containers.json
================================================
[
{
"metadata": {
"name": "app-test-env-1-6f4c8896d-xrcfq",
"generateName": "app-test-env-1-6f4c8896d-",
"namespace": "test-env-1",
"uid": "7ed22d85-495e-457f-9916-880b54f8d1bc",
"resourceVersion": "72170704",
"creationTimestamp": "2021-08-20T13:05:33Z",
"labels": {
"app": "app-test-env-1",
"app.kubernetes.io/managed-by": "uffizzi",
"pod-template-hash": "6f4c8896d"
},
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2021-08-20T13:05:33Z"
},
"ownerReferences": [
{
"apiVersion": "apps/v1",
"kind": "ReplicaSet",
"name": "app-test-env-1-6f4c8896d",
"uid": "81df5b4b-acbd-4384-9e97-9dc663275728",
"controller": true,
"blockOwnerDeletion": true
}
]
},
"spec": {
"containers": [
{
"name": "f03d008a48",
"image": "uffizzitest/webhooks-test-app:latest",
"ports": [
{
"name": "default-port",
"containerPort": 80,
"protocol": "TCP"
}
],
"env": [
{
"name": "PORT",
"value": "80"
}
],
"resources": {
"limits": {
"cpu": "25m",
"memory": "250Mi"
},
"requests": {
"cpu": "25m",
"memory": "250Mi"
}
},
"startupProbe": {
"tcpSocket": {
"port": 80
},
"initialDelaySeconds": 10,
"timeoutSeconds": 1,
"periodSeconds": 15,
"successThreshold": 1,
"failureThreshold": 80
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always",
"controllerName" : "f03d008a48"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "default",
"serviceAccount": "default",
"automountServiceAccountToken": false,
"nodeName": "gke-example-default-pool-8139be1d-wmaj",
"securityContext": {},
"imagePullSecrets": [
{
"name": "credential-dockerhub-3"
}
],
"schedulerName": "default-scheduler",
"tolerations": [
{
"key": "sandbox.gke.io/runtime",
"operator": "Exists"
},
{
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
},
{
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
}
],
"priority": 0,
"enableServiceLinks": true,
"preemptionPolicy": "PreemptLowerPriority"
},
"status": {
"phase": "Running",
"conditions": [
{
"type": "Initialized",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2021-08-20T13:05:33Z"
},
{
"type": "Ready",
"status": "False",
"lastProbeTime": null,
"lastTransitionTime": "2021-08-20T13:05:33Z",
"reason": "ContainersNotReady",
"message": "containers with unready status: [f03d008a48]"
},
{
"type": "ContainersReady",
"status": "False",
"lastProbeTime": null,
"lastTransitionTime": "2021-08-20T13:05:33Z",
"reason": "ContainersNotReady",
"message": "containers with unready status: [f03d008a48]"
},
{
"type": "PodScheduled",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2021-08-20T13:05:33Z"
}
],
"hostIP": "10.128.0.18",
"podIP": "10.108.4.232",
"podIPs": [
{
"ip": "10.108.4.232"
}
],
"startTime": "2021-08-20T13:05:33Z",
"containerStatuses": [
{
"name": "f03d008a48",
"state": {
"running": {
"startedAt": "2021-08-20T13:05:57Z"
}
},
"lastState": {},
"ready": false,
"restartCount": 0,
"image": "uffizzitest/webhooks-test-app:latest",
"imageID": "docker-pullable://uffizzitest/webhooks-test-app@sha256:a1efdad4c909517ae9eecd1a362d6f30046822f8e47793d0211aa0d5a3cc2b16",
"containerID": "docker://4b04f42cb9c9c24837b9ae4e458c57a31abebc2f03529447aae5350834185c80",
"started": true
}
],
"qosClass": "Guaranteed"
}
}
]
================================================
FILE: core/test/fixtures/files/controller/deployment_containers_with_error.json
================================================
[
{
"metadata": {
"name": "app-deployment-1-54c7f5f884-xcn5h",
"generate_name": "app-deployment-1-54c7f5f884-",
"namespace": "deployment-1",
"uid": "4992a195-4156-4aa0-a76a-43345621e7ff",
"resource_version": "249176761",
"creation_timestamp": "2022-12-05T18:11:04Z",
"labels": {
"app": "app-deployment-1",
"app.kubernetes.io/managed_by": "uffizzi",
"pod_template_hash": "54c7f5f884"
},
"annotations": {
"kubectl.kubernetes.io/restarted_at": "2022-12-05T18:11:03Z"
},
"owner_references": [
{
"api_version": "apps/v1",
"kind": "ReplicaSet",
"name": "app-deployment-1-54c7f5f884",
"uid": "e7615d70-e805-423c-91f4-a6679b194365",
"controller": true,
"block_owner_deletion": true
}
]
},
"spec": {
"containers": [
{
"name": "f03d008a48",
"image": "library/nginx:latest",
"args": [
"exit"
],
"ports": [
{
"name": "default-port",
"container_port": 80,
"protocol": "TCP"
}
],
"env": [
{
"name": "PORT",
"value": "80"
},
{
"name": "UFFIZZI_URL",
"value": "https://deployment-1-test-preview-jymy.localhost:7000"
},
{
"name": "UFFIZZI_DOMAIN",
"value": "deployment-1-test-preview-jymy.localhost:7000"
}
],
"resources": {
"limits": {
"cpu": "1",
"memory": "125Mi"
},
"requests": {
"cpu": "0",
"memory": "125Mi"
}
},
"startup_probe": {
"tcp_socket": {
"port": 80
},
"initial_delay_seconds": 10,
"timeout_seconds": 1,
"period_seconds": 15,
"success_threshold": 1,
"failure_threshold": 80
},
"termination_message_path": "/dev/termination-log",
"termination_message_policy": "File",
"image_pull_policy": "Always"
}
],
"restart_policy": "Always",
"termination_grace_period_seconds": 30,
"dns_policy": "ClusterFirst",
"node_selector": {
"sandbox.gke.io/runtime": "gvisor"
},
"service_account_name": "default",
"service_account": "default",
"automount_service_account_token": false,
"node_name": "gke-uffizzi-client-sandbox-588e8350-tk79",
"security_context": {},
"scheduler_name": "default-scheduler",
"tolerations": [
{
"key": "sandbox.gke.io/runtime",
"operator": "Exists"
},
{
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"effect": "NoExecute",
"toleration_seconds": 300
},
{
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"effect": "NoExecute",
"toleration_seconds": 300
}
],
"priority": 0,
"enable_service_links": true,
"preemption_policy": "PreemptLowerPriority"
},
"status": {
"phase": "Running",
"conditions": [
{
"type": "Initialized",
"status": "True",
"last_probe_time": null,
"last_transition_time": "2022-12-05T18:11:04Z"
},
{
"type": "Ready",
"status": "False",
"last_probe_time": null,
"last_transition_time": "2022-12-05T18:11:04Z",
"reason": "ContainersNotReady",
"message": "containers with unready status: [f03d008a48]"
},
{
"type": "ContainersReady",
"status": "False",
"last_probe_time": null,
"last_transition_time": "2022-12-05T18:11:04Z",
"reason": "ContainersNotReady",
"message": "containers with unready status: [f03d008a48]"
},
{
"type": "PodScheduled",
"status": "True",
"last_probe_time": null,
"last_transition_time": "2022-12-05T18:11:04Z"
}
],
"host_ip": "10.128.0.49",
"pod_ip": "10.20.6.38",
"pod_i_ps": [
{
"ip": "10.20.6.38"
}
],
"start_time": "2022-12-05T18:11:04Z",
"container_statuses": [
{
"name": "f03d008a48",
"state": {
"waiting": {
"reason": "CrashLoopBackOff",
"message": "back-off 40s restarting failed container=f03d008a48 pod=app-deployment-1-54c7f5f884-xcn5h_deployment-1(4992a195-4156-4aa0-a76a-43345621e7ff)"
}
},
"last_state": {
"terminated": {
"exit_code": 127,
"reason": "Error",
"started_at": "2022-12-05T18:11:46Z",
"finished_at": "2022-12-05T18:11:46Z",
"container_id": "containerd://f467da4f32e086c93a92a54917c054ef15e8ab84413b843495c12a277e94b2dd"
}
},
"ready": false,
"restart_count": 3,
"image": "docker.io/library/nginx:latest",
"image_id": "docker.io/library/nginx@sha256:e209ac2f37c70c1e0e9873a5f7231e91dcd83fdf1178d8ed36c2ec09974210ba",
"container_id": "containerd://f467da4f32e086c93a92a54917c054ef15e8ab84413b843495c12a277e94b2dd",
"started": false
}
],
"qos_class": "Burstable"
}
}
]
================================================
FILE: core/test/fixtures/files/controller/deployments.json
================================================
{
"metadata": {
"name": "test-env-1",
"uid": "e7edcd16-0708-4d84-946b-c56db6127916",
"resourceVersion": "72171313",
"creationTimestamp": "2021-08-20T13:05:25Z",
"labels": {
"app.kubernetes.io/managed-by": "uffizzi",
"kind": "standard",
"name": "test-env-1"
},
"annotations": {
"errors": "",
"ingressName": "ingress-1629464776",
"ingress_ip": "104.154.78.201",
"network_connectivity": "",
"serviceName": "service-1629464734"
},
"managedFields": [
{
"manager": "controller",
"operation": "Update",
"apiVersion": "v1",
"time": "2021-08-20T13:06:59Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:errors": {},
"f:ingressName": {},
"f:ingress_ip": {},
"f:network_connectivity": {},
"f:serviceName": {}
},
"f:labels": {
".": {},
"f:app.kubernetes.io/managed-by": {},
"f:kind": {},
"f:name": {}
}
},
"f:status": {
"f:phase": {}
}
}
}
]
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
================================================
FILE: core/test/fixtures/files/controller/ingresses.json
================================================
{
"metadata": {
"resource_version": "63142264"
},
"items": [
{
"metadata": {
"name": "result-x-default-x-uc-pr-82",
"namespace": "c169515535810170",
"uid": "a46d2a3c-f111-4bc8-8301-b8d51c668059",
"resource_version": "62737159",
"generation": 1,
"creation_timestamp": "2023-09-19T20:30:07Z",
"labels": {
"vcluster.loft.sh/managed_by": "uc-pr-82",
"vcluster.loft.sh/namespace": "default"
},
"annotations": {
"app.uffizzi.com/ingress_sync": "true",
"kubectl.kubernetes.io/last_applied_configuration": "{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"Ingress\",\"metadata\":{\"annotations\":{},\"name\":\"result\",\"namespace\":\"default\"},\"spec\":{\"ingressClassName\":\"uffizzi\",\"rules\":[{\"host\":\"result.example.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"result\",\"port\":{\"number\":5001}}},\"path\":\"/\",\"pathType\":\"Prefix\"}]}}],\"tls\":[{\"hosts\":[\"result.example.com\"]}]}}\n",
"vcluster.loft.sh/managed_annotations": "app.uffizzi.com/ingress-sync\nkubectl.kubernetes.io/last-applied-configuration",
"vcluster.loft.sh/object_name": "result",
"vcluster.loft.sh/object_namespace": "default"
},
"owner_references": [
{
"api_version": "v1",
"kind": "Service",
"name": "uc-pr-82",
"uid": "8c502e06-1e48-40bd-a36c-96ecfd7ac414"
}
],
"managed_fields": [
{
"manager": "plugin",
"operation": "Update",
"api_version": "networking.k8s.io/v1",
"time": "2023-09-19T20:30:07Z",
"fields_type": "FieldsV1",
"fields_v1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:app.uffizzi.com/ingress_sync": {},
"f:kubectl.kubernetes.io/last_applied_configuration": {},
"f:vcluster.loft.sh/managed_annotations": {},
"f:vcluster.loft.sh/object_name": {},
"f:vcluster.loft.sh/object_namespace": {}
},
"f:labels": {
".": {},
"f:vcluster.loft.sh/managed_by": {},
"f:vcluster.loft.sh/namespace": {}
},
"f:owner_references": {
".": {},
"k:{\"uid\":\"8c502e06_1e48_40bd_a36c_96ecfd7ac414\"}": {}
}
},
"f:spec": {
"f:ingress_class_name": {},
"f:rules": {},
"f:tls": {}
}
}
},
{
"manager": "nginx-ingress-controller",
"operation": "Update",
"api_version": "networking.k8s.io/v1",
"time": "2023-09-19T22:14:31Z",
"fields_type": "FieldsV1",
"fields_v1": {
"f:status": {
"f:load_balancer": {
"f:ingress": {}
}
}
},
"subresource": "status"
}
]
},
"spec": {
"ingress_class_name": "uffizzi",
"tls": [
{
"hosts": [
"result-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com"
]
}
],
"rules": [
{
"host": "result-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com",
"http": {
"paths": [
{
"path": "/",
"path_type": "Prefix",
"backend": {
"service": {
"name": "result-x-default-x-uc-pr-82",
"port": {
"number": 5001
}
}
}
}
]
}
}
]
},
"status": {
"load_balancer": {
"ingress": [
{
"ip": "34.134.99.100"
}
]
}
}
},
{
"metadata": {
"name": "uc-pr-82",
"namespace": "c169515535810170",
"uid": "ac012e17-b76c-4630-bc76-2039d1ff6270",
"resource_version": "62737153",
"generation": 1,
"creation_timestamp": "2023-09-19T20:29:21Z",
"labels": {
"app.kubernetes.io/managed_by": "Helm",
"helm.toolkit.fluxcd.io/name": "uc-pr-82",
"helm.toolkit.fluxcd.io/namespace": "c169515535810170"
},
"annotations": {
"app.uffizzi.com/ingress_sync": "true",
"meta.helm.sh/release_name": "uc-pr-82",
"meta.helm.sh/release_namespace": "c169515535810170",
"nginx.ingress.kubernetes.io/backend_protocol": "HTTPS",
"nginx.ingress.kubernetes.io/ssl_passthrough": "true",
"nginx.ingress.kubernetes.io/ssl_redirect": "true"
},
"managed_fields": [
{
"manager": "helm-controller",
"operation": "Update",
"api_version": "networking.k8s.io/v1",
"time": "2023-09-19T20:29:21Z",
"fields_type": "FieldsV1",
"fields_v1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:app.uffizzi.com/ingress_sync": {},
"f:meta.helm.sh/release_name": {},
"f:meta.helm.sh/release_namespace": {},
"f:nginx.ingress.kubernetes.io/backend_protocol": {},
"f:nginx.ingress.kubernetes.io/ssl_passthrough": {},
"f:nginx.ingress.kubernetes.io/ssl_redirect": {}
},
"f:labels": {
".": {},
"f:app.kubernetes.io/managed_by": {},
"f:helm.toolkit.fluxcd.io/name": {},
"f:helm.toolkit.fluxcd.io/namespace": {}
}
},
"f:spec": {
"f:rules": {}
}
}
},
{
"manager": "nginx-ingress-controller",
"operation": "Update",
"api_version": "networking.k8s.io/v1",
"time": "2023-09-19T22:14:30Z",
"fields_type": "FieldsV1",
"fields_v1": {
"f:status": {
"f:load_balancer": {
"f:ingress": {}
}
}
},
"subresource": "status"
}
]
},
"spec": {
"ingress_class_name": "uffizzi",
"rules": [
{
"host": "pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com",
"http": {
"paths": [
{
"path": "/",
"path_type": "ImplementationSpecific",
"backend": {
"service": {
"name": "uc-pr-82",
"port": {
"name": "https"
}
}
}
}
]
}
}
]
},
"status": {
"load_balancer": {
"ingress": [
{
"ip": "34.134.99.100"
}
]
}
}
},
{
"metadata": {
"name": "vote-x-default-x-uc-pr-82",
"namespace": "c169515535810170",
"uid": "114b3dc2-6644-4d88-89f2-bbaa0b141787",
"resource_version": "62737156",
"generation": 1,
"creation_timestamp": "2023-09-19T20:30:07Z",
"labels": {
"vcluster.loft.sh/managed_by": "uc-pr-82",
"vcluster.loft.sh/namespace": "default"
},
"annotations": {
"app.uffizzi.com/ingress_sync": "true",
"kubectl.kubernetes.io/last_applied_configuration": "{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"Ingress\",\"metadata\":{\"annotations\":{},\"name\":\"vote\",\"namespace\":\"default\"},\"spec\":{\"ingressClassName\":\"uffizzi\",\"rules\":[{\"host\":\"vote.example.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"vote\",\"port\":{\"number\":5000}}},\"path\":\"/\",\"pathType\":\"Prefix\"}]}}],\"tls\":[{\"hosts\":[\"vote.example.com\"]}]}}\n",
"vcluster.loft.sh/managed_annotations": "app.uffizzi.com/ingress-sync\nkubectl.kubernetes.io/last-applied-configuration",
"vcluster.loft.sh/object_name": "vote",
"vcluster.loft.sh/object_namespace": "default"
},
"owner_references": [
{
"api_version": "v1",
"kind": "Service",
"name": "uc-pr-82",
"uid": "8c502e06-1e48-40bd-a36c-96ecfd7ac414"
}
],
"managed_fields": [
{
"manager": "plugin",
"operation": "Update",
"api_version": "networking.k8s.io/v1",
"time": "2023-09-19T20:30:07Z",
"fields_type": "FieldsV1",
"fields_v1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:app.uffizzi.com/ingress_sync": {},
"f:kubectl.kubernetes.io/last_applied_configuration": {},
"f:vcluster.loft.sh/managed_annotations": {},
"f:vcluster.loft.sh/object_name": {},
"f:vcluster.loft.sh/object_namespace": {}
},
"f:labels": {
".": {},
"f:vcluster.loft.sh/managed_by": {},
"f:vcluster.loft.sh/namespace": {}
},
"f:owner_references": {
".": {},
"k:{\"uid\":\"8c502e06_1e48_40bd_a36c_96ecfd7ac414\"}": {}
}
},
"f:spec": {
"f:ingress_class_name": {},
"f:rules": {},
"f:tls": {}
}
}
},
{
"manager": "nginx-ingress-controller",
"operation": "Update",
"api_version": "networking.k8s.io/v1",
"time": "2023-09-19T22:14:30Z",
"fields_type": "FieldsV1",
"fields_v1": {
"f:status": {
"f:load_balancer": {
"f:ingress": {}
}
}
},
"subresource": "status"
}
]
},
"spec": {
"ingress_class_name": "uffizzi",
"tls": [
{
"hosts": [
"vote-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com"
]
}
],
"rules": [
{
"host": "vote-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com",
"http": {
"paths": [
{
"path": "/",
"path_type": "Prefix",
"backend": {
"service": {
"name": "vote-x-default-x-uc-pr-82",
"port": {
"number": 5000
}
}
}
}
]
}
}
]
},
"status": {
"load_balancer": {
"ingress": [
{
"ip": "34.134.99.100"
}
]
}
}
}
]
}
================================================
FILE: core/test/fixtures/files/controller/logs.json
================================================
{ "logs":
[
"2022-11-14T11:46:55.474292870Z /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration"
]
}
================================================
FILE: core/test/fixtures/files/dockerhub/digest.json
================================================
{
"status": 200,
"headers": {
"content-length": 1574,
"content-type": "application/vnd.docker.distribution.manifest.v2+json",
"docker-content-digest": "sha256:cd0c68c5479f2db4b9e2c5fbfdb7a8acb77625322dd5b474578515422d3ddb59",
"docker-distribution-api-version": "registry/2.0",
"etag": "sha256:cd0c68c5479f2db4b9e2c5fbfdb7a8acb77625322dd5b474578515422d3ddb59",
"date": "Sun, 22 Aug 2021 10:11:10 GMT",
"strict-transport-security": "max-age=31536000",
"ratelimit-limit": "100;w=21600",
"ratelimit-remaining": "100;w=21600"
},
"body": {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7699,
"digest": "sha256:ddcca4b8a6f0367b5de2764dfe76b0a4bfa6d75237932185923705da47004347"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 27145985,
"digest": "sha256:e1acddbe380c63f0de4b77d3f287b7c81cd9d89563a230692378126b46ea6546"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 1732,
"digest": "sha256:a31098369fccd4adaac914a87e5def0f96dc1dbc385921d38db1f1f4cef8242a"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 1417929,
"digest": "sha256:4a49b0eba86d37bb4fa4d1a9f8826a1222ea4eb7717192674d79341a998febf8"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 10116853,
"digest": "sha256:fddf1399eface27528e89bb5296dc40fbabe7c6b2186c0fca6dce96ae9c5d9a1"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 134,
"digest": "sha256:5c6658b59b7200ac5a9ec19f428dc5739a95ea2f6965715443692bff6da9a79d"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 408,
"digest": "sha256:0b88638a5b778b573dd7c4e3fda17cab7d4b229defd7943a07ac982c65e98cbb"
}
]
}
}
================================================
FILE: core/test/fixtures/files/dockerhub/login_fail.json
================================================
{
"status": 401,
"body": "{\"detail\":\"Incorrect authentication credentials\"}\n\n",
"response_headers": {
"date": "Thu, 17 Mar 2022 09:49:37 GMT",
"transfer-encoding": "chunked",
"server": "nginx",
"x-frame-options": "deny",
"x-content-type-options": "nosniff",
"x-xss-protection": "1; mode=block",
"strict-transport-security": "max-age=31536000"
}
}
================================================
FILE: core/test/fixtures/files/dockerhub/repository.json
================================================
{
"user":"library",
"name":"nginx",
"namespace":"library",
"repository_type":"image",
"status":1,
"description":"Official build of Nginx.",
"is_private":false,
"is_automated":false,
"can_edit":false,
"star_count":17333,
"pull_count":6917945936,
"last_updated":"2022-08-23T22:01:16.714365Z",
"date_registered":"2014-06-05T19:14:45Z",
"collaborator_count":0,
"affiliation":null,
"hub_user":"stackbrew",
"has_starred":false,
"full_description":"# Quick reference\n\n-\t**Maintained by**: \n\t[the NGINX Docker Maintainers](https://github.com/nginxinc/docker-nginx)\n\n-\t**Where to get help**: \n\t[the Docker Community Forums](https://forums.docker.com/), [the Docker Community Slack](https://dockr.ly/slack), or [Stack Overflow](https://stackoverflow.com/search?tab=newest\u0026q=docker)\n\n# Supported tags and respective `Dockerfile` links\n\n-\t[`1.23.1`, `mainline`, `1`, `1.23`, `latest`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/mainline/debian/Dockerfile)\n-\t[`1.23.1-perl`, `mainline-perl`, `1-perl`, `1.23-perl`, `perl`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/mainline/debian-perl/Dockerfile)\n-\t[`1.23.1-alpine`, `mainline-alpine`, `1-alpine`, `1.23-alpine`, `alpine`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/mainline/alpine/Dockerfile)\n-\t[`1.23.1-alpine-perl`, `mainline-alpine-perl`, `1-alpine-perl`, `1.23-alpine-perl`, `alpine-perl`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/mainline/alpine-perl/Dockerfile)\n-\t[`1.22.0`, `stable`, `1.22`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/stable/debian/Dockerfile)\n-\t[`1.22.0-perl`, `stable-perl`, `1.22-perl`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/stable/debian-perl/Dockerfile)\n-\t[`1.22.0-alpine`, `stable-alpine`, `1.22-alpine`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/stable/alpine/Dockerfile)\n-\t[`1.22.0-alpine-perl`, `stable-alpine-perl`, `1.22-alpine-perl`](https://github.com/nginxinc/docker-nginx/blob/f3d86e99ba2db5d9918ede7b094fcad7b9128cd8/stable/alpine-perl/Dockerfile)\n\n# Quick reference (cont.)\n\n-\t**Where to file issues**: \n\t[https://github.com/nginxinc/docker-nginx/issues](https://github.com/nginxinc/docker-nginx/issues)\n\n-\t**Supported architectures**: ([more info](https://github.com/docker-library/official-images#architectures-other-than-amd64)) \n\t[`amd64`](https://hub.docker.com/r/amd64/nginx/), [`arm32v5`](https://hub.docker.com/r/arm32v5/nginx/), [`arm32v6`](https://hub.docker.com/r/arm32v6/nginx/), [`arm32v7`](https://hub.docker.com/r/arm32v7/nginx/), [`arm64v8`](https://hub.docker.com/r/arm64v8/nginx/), [`i386`](https://hub.docker.com/r/i386/nginx/), [`mips64le`](https://hub.docker.com/r/mips64le/nginx/), [`ppc64le`](https://hub.docker.com/r/ppc64le/nginx/), [`s390x`](https://hub.docker.com/r/s390x/nginx/)\n\n-\t**Published image artifact details**: \n\t[repo-info repo's `repos/nginx/` directory](https://github.com/docker-library/repo-info/blob/master/repos/nginx) ([history](https://github.com/docker-library/repo-info/commits/master/repos/nginx)) \n\t(image metadata, transfer size, etc)\n\n-\t**Image updates**: \n\t[official-images repo's `library/nginx` label](https://github.com/docker-library/official-images/issues?q=label%3Alibrary%2Fnginx) \n\t[official-images repo's `library/nginx` file](https://github.com/docker-library/official-images/blob/master/library/nginx) ([history](https://github.com/docker-library/official-images/commits/master/library/nginx))\n\n-\t**Source of this description**: \n\t[docs repo's `nginx/` directory](https://github.com/docker-library/docs/tree/master/nginx) ([history](https://github.com/docker-library/docs/commits/master/nginx))\n\n# What is nginx?\n\nNginx (pronounced \"engine-x\") is an open source reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer, HTTP cache, and a web server (origin server). The nginx project started with a strong focus on high concurrency, high performance and low memory usage. It is licensed under the 2-clause BSD-like license and it runs on Linux, BSD variants, Mac OS X, Solaris, AIX, HP-UX, as well as on other *nix flavors. It also has a proof of concept port for Microsoft Windows.\n\n\u003e [wikipedia.org/wiki/Nginx](https://en.wikipedia.org/wiki/Nginx)\n\n\n\n# How to use this image\n\n## Hosting some simple static content\n\n```console\n$ docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx\n```\n\nAlternatively, a simple `Dockerfile` can be used to generate a new image that includes the necessary content (which is a much cleaner solution than the bind mount above):\n\n```dockerfile\nFROM nginx\nCOPY static-html-directory /usr/share/nginx/html\n```\n\nPlace this file in the same directory as your directory of content (\"static-html-directory\"), run `docker build -t some-content-nginx .`, then start your container:\n\n```console\n$ docker run --name some-nginx -d some-content-nginx\n```\n\n## Exposing external port\n\n```console\n$ docker run --name some-nginx -d -p 8080:80 some-content-nginx\n```\n\nThen you can hit `http://localhost:8080` or `http://host-ip:8080` in your browser.\n\n## Complex configuration\n\n```console\n$ docker run --name my-custom-nginx-container -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx\n```\n\nFor information on the syntax of the nginx configuration files, see [the official documentation](http://nginx.org/en/docs/) (specifically the [Beginner's Guide](http://nginx.org/en/docs/beginners_guide.html#conf_structure)).\n\nIf you wish to adapt the default configuration, use something like the following to copy it from a running nginx container:\n\n```console\n$ docker run --name tmp-nginx-container -d nginx\n$ docker cp tmp-nginx-container:/etc/nginx/nginx.conf /host/path/nginx.conf\n$ docker rm -f tmp-nginx-container\n```\n\nThis can also be accomplished more cleanly using a simple `Dockerfile` (in `/host/path/`):\n\n```dockerfile\nFROM nginx\nCOPY nginx.conf /etc/nginx/nginx.conf\n```\n\nIf you add a custom `CMD` in the Dockerfile, be sure to include `-g daemon off;` in the `CMD` in order for nginx to stay in the foreground, so that Docker can track the process properly (otherwise your container will stop immediately after starting)!\n\nThen build the image with `docker build -t custom-nginx .` and run it as follows:\n\n```console\n$ docker run --name my-custom-nginx-container -d custom-nginx\n```\n\n### Using environment variables in nginx configuration (new in 1.19)\n\nOut-of-the-box, nginx doesn't support environment variables inside most configuration blocks. But this image has a function, which will extract environment variables before nginx starts.\n\nHere is an example using docker-compose.yml:\n\n```yaml\nweb:\n image: nginx\n volumes:\n - ./templates:/etc/nginx/templates\n ports:\n - \"8080:80\"\n environment:\n - NGINX_HOST=foobar.com\n - NGINX_PORT=80\n```\n\nBy default, this function reads template files in `/etc/nginx/templates/*.template` and outputs the result of executing `envsubst` to `/etc/nginx/conf.d`.\n\nSo if you place `templates/default.conf.template` file, which contains variable references like this:\n\n\tlisten ${NGINX_PORT};\n\noutputs to `/etc/nginx/conf.d/default.conf` like this:\n\n\tlisten 80;\n\nThis behavior can be changed via the following environment variables:\n\n-\t`NGINX_ENVSUBST_TEMPLATE_DIR`\n\t-\tA directory which contains template files (default: `/etc/nginx/templates`)\n\t-\tWhen this directory doesn't exist, this function will do nothing about template processing.\n-\t`NGINX_ENVSUBST_TEMPLATE_SUFFIX`\n\t-\tA suffix of template files (default: `.template`)\n\t-\tThis function only processes the files whose name ends with this suffix.\n-\t`NGINX_ENVSUBST_OUTPUT_DIR`\n\t-\tA directory where the result of executing envsubst is output (default: `/etc/nginx/conf.d`)\n\t-\tThe output filename is the template filename with the suffix removed.\n\t\t-\tex.) `/etc/nginx/templates/default.conf.template` will be output with the filename `/etc/nginx/conf.d/default.conf`.\n\t-\tThis directory must be writable by the user running a container.\n\n## Running nginx in read-only mode\n\nTo run nginx in read-only mode, you will need to mount a Docker volume to every location where nginx writes information. The default nginx configuration requires write access to `/var/cache` and `/var/run`. This can be easily accomplished by running nginx as follows:\n\n```console\n$ docker run -d -p 80:80 --read-only -v $(pwd)/nginx-cache:/var/cache/nginx -v $(pwd)/nginx-pid:/var/run nginx\n```\n\nIf you have a more advanced configuration that requires nginx to write to other locations, simply add more volume mounts to those locations.\n\n## Running nginx in debug mode\n\nImages since version 1.9.8 come with `nginx-debug` binary that produces verbose output when using higher log levels. It can be used with simple CMD substitution:\n\n```console\n$ docker run --name my-nginx -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx nginx-debug -g 'daemon off;'\n```\n\nSimilar configuration in docker-compose.yml may look like this:\n\n```yaml\nweb:\n image: nginx\n volumes:\n - ./nginx.conf:/etc/nginx/nginx.conf:ro\n command: [nginx-debug, '-g', 'daemon off;']\n```\n\n## Entrypoint quiet logs\n\nSince version 1.19.0, a verbose entrypoint was added. It provides information on what's happening during container startup. You can silence this output by setting environment variable `NGINX_ENTRYPOINT_QUIET_LOGS`:\n\n```console\n$ docker run -d -e NGINX_ENTRYPOINT_QUIET_LOGS=1 nginx\n```\n\n## User and group id\n\nSince 1.17.0, both alpine- and debian-based images variants use the same user and group ids to drop the privileges for worker processes:\n\n```console\n$ id\nuid=101(nginx) gid=101(nginx) groups=101(nginx)\n```\n\n## Running nginx as a non-root user\n\nIt is possible to run the image as a less privileged arbitrary UID/GID. This, however, requires modification of nginx configuration to use directories writeable by that specific UID/GID pair:\n\n```console\n$ docker run -d -v $PWD/nginx.conf:/etc/nginx/nginx.conf nginx\n```\n\nwhere nginx.conf in the current directory should have the following directives re-defined:\n\n```nginx\npid /tmp/nginx.pid;\n```\n\nAnd in the http context:\n\n```nginx\nhttp {\n client_body_temp_path /tmp/client_temp;\n proxy_temp_path /tmp/proxy_temp_path;\n fastcgi_temp_path /tmp/fastcgi_temp;\n uwsgi_temp_path /tmp/uwsgi_temp;\n scgi_temp_path /tmp/scgi_temp;\n...\n}\n```\n\nAlternatively, check out the official [Docker NGINX unprivileged image](https://hub.docker.com/r/nginxinc/nginx-unprivileged).\n\n# Image Variants\n\nThe `nginx` images come in many flavors, each designed for a specific use case.\n\n## `nginx:\u003cversion\u003e`\n\nThis is the defacto image. If you are unsure about what your needs are, you probably want to use this one. It is designed to be used both as a throw away container (mount your source code and start the container to start your app), as well as the base to build other images off of.\n\n## `nginx:\u003cversion\u003e-perl` / `nginx:\u003cversion\u003e-alpine-perl`\n\nStarting with nginx:1.13.0 / mainline and nginx:1.12.0 / stable, the perl module has been removed from the default images. A separate `-perl` tag variant is available if you wish to use the perl module.\n\n## `nginx:\u003cversion\u003e-alpine`\n\nThis image is based on the popular [Alpine Linux project](https://alpinelinux.org), available in [the `alpine` official image](https://hub.docker.com/_/alpine). Alpine Linux is much smaller than most distribution base images (~5MB), and thus leads to much slimmer images in general.\n\nThis variant is useful when final image size being as small as possible is your primary concern. The main caveat to note is that it does use [musl libc](https://musl.libc.org) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), so software will often run into issues depending on the depth of their libc requirements/assumptions. See [this Hacker News comment thread](https://news.ycombinator.com/item?id=10782897) for more discussion of the issues that might arise and some pro/con comparisons of using Alpine-based images.\n\nTo minimize image size, it's uncommon for additional related tools (such as `git` or `bash`) to be included in Alpine-based images. Using this image as a base, add the things you need in your own Dockerfile (see the [`alpine` image description](https://hub.docker.com/_/alpine/) for examples of how to install packages if you are unfamiliar).\n\n# License\n\nView [license information](http://nginx.org/LICENSE) for the software contained in this image.\n\nAs with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained).\n\nSome additional license information which was able to be auto-detected might be found in [the `repo-info` repository's `nginx/` directory](https://github.com/docker-library/repo-info/tree/master/repos/nginx).\n\nAs for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within.",
"permissions":{
"read":true,
"write":false,
"admin":false
},
"media_types":[
"application/vnd.docker.distribution.manifest.list.v2+json"
]
}
================================================
FILE: core/test/fixtures/files/dockerhub/webhooks/push/event_data.json
================================================
{
"push_data": {
"pushed_at": 1613134095,
"images": [],
"tag": "latest",
"pusher": "uffizzitest"
},
"callback_url": "https://registry.hub.docker.com/u/uffizzitest/webhooks-test-app/hook/2i4i3icb013ec45fffigg200jaf2hjf50/",
"repository": {
"status": "Active",
"description": "",
"is_trusted": false,
"full_description": "",
"repo_url": "https://hub.docker.com/r/uffizzitest/webhooks-test-app",
"owner": "uffizzitest",
"is_official": false,
"is_private": false,
"name": "webhooks-test-app",
"namespace": "uffizzitest",
"star_count": 0,
"comment_count": 0,
"date_created": 1613133268,
"repo_name": "uffizzitest/webhooks-test-app"
},
"webhook": {
"push_data": {
"pushed_at": 1613134095,
"images": [
],
"tag": "latest",
"pusher": "uffizzitest"
},
"callback_url": "https://registry.hub.docker.com/u/uffizzitest/webhooks-test-app/hook/2i4i3icb013ec45fffigg200jaf2hjf50/",
"repository": {
"status": "Active",
"description": "",
"is_trusted": false,
"full_description": "",
"repo_url": "https://hub.docker.com/r/uffizzitest/webhooks-test-app",
"owner": "uffizzitest",
"is_official": false,
"is_private": false,
"name": "webhooks-test-app",
"namespace": "uffizzitest",
"star_count": 0,
"comment_count": 0,
"date_created": 1613133268,
"repo_name": "uffizzitest/webhooks-test-app"
}
}
}
================================================
FILE: core/test/fixtures/files/github/compose_files/hello_world_compose_github_container_registry.json
================================================
{
"name": "uffizzi-compose-github-container-registry.yml.yml",
"path": "uffizzi-compose-github-container-registry.yml.yml",
"sha": "401853dba1c7299da89c6593d9b8347cf6442b1a",
"size": 141,
"url": "https://api.github.com/repos/ACCOUNT/hello-world/contents/uffizzi-compose-github-container-registry.yml.yml?ref=compose",
"html_url": "https://github.com/ACCOUNT/hello-world/blob/compose/uffizzi-compose-github-container-registry.yml.yml",
"git_url": "https://api.github.com/repos/ACCOUNT/hello-world/git/blobs/401853dba1c7299da89c6593d9b8347cf6442b1a",
"download_url": "https://raw.githubusercontent.com/ACCOUNT/hello-world/compose/uffizzi-compose-github-container-registry.yml.yml",
"type": "file",
"content": "c2VydmljZXM6CiAgaGVsbG8td29ybGQtYToKICAgIGltYWdlOiBnaGNyLmlv\nL3VmZml6emkvdGVzdC1jb21wb3NlOmxhdGVzdAoKeC11ZmZpenppLWluZ3Jl\nc3M6CiAgc2VydmljZTogaGVsbG8td29ybGQtYQogIHBvcnQ6IDgwCgp4LXVm\nZml6emktY29udGludW91c19wcmV2aWV3OgogIGRlcGxveV9wcmV2aWV3X3do\nZW5fcHVsbF9yZXF1ZXN0X2lzX29wZW5lZDogdHJ1ZQogIGRlbGV0ZV9wcmV2\naWV3X3doZW5fcHVsbF9yZXF1ZXN0X2lzX2Nsb3NlZDogdHJ1ZQogIGRlbGV0\nZV9wcmV2aWV3X2FmdGVyOiAxMGgKICBzaGFyZV90b19naXRodWI6IHRydWUK\nICBkZXBsb3lfcHJldmlld193aGVuX2ltYWdlX3RhZ19pc19jcmVhdGVkOiB0\ncnVlCiAgZGVsZXRlX3ByZXZpZXdfd2hlbl9pbWFnZV90YWdfaXNfdXBkYXRl\nZDogdHJ1ZQ==\n",
"encoding": "base64",
"_links": {
"self": "https://api.github.com/repos/ACCOUNT/hello-world/contents/uffizzi-compose-github-container-registry.yml.yml?ref=compose",
"git": "https://api.github.com/repos/ACCOUNT/hello-world/git/blobs/401853dba1c7299da89c6593d9b8347cf6442b1a",
"html": "https://github.com/ACCOUNT/hello-world/blob/compose/uffizzi-compose-github-container-registry.yml.yml"
}
}
================================================
FILE: core/test/fixtures/files/test-compose-full.yml
================================================
services:
app:
image: uffizzicloud/app
env_file:
- local.env
- ./env_files/env_file.env
configs:
- source: app_conf
target: /etc/nginxz
volumes:
- ./some_app_dir:/var/app/some_dir
- ./files/some_app_file:/var/app/some_app_files
- ./:/var/entire_app
- app_share:/some_app_share:ro
- /some_anonymous_dir
db:
image: postgres:latest
volumes:
- source: ./some_app_dir
target: /var/db/some_dir_2
- source: ./some_app_dir
target: /var/db/some_dir_2
- source: ./some_db_dir
target: /var/db/some_dir_3
- source: ./some_db_file
target: /var/db/some_db_files
- source: db_share
target: /some_db_share
read_only: true
nginx:
image: nginx:1.32
configs:
app_conf:
file: ./app.conf
default_conf:
file: config_files/config_file.conf
volumes:
app_share:
db_share:
x-uffizzi:
ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/test-compose-success-jfrog.yml
================================================
services:
webhooks-test-app:
image: elnealo.jfrog.io/uffizzi-test-docker/webhook-test-app
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
x-uffizzi-ingress:
service: webhooks-test-app
port: 80
x-uffizzi-continuous-preview:
delete_preview_after: 1h
delete_preview_when_image_tag_is_updated: true
deploy_preview_when_image_tag_is_created: true
================================================
FILE: core/test/fixtures/files/test-compose-success-without-dependencies.yml
================================================
services:
app:
image: uffizzicloud/app
db:
image: postgres:latest
nginx:
image: nginx:1.32
x-uffizzi:
ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/test-compose-success.yml
================================================
services:
hello-world:
image: redis
env_file:
- local.env
- ./env_files/env_file.env
configs:
- source: vote_conf
target: /etc/nginxz
volumes:
- share_data:/var/cache
- share_data_2:/var/cache_2:ro
- /var/some_dir
configs:
vote_conf:
file: ./vote.conf
defaulf_conf:
file: config_files/config_file.conf
volumes:
share_data:
share_data_2:
x-uffizzi:
ingress:
service: hello-world
port: 80
additional_subdomains:
- "example"
- "example-hostname"
================================================
FILE: core/test/fixtures/files/test-compose-without-images.yml
================================================
services:
hello-world:
env_file:
- local.env
- ./env_files/env_file.env
configs:
- source: vote_conf
target: /etc/nginxz
configs:
vote_conf:
file: ./vote.conf
defaulf_conf:
file: config_files/config_file.conf
ingress:
service: hello-world
port: 80
================================================
FILE: core/test/fixtures/files/uffizzi-compose-amazon.yml
================================================
services:
hello-world-a:
image: 323707565364.dkr.ecr.us-east-1.amazonaws.com/test-compose:latest
x-uffizzi-ingress:
service: hello-world-a
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-azure.yml
================================================
services:
hello-world-a:
image: account.azurecr.io/test-compose:latest
x-uffizzi-ingress:
service: hello-world-a
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-docker-registry-anonymous.yml
================================================
services:
hello-world-a:
image: ttl.sh/abc:1h
x-uffizzi-ingress:
service: hello-world-a
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-dockerhub.yml
================================================
services:
hello-world-a:
image: project1/test-compose:latest
x-uffizzi-ingress:
service: hello-world-a
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-ghcr.yml
================================================
services:
hello-world-a:
image: ghcr.io/project1/test-compose:latest
x-uffizzi-ingress:
service: hello-world-a
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-google.yml
================================================
services:
hello-world-a:
image: gcr.io/project1/test-compose:latest
x-uffizzi-ingress:
service: hello-world-a
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-invalid-service-name.yml
================================================
services:
hello_world:
image: project1/test-compose:latest
x-uffizzi-ingress:
service: hello_world
port: 80
x-uffizzi-continuous_preview:
deploy_preview_when_pull_request_is_opened: true
delete_preview_when_pull_request_is_closed: true
delete_preview_after: 10h
share_to_github: true
deploy_preview_when_image_tag_is_created: true
delete_preview_when_image_tag_is_updated: true
================================================
FILE: core/test/fixtures/files/uffizzi-compose-vote-app-docker-with-memory-request.yml
================================================
services:
redis:
image: redis:latest
postgres:
image: postgres:9.6
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
nginx:
image: nginx:latest
deploy:
resources:
limits:
memory: 4000M
configs:
- source: vote_conf
target: /etc/nginx/conf.d
entrypoint: /usr/sbin/nginx-debug
command:
- "-g"
- "daemon off;"
worker:
image: uffizzicloud/example-worker:latest
deploy:
resources:
limits:
memory: 4000M
vote:
image: uffizzicloud/example-vote:latest
deploy:
resources:
limits:
memory: 4000M
result:
image: uffizzicloud/example-result:latest
x-uffizzi-ingress:
service: nginx
port: 8080
configs:
vote_conf:
file: configs/vote.conf
defaulf_conf:
file: config_files/config_file.conf
================================================
FILE: core/test/fixtures/files/uffizzi-compose-vote-app-docker.yml
================================================
services:
redis:
image: redis:latest
postgres:
image: postgres:9.6
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
nginx:
image: nginx:latest
configs:
- source: vote_conf
target: /etc/nginx/conf.d
entrypoint: /usr/sbin/nginx-debug
command:
- "-g"
- "daemon off;"
worker:
image: uffizzicloud/example-worker:latest
deploy:
resources:
limits:
memory: 250M
vote:
image: uffizzicloud/example-vote:latest
result:
image: uffizzicloud/example-result:latest
x-uffizzi-ingress:
service: nginx
port: 8080
configs:
vote_conf:
file: configs/vote.conf
defaulf_conf:
file: config_files/config_file.conf
================================================
FILE: core/test/fixtures/files/uffizzi-compose-vote-app-with-command-as-string.yml
================================================
services:
postgres:
image: postgres:12
command: postgres -c jit=off
restart: always
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgrespassword
nginx:
image: nginx:1.32
command:
- "-g"
- "daemon off;"
x-uffizzi:
ingress:
service: nginx
port: 80
================================================
FILE: core/test/fixtures/files/uffizzi-compose-with-host-volumes.yml
================================================
services:
web:
image: nginx
volumes:
- ./share_dir:/var/share_dir
x-uffizzi-ingress:
service: web
port: 80
================================================
FILE: core/test/fixtures/files/uffizzi-compose-with_alias.yml
================================================
x-nginx-configs: &nginx-configs
- source: vote_conf
target: /etc/nginx/conf.d
x-srv-nginx: &srv-nginx
nginx:
image: nginx:latest
configs: *nginx-configs
entrypoint: /usr/sbin/nginx-debug
command:
- "-g"
- "daemon off;"
services:
<<: *srv-nginx
x-uffizzi-ingress:
service: nginx
port: 8080
configs:
vote_conf:
file: configs/vote.conf
defaulf_conf:
file: config_files/config_file.conf
================================================
FILE: core/test/services/activity_item_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::ActivityItemServiceTest < ActiveSupport::TestCase
setup do
Sidekiq::Testing.fake!
Sidekiq::Worker.clear_all
@user = create(:user, :with_personal_account)
@project = create(:project, account: @user.personal_account)
@deployment = create(:deployment, project: @project)
end
teardown do
Sidekiq::Testing.inline!
end
test '#manage_deploy_activity_item - pending resource availability' do
repo = create(:repo, :docker_hub, project: @project)
container = create(:container, :with_public_port, deployment: @deployment, repo: repo, image: 'library/nginx')
namespace, name = container.image.split('/')
activity_item = create(:activity_item,
:docker,
:with_deploying_event,
namespace: namespace,
name: name,
tag: container.tag,
container: container,
deployment: @deployment)
namespace = UffizziCore::Converters.deep_lower_camelize_keys(
{
metadata: {
annotations: {
network_connectivity: {
containers: {
container.id => {
service: {
status: :pending,
},
},
},
}.to_json,
},
},
},
)
stubbed_controller_get_namespace_request = stub_controller_get_namespace_request(@deployment, namespace)
pod_name = generate(:name)
deployed_at = Time.current
current_time = (deployed_at - 25.minute).iso8601
node_name = generate(:name)
restart_count = 3
pods = UffizziCore::Converters.deep_lower_camelize_keys(
[
{
metadata: {
name: pod_name,
creation_timestamp: current_time,
},
spec: {
node_name: node_name,
},
status: {
container_statuses: [
{
name: UffizziCore::ContainerService.pod_name(container),
restart_count: restart_count,
image: container.image_name,
state: {
running: {
started_at: current_time,
},
},
},
],
},
},
],
)
stubbed_controller_containers_request = stub_controller_containers_request(@deployment, pods)
differences = {
-> { UffizziCore::Event.count } => 0,
-> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 1,
}
assert_difference differences do
UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)
assert_requested stubbed_controller_get_namespace_request
assert_requested stubbed_controller_containers_request
end
end
test '#manage_deploy_activity_item - resource is unavailable' do
repo = create(:repo, :docker_hub, project: @project)
container = create(:container, :with_public_port, deployment: @deployment, repo: repo, image: 'library/nginx')
namespace, name = container.image.split('/')
activity_item = create(:activity_item,
:with_building_event,
:docker,
namespace: namespace,
name: name,
tag: container.tag,
container: container,
deployment: @deployment)
domain_name = generate(:kubernetes_name)
namespace = UffizziCore::Converters.deep_lower_camelize_keys(
{
metadata: {
annotations: {
network_connectivity: {
containers: {
container.id => {
service: {
ip: '127.0.0.1',
status: :failed,
port: container.port,
domain_name: domain_name,
},
},
},
}.to_json,
},
},
},
)
stubbed_controller_get_namespace_request = stub_controller_get_namespace_request(@deployment, namespace)
pod_name = generate(:name)
deployed_at = Time.current
current_time = (deployed_at - 25.minute).iso8601
node_name = generate(:name)
restart_count = 3
pods = UffizziCore::Converters.deep_lower_camelize_keys(
[
{
metadata: {
name: "#{Settings.controller.namespace_prefix}#{pod_name}",
creation_timestamp: current_time,
},
spec: {
node_name: node_name,
},
status: {
container_statuses: [
{
name: UffizziCore::ContainerService.pod_name(container),
restart_count: restart_count,
image: container.image_name,
state: {
terminated: {},
},
},
],
},
},
],
)
stubbed_controller_containers_request = stub_controller_containers_request(@deployment, pods)
differences = {
-> { UffizziCore::Event.count } => 1,
-> { UffizziCore::Event.with_state(UffizziCore::Event.state.failed).count } => 1,
-> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 0,
}
assert_difference differences do
UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)
assert_requested stubbed_controller_get_namespace_request
assert_requested stubbed_controller_containers_request
end
end
test '#manage_deploy_activity_item - successfully deployed resources' do
repo = create(:repo, :docker_hub, project: @project)
container = create(:container, :with_public_port, deployment: @deployment, repo: repo, image: 'library/nginx')
namespace, name = container.image.split('/')
activity_item = create(:activity_item,
:with_deploying_event,
:docker,
namespace: namespace,
name: name,
tag: container.tag,
container: container,
deployment: @deployment)
domain_name = generate(:kubernetes_name)
namespace = UffizziCore::Converters.deep_lower_camelize_keys(
{
metadata: {
annotations: {
network_connectivity: {
containers: {
container.id => {
service: {
ip: '127.0.0.1',
status: :success,
port: container.port,
domain_name: domain_name,
},
},
},
}.to_json,
},
},
},
)
stubbed_controller_get_namespace_request = stub_controller_get_namespace_request(@deployment, namespace)
pod_name = generate(:name)
deployed_at = Time.current
current_time = (deployed_at - 25.minute).iso8601
node_name = generate(:name)
restart_count = 0
pods = UffizziCore::Converters.deep_lower_camelize_keys(
[
{
metadata: {
name: "#{Settings.controller.namespace_prefix}#{pod_name}",
creation_timestamp: current_time,
},
spec: {
node_name: node_name,
},
status: {
container_statuses: [
{
name: UffizziCore::ContainerService.pod_name(container),
restart_count: restart_count,
image: container.image_name,
state: {
running: {
started_at: current_time,
},
},
},
],
},
},
],
)
stubbed_controller_containers_request = stub_controller_containers_request(@deployment, pods)
differences = {
-> { UffizziCore::Event.count } => 1,
-> { UffizziCore::Event.with_state(UffizziCore::Event.state.deployed).count } => 1,
-> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 0,
}
assert_difference differences do
UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)
assert_requested stubbed_controller_get_namespace_request
assert_requested stubbed_controller_containers_request
end
end
end
================================================
FILE: core/test/services/compose_file_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::ComposeFileServiceTest < ActiveSupport::TestCase
setup do
@user = create(:user, :with_personal_account)
@account = @user.personal_account
@project = create(:project, account: @account)
end
test '#parse - check parse services with continuous preview' do
content = file_fixture('files/compose_files/compose_with_continuous_preview.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
assert_equal(2, parsed_data[:containers].count)
refute_empty(parsed_data[:continuous_preview])
refute_empty(parsed_data[:containers][1][:'x-uffizzi-continuous-preview'])
end
test '#parse - check invalid service name' do
content = file_fixture('files/compose_files/invalid_service.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Invalid config option', e.message)
end
test '#parse - check parse docker image' do
content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
assert_equal(1, parsed_data[:containers].count)
refute_empty(parsed_data[:containers].first[:image])
end
test '#parse - check parse docker image without tag' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_without_tag.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
container = parsed_data[:containers].first
image = container[:image]
tag = image[:tag]
assert_equal(Settings.compose.default_tag, tag)
end
test '#parse - check parse env variables from an environment key' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_envs.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
content_data = YAML.safe_load(content)
variables = parsed_data[:containers].first[:environment]
assert_equal(variables.count, content_data['services']['hello-world']['environment'].keys.count)
end
test '#parse - check invalid ingress service' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_config_invalid_ingress_service.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Invalid ingress service', e.message)
end
test '#parse - check if an ingress service not found' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_without_ingress_service.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Ingress service not found', e.message)
end
test '#parse - check if an ingress port not specified' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_without_ingress_port.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Ingress port not specified', e.message)
end
test '#parse - check if an ingress port is not Integer' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_ingress_port_non_integer.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('should be an Integer type', e.message)
end
test '#parse - check compose without ingress' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_without_ingress.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Service ingress has not been defined', e.message)
end
test '#parse - check compose with ingress without x-uffizzi' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_with_ingress_without_x-uffizzi.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Service ingress has not been defined', e.message)
end
test '#parse - check compose with continuous_preview on top level without x-uffizzi' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_with_continuous_preview_without_x-uffizzi.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
assert_empty(parsed_data[:continuous_preview])
end
test '#parse - check compose with continuous_preview on service level without x-uffizzi' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_with_continuous_preview_without_x-uffizzi.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
refute(parsed_data[:containers][0][:'x-uffizzi-continuous-preview'])
end
test '#parse - check compose without services' do
content = file_fixture('files/compose_files/compose_without_services.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('At least one must be provided', e.message)
end
test '#parse - check if a port is out of acceptable range' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_port.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match("Port should be specified between #{Settings.compose.port_min_value} - #{Settings.compose.port_max_value}", e.message)
end
test '#parse - check if a memory has invalid postfix' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_memory_postfix.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('The postfix should be one of', e.message)
end
test '#parse - check if a memory has invalid type' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_memory_type.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('it should be a string', e.message)
end
test '#parse - check if image and build data no specified' do
content = file_fixture('files/compose_files/compose_without_image.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('At least one must be provided', e.message)
end
test '#parse - check if delete_preview_after has invalid postfix' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_postfix.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('value should be `h`', e.message)
end
test '#parse - check if delete_preview_after has invalid value' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_hours.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('should be an Integer type', e.message)
end
test '#parse - check if delete_preview_after less than min value' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_min.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Minimum delete_preview_after', e.message)
end
test '#parse - check if delete_preview_after more than max value' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_max.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Maximum delete_preview_after', e.message)
end
test '#parse - check if delete_preview_after has only integer value' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_delete_after_integer.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('should be a String type', e.message)
end
test '#parse - check if deploy auto is invalid' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_deploy_auto.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Invalid auto value', e.message)
end
test '#parse - check if an env_file is empty' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_env_file_empty.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match("Unsupported type of 'env_file'", e.message)
end
test '#parse - check if a list of env files has an empty value' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_env_file_empty_in_array.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('env_file contains an empty value', e.message)
end
test '#parse - check if a list of env files has duplicated values' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_env_file_duplicates.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('env_file contains non-unique items', e.message)
end
test '#parse - check a boolean option' do
content = file_fixture('files/compose_files/boolean_option.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('The service name true must be a quoted string', e.message)
end
test '#parse - check if a config is unknown in short syntax' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_short_syntax_unknown_config.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('undefined config', e.message)
end
test '#parse - check if a config path is not specified' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_short_syntax_invalid_path.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('has an empty file', e.message)
end
test '#parse - check parse configs with a long syntax' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_long_syntax.yml').read
parsed_content = UffizziCore::ComposeFileService.parse(content)
content_data = YAML.safe_load(content)
config = parsed_content[:containers].first[:configs].first
assert_equal(content_data['services']['nginx']['configs'].first['target'], config[:target])
end
test '#parse - check parse configs with a long syntax if a target is not specified' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_long_syntax_without_target.yml').read
parsed_content = UffizziCore::ComposeFileService.parse(content)
content_data = YAML.safe_load(content)
config = parsed_content[:containers].first[:configs].first
assert_equal(content_data['configs']['vote_conf']['file'], config[:target])
end
test '#parse - check if a secret is unknown' do
content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_unknown.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('undefined secret', e.message)
end
test '#parse - check if a secret is not external' do
content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_without_external.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match("'postgres_password' should be external", e.message)
end
test '#parse - check if a secret doesn\'t have a name' do
content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_without_name.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match("'postgres_password' secret can not be blank", e.message)
end
test '#parse - raise an error if a build option is specified' do
content = file_fixture('files/compose_files/github_services/hello_world.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match("The 'build' directive is not supported", e.message)
end
test '#parse - parses compose file with healthcheck and converts time to seconds when the test command is array' do
content = file_fixture('files/compose_files/healthcheck/array_command_success.yml').read
result = UffizziCore::ComposeFileService.parse(content)
container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first
assert_equal(90, container_with_healthcheck[:healthcheck][:interval])
refute(container_with_healthcheck[:healthcheck][:disable])
end
test '#parse - parses compose file with healthcheck and converts time to seconds when the test command is string' do
content = file_fixture('files/compose_files/healthcheck/string_command_success.yml').read
result = UffizziCore::ComposeFileService.parse(content)
container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first
assert_equal(90, container_with_healthcheck[:healthcheck][:interval])
refute(container_with_healthcheck[:healthcheck][:disable])
end
test "#parse - parses compose file with healthcheck and sets disabled to false if the command is 'NONE'" do
content = file_fixture('files/compose_files/healthcheck/disabled_healthcheck.yml').read
result = UffizziCore::ComposeFileService.parse(content)
container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first
assert(container_with_healthcheck[:healthcheck][:disable])
end
test '#parse - parses compose file with healthcheck disabled is true and test not set' do
content = file_fixture('files/compose_files/healthcheck/healthcheck_with_disable.yml').read
result = UffizziCore::ComposeFileService.parse(content)
container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first
assert(container_with_healthcheck[:healthcheck][:disable])
end
test '#parse - raises error if the healthcheck command has invalid type' do
content = file_fixture('files/compose_files/healthcheck/invalid_command.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match("Unsupported type of 'test' option", e.message)
end
test '#parse - raises error if the retries field has invalid type' do
content = file_fixture('files/compose_files/healthcheck/invalid_retries.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_match('Invalid retries value', e.message)
end
test '#parse - raises error if healthcheck has invalid interval' do
content = file_fixture('files/compose_files/healthcheck/invalid_interval.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
error_message = "The time interval should be in the following format '{hours}h{minutes}m{seconds}s'. " \
'At least one value must be present.'
assert_match(error_message, e.message)
end
test '#parse - raises error if test or disble option are not present' do
content = file_fixture('files/compose_files/healthcheck/invalid_options.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
error_message = 'One of these options is required: test, disable'
assert_match(error_message, e.message)
end
test '#build_template_attributes - check if x-uffizzi ingress is specified' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_uffizzi_ingress.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'nginx')
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
content_data = YAML.safe_load(content)
nginx_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/nginx/) }
assert(nginx_container[:public])
assert_equal(nginx_container[:port], content_data['x-uffizzi']['ingress']['port'])
end
test '#build_template_attributes - check if deploy auto is disabled' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_auto_deploy_off.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'nginx')
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
continuously_deploy = attributes[:payload][:containers_attributes].first[:continuously_deploy]
assert_equal(:disabled, continuously_deploy)
end
test '#build_template_attributes - check container default memory' do
content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'nginx')
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
memory_limit = attributes[:payload][:containers_attributes].first[:memory_limit]
assert_equal(Settings.compose.default_memory, memory_limit)
end
test '#build_template_attributes - public image and no credential exists' do
content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stubbed_dockerhub_repository = stub_dockerhub_repository('library', 'nginx')
UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
assert_requested(stubbed_dockerhub_repository)
end
test '#build_template_attributes - private image and no credential exists' do
content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stubbed_dockerhub_repository = stub_dockerhub_private_repository('library', 'nginx')
assert_raise(UffizziCore::ComposeFile::BuildError) do
UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
end
assert_requested(stubbed_dockerhub_repository)
end
test '#build_template_attributes - check if a memory is not in list of available values' do
content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_memory.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'nginx')
e = assert_raise(UffizziCore::ComposeFile::BuildError) do
UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
end
assert_match('The memory should be one of the', e.message)
end
test '#build_template_attributes - check container memory' do
content = file_fixture('files/compose_files/compose_memory.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'nginx')
stub_dockerhub_repository('library', 'redis')
stub_dockerhub_repository('library', 'postgres')
stub_dockerhub_repository('library', 'ubuntu')
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
postgres_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/postgres/) }
redis_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/redis/) }
nginx_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/nginx/) }
ubuntu_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/ubuntu/) }
assert_equal(125, postgres_container[:memory_limit])
assert_equal(250, redis_container[:memory_limit])
assert_equal(1000, nginx_container[:memory_limit])
assert_equal(4000, ubuntu_container[:memory_limit])
end
test '#build_template_attributes - check azure image build' do
create(:credential, :azure, account: @account)
content = file_fixture('files/compose_files/azure_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
container = attributes[:payload][:containers_attributes].first
content_data = YAML.safe_load(content)
image_url = content_data['services']['nginx']['image'].split(':').first
tag = content_data['services']['nginx']['image'].split(':').last
image_name = image_url.split('/').last
assert_equal(image_name, container[:image])
assert_equal("#{image_url}:latest", container[:full_image_name])
assert_equal(tag, container[:tag])
assert_equal(image_name, container[:repo_attributes][:name])
refute(container[:repo_attributes][:namespace])
assert_equal(UffizziCore::Repo::Azure.name, container[:repo_attributes][:type])
end
test '#build_template_attributes - check invalid azure credential' do
content = file_fixture('files/compose_files/azure_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
e = assert_raise(UffizziCore::ComposeFile::BuildError) do
UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
end
assert_match(I18n.t('compose.unprocessable_image', value: 'azure'), e.message)
end
test '#build_template_attributes - check google image build' do
create(:credential, :google, account: @account)
content = file_fixture('files/compose_files/google_services/nginx.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
container = attributes[:payload][:containers_attributes].first
content_data = YAML.safe_load(content)
image_url = content_data['services']['nginx']['image'].split(':').first
tag = content_data['services']['nginx']['image'].split(':').last
project_name = image_url.split('/').second
image_name = image_url.split('/').last
assert_equal("#{project_name}/#{image_name}", container[:image])
assert_equal(tag, container[:tag])
assert_equal("#{image_url}:latest", container[:full_image_name])
assert_equal(image_name, container[:repo_attributes][:name])
assert_equal(project_name, container[:repo_attributes][:namespace])
assert_equal(UffizziCore::Repo::Google.name, container[:repo_attributes][:type])
end
test '#build_template_attributes - check build of custom docker account' do
content = file_fixture('files/compose_files/dockerhub_services/account_custom_image.yml').read
stub_dockerhub_repository('account', 'custom_image')
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
container = attributes[:payload][:containers_attributes].first
content_data = YAML.safe_load(content)
image_url = content_data['services']['nginx']['image'].split(':').first
tag = content_data['services']['nginx']['image'].split(':').last
account_name = image_url.split('/').first
image_name = image_url.split('/').last
assert_equal("#{account_name}/#{image_name}", container[:image])
assert_equal(tag, container[:tag])
assert_equal(image_name, container[:repo_attributes][:name])
assert_equal(account_name, container[:repo_attributes][:namespace])
assert_equal(UffizziCore::Repo::DockerHub.name, container[:repo_attributes][:type])
end
test '#build_template_attributes - check a command build' do
create(:credential, :google, account: @account)
content = file_fixture('files/compose_files/google_services/cloudsql.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
container = attributes[:payload][:containers_attributes].last
refute_nil(container[:command])
assert_nil(container[:entrypoint])
end
test '#build_template_attributes - check an entrypoint build' do
create(:credential, :google, account: @account)
content = file_fixture('files/compose_files/google_services/cloudsql_entrypoint.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
container = attributes[:payload][:containers_attributes].last
refute_nil(container[:entrypoint])
assert_nil(container[:command])
end
test '#build_template_attributes - check secrets variables build' do
project_secrets = [build(:secret, name: 'POSTGRES_USER', value: generate(:string)),
build(:secret, name: 'POSTGRES_PASSWORD', value: generate(:string))]
@project.secrets.append(project_secrets)
stub_dockerhub_repository('library', 'postgres')
content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
content_data = YAML.safe_load(content)
postgres_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/postgres/) }
secret_variables = postgres_container[:secret_variables]
assert_equal(2, secret_variables.size)
refute_nil(secret_variables.detect { |secret| secret[:name] == content_data['secrets']['postgres_user']['name'] })
refute_nil(secret_variables.detect { |secret| secret[:name] == content_data['secrets']['postgres_password']['name'] })
end
test '#build_template_attributes - check secrets variables build if a project secret doesn\'t exist' do
content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'postgres')
e = assert_raise(UffizziCore::ComposeFile::SecretsError) do
UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
end
assert_match("Project secret 'POSTGRES_USER' not found", e.message)
end
test '#build_template_attributes - check secrets variables build if a compose has duplicates' do
project_secrets = [build(:secret, name: 'POSTGRES_USER', value: generate(:string)),
build(:secret, name: 'POSTGRES_PASSWORD', value: generate(:string))]
@project.secrets.append(project_secrets)
stub_dockerhub_repository('library', 'postgres')
content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_duplicates.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
content_data = YAML.safe_load(content)
postgres_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/postgres/) }
secret_variables = postgres_container[:secret_variables]
assert_equal(1, secret_variables.size)
refute_nil(secret_variables.detect { |secret| secret[:name] == content_data['secrets']['postgres_user']['name'] })
assert_nil(secret_variables.detect { |secret| secret[:name] == 'POSTGRES_PASSWORD' })
end
test '#build_template_attributes - check global and container continuous preview attributes' do
content = file_fixture('files/compose_files/compose_with_continuous_preview.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'redis')
stub_dockerhub_repository('library', 'nginx')
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
nginx_repo_attributes = attributes[:payload][:containers_attributes].detect { |item| item[:image] == 'library/nginx' }[:repo_attributes]
redis_repo_attributes = attributes[:payload][:containers_attributes].detect { |item| item[:image] == 'library/redis' }[:repo_attributes]
assert { nginx_repo_attributes[:delete_preview_after] == 12 }
assert { redis_repo_attributes[:delete_preview_after] == 10 }
end
test '#build_template_attributes - check named volumes' do
content = file_fixture('files/compose_files/dockerhub_services/volumes_named.yml').read
parsed_data = UffizziCore::ComposeFileService.parse(content)
stub_dockerhub_repository('library', 'nginx')
attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)
content_data = YAML.safe_load(content)
nginx_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/nginx/) }
first_volume = nginx_container[:volumes].first
assert(nginx_container[:volumes])
assert_equal(UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::NAMED_VOLUME_TYPE, first_volume[:type])
assert_equal(content_data.dig('services', 'nginx', 'volumes').first.split(':').first, first_volume[:source])
assert_equal(content_data.dig('services', 'nginx', 'volumes').first.split(':').second, first_volume[:target])
end
test '#parse - handle Psych::SyntaxError' do
content = file_fixture('files/compose_files/compose_with_syntax_error.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_equal("Syntax error: could not find expected ':' while scanning a simple key at line 5 column 3", e.message)
end
test '#parse - handle empty compose file' do
content = file_fixture('files/compose_files/compose_empty.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_equal('Unsupported compose file', e.message)
end
test '#parse - handle when file has just a string' do
content = file_fixture('files/compose_files/compose_with_only_line.yml').read
e = assert_raise(UffizziCore::ComposeFile::ParseError) do
UffizziCore::ComposeFileService.parse(content)
end
assert_equal('Unsupported compose file', e.message)
end
end
================================================
FILE: core/test/services/deployment_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::DeploymentServiceTest < ActiveSupport::TestCase
setup do
Sidekiq::Testing.fake!
Sidekiq::Worker.clear_all
@user = create(:user, :with_personal_account)
@project = create(:project, account: @user.personal_account)
@deployment = create(:deployment, project: @project)
end
teardown do
Sidekiq::Testing.inline!
end
test '#deploy_containers - start to deploy dockerhub container' do
repo = create(:repo, :docker_hub, project: @project)
create(:container, :with_public_port, :active, deployment: @deployment, repo: repo)
stubbed_deploy_containers_request = stub_request(:post, "#{Settings.controller.url}/deployments/#{@deployment.id}/containers")
differences = {
-> { UffizziCore::Event.count } => 1,
-> { UffizziCore::Event.with_state(UffizziCore::Event.state.deploying).count } => 1,
-> { UffizziCore::Deployment::DeployContainersJob.jobs.size } => 0,
-> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 1,
}
assert_difference differences do
UffizziCore::DeploymentService.deploy_containers(@deployment)
end
assert_requested stubbed_deploy_containers_request
end
test '#disable when deployment has no compose file' do
container = create(:container, :with_public_port, deployment: @deployment)
differences = {
-> { UffizziCore::Deployment.active.count } => -1,
-> { UffizziCore::ComposeFile.count } => 0,
-> { UffizziCore::Template.count } => 0,
}
assert_difference differences do
UffizziCore::DeploymentService.disable!(@deployment)
end
container.reload
assert { container.disabled? }
end
test '#disable when the main compose file exists' do
user = create(:user, :with_personal_account)
compose_file = create(:compose_file, project: @project, added_by: user)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
template = create(:template, :compose_file_source, compose_file: compose_file, project: @project, payload: template_payload,
added_by: user)
deployment = create(:deployment, project: @project, compose_file: compose_file, template: template)
container = create(:container, :with_public_port, deployment: deployment)
differences = {
-> { UffizziCore::Deployment.active.count } => -1,
-> { UffizziCore::ComposeFile.count } => 0,
-> { UffizziCore::Template.count } => 0,
}
assert_difference differences do
UffizziCore::DeploymentService.disable!(deployment)
end
container.reload
assert { container.disabled? }
end
test '#disable when a temporary compose file exists' do
user = create(:user, :with_personal_account)
compose_file = create(:compose_file, :temporary, project: @project, added_by: user)
image = generate(:image)
image_namespace, image_name = image.split('/')
target_branch = generate(:branch)
repo_attributes = attributes_for(
:repo,
:docker_hub,
namespace: image_namespace,
name: image_name,
branch: target_branch,
)
container_attributes = attributes_for(
:container,
:with_public_port,
image: image,
tag: target_branch,
receive_incoming_requests: true,
repo_attributes: repo_attributes,
)
template_payload = {
containers_attributes: [container_attributes],
}
template = create(:template, :compose_file_source, compose_file: compose_file, project: @project, payload: template_payload,
added_by: user)
deployment = create(:deployment, project: @project, compose_file: compose_file, template: template)
container = create(:container, :with_public_port, deployment: deployment)
differences = {
-> { UffizziCore::Deployment.active.count } => -1,
-> { UffizziCore::ComposeFile.count } => -1,
-> { UffizziCore::Template.count } => -1,
}
assert_difference differences do
UffizziCore::DeploymentService.disable!(deployment)
end
container.reload
assert { container.disabled? }
end
test '#failed - container failed' do
container = create(:container, deployment: @deployment)
create(:activity_item, :with_failed_event, container: container, deployment: @deployment)
deployment_failed = UffizziCore::DeploymentService.failed?(@deployment)
assert(deployment_failed)
end
test '#failed - container deployed' do
container = create(:container, deployment: @deployment)
create(:activity_item, :with_deployed_event, container: container, deployment: @deployment)
deployment_failed = UffizziCore::DeploymentService.failed?(@deployment)
refute(deployment_failed)
end
test '#failed - no containers' do
deployment_failed = UffizziCore::DeploymentService.failed?(@deployment)
refute(deployment_failed)
end
end
================================================
FILE: core/test/services/google_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::GoogleServiceTest < ActiveSupport::TestCase
test '#digest' do
image = generate(:image)
tag = generate(:tag)
user = create(:user, :with_personal_account)
credential = create(:credential, :google, :active, account: user.personal_account)
headers_response = { 'docker-content-digest': generate(:string) }
token_response = { token: generate(:string) }
stubbed_google_registry_token = stub_google_registry_token(token_response)
stubbed_google_registry_manifests = stub_google_registry_manifests(image, tag, headers_response, {})
digest = UffizziCore::ContainerRegistry::GoogleService.digest(credential, image, tag)
assert_requested stubbed_google_registry_token
assert_requested stubbed_google_registry_manifests
assert digest.present?
end
end
================================================
FILE: core/test/services/image_parser_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::ImageParserServiceTest < ActiveSupport::TestCase
test '#parse' do
image_parser_service = UffizziCore::ComposeFile::Parsers::Services::ImageParserService
assert_equal(
{ registry_url: 'docker.io', namespace: 'library', name: 'redis', tag: 'latest', full_image_name: 'docker.io/library/redis:latest' },
image_parser_service.parse('redis'),
)
assert_equal(
{ registry_url: 'docker.io', namespace: 'library', name: 'redis', tag: '5', full_image_name: 'docker.io/library/redis:5' },
image_parser_service.parse('redis:5'),
)
assert_equal(
{ registry_url: 'docker.io', namespace: 'namespace', name: 'redis', tag: 'latest',
full_image_name: 'docker.io/namespace/redis:latest' },
image_parser_service.parse('namespace/redis'),
)
assert_equal(
{ registry_url: 'docker.io', namespace: 'namespace', name: 'redis', tag: 'latest',
full_image_name: 'docker.io/namespace/redis:latest' },
image_parser_service.parse('docker.io/namespace/redis'),
)
assert_equal(
{ registry_url: 'my-private.registry:5000', namespace: 'namespace', name: 'redis', tag: '5.3',
full_image_name: 'my-private.registry:5000/namespace/redis:5.3' },
image_parser_service.parse('my-private.registry:5000/namespace/redis:5.3'),
)
assert_equal(
{ registry_url: 'localhost:80', namespace: nil, name: 'redis', tag: '5.3', full_image_name: 'localhost:80/redis:5.3' },
image_parser_service.parse('localhost:80/redis:5.3'),
)
assert_equal(
{ registry_url: 'docker.io', namespace: 'library', name: 'lower_case_name', tag: 'lower_case_tag',
full_image_name: 'docker.io/library/lower_case_name:lower_case_tag' },
image_parser_service.parse('lower_case_name:lower_case_tag'),
)
assert_equal(
{ registry_url: 'docker.io', namespace: 'namespace', name: 'lower_case_name', tag: 'UPPERCASE_TAG',
full_image_name: 'docker.io/namespace/lower_case_name:UPPERCASE_TAG' },
image_parser_service.parse('namespace/lower_case_name:UPPERCASE_TAG'),
)
assert_equal(
{ registry_url: 'asia.gcr.io', namespace: 'uffizzi', name: 'nginx', tag: 'latest',
full_image_name: 'asia.gcr.io/uffizzi/nginx:latest' },
image_parser_service.parse('asia.gcr.io/uffizzi/nginx:latest'),
)
end
test '#parse with exception' do
image_parser_service = UffizziCore::ComposeFile::Parsers::Services::ImageParserService
assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('very:wrong:image:path') }
assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('UPPERCASE_NAMESPACE/UPPERCASE_NAME:UPPERCASE_TAG') }
assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('UPPERCASE_NAME:UPPERCASE_TAG') }
assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('UPPERCASE_NAME') }
end
end
================================================
FILE: core/test/services/manage_activity_items_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::ManageActivityItemsServiceTest < ActiveSupport::TestCase
setup do
@user = create(:user, :with_personal_account)
@project = create(:project, account: @user.personal_account)
end
test '#container_status_items - deployment has no containers' do
deployment = create(:deployment, project: @project)
stubbed_response_containers = []
namespace = UffizziCore::Converters.deep_lower_camelize_keys(
{
metadata: {
annotations: {
network_connectivity: {}.to_json,
},
},
},
)
stub_controller_containers = stub_controller_containers_request(deployment, stubbed_response_containers)
stub_get_controller_deployment = stub_controller_get_namespace_request(deployment, namespace)
service = UffizziCore::ManageActivityItemsService.new(deployment)
container_status_items = service.container_status_items
assert { container_status_items.empty? }
assert_requested(stub_get_controller_deployment)
assert_requested(stub_controller_containers)
end
end
================================================
FILE: core/test/services/user_generator_service_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCore::UserGeneratorServiceTest < ActiveSupport::TestCase
test '#generate' do
email = generate(:email)
password = generate(:password)
project_name = generate(:string)
differences = {
-> { UffizziCore::User.count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.count } => 1,
}
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if an email gets by a user' do
email = nil
password = generate(:password)
project_name = generate(:string)
new_email = generate(:email)
differences = {
-> { UffizziCore::User.where(email: new_email).count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.count } => 1,
}
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:gets).returns(new_email)
IO.stubs(:console).returns(console_mock)
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if an email is empty' do
email = nil
password = generate(:password)
project_name = generate(:string)
differences = {
-> { UffizziCore::User.where(email: UffizziCore::UserGeneratorService::DEFAULT_USER_EMAIL).count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.count } => 1,
}
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:gets).returns('')
IO.stubs(:console).returns(console_mock)
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if a password gets by a user' do
email = generate(:email)
password = nil
project_name = generate(:string)
new_password = generate(:password)
differences = {
-> { UffizziCore::User.count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.count } => 1,
}
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:getpass).returns(new_password)
IO.stubs(:console).returns(console_mock)
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if a password is empty' do
email = generate(:email)
password = nil
project_name = generate(:string)
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:getpass).returns('')
IO.stubs(:console).returns(console_mock)
assert_raises StandardError do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if a project name gets by a user' do
email = generate(:email)
password = generate(:password)
project_name = nil
new_project_name = generate(:string)
differences = {
-> { UffizziCore::User.count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.where(name: new_project_name).count } => 1,
}
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:gets).returns(new_project_name)
IO.stubs(:console).returns(console_mock)
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if a project name is empty' do
email = generate(:email)
password = generate(:password)
project_name = nil
differences = {
-> { UffizziCore::User.count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.where(name: UffizziCore::UserGeneratorService::DEFAULT_PROJECT_NAME).count } => 1,
}
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:gets).returns('')
IO.stubs(:console).returns(console_mock)
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if a user already exists' do
email = generate(:email)
password = generate(:password)
project_name = generate(:string)
create(:user, :with_personal_account, email: email)
assert_raises ActiveRecord::RecordInvalid do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if a user already exists and email gets by user' do
email = generate(:email)
password = generate(:password)
project_name = generate(:string)
create(:user, :with_personal_account, email: email)
console_mock = mock('console_mock')
console_mock.stubs(:write)
console_mock.stubs(:gets).returns(email)
IO.stubs(:console).returns(console_mock)
assert_raises ActiveRecord::RecordInvalid do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if have data but no console' do
email = generate(:email)
password = generate(:password)
project_name = generate(:string)
IO.stubs(:console).returns(nil)
differences = {
-> { UffizziCore::User.count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.count } => 1,
}
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if no email and no console' do
email = nil
password = generate(:password)
project_name = generate(:string)
IO.stubs(:console).returns(nil)
assert_raises ActiveRecord::RecordInvalid do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if no password and no console' do
email = generate(:email)
password = nil
project_name = generate(:string)
IO.stubs(:console).returns(nil)
assert_raises ActiveRecord::RecordInvalid do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
test '#generate if no project_name and no console' do
email = generate(:email)
password = generate(:password)
project_name = nil
IO.stubs(:console).returns(nil)
differences = {
-> { UffizziCore::User.count } => 1,
-> { UffizziCore::Account.count } => 1,
-> { UffizziCore::Membership.count } => 1,
-> { UffizziCore::Project.count } => 1,
}
assert_difference differences do
UffizziCore::UserGeneratorService.generate(email, password, project_name)
end
end
end
================================================
FILE: core/test/support/azure_registry_stub_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::AzureRegistryStubSupport
def stub_azure_registry_oauth2_token(registry_url, response, code = 200)
service = URI.parse(registry_url).hostname
uri = "#{registry_url}/oauth2/token?service=#{service}"
stub_request(:get, uri).to_return(status: code, body: response.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_azure_registry_manifests(registry_url, image, tag, headers, body)
uri = "#{registry_url}/v2/#{image}/manifests/#{tag}"
stub_request(:get, uri).to_return(status: 200, headers: headers, body: body.to_json)
end
end
================================================
FILE: core/test/support/controller_stub_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::ControllerStubSupport
def stub_controller_apply_credential
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/deployments/[0-9]*/credentials}
stub_request(:post, uri)
end
def stub_put_controller_deployment_request(deployment)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}"
stub_request(:put, uri)
end
def stub_controller_get_namespace_request(deployable, data = nil)
uri = "#{Settings.controller.url}/namespaces/#{deployable.namespace}"
stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_controller_get_namespace_request_any(data = nil)
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/deployment-[0-9]*}
stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_delete_namespace_request(deployable)
uri = "#{Settings.controller.url}/namespaces/#{deployable.namespace}"
stub_request(:delete, uri)
end
def stub_controller_containers_request(deployment, data = nil)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}/containers"
stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_controller_nodes_request
uri = "#{Settings.controller.url}/nodes"
stub_request(:get, uri)
end
def stub_deploy_containers_request(deployment)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}/containers"
stub_request(:post, uri)
end
def stub_apply_config_file_request(deployment, config_file)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}/config_files/#{config_file.id}"
stub_request(:post, uri)
end
def stub_apply_config_file_request_with_expected(deployment, config_file, expected_request)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}/config_files/#{config_file.id}"
stub_request(:post, uri).with do |req|
actual_body = JSON.parse(req.body).deep_symbolize_keys.deep_sort
expected_body = expected_request.deep_symbolize_keys.deep_sort
is_equal = actual_body == expected_body
ap(HashDiff.diff(actual_body, expected_body)) unless is_equal
is_equal
end
end
def stub_controller_get_deployment_events(deployment, body)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}/containers/events"
stub_request(:get, uri).to_return(status: 200, body: body.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_container_log_request(deployment_id, pod_name, limit, previous, data)
uri = "#{Settings.controller.url}/deployments/#{deployment_id}/containers/#{pod_name}/logs?limit=#{limit}&previous=#{previous}"
stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_deploy_containers_request_with_expected(deployment, expected_request)
uri = "#{Settings.controller.url}/deployments/#{deployment.id}/containers"
stub_request(:post, uri).with do |req|
actual_body = JSON.parse(req.body).deep_symbolize_keys.deep_sort
expected_body = expected_request.deep_symbolize_keys.deep_sort
is_equal = actual_body == expected_body
ap(HashDiff.diff(actual_body, expected_body)) unless is_equal
is_equal
end
end
def stub_create_namespace_request
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces$}
stub_request(:post, uri).to_return(status: 200, body: { namespace: 'namespace' }.to_json,
headers: { 'Content-Type' => 'application/json' })
end
def stub_get_cluster_request(data = {}, _status = 200)
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\-]+)/cluster/([A-Za-z0-9\-]+)}
stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_create_cluster_request(data = {}, status = 200)
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\-]+)/cluster}
stub_request(:post, uri).to_return(status: status, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_scale_cluster_request(status = 200)
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\-]+)/cluster/([A-Za-z0-9\-]+)}
stub_request(:patch, uri).to_return(status: status, headers: { 'Content-Type' => 'application/json' })
end
def stub_create_cluster_request_with_expected(returned_data = {}, expected_request = {}, status = 200)
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\-]+)/cluster}
stub_request(:post, uri).with do |req|
actual_body = JSON.parse(req.body).deep_symbolize_keys.deep_sort
expected_body = expected_request.deep_symbolize_keys.deep_sort
is_equal = equal_hashes?(expected_body, actual_body)
ap(HashDiff.diff(actual_body, expected_body)) unless is_equal
is_equal
end.to_return(status: status, body: returned_data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_get_ingresses(data = {}, status = 200)
uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\-]+)/ingresses}
stub_request(:get, uri).to_return(status: status, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
private
def equal_hashes?(expected, actual)
actual = actual.deep_symbolize_keys
expected.deep_symbolize_keys.all? do |k, v|
if v.is_a?(Regexp)
v.match?(actual[k])
else
v == actual[k]
end
end
end
end
================================================
FILE: core/test/support/docker_hub_stub_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::DockerHubStubSupport
API_URL = 'https://hub.docker.com/v2'
AUTH_URL = 'https://auth.docker.io'
def stub_dockerhub_login
response = { token: 'mytoken' }
uri = %r{#{API_URL}/users/login/}
stub_request(:post, uri).to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_dockerhub_login_fail(data)
uri = %r{#{API_URL}/users/login/}
stub_request(:post, uri).to_return(status: 401, body: data.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_dockerhub_auth_for_digest(repository)
response = { token: 'mytoken' }
url = %r{#{AUTH_URL}.+scope=repository:#{repository}:pull&service=registry[.]docker[.]io.+}
stub_request(:get, url).to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_dockerhub_get_digest(image, tag, data)
url = "https://index.docker.io/v2/#{image}/manifests/#{tag}"
stub_request(:get, url).to_return(status: 200, body: data[:body].to_json, headers: data[:headers])
end
def stub_dockerhub_api_tokens(data)
uri = "#{API_URL}/api_tokens"
stub_request(:post, uri).to_return(status: 201, body: data.to_json)
end
def stub_dockerhub_repository(namespace, repo_name)
url = "#{API_URL}/repositories/#{namespace}/#{repo_name}"
stub_request(:get, url).to_return(status: 200)
end
def stub_dockerhub_private_repository(namespace, repo_name)
url = "#{API_URL}/repositories/#{namespace}/#{repo_name}"
stub_request(:get, url).to_return(status: 404)
end
def stub_dockerhub_repository_any
uri = %r{#{API_URL}/repositories/.*/.*}
stub_request(:get, uri).to_return(status: 200)
end
end
================================================
FILE: core/test/support/docker_registry_stub_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::DockerRegistryStubSupport
def stub_docker_registry_manifests(registry_url, image, tag, headers: {}, body: {}, status: 200)
uri = "#{registry_url}/v2/#{image}/manifests/#{tag}"
stub_request(:get, uri).to_return(status: status, headers: headers, body: body.to_json)
end
end
================================================
FILE: core/test/support/fixture_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::FixtureSupport
def file_fixture(file_path)
full_path = "#{ActiveSupport::TestCase.fixture_path}/#{file_path}"
File.new(full_path)
end
def json_fixture(file_path, symbolize_names: true)
data = file_fixture(file_path).read
JSON.parse(data, symbolize_names: symbolize_names)
end
end
================================================
FILE: core/test/support/github_container_registry_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::GithubContainerRegistryStubSupport
def stub_github_container_registry_access_token(registry_url, response)
service = URI.parse(registry_url).hostname
uri = "#{registry_url}/token?service=#{service}"
stub_request(:get, uri).to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' })
end
end
================================================
FILE: core/test/support/google_registry_stub_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::GoogleRegistryStubSupport
REGISTRY_URL = 'https://gcr.io'
def stub_google_registry_token(response, code = 200)
service = URI.parse(REGISTRY_URL).hostname
uri = "#{REGISTRY_URL}/v2/token?service=#{service}"
stub_request(:get, uri).to_return(status: code, body: response.to_json, headers: { 'Content-Type' => 'application/json' })
end
def stub_google_registry_manifests(image, tag, headers, body)
uri = "#{REGISTRY_URL}/v2/#{image}/manifests/#{tag}"
stub_request(:get, uri).to_return(status: 200, headers: headers, body: body.to_json)
end
end
================================================
FILE: core/test/support/stub_support.rb
================================================
# frozen_string_literal: true
module UffizziCore::StubSupport
def stub_controller
stub_request(:any, /#{Regexp.quote(Settings.controller.url.to_s)}\/*./)
end
end
================================================
FILE: core/test/test_helper.rb
================================================
# frozen_string_literal: true
ENV['RAILS_ENV'] = 'test'
require_relative '../test/dummy/config/environment'
ActiveRecord::Migrator.migrations_paths = [File.expand_path('../test/dummy/db/migrate', __dir__)]
ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__)
Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |f| require f }
require 'awesome_print'
require 'byebug'
require 'factory_bot'
require 'faker'
require 'minitest/hooks/default'
require 'minitest-power_assert'
require 'minitest/mock'
require 'mocha/minitest'
require 'rails/test_help'
require 'sidekiq/testing'
require 'webmock/minitest'
require 'octokit'
require 'deepsort'
require 'hash_diff'
FactoryBot.reload
WebMock.disable_net_connect!
Sidekiq::Testing.inline!
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path('fixtures', __dir__)
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
ActiveSupport::TestCase.file_fixture_path = "#{ActiveSupport::TestCase.fixture_path}/files"
ActiveSupport::TestCase.fixtures(:all)
end
FactoryBot.define do
initialize_with { new(attributes) }
end
class ActiveSupport::TestCase
include FactoryBot::Syntax::Methods
include Minitest::Hooks
include ActiveModel::Validations
include UffizziCore::AuthManagement
include UffizziCore::ControllerStubSupport
include UffizziCore::DockerHubStubSupport
include UffizziCore::FixtureSupport
include UffizziCore::GoogleRegistryStubSupport
include UffizziCore::StubSupport
include UffizziCore::AzureRegistryStubSupport
include UffizziCore::GithubContainerRegistryStubSupport
include UffizziCore::DockerRegistryStubSupport
setup do
@routes = UffizziCore::Engine.routes
end
end
================================================
FILE: core/test/uffizzi_core_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class UffizziCoreTest < ActiveSupport::TestCase
test 'it has a version number' do
assert UffizziCore::VERSION
end
end
================================================
FILE: core/uffizzi_core.gemspec
================================================
# frozen_string_literal: true
require_relative 'lib/uffizzi_core/version'
# rubocop:disable Metrics/BlockLength
Gem::Specification.new do |spec|
spec.name = 'uffizzi_core'
spec.version = UffizziCore::VERSION
spec.authors = ['Josh Thurman', 'Grayson Adkins']
spec.email = ['info@uffizzi.com']
spec.summary = 'uffizzi_core'
spec.description = 'uffizzi_core'
spec.homepage = 'https://uffizzi.com'
spec.license = 'Apache-2.0'
spec.required_ruby_version = '>= 2.5.0'
spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = 'https://github.com/UffizziCloud/uffizzi_app/tree/main/core'
spec.metadata['changelog_uri'] = 'https://github.com/UffizziCloud/uffizzi_app/blob/main/core/CHANGELOG.md'
spec.files = Dir['{app,config,db,lib,swagger}/**/*', 'LICENSE', 'Rakefile', 'README.md']
spec.add_dependency 'aasm'
spec.add_dependency 'actionpack', '~> 6.1.0'
spec.add_dependency 'active_model_serializers'
spec.add_dependency 'activerecord', '~> 6.1.0'
spec.add_dependency 'activesupport', '~> 6.1.0'
spec.add_dependency 'ancestry'
spec.add_dependency 'aws-sdk-ecr', '~> 1.47'
spec.add_dependency 'aws-sdk-eventbridge', '~> 1.30'
spec.add_dependency 'aws-sdk-iam', '~> 1.61'
spec.add_dependency 'docker_distribution'
spec.add_dependency 'dotenv'
spec.add_dependency 'enumerize'
spec.add_dependency 'faraday'
spec.add_dependency 'faraday_curl'
spec.add_dependency 'faraday-follow_redirects'
spec.add_dependency 'faraday_middleware'
spec.add_dependency 'hashie'
spec.add_dependency 'jwt'
spec.add_dependency 'kaminari'
spec.add_dependency 'octokit'
spec.add_dependency 'pg', '>= 0.18', '< 2.0'
spec.add_dependency 'pundit'
spec.add_dependency 'rails', '~> 6.1.0'
spec.add_dependency 'ransack'
spec.add_dependency 'responders'
spec.add_dependency 'rolify'
spec.add_dependency 'rswag-api'
spec.add_dependency 'rswag-ui'
spec.add_dependency 'sidekiq'
spec.add_dependency 'sidekiq-unique-jobs'
spec.add_dependency 'swagger_yard'
spec.add_dependency 'virtus'
spec.add_development_dependency 'awesome_print'
spec.add_development_dependency 'bcrypt', '~> 3.1.7'
spec.add_development_dependency 'bump'
spec.add_development_dependency 'bundler', '~> 2.2'
spec.add_development_dependency 'byebug'
spec.add_development_dependency 'config'
spec.add_development_dependency 'deepsort', '~> 0.4.5'
spec.add_development_dependency 'factory_bot'
spec.add_development_dependency 'faker'
spec.add_development_dependency 'hash_diff', '~> 1.1'
spec.add_development_dependency 'minitest'
spec.add_development_dependency 'minitest-hooks'
spec.add_development_dependency 'minitest-power_assert'
spec.add_development_dependency 'mocha'
spec.add_development_dependency 'pry-byebug'
spec.add_development_dependency 'pry-inline'
spec.add_development_dependency 'puma'
spec.add_development_dependency 'rack-cors'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'webmock'
end
# rubocop:enable Metrics/BlockLength
================================================
FILE: db/migrate/20220219114713_create_uffizzi_core_tables.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220218121438)
class CreateUffizziCoreTables < ActiveRecord::Migration[6.1]
def change
create_table 'uffizzi_core_accounts', force: :cascade do |t|
t.text 'name'
t.text 'kind', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'customer_token'
t.string 'state'
t.string 'subscription_token'
t.datetime 'payment_issue_at'
t.string 'domain'
t.boolean 'sso_enabled', default: false
t.bigint 'owner_id'
t.integer 'container_memory_limit'
t.string 'workos_organization_id'
t.string 'sso_state'
t.index ['customer_token'], name: 'index_accounts_on_customer_token', unique: true
t.index ['domain'], name: 'index_accounts_on_domain', unique: true
t.index ['subscription_token'], name: 'index_accounts_on_subscription_token', unique: true
end
create_table 'uffizzi_core_activity_items', force: :cascade do |t|
t.bigint 'deployment_id', null: false
t.string 'namespace'
t.string 'name'
t.string 'tag'
t.string 'branch'
t.string 'type'
t.bigint 'container_id', null: false
t.string 'commit'
t.string 'commit_message'
t.bigint 'build_id'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.jsonb 'data', default: {}, null: false
t.string 'digest'
t.index ['container_id'], name: 'index_activity_items_on_container_id'
t.index ['deployment_id'], name: 'index_activity_items_on_deployment_id'
end
create_table 'uffizzi_core_builds', force: :cascade do |t|
t.bigint 'repo_id', null: false
t.string 'build_id'
t.string 'repository'
t.string 'branch'
t.string 'commit'
t.string 'committer'
t.string 'message'
t.string 'log_url'
t.integer 'status'
t.datetime 'started_at'
t.datetime 'ended_at'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.boolean 'deployed'
t.index ['build_id'], name: 'index_builds_on_build_id', unique: true
t.index ['repo_id'], name: 'index_builds_on_repo_id'
end
create_table 'uffizzi_core_comments', force: :cascade do |t|
t.string 'commentable_type'
t.bigint 'commentable_id'
t.text 'content'
t.bigint 'user_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'ancestry'
t.integer 'ancestry_depth', default: 0
t.index ['ancestry'], name: 'index_comments_on_ancestry'
t.index ['commentable_type', 'commentable_id'], name: 'index_comments_on_commentable'
t.index ['user_id'], name: 'index_comments_on_user_id'
end
create_table 'uffizzi_core_compose_files', force: :cascade do |t|
t.string 'source'
t.bigint 'repository_id'
t.string 'branch'
t.string 'path'
t.string 'auto_deploy'
t.bigint 'added_by_id'
t.bigint 'project_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'state'
t.jsonb 'payload', default: {}, null: false
t.text 'content'
t.string 'kind', default: 'main'
t.index ['project_id'], name: 'index_compose_files_on_project_id'
end
create_table 'uffizzi_core_config_files', force: :cascade do |t|
t.string 'filename'
t.string 'kind'
t.bigint 'added_by_id'
t.text 'payload'
t.bigint 'project_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.bigint 'compose_file_id'
t.string 'creation_source'
t.string 'source'
t.index ['compose_file_id'], name: 'index_config_files_on_compose_file_id'
t.index ['project_id'], name: 'index_config_files_on_project_id'
end
create_table 'uffizzi_core_container_config_files', force: :cascade do |t|
t.string 'mount_path'
t.bigint 'container_id', null: false
t.bigint 'config_file_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.index ['config_file_id'], name: 'index_container_config_files_on_config_file_id'
t.index ['container_id'], name: 'index_container_config_files_on_container_id'
end
create_table 'uffizzi_core_containers', force: :cascade do |t|
t.string 'image'
t.string 'tag'
t.jsonb 'variables'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.bigint 'deployment_id'
t.boolean 'public', default: false, null: false
t.integer 'port'
t.bigint 'repo_id'
t.string 'state'
t.string 'continuously_deploy', null: false
t.string 'kind', default: 'user'
t.integer 'target_port'
t.string 'controller_name'
t.boolean 'receive_incoming_requests'
t.integer 'memory_request'
t.integer 'memory_limit'
t.jsonb 'secret_variables'
t.string 'entrypoint'
t.string 'command'
t.index ['deployment_id'], name: 'index_containers_on_deployment_id'
t.index ['repo_id'], name: 'index_containers_on_repo_id'
end
create_table 'uffizzi_core_coupons', force: :cascade do |t|
t.string 'token'
t.string 'name', null: false
t.string 'currency', null: false
t.bigint 'amount_off', null: false
t.string 'duration', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
end
create_table 'uffizzi_core_credentials', force: :cascade do |t|
t.string 'type'
t.string 'username'
t.string 'password'
t.bigint 'project_id'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'provider_ref'
t.bigint 'account_id'
t.string 'state'
t.string 'registry_url'
t.index ['account_id'], name: 'index_credentials_on_account_id'
t.index ['project_id'], name: 'index_credentials_on_project_id'
t.index ['provider_ref'], name: 'index_credentials_on_provider_ref'
end
create_table 'uffizzi_core_deployments', force: :cascade do |t|
t.bigint 'project_id', null: false
t.text 'kind', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'creator_name'
t.string 'subdomain'
t.string 'state'
t.float 'memory_limit'
t.bigint 'deployed_by_id'
t.bigint 'continuous_preview_id_deprecated'
t.jsonb 'continuous_preview_payload'
t.string 'creation_source'
t.bigint 'compose_file_id'
t.bigint 'template_id'
t.index ['compose_file_id'], name: 'index_deployments_on_compose_file_id'
t.index ['project_id'], name: 'index_deployments_on_project_id'
t.index ['template_id'], name: 'index_deployments_on_template_id'
end
create_table 'uffizzi_core_events', force: :cascade do |t|
t.bigint 'activity_item_id', null: false
t.string 'state'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.index ['activity_item_id'], name: 'index_events_on_activity_item_id'
end
create_table 'uffizzi_core_invitations', force: :cascade do |t|
t.text 'email', null: false
t.text 'token', null: false
t.string 'status', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.bigint 'invited_by_id', null: false
t.bigint 'entityable_id', null: false
t.string 'entityable_type', null: false
t.string 'role', null: false
t.bigint 'invitee_id'
t.index ['token'], name: 'index_invitations_on_token', unique: true
end
create_table 'uffizzi_core_memberships', force: :cascade do |t|
t.bigint 'user_id', null: false
t.bigint 'account_id', null: false
t.text 'role', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.index ['account_id'], name: 'index_memberships_on_account_id'
t.index ['user_id', 'account_id'], name: 'index_memberships_on_user_id_and_account_id'
t.index ['user_id'], name: 'index_memberships_on_user_id'
end
create_table 'uffizzi_core_payments', force: :cascade do |t|
t.bigint 'account_id', null: false
t.string 'charge_id'
t.string 'status'
t.float 'amount'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.index ['account_id'], name: 'index_payments_on_account_id'
end
create_table 'uffizzi_core_prices', force: :cascade do |t|
t.string 'token'
t.string 'slug', null: false
t.string 'name', null: false
t.float 'units_price', null: false
t.bigint 'units_amount', null: false
t.bigint 'product_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.index ['product_id'], name: 'index_prices_on_product_id'
end
create_table 'uffizzi_core_products', force: :cascade do |t|
t.string 'token'
t.string 'slug', null: false
t.string 'name', null: false
t.string 'type', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'kind'
end
create_table 'uffizzi_core_projects', force: :cascade do |t|
t.text 'name', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.bigint 'account_id', null: false
t.string 'state'
t.string 'slug'
t.string 'description'
t.jsonb 'secrets'
t.index ['account_id', 'name'], name: 'index_projects_on_account_id_and_name', unique: true
t.index ['account_id'], name: 'index_projects_on_account_id'
end
create_table 'uffizzi_core_ratings', force: :cascade do |t|
t.string 'name'
t.string 'state'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
end
create_table 'uffizzi_core_repos', force: :cascade do |t|
t.string 'namespace'
t.string 'name'
t.string 'tag'
t.string 'type'
t.string 'branch'
t.bigint 'project_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'description'
t.boolean 'is_private'
t.string 'slug'
t.bigint 'repository_id'
t.string 'kind'
t.string 'dockerfile_path'
t.jsonb 'args'
t.string 'dockerfile_context_path'
t.boolean 'deploy_preview_when_pull_request_is_opened'
t.boolean 'delete_preview_when_pull_request_is_closed'
t.boolean 'deploy_preview_when_image_tag_is_created'
t.boolean 'delete_preview_when_image_tag_is_updated'
t.boolean 'share_to_github'
t.integer 'delete_preview_after'
t.string 'tag_pattern_deprecated'
t.index ['project_id'], name: 'index_repos_on_project_id'
end
create_table 'uffizzi_core_roles', force: :cascade do |t|
t.string 'name'
t.string 'resource_type'
t.bigint 'resource_id'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.index ['name', 'resource_type', 'resource_id'], name: 'index_roles_on_name_and_resource_type_and_resource_id'
t.index ['resource_type', 'resource_id'], name: 'index_roles_on_resource_type_and_resource_id'
end
create_table 'uffizzi_core_templates', force: :cascade do |t|
t.string 'name'
t.bigint 'added_by_id'
t.jsonb 'payload', null: false
t.bigint 'project_id', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.bigint 'compose_file_id'
t.string 'creation_source'
t.index ['project_id'], name: 'index_templates_on_project_id'
end
create_table 'uffizzi_core_user_projects', force: :cascade do |t|
t.bigint 'user_id', null: false
t.bigint 'project_id', null: false
t.text 'role', null: false
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.bigint 'invited_by_id'
t.index ['project_id'], name: 'index_user_projects_on_project_id'
t.index ['user_id', 'project_id'], name: 'index_user_projects_on_user_id_and_project_id'
t.index ['user_id'], name: 'index_user_projects_on_user_id'
end
create_table 'uffizzi_core_users', force: :cascade do |t|
t.string 'first_name'
t.string 'last_name'
t.string 'email', default: '', null: false
t.string 'password_digest', default: '', null: false
t.string 'confirmation_token'
t.string 'state'
t.string 'phone'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.string 'github'
t.string 'website'
t.string 'twitter'
t.string 'linkedin'
t.string 'devto'
t.string 'facebook'
t.string 'blog'
t.text 'bio'
t.string 'status'
t.string 'availability'
t.string 'primary_skills'
t.string 'learning'
t.string 'coding_for'
t.string 'education'
t.string 'title'
t.string 'work'
t.string 'primary_location'
t.string 'creation_source'
t.index 'lower((email)::text)', name: 'index_email_on_lower_email', unique: true
end
create_table 'uffizzi_core_users_roles', id: false, force: :cascade do |t|
t.bigint 'user_id'
t.bigint 'role_id'
t.index ['role_id'], name: 'index_users_roles_on_role_id'
t.index ['user_id', 'role_id'], name: 'index_users_roles_on_user_id_and_role_id'
t.index ['user_id'], name: 'index_users_roles_on_user_id'
end
end
end
================================================
FILE: db/migrate/20220317112742_remove_secrets_from_projects.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220309110201)
class RemoveSecretsFromProjects < ActiveRecord::Migration[6.1]
def change
remove_column('uffizzi_core_projects', 'secrets')
end
end
================================================
FILE: db/migrate/20220317112743_create_project_secrets.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220310110150)
class CreateProjectSecrets < ActiveRecord::Migration[6.1]
def change
create_table('uffizzi_core_project_secrets', force: :cascade) do |t|
t.bigint('project_id', null: false)
t.string('name')
t.string('value')
t.datetime('created_at', precision: 6, null: false)
t.datetime('updated_at', precision: 6, null: false)
t.index(['project_id'], name: 'index_project_secrets_on_project_id')
end
end
end
================================================
FILE: db/migrate/20220325113623_add_name_to_uffizzi_containers.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220325113342)
class AddNameToUffizziContainers < ActiveRecord::Migration[6.1]
def change
add_column :uffizzi_core_containers, :name, :string
end
end
================================================
FILE: db/migrate/20220412133606_rename_project_secrets_to_secrets.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220329123323)
class RenameProjectSecretsToSecrets < ActiveRecord::Migration[6.1]
def change
rename_table(:uffizzi_core_project_secrets, :uffizzi_core_secrets)
end
end
================================================
FILE: db/migrate/20220412133607_add_resource_to_secrets.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220329124542)
class AddResourceToSecrets < ActiveRecord::Migration[6.1]
def change
add_belongs_to(:uffizzi_core_secrets, :resource, polymorphic: true)
end
end
================================================
FILE: db/migrate/20220412133608_remove_project_ref_from_secrets.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220329143241)
class RemoveProjectRefFromSecrets < ActiveRecord::Migration[6.1]
def change
remove_reference(:uffizzi_core_secrets, :project)
end
end
================================================
FILE: db/migrate/20220420103952_add_health_check_to_containers.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220419074956)
class AddHealthCheckToContainers < ActiveRecord::Migration[6.1]
def change
add_column :uffizzi_core_containers, :healthcheck, :jsonb
end
end
================================================
FILE: db/migrate/20220527135654_rename_name_to_uffizzi_containers.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220525113412)
class RenameNameToUffizziContainers < ActiveRecord::Migration[6.1]
def change
rename_column(:uffizzi_core_containers, :name, :service_name)
end
end
================================================
FILE: db/migrate/20220617184754_add_volumes_to_uffizzi_core_containers.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220422151523)
class AddVolumesToUffizziCoreContainers < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :volumes, :jsonb)
end
end
================================================
FILE: db/migrate/20220708093405_add_disabled_at_to_deployments.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220704135629)
class AddDisabledAtToDeployments < ActiveRecord::Migration[6.1]
def up
change_table :uffizzi_core_deployments do |t|
t.datetime :disabled_at
end
UffizziCore::Deployment.disabled.update_all('disabled_at = updated_at')
end
def down
change_table :uffizzi_core_deployments do |t|
t.remove :disabled_at
end
end
end
================================================
FILE: db/migrate/20220817140346_add_metadata_to_deployment.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20220805164628)
class AddMetadataToDeployment < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_deployments, :metadata, :jsonb, default: {})
end
end
================================================
FILE: db/migrate/20220901110752_create_host_volume_files.rb
================================================
# frozen_string_literal: true
class CreateHostVolumeFiles < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_host_volume_files do |t|
t.string :source
t.string :path
t.boolean :is_file
t.binary :payload
t.bigint :added_by_id
t.timestamps
t.references :project, null: false,
foreign_key: true,
index: { name: :index_host_volume_file_on_project_id },
foreign_key: { to_table: :uffizzi_core_projects }
t.references :compose_file, null: false,
foreign_key: true,
index: { name: :index_host_volume_file_on_compose_file_id },
foreign_key: { to_table: :uffizzi_core_compose_files }
end
end
end
================================================
FILE: db/migrate/20220901165313_create_container_host_volume_files.rb
================================================
# frozen_string_literal: true
class CreateContainerHostVolumeFiles < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_container_host_volume_files do |t|
t.string :source_path
t.timestamps
t.references :container, null: false,
foreign_key: true,
index: { name: :uf_core_cont_h_v_on_cont },
foreign_key: { to_table: :uffizzi_core_containers }
t.references :host_volume_file, null: false,
foreign_key: true,
index: { name: :uf_core_cont_h_v_on_h_v_file },
foreign_key: { to_table: :uffizzi_core_host_volume_files }
end
end
end
================================================
FILE: db/migrate/20220927113647_add_additional_subdomains_to_containers.rb
================================================
# frozen_string_literal: true
class AddAdditionalSubdomainsToContainers < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :additional_subdomains, :string, array: true, default: [])
end
end
================================================
FILE: db/migrate/20230111000000_add_state_to_memberships.rb
================================================
# frozen_string_literal: true
class AddStateToMemberships < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_memberships, :state, :string)
end
end
================================================
FILE: db/migrate/20230306142805_add_last_deploy_at_to_deployments.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20230306142513)
class AddLastDeployAtToDeployments < ActiveRecord::Migration[6.1]
def up
add_column :uffizzi_core_deployments, :last_deploy_at, :datetime
UffizziCore::Deployment.update_all('last_deploy_at = updated_at')
end
def down
remove_column :uffizzi_core_deployments, :last_deploy_at
end
end
================================================
FILE: db/migrate/20230406154547_add_full_image_name_to_container.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20230406154451)
class AddFullImageNameToContainer < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_containers, :full_image_name, :string)
end
end
================================================
FILE: db/migrate/20230531135739_create_deployment_events.rb
================================================
# frozen_string_literal: true
class CreateDeploymentEvents < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_deployment_events do |t|
t.string :deployment_state
t.string :message
t.timestamps
t.references :deployment, null: false,
index: { name: :uf_core_dep_events_on_dep },
foreign_key: { to_table: :uffizzi_core_deployments }
end
end
end
================================================
FILE: db/migrate/20230613110517_create_clusters.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20230613101901)
class CreateClusters < ActiveRecord::Migration[6.1]
def change
create_table('uffizzi_core_clusters', force: :cascade) do |t|
t.references :project, null: false,
foreign_key: true,
index: { name: :index_cluster_on_project_id },
foreign_key: { to_table: :uffizzi_core_projects }
t.bigint 'deployed_by_id', foreign_key: true
t.string 'state'
t.string 'name'
t.text 'manifest'
t.text 'kubeconfig'
t.timestamps
end
end
end
================================================
FILE: db/migrate/20230711101901_add_host_to_clusters.rb
================================================
# frozen_string_literal: true
class AddHostToClusters < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :host, :string)
end
end
================================================
FILE: db/migrate/20230824150022_update_name_constraint_to_projects.rb
================================================
# frozen_string_literal: true
class UpdateNameConstraintToProjects < ActiveRecord::Migration[6.1]
def change
remove_index(:uffizzi_core_projects, [:account_id, :name])
add_index(:uffizzi_core_projects, [:account_id, :name], unique: true,
where: "state = 'active'",
name: 'proj_uniq_name')
end
end
================================================
FILE: db/migrate/20231009103942_add_source_to_uffizzi_core_clusters.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20230810140316)
class AddSourceToUffizziCoreClusters < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :creation_source, :string)
end
end
================================================
FILE: db/migrate/20231009163516_create_uffizzi_core_kubernetes_distributions.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20231009162719)
class CreateUffizziCoreKubernetesDistributions < ActiveRecord::Migration[6.1]
def change
create_table :uffizzi_core_kubernetes_distributions do |t|
t.string :version
t.string :distro
t.string :image
t.boolean :default
t.timestamps
end
end
end
================================================
FILE: db/migrate/20231009201239_add_kubernetes_distribution_id_to_uffizzi_core_clusters.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20231009182412)
class AddKubernetesDistributionIdToUffizziCoreClusters < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :kubernetes_distribution_id, :integer, foreign_key: true)
end
end
================================================
FILE: db/migrate/20240301200916_add_node_selector_to_cluster.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20240301200235)
class AddNodeSelectorToCluster < ActiveRecord::Migration[6.1]
def change
add_column(:uffizzi_core_clusters, :node_selector, :string)
end
end
================================================
FILE: db/migrate/20240314170425_delete_node_selector_from_cluster.uffizzi_core.rb
================================================
# frozen_string_literal: true
# This migration comes from uffizzi_core (originally 20240314170113)
class DeleteNodeSelectorFromCluster < ActiveRecord::Migration[6.1]
def change
remove_column(:uffizzi_core_clusters, :node_selector, :string)
end
end
================================================
FILE: db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2024_03_14_170425) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "uffizzi_core_accounts", force: :cascade do |t|
t.text "name"
t.text "kind", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "customer_token"
t.string "state"
t.string "subscription_token"
t.datetime "payment_issue_at"
t.string "domain"
t.boolean "sso_enabled", default: false
t.bigint "owner_id"
t.integer "container_memory_limit"
t.string "workos_organization_id"
t.string "sso_state"
t.index ["customer_token"], name: "index_accounts_on_customer_token", unique: true
t.index ["domain"], name: "index_accounts_on_domain", unique: true
t.index ["subscription_token"], name: "index_accounts_on_subscription_token", unique: true
end
create_table "uffizzi_core_activity_items", force: :cascade do |t|
t.bigint "deployment_id", null: false
t.string "namespace"
t.string "name"
t.string "tag"
t.string "branch"
t.string "type"
t.bigint "container_id", null: false
t.string "commit"
t.string "commit_message"
t.bigint "build_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.jsonb "data", default: {}, null: false
t.string "digest"
t.index ["container_id"], name: "index_activity_items_on_container_id"
t.index ["deployment_id"], name: "index_activity_items_on_deployment_id"
end
create_table "uffizzi_core_builds", force: :cascade do |t|
t.bigint "repo_id", null: false
t.string "build_id"
t.string "repository"
t.string "branch"
t.string "commit"
t.string "committer"
t.string "message"
t.string "log_url"
t.integer "status"
t.datetime "started_at"
t.datetime "ended_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "deployed"
t.index ["build_id"], name: "index_builds_on_build_id", unique: true
t.index ["repo_id"], name: "index_builds_on_repo_id"
end
create_table "uffizzi_core_clusters", force: :cascade do |t|
t.bigint "project_id", null: false
t.bigint "deployed_by_id"
t.string "state"
t.string "name"
t.text "manifest"
t.text "kubeconfig"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "host"
t.string "creation_source"
t.integer "kubernetes_distribution_id"
t.index ["project_id"], name: "index_cluster_on_project_id"
end
create_table "uffizzi_core_comments", force: :cascade do |t|
t.string "commentable_type"
t.bigint "commentable_id"
t.text "content"
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "ancestry"
t.integer "ancestry_depth", default: 0
t.index ["ancestry"], name: "index_comments_on_ancestry"
t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable"
t.index ["user_id"], name: "index_comments_on_user_id"
end
create_table "uffizzi_core_compose_files", force: :cascade do |t|
t.string "source"
t.bigint "repository_id"
t.string "branch"
t.string "path"
t.string "auto_deploy"
t.bigint "added_by_id"
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "state"
t.jsonb "payload", default: {}, null: false
t.text "content"
t.string "kind", default: "main"
t.index ["project_id"], name: "index_compose_files_on_project_id"
end
create_table "uffizzi_core_config_files", force: :cascade do |t|
t.string "filename"
t.string "kind"
t.bigint "added_by_id"
t.text "payload"
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "compose_file_id"
t.string "creation_source"
t.string "source"
t.index ["compose_file_id"], name: "index_config_files_on_compose_file_id"
t.index ["project_id"], name: "index_config_files_on_project_id"
end
create_table "uffizzi_core_container_config_files", force: :cascade do |t|
t.string "mount_path"
t.bigint "container_id", null: false
t.bigint "config_file_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["config_file_id"], name: "index_container_config_files_on_config_file_id"
t.index ["container_id"], name: "index_container_config_files_on_container_id"
end
create_table "uffizzi_core_container_host_volume_files", force: :cascade do |t|
t.string "source_path"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "container_id", null: false
t.bigint "host_volume_file_id", null: false
t.index ["container_id"], name: "uf_core_cont_h_v_on_cont"
t.index ["host_volume_file_id"], name: "uf_core_cont_h_v_on_h_v_file"
end
create_table "uffizzi_core_containers", force: :cascade do |t|
t.string "image"
t.string "tag"
t.jsonb "variables"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "deployment_id"
t.boolean "public", default: false, null: false
t.integer "port"
t.bigint "repo_id"
t.string "state"
t.string "continuously_deploy", null: false
t.string "kind", default: "user"
t.integer "target_port"
t.string "controller_name"
t.boolean "receive_incoming_requests"
t.integer "memory_request"
t.integer "memory_limit"
t.jsonb "secret_variables"
t.string "entrypoint"
t.string "command"
t.string "service_name"
t.jsonb "healthcheck"
t.jsonb "volumes"
t.string "additional_subdomains", default: [], array: true
t.string "full_image_name"
t.index ["deployment_id"], name: "index_containers_on_deployment_id"
t.index ["repo_id"], name: "index_containers_on_repo_id"
end
create_table "uffizzi_core_coupons", force: :cascade do |t|
t.string "token"
t.string "name", null: false
t.string "currency", null: false
t.bigint "amount_off", null: false
t.string "duration", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "uffizzi_core_credentials", force: :cascade do |t|
t.string "type"
t.string "username"
t.string "password"
t.bigint "project_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "provider_ref"
t.bigint "account_id"
t.string "state"
t.string "registry_url"
t.index ["account_id"], name: "index_credentials_on_account_id"
t.index ["project_id"], name: "index_credentials_on_project_id"
t.index ["provider_ref"], name: "index_credentials_on_provider_ref"
end
create_table "uffizzi_core_deployment_events", force: :cascade do |t|
t.string "deployment_state"
t.string "message"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "deployment_id", null: false
t.index ["deployment_id"], name: "uf_core_dep_events_on_dep"
end
create_table "uffizzi_core_deployments", force: :cascade do |t|
t.bigint "project_id", null: false
t.text "kind", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "creator_name"
t.string "subdomain"
t.string "state"
t.float "memory_limit"
t.bigint "deployed_by_id"
t.bigint "continuous_preview_id_deprecated"
t.jsonb "continuous_preview_payload"
t.string "creation_source"
t.bigint "compose_file_id"
t.bigint "template_id"
t.datetime "disabled_at"
t.jsonb "metadata", default: {}
t.datetime "last_deploy_at"
t.index ["compose_file_id"], name: "index_deployments_on_compose_file_id"
t.index ["project_id"], name: "index_deployments_on_project_id"
t.index ["template_id"], name: "index_deployments_on_template_id"
end
create_table "uffizzi_core_events", force: :cascade do |t|
t.bigint "activity_item_id", null: false
t.string "state"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["activity_item_id"], name: "index_events_on_activity_item_id"
end
create_table "uffizzi_core_host_volume_files", force: :cascade do |t|
t.string "source"
t.string "path"
t.boolean "is_file"
t.binary "payload"
t.bigint "added_by_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "project_id", null: false
t.bigint "compose_file_id", null: false
t.index ["compose_file_id"], name: "index_host_volume_file_on_compose_file_id"
t.index ["project_id"], name: "index_host_volume_file_on_project_id"
end
create_table "uffizzi_core_invitations", force: :cascade do |t|
t.text "email", null: false
t.text "token", null: false
t.string "status", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "invited_by_id", null: false
t.bigint "entityable_id", null: false
t.string "entityable_type", null: false
t.string "role", null: false
t.bigint "invitee_id"
t.index ["token"], name: "index_invitations_on_token", unique: true
end
create_table "uffizzi_core_kubernetes_distributions", force: :cascade do |t|
t.string "version"
t.string "distro"
t.string "image"
t.boolean "default"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "uffizzi_core_memberships", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "account_id", null: false
t.text "role", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "state"
t.index ["account_id"], name: "index_memberships_on_account_id"
t.index ["user_id", "account_id"], name: "index_memberships_on_user_id_and_account_id"
t.index ["user_id"], name: "index_memberships_on_user_id"
end
create_table "uffizzi_core_payments", force: :cascade do |t|
t.bigint "account_id", null: false
t.string "charge_id"
t.string "status"
t.float "amount"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_payments_on_account_id"
end
create_table "uffizzi_core_prices", force: :cascade do |t|
t.string "token"
t.string "slug", null: false
t.string "name", null: false
t.float "units_price", null: false
t.bigint "units_amount", null: false
t.bigint "product_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["product_id"], name: "index_prices_on_product_id"
end
create_table "uffizzi_core_products", force: :cascade do |t|
t.string "token"
t.string "slug", null: false
t.string "name", null: false
t.string "type", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "kind"
end
create_table "uffizzi_core_projects", force: :cascade do |t|
t.text "name", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "account_id", null: false
t.string "state"
t.string "slug"
t.string "description"
t.index ["account_id", "name"], name: "proj_uniq_name", unique: true, where: "((state)::text = 'active'::text)"
t.index ["account_id"], name: "index_projects_on_account_id"
end
create_table "uffizzi_core_ratings", force: :cascade do |t|
t.string "name"
t.string "state"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "uffizzi_core_repos", force: :cascade do |t|
t.string "namespace"
t.string "name"
t.string "tag"
t.string "type"
t.string "branch"
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "description"
t.boolean "is_private"
t.string "slug"
t.bigint "repository_id"
t.string "kind"
t.string "dockerfile_path"
t.jsonb "args"
t.string "dockerfile_context_path"
t.boolean "deploy_preview_when_pull_request_is_opened"
t.boolean "delete_preview_when_pull_request_is_closed"
t.boolean "deploy_preview_when_image_tag_is_created"
t.boolean "delete_preview_when_image_tag_is_updated"
t.boolean "share_to_github"
t.integer "delete_preview_after"
t.string "tag_pattern_deprecated"
t.index ["project_id"], name: "index_repos_on_project_id"
end
create_table "uffizzi_core_roles", force: :cascade do |t|
t.string "name"
t.string "resource_type"
t.bigint "resource_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
end
create_table "uffizzi_core_secrets", force: :cascade do |t|
t.string "name"
t.string "value"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "resource_type"
t.bigint "resource_id"
t.index ["resource_type", "resource_id"], name: "index_uffizzi_core_secrets_on_resource"
end
create_table "uffizzi_core_templates", force: :cascade do |t|
t.string "name"
t.bigint "added_by_id"
t.jsonb "payload", null: false
t.bigint "project_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "compose_file_id"
t.string "creation_source"
t.index ["project_id"], name: "index_templates_on_project_id"
end
create_table "uffizzi_core_user_projects", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "project_id", null: false
t.text "role", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "invited_by_id"
t.index ["project_id"], name: "index_user_projects_on_project_id"
t.index ["user_id", "project_id"], name: "index_user_projects_on_user_id_and_project_id"
t.index ["user_id"], name: "index_user_projects_on_user_id"
end
create_table "uffizzi_core_users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email", default: "", null: false
t.string "password_digest", default: "", null: false
t.string "confirmation_token"
t.string "state"
t.string "phone"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "github"
t.string "website"
t.string "twitter"
t.string "linkedin"
t.string "devto"
t.string "facebook"
t.string "blog"
t.text "bio"
t.string "status"
t.string "availability"
t.string "primary_skills"
t.string "learning"
t.string "coding_for"
t.string "education"
t.string "title"
t.string "work"
t.string "primary_location"
t.string "creation_source"
t.index "lower((email)::text)", name: "index_email_on_lower_email", unique: true
end
create_table "uffizzi_core_users_roles", id: false, force: :cascade do |t|
t.bigint "user_id"
t.bigint "role_id"
t.index ["role_id"], name: "index_users_roles_on_role_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
t.index ["user_id"], name: "index_users_roles_on_user_id"
end
add_foreign_key "uffizzi_core_clusters", "uffizzi_core_projects", column: "project_id"
add_foreign_key "uffizzi_core_container_host_volume_files", "uffizzi_core_containers", column: "container_id"
add_foreign_key "uffizzi_core_container_host_volume_files", "uffizzi_core_host_volume_files", column: "host_volume_file_id"
add_foreign_key "uffizzi_core_deployment_events", "uffizzi_core_deployments", column: "deployment_id"
add_foreign_key "uffizzi_core_host_volume_files", "uffizzi_core_compose_files", column: "compose_file_id"
add_foreign_key "uffizzi_core_host_volume_files", "uffizzi_core_projects", column: "project_id"
end
================================================
FILE: db/seeds.rb
================================================
# frozen_string_literal: true
puts 'Creating User'
user = UffizziCore::User.create!(
email: 'admin@uffizzi.com',
password: 'password',
state: UffizziCore::User::STATE_ACTIVE,
creation_source: UffizziCore::User.creation_source.system,
)
puts 'Creating Accounts'
personal_account = UffizziCore::Account.create!(
owner: user,
name: 'personal',
state: UffizziCore::Account::STATE_ACTIVE,
kind: UffizziCore::Account.kind.personal,
)
organizational_account = UffizziCore::Account.create!(
owner: user,
name: 'organizational',
state: UffizziCore::Account::STATE_ACTIVE,
kind: UffizziCore::Account.kind.organizational,
)
user.memberships.create!(account: personal_account, role: UffizziCore::Membership.role.admin)
user.memberships.create!(account: organizational_account, role: UffizziCore::Membership.role.admin)
personal_project = personal_account.projects.create!(name: 'default', slug: 'default', state: UffizziCore::Project::STATE_ACTIVE)
personal_project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)
organizational_project = organizational_account.projects.create!(name: 'uffizzi', slug: 'uffizzi',
state: UffizziCore::Project::STATE_ACTIVE)
organizational_project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)
puts 'Creating Kubernetes Distributions'
UffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.25', image: 'rancher/k3s:v1.25.14-k3s1')
UffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.26', image: 'rancher/k3s:v1.26.9-k3s1')
UffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.27', default: true, image: 'rancher/k3s:v1.27.6-k3s1')
UffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.28', image: 'rancher/k3s:v1.28.2-k3s1')
================================================
FILE: docker-compose.yml
================================================
version: '3.9'
x-web-environment: &web-environment
RAILS_SECRET_KEY_BASE: 44a599292ee918ca52c5060bb73b9a5b754628d6d67c64d0066c2ecf25381ef67b2b7a9981332316cc09e0a4bdbd08f07b7a9277d77fd4b4f2a39a488860c18c
DATABASE_HOST: db
DATABASE_USERNAME: postgres
DATABASE_PASSWORD: postgres
BUNDLE_PATH: /bundle_cache
GEM_HOME: /bundle_cache
GEM_PATH: /bundle_cache
RAILS_WORKERS_COUNT: 0
RAILS_THREADS_COUNT: 10
SIDEKIQ_CONCURRENCY: 1
RAILS_PORT: 7001
RAILS_ENV: development
ALLOWED_HOSTS: lvh.me,.lvh.me,lvh.me:1313,.lvh.me:1313,lvh.me:7001,.lvh.me:7001,localhost,.ngrok.io,localhost:3000,web
REDIS_URL: redis://redis
APP_URL: http://web:7001
RUBYGEMS_API_KEY: ${RUBYGEMS_API_KEY}
services:
web:
build: .
environment: *web-environment
env_file:
- .env
volumes: &web-volumes
- &app-volume .:/app:cached
- ~/.ssh:/root/.ssh
- &bash-history ~/.bash_history:/root/.bash_history
- &bundle-cache-volume bundle_cache:/bundle_cache
ports:
- 7001:7001
depends_on:
- db
command: bash -c "bundle install && bundle exec rails db:create db:migrate && bundle exec puma -C config/puma.rb"
core:
build: core
environment: *web-environment
env_file:
- .env
volumes:
- ./core:/gem:cached
- *bash-history
- *bundle-cache-volume
depends_on:
- db
command: bash -c "bundle install && bundle exec rails db:create db:migrate"
db:
image: postgres:11.4
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
sidekiq:
build: .
environment: *web-environment
env_file:
- .env
volumes: *web-volumes
depends_on:
- redis
- web
command: bash -c "bundle exec sidekiq -C /app/config/sidekiq.yml"
redis:
image: redis
bundle_cache:
image: busybox
volumes:
- *bundle-cache-volume
volumes:
bundle_cache:
core:
networks:
default:
name: 'uffizzi_default_network'
================================================
FILE: docs/continuous-previews.md
================================================
## INTRODUCTION-
# Why this Open Source Project is Needed:
These are our observations after 2+ years interviewing over 150+ developers, DevOps, and cross-functional team members across a variety of industries and at various levels of business maturity - from nascent start-ups to long-running enterprises:
-It's universally beneficial to bring QA into the Development process, to catch issues early, iterate quickly, and to merge clean code into Develop or Main.
-Virtually all teams from can benefit from an on-demand preview environment capability and industry defined best practices.
-Many teams - typically more advanced teams with time, resources, and expertise - build and maintain an Internal Development Platform (IDP) to address a Preview requirement.
-Most teams lack the time, resources, and expertise to build and maintain an internal capability - for most a Preview tool is on a wish list.
-As a community we are not actively collaborating or innovating towards a well-defined Preview Process and technical capability: we should be.
-There's no official Open Source Preview specific tool within the CNCF. There are several CI, CD, and deployment tools within the CNCF and more broadly across the industry. These tools, while useful, are not purpose built for the task of Previewing. They fit in the Ops and DevOps lane and do not provide a Developer friendly interface for configuration.
-Docker compose is a configuration-as-code format that best balances DevOps requirements (GitOps, Infrastructure-as-code, Functionality) with Developer accessibility.
-Previewing should be more about collaboration between the teammate(s) writing the code and the teammate(s) previewing what they've done than it is about deploying - "Individuals and Interactions over processes and tools." https://agilemanifesto.org/
-The maturity of cloud native development, containerization, and container orchestration have laid the foundation for a Preview capability and best practices that nearly all teams can benefit from.
# What this Project/Team aims to accomplish:
-Lead a community effort to define best practices for Previewing and to provide a modular, purpose-built, open source Preview engine that will more broadly enable teams to benefit from a Preview capability.
-Provide well-defined guidelines for Previewing - see https://continuous-previews.org
================================================
FILE: docs/quickstart-guide.md
================================================
# Quickstart guide
This guide describes configuring a [GitHub Action](https://github.com/UffizziCloud/preview-action) to create event-driven test environments for every pull requests.
## Reusable Workflow
We've published a [Reusable Workflow](https://docs.github.com/en/actions/using-workflows/reusing-workflows#calling-a-reusable-workflow) for your GitHub Actions. This can handle creating, updating, and deleting test lightweight test environments with Uffizzi. It will also publish environment URLs (also known as "preview URLs") within comments on your pull requests. We recommend using this workflow instead of the individual actions.
### Workflow Calling Example
This example builds and publishes an image to Docker Hub. It then renders a Uffizzi Docker Compose file from a template and caches it. Finally, calls the reusable workflow to create, update, or delete the preview associated with this pull request.
```
name: Build Images and Handle Uffizzi Previews.
on:
pull_request:
types: [opened,reopened,synchronize,closed]
jobs:
build-image:
name: Build and Push image
runs-on: ubuntu-latest
outputs:
# You'll need this output to later render the Compose file.
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Checkout git repo
uses: actions/checkout@v3
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: example/image
- name: Build and Push Image to Docker Hub
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
render-compose-file:
name: Render Docker Compose File
runs-on: ubuntu-latest
needs:
- build-image
outputs:
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
compose-file-cache-path: docker-compose.rendered.yml
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Render Compose File
run: |
IMAGE=$(echo ${{ needs.build-image.outputs.tags }})
export IMAGE
# Render simple template from environment variables.
envsubst < docker-compose.template.yml > docker-compose.rendered.yml
cat docker-compose.rendered.yml
- name: Hash Rendered Compose File
id: hash
run: echo "::set-output name=hash::$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')"
- name: Cache Rendered Compose File
uses: actions/cache@v3
with:
path: docker-compose.rendered.yml
key: ${{ steps.hash.outputs.hash }}
deploy-uffizzi-preview:
name: Use Remote Workflow to Preview on Uffizzi
needs: render-compose-file
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml
with:
compose-file-cache-key: ${{ needs.render-compose-file.outputs.compose-file-cache-key }}
compose-file-cache-path: ${{ needs.render-compose-file.outputs.compose-file-cache-path }}
username: user@example.com
server: https://uffizzi.example.com/
project: default
secrets:
password: ${{ secrets.UFFIZZI_PASSWORD }}
permissions:
contents: read
pull-requests: write
```
### Workflow Inputs
#### `compose-file-cache-key`
**Required** Key of hashed compose file, using [GitHub's `cache` action](https://github.com/marketplace/actions/cache)
#### `compose-file-cache-path`
**Required** Path of hashed compose file, using [GitHub's `cache` action](https://github.com/marketplace/actions/cache)
#### `username`
**Required** Uffizzi username. To use the Uffizzi managed API service, you must first [create an account here](https://app.uffizzi.com/sign_up).
#### `project`
**Required** Uffizzi project name
#### `server`
URL of your Uffizzi installation. To use the Uffizzi managed API service, set this value to `https://app.uffizzi.com`.
### Workflow Secrets
#### `password`
Your Uffizzi password. Specify a GitHub Encrypted Secret and use it! See example above.
# The Action Itself
If you wish to use this action by itself outside of the reusable workflow, you can. It will only create new previews, not update nor delete.
## Inputs
### `compose-file`
**Required** Path to a compose file within your repository
### `username`
**Required** Uffizzi username
### `project`
**Required** Uffizzi project name
### `server`
URL of your Uffizzi installation
### `password`
Your Uffizzi password. Specify a GitHub Encrypted Secret and use it! See example below.
## Example usage
```yaml
uses: UffizziCloud/preview-action@v2
with:
compose-file: 'docker-compose.uffizzi.yaml'
username: 'admin@uffizzi.com'
server: 'https://app.uffizzi.com'
project: 'default'
password: ${{ secrets.UFFIZZI_PASSWORD }}
```
================================================
FILE: lib/assets/.keep
================================================
================================================
FILE: lib/tasks/.keep
================================================
================================================
FILE: log/.keep
================================================
================================================
FILE: public/404.html
================================================
The page you were looking for doesn't exist (404)
The page you were looking for doesn't exist.
You may have mistyped the address or the page may have moved.
If you are the application owner check the logs for more information.
================================================
FILE: public/422.html
================================================
The change you wanted was rejected (422)
The change you wanted was rejected.
Maybe you tried to change something you didn't have access to.
If you are the application owner check the logs for more information.
================================================
FILE: public/500.html
================================================
We're sorry, but something went wrong (500)
We're sorry, but something went wrong.
If you are the application owner check the logs for more information.
================================================
FILE: public/robots.txt
================================================
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
================================================
FILE: storage/.keep
================================================
================================================
FILE: test/application_system_test_case.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
================================================
FILE: test/channels/application_cable/connection_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
# test "connects with cookies" do
# cookies.signed[:user_id] = 42
#
# connect
#
# assert_equal connection.user_id, "42"
# end
end
================================================
FILE: test/controllers/.keep
================================================
================================================
FILE: test/fixtures/.keep
================================================
================================================
FILE: test/fixtures/files/.keep
================================================
================================================
FILE: test/helpers/.keep
================================================
================================================
FILE: test/integration/.keep
================================================
================================================
FILE: test/mailers/.keep
================================================
================================================
FILE: test/models/.keep
================================================
================================================
FILE: test/system/.keep
================================================
================================================
FILE: test/test_helper.rb
================================================
# frozen_string_literal: true
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
================================================
FILE: tmp/.keep
================================================
================================================
FILE: uffizzi-compose-example.yml
================================================
services:
redis:
image: redis:latest
postgres:
image: postgres:9.6
environment:
POSTGRES_USER: ${VOTE_APP_POSTGRES.USER}
POSTGRES_PASSWORD: ${VOTE_APP_POSTGRES.PASSWORD}
nginx:
image: nginx:latest
configs:
- source: vote.conf
target: /etc/nginx/conf.d
worker:
build:
context: https://github.com/UffizziCloud/example-voting-worker:main
dockerfile: Dockerfile
deploy:
resources:
limits:
memory: 250M
vote:
build:
context: https://github.com/UffizziCloud/example-voting-vote:main
dockerfile: Dockerfile
deploy:
resources:
limits:
memory: 250M
result:
build:
context: https://github.com/UffizziCloud/example-voting-result:main
dockerfile: Dockerfile
continuous_preview:
deploy_preview_when_pull_request_is_opened: false
delete_preview_when_pull_request_is_closed: false
deploy_preview_for_image_tag_PR_#-branchname: true
delete_preview_when_pull_request_is_closed: false
delete_preview_in_x_hours: 24
share_to_github: true
ingress:
service: nginx
port: 8080
================================================
FILE: vendor/.keep
================================================