[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: Bug report\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Configuration:**\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n - Open Source, CLI, Cloud, Docker Desktop Extension\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: community-new\nassignees: ''\n\n---\n\n**Tell us about your request**\nA clear and concise description of what you want to happen or the change you would like to see\n\n**Which service(s) is this request for?**\nLet us know which product(s) you want this for - Open Source or Cloud solution or both?\n\n**Tell us about the problem you're trying to solve. What are you trying to do, and why is it hard?**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Are you currently working around the issue?**\nA clear and concise description of any alternative solutions or features you've considered or are using today.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/helm-release.yml",
    "content": "name: Release Helm Charts\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Configure Git\n        run: |\n          git config user.name \"$GITHUB_ACTOR\"\n          git config user.email \"$GITHUB_ACTOR@users.noreply.github.com\"\n\n      - name: Run chart-releaser\n        uses: helm/chart-releaser-action@v1.3.0\n        env:\n          CR_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Test, Lint, Build, and Publish Image\non:\n  push:\n    branches:\n      - qa\n      - develop\n      - main\n  pull_request:\n    types: [opened,synchronize,reopened]\nconcurrency:\n  group: ${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 2.7.5\n          bundler-cache: true\n      - name: Run rubocop\n        run: bundle exec rubocop\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Create env file\n        uses: SpicyPizza/create-envfile@v1.3\n      - name: Run tests\n        run: docker-compose run --rm core bash -c \"bundle install && bundle exec rails db:create && bundle exec rails test\"\n  build-and-push-some-branches:\n    runs-on: ubuntu-latest\n    needs:\n      - lint\n      - test\n    if: ${{ github.ref_name == 'main' || github.ref_name == 'qa' || github.ref_name == 'develop' || github.event_name == 'pull_request' }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n      - name: Login to DockerHub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n      - name: Docker metadata\n        id: meta\n        uses: docker/metadata-action@v3\n        with:\n          images: uffizzi/app\n          tags: |\n            type=raw,value=latest,enable=${{ github.ref_name == 'main' }}\n            type=ref,event=branch,enable=${{ github.ref_name == 'qa' || github.ref_name == 'develop' }}\n            type=ref,event=pr\n      - name: Build and Push Image to Docker Hub\n        uses: docker/build-push-action@v2\n        with:\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n      - name: Update Docker Hub Description for Default Branch\n        uses: peter-evans/dockerhub-description@v2.4.3\n        if: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_PASSWORD }}\n          repository: uffizzi/app\n  notify:\n    needs:\n      - lint\n      - test\n      - build-and-push-some-branches\n    if: ${{ always() }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: technote-space/workflow-conclusion-action@v2\n      - uses: 8398a7/action-slack@v3\n        with:\n          status: ${{ env.WORKFLOW_CONCLUSION }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n        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') }}\n\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Uffizzi Core Release\non:\n  push:\n    tags:\n      - core_v*\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n      - name: Setup ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 2.5.0\n      - name: Create env file\n        uses: SpicyPizza/create-envfile@v1.3\n      - name: Release Gem\n        run: docker compose run --rm core bash -c \"make release_gem\"\n        env:\n          RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n      - name: Login to DockerHub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n      - name: Build and push\n        uses: docker/build-push-action@v2\n        with:\n          context: core\n          push: true\n          tags: uffizzi/core:${{ github.ref_name }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring temporary files generated by your text editor\n# or operating system, you probably want to add a global ignore instead:\n#   git config --global core.excludesfile '~/.gitignore_global'\n\n# Ignore bundler config.\n/.bundle\n\n# Ignore the default SQLite database.\n/db/*.sqlite3\n/db/*.sqlite3-journal\n/db/*.sqlite3-*\n\n# Ignore all logfiles and tempfiles.\n/log/*\n/tmp/*\n!/log/.keep\n!/tmp/.keep\n\n# Ignore pidfiles, but keep the directory.\n/tmp/pids/*\n!/tmp/pids/\n!/tmp/pids/.keep\n\n# Ignore uploaded files in development.\n/storage/*\n!/storage/.keep\n\n/public/assets\n.byebug_history\n\n# Ignore master key for decrypting credentials and more.\n/config/master.key\n\n/credentials/*.json\n.env\n\n# Ignore (intellij) idea config directory\n/.idea\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "require:\n - rubocop-minitest\n\nAllCops:\n  NewCops: disable\n  SuggestExtensions: false\n  Exclude:\n    - core/test/dummy/**/*\n    - db/schema.rb\n    - vendor/**/*\n\nNaming/AccessorMethodName:\n  Description: Check the naming of accessor methods for get_/set_.\n  Enabled: false\n\nNaming/MethodParameterName:\n  Enabled: false\n\nStyle/Alias:\n  Description: 'Use alias_method instead of alias.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'\n  Enabled: false\n\nStyle/ArrayJoin:\n  Description: 'Use Array#join instead of Array#*.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join'\n  Enabled: false\n\nStyle/AsciiComments:\n  Description: 'Use only ascii symbols in comments.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments'\n  Enabled: false\n\nNaming/AsciiIdentifiers:\n  Description: 'Use only ascii symbols in identifiers.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers'\n  Enabled: false\n\nStyle/Attr:\n  Description: 'Checks for uses of Module#attr.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr'\n  Enabled: false\n\nMetrics/BlockNesting:\n  Description: 'Avoid excessive block nesting'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'\n  Enabled: false\n\nStyle/CaseEquality:\n  Description: 'Avoid explicit use of the case equality operator(===).'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'\n  Enabled: false\n\nStyle/CharacterLiteral:\n  Description: 'Checks for uses of character literals.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals'\n  Enabled: false\n\nStyle/ClassAndModuleChildren:\n  Description: 'Checks style of children classes and modules.'\n  Enabled: false\n\nMetrics/ClassLength:\n  Description: 'Avoid classes longer than 100 lines of code.'\n  Enabled: false\n\nMetrics/ModuleLength:\n  Description: 'Avoid modules longer than 100 lines of code.'\n  Enabled: false\n\nStyle/ClassVars:\n  Description: 'Avoid the use of class variables.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'\n  Enabled: false\n\nStyle/CollectionMethods:\n  Enabled: true\n  PreferredMethods:\n    find: detect\n    inject: reduce\n    collect: map\n    find_all: select\n\nStyle/ColonMethodCall:\n  Description: 'Do not use :: for method call.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons'\n  Enabled: false\n\nStyle/CommentAnnotation:\n  Description: >-\n                 Checks formatting of special comments\n                 (TODO, FIXME, OPTIMIZE, HACK, REVIEW).\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords'\n  Enabled: false\n\nStyle/PreferredHashMethods:\n  Description: 'Checks use of `has_key?` and `has_value?` Hash methods.'\n  StyleGuide: '#hash-key'\n  Enabled: false\n\nStyle/Documentation:\n  Description: 'Document classes and non-namespace modules.'\n  Enabled: false\n\nStyle/DoubleNegation:\n  Description: 'Checks for uses of double negation (!!).'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang'\n  Enabled: false\n\nStyle/EachWithObject:\n  Description: 'Prefer `each_with_object` over `inject` or `reduce`.'\n  Enabled: false\n\nStyle/EmptyLiteral:\n  Description: 'Prefer literals to Array.new/Hash.new/String.new.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash'\n  Enabled: false\n\nStyle/Encoding:\n  Enabled: false\n\nStyle/EvenOdd:\n  Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'\n  Enabled: false\n\nNaming/FileName:\n  Description: 'Use snake_case for source file names.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'\n  Enabled: false\n\nStyle/FrozenStringLiteralComment:\n  Description: >-\n    Add the frozen_string_literal comment to the top of files\n    to help transition from Ruby 2.3.0 to Ruby 3.0.\n  Enabled: true\n\nStyle/FormatString:\n  Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'\n  Enabled: false\n\nStyle/GlobalVars:\n  Description: 'Do not introduce global variables.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'\n  Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html'\n  Enabled: false\n\nStyle/GuardClause:\n  Description: 'Check for conditionals that can be replaced with guard clauses'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'\n  Enabled: false\n\nStyle/IfUnlessModifier:\n  Description: >-\n                 Favor modifier if/unless usage when you have a\n                 single-line body.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'\n  Enabled: false\n\nStyle/IfWithSemicolon:\n  Description: 'Do not use if x; .... Use the ternary operator instead.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs'\n  Enabled: false\n\nStyle/InlineComment:\n  Description: 'Avoid inline comments.'\n  Enabled: false\n\nStyle/Lambda:\n  Description: 'Use the new lambda literal syntax for single-line blocks.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line'\n  Enabled: false\n\nStyle/LambdaCall:\n  Description: 'Use lambda.call(...) instead of lambda.(...).'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call'\n  Enabled: false\n\nStyle/LineEndConcatenation:\n  Description: >-\n                 Use \\ instead of + or << to concatenate two string literals at\n                 line end.\n  Enabled: false\n\nMetrics/AbcSize:\n  Description: >-\n                 A calculated magnitude based on number of assignments,\n                 branches, and conditions.\n  Enabled: false\n\nMetrics/BlockLength:\n  CountComments: true\n  Max: 50\n  IgnoredMethods: []\n  Exclude:\n    - test/**/*\n    - app/repositories/**/*\n    - config/environments/**/*\n    - core/test/**/*\n    - core/app/repositories/**/*\n    - core/config/routes.rb\n\nMetrics/CyclomaticComplexity:\n  Description: >-\n                 A complexity metric that is strongly correlated to the number\n                 of test cases needed to validate a method.\n  Enabled: false\n\nMetrics/MethodLength:\n  Description: 'Avoid methods longer than 10 lines of code.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'\n  Enabled: false\n\nStyle/MixinUsage:\n  Enabled: true\n  Exclude:\n    - test/test_helper.rb\n\nStyle/ModuleFunction:\n  Description: 'Checks for usage of `extend self` in modules.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function'\n  Enabled: false\n\nStyle/MultilineBlockChain:\n  Description: 'Avoid multi-line chains of blocks.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'\n  Enabled: false\n\nStyle/NegatedIf:\n  Description: >-\n                 Favor unless over if for negative conditions\n                 (or control flow or).\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'\n  Enabled: false\n\nStyle/NegatedWhile:\n  Description: 'Favor until over while for negative conditions.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives'\n  Enabled: false\n\nStyle/Next:\n  Description: 'Use `next` to skip iteration instead of a condition at the end.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'\n  Enabled: false\n\nStyle/NilComparison:\n  Description: 'Prefer x.nil? to x == nil.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'\n  Enabled: false\n\nStyle/Not:\n  Description: 'Use ! instead of not.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not'\n  Enabled: false\n\nStyle/NumericLiterals:\n  Description: >-\n                 Add underscores to large numeric literals to improve their\n                 readability.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics'\n  Enabled: false\n\nStyle/OneLineConditional:\n  Description: >-\n                 Favor the ternary operator(?:) over\n                 if/then/else/end constructs.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'\n  Enabled: false\n\nNaming/BinaryOperatorParameterName:\n  Description: 'When defining binary operators, name the argument other.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'\n  Enabled: false\n\nMetrics/ParameterLists:\n  Description: 'Avoid parameter lists longer than three or four parameters.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'\n  Enabled: false\n\nStyle/PercentLiteralDelimiters:\n  Description: 'Use `%`-literal delimiters consistently'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces'\n  Enabled: false\n\nStyle/PerlBackrefs:\n  Description: 'Avoid Perl-style regex back references.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'\n  Enabled: false\n\nNaming/PredicateName:\n  Description: 'Check the names of predicate methods.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'\n  ForbiddenPrefixes:\n    - is_\n  Exclude:\n    - spec/**/*\n\nStyle/Proc:\n  Description: 'Use proc instead of Proc.new.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc'\n  Enabled: false\n\nStyle/RaiseArgs:\n  Description: 'Checks the arguments passed to raise/fail.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages'\n  Enabled: false\n\nStyle/RegexpLiteral:\n  Description: 'Use / or %r around regular expressions.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r'\n  Enabled: false\n\nStyle/Sample:\n  Description: >-\n                  Use `sample` instead of `shuffle.first`,\n                  `shuffle.last`, and `shuffle[Fixnum]`.\n  Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code'\n  Enabled: false\n\nStyle/SelfAssignment:\n  Description: >-\n                 Checks for places where self-assignment shorthand should have\n                 been used.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment'\n  Enabled: false\n\nStyle/SingleLineBlockParams:\n  Description: 'Enforces the names of some block params.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'\n  Enabled: false\n\nStyle/SingleLineMethods:\n  Description: 'Avoid single-line methods.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods'\n  Enabled: false\n\nStyle/SignalException:\n  Description: 'Checks for proper usage of fail and raise.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method'\n  Enabled: false\n\nStyle/SpecialGlobalVars:\n  Description: 'Avoid Perl-style global variables.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms'\n  Enabled: false\n\nStyle/StringLiterals:\n  Description: 'Checks if uses of quotes match the configured preference.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'\n  EnforcedStyle: single_quotes\n  Enabled: true\n\nStyle/StructInheritance:\n  Enabled: true\n\nStyle/SymbolArray:\n  EnforcedStyle: brackets\n\nStyle/SymbolProc:\n  Enabled: true\n\nStyle/TrailingCommaInArguments:\n  Description: 'Checks for trailing comma in argument lists.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'\n  EnforcedStyleForMultiline: comma\n  SupportedStylesForMultiline:\n    - comma\n    - consistent_comma\n    - no_comma\n  Enabled: true\n\nStyle/TrailingCommaInArrayLiteral:\n  Description: 'Checks for trailing comma in array literals.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'\n  EnforcedStyleForMultiline: comma\n  SupportedStylesForMultiline:\n    - comma\n    - consistent_comma\n    - no_comma\n  Enabled: true\n\nStyle/TrailingCommaInHashLiteral:\n  Description: 'Checks for trailing comma in hash literals.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'\n  EnforcedStyleForMultiline: comma\n  SupportedStylesForMultiline:\n    - comma\n    - consistent_comma\n    - no_comma\n  Enabled: true\n\nStyle/TrivialAccessors:\n  Description: 'Prefer attr_* methods to trivial readers/writers.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'\n  Enabled: false\n\nStyle/VariableInterpolation:\n  Description: >-\n                 Don't interpolate global, instance and class variables\n                 directly in strings.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate'\n  Enabled: false\n\nStyle/WhenThen:\n  Description: 'Use when x then ... for one-line cases.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases'\n  Enabled: false\n\nStyle/WhileUntilModifier:\n  Description: >-\n                 Favor modifier while/until usage when you have a\n                 single-line body.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier'\n  Enabled: false\n\nStyle/WordArray:\n  Description: 'Use %w or %W for arrays of words.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w'\n  EnforcedStyle: brackets\n\nStyle/MethodCallWithArgsParentheses:\n  Description: 'Use parentheses for method calls with arguments.'\n  Enabled: true\n  IgnoredMethods:\n    - require\n    - require_relative\n    - raise\n    - head\n    - render\n    - respond_with\n    - puts\n  Exclude:\n    - Gemfile\n    - test/test_helper.rb\n    - db/migrate/**/*\n    - core/uffizzi_core.gemspec\n    - core/db/migrate/**/*\n\nLayout/ParameterAlignment:\n  Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'\n  Enabled: false\n\nLayout/ConditionPosition:\n  Description: >-\n                 Checks for condition placed in a confusing position relative to\n                 the keyword.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'\n  Enabled: false\n\nLayout/DotPosition:\n  Description: 'Checks the position of the dot in multi-line method calls.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'\n  EnforcedStyle: leading\n\nLayout/ExtraSpacing:\n  Description: 'Do not use unnecessary spacing.'\n  Enabled: true\n\nLayout/MultilineOperationIndentation:\n  Description: >-\n                 Checks indentation of binary operations that span more than\n                 one line.\n  Enabled: true\n  EnforcedStyle: indented\n\nLayout/MultilineMethodCallIndentation:\n  Description: >-\n                 Checks indentation of method calls with the dot operator\n                 that span more than one line.\n  Enabled: true\n  EnforcedStyle: indented\n\nLayout/InitialIndentation:\n  Description: >-\n    Checks the indentation of the first non-blank non-comment line in a file.\n  Enabled: false\n\nLayout/EndAlignment:\n  Description: >-\n    Checks whether the end keywords are aligned properly.\n  EnforcedStyleAlignWith: variable\n\nLayout/LineLength:\n  Description: 'Limit lines to 140 characters.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'\n  Max: 140\n\nLint/AmbiguousOperator:\n  Description: >-\n                 Checks for ambiguous operators in the first argument of a\n                 method invocation without parentheses.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args'\n  Enabled: false\n\nLint/AmbiguousRegexpLiteral:\n  Description: >-\n                 Checks for ambiguous regexp literals in the first argument of\n                 a method invocation without parenthesis.\n  Enabled: false\n\nLint/AssignmentInCondition:\n  Description: \"Don't use assignment in conditions.\"\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition'\n  Enabled: false\n\nLint/CircularArgumentReference:\n  Description: \"Don't refer to the keyword argument in the default value.\"\n  Enabled: false\n\nLint/DeprecatedClassMethods:\n  Description: 'Check for deprecated class method calls.'\n  Enabled: false\n\nLint/DuplicateHashKey:\n  Description: 'Check for duplicate keys in hash literals.'\n  Enabled: false\n\nLint/EachWithObjectArgument:\n  Description: 'Check for immutable argument given to each_with_object.'\n  Enabled: false\n\nLint/ElseLayout:\n  Description: 'Check for odd code arrangement in an else block.'\n  Enabled: false\n\nLint/FlipFlop:\n  Description: 'Checks for flip flops'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'\n  Enabled: false\n\nLint/FormatParameterMismatch:\n  Description: 'The number of parameters to format/sprint must match the fields.'\n  Enabled: false\n\nLint/SuppressedException:\n  Description: \"Don't suppress exception.\"\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'\n  Enabled: false\n\nLint/LiteralAsCondition:\n  Description: 'Checks of literals used in conditions.'\n  Enabled: false\n\nLint/LiteralInInterpolation:\n  Description: 'Checks for literals used in interpolation.'\n  Enabled: false\n\nLint/Loop:\n  Description: >-\n                 Use Kernel#loop with break rather than begin/end/until or\n                 begin/end/while for post-loop tests.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break'\n  Enabled: false\n\nLint/NestedMethodDefinition:\n  Description: 'Do not use nested method definitions.'\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods'\n  Enabled: false\n\nLint/NonLocalExitFromIterator:\n  Description: 'Do not use return in iterator to cause non-local exit.'\n  Enabled: false\n\nLint/ParenthesesAsGroupedExpression:\n  Description: >-\n                 Checks for method calls with a space before the opening\n                 parenthesis.\n  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'\n  Enabled: false\n\nLint/RedundantCopDisableDirective:\n  Description: >-\n                 Checks for rubocop:disable comments that can be removed.\n                 Note: this cop is not disabled when disabling all cops.\n                 It must be explicitly disabled.\n  Enabled: false\n\nLint/RequireParentheses:\n  Description: >-\n                 Use parentheses in the method call to avoid confusion\n                 about precedence.\n  Enabled: false\n\nLint/UnderscorePrefixedVariableName:\n  Description: 'Do not use prefix `_` for a variable that is used.'\n  Enabled: false\n\nLint/Void:\n  Description: 'Possible use of operator/literal/variable in void context.'\n  Enabled: false\n\nGemspec/DateAssignment:\n  Enabled: true\nGemspec/RequireMFA:\n  Enabled: false\nLayout/LineEndStringConcatenationIndentation:\n  Enabled: true\nLayout/SpaceBeforeBrackets:\n  Enabled: true\nLint/AmbiguousAssignment:\n  Enabled: true\nLint/AmbiguousOperatorPrecedence:\n  Enabled: true\nLint/AmbiguousRange:\n  Enabled: true\nLint/DeprecatedConstants:\n  Enabled: true\nLint/DuplicateBranch:\n  Enabled: true\nLint/DuplicateRegexpCharacterClassElement:\n  Enabled: true\nLint/EmptyBlock:\n  Enabled: false\nLint/EmptyClass:\n  Enabled: true\nLint/EmptyInPattern:\n  Enabled: true\nLint/IncompatibleIoSelectWithFiberScheduler:\n  Enabled: true\nLint/LambdaWithoutLiteralBlock:\n  Enabled: true\nLint/NoReturnInBeginEndBlocks:\n  Enabled: true\nLint/NumberedParameterAssignment:\n  Enabled: true\nLint/OrAssignmentToConstant:\n  Enabled: true\nLint/RedundantDirGlobSort:\n  Enabled: true\nLint/RequireRelativeSelfPath:\n  Enabled: true\nLint/SymbolConversion:\n  Enabled: true\nLint/ToEnumArguments:\n  Enabled: true\nLint/TripleQuotes:\n  Enabled: true\nLint/UnexpectedBlockArity:\n  Enabled: true\nLint/UnmodifiedReduceAccumulator:\n  Enabled: true\nLint/UselessRuby2Keywords:\n  Enabled: true\nNaming/BlockForwarding:\n  Enabled: true\nSecurity/IoMethods:\n  Enabled: true\nStyle/ArgumentsForwarding:\n  Enabled: true\nStyle/CollectionCompact:\n  Enabled: true\nStyle/DocumentDynamicEvalDefinition:\n  Enabled: true\nStyle/EndlessMethod:\n  Enabled: true\nStyle/FileRead:\n  Enabled: true\nStyle/FileWrite:\n  Enabled: true\nStyle/HashConversion:\n  Enabled: true\nStyle/HashExcept:\n  Enabled: true\nStyle/IfWithBooleanLiteralBranches:\n  Enabled: true\nStyle/InPatternThen:\n  Enabled: true\nStyle/MapToHash:\n  Enabled: true\nStyle/MultilineInPatternThen:\n  Enabled: true\nStyle/NegatedIfElseCondition:\n  Enabled: true\nStyle/NilLambda:\n  Enabled: true\nStyle/NumberedParameters:\n  Enabled: true\nStyle/NumberedParametersLimit:\n  Enabled: true\nStyle/OpenStructUse:\n  Enabled: false\nStyle/OptionalBooleanParameter:\n  Enabled: false\nStyle/QuotedSymbols:\n  Enabled: true\nStyle/RedundantArgument:\n  Enabled: true\nStyle/RedundantSelfAssignmentBranch:\n  Enabled: true\nStyle/SelectByRegexp:\n  Enabled: true\nStyle/StringChars:\n  Enabled: true\nStyle/SwapValues:\n  Enabled: true\nMinitest/AssertInDelta:\n  Enabled: true\nMinitest/AssertionInLifecycleHook:\n  Enabled: true\nMinitest/AssertKindOf:\n  Enabled: true\nMinitest/AssertOutput:\n  Enabled: true\nMinitest/AssertPathExists:\n  Enabled: true\nMinitest/AssertSilent:\n  Enabled: true\nMinitest/AssertWithExpectedArgument:\n  Enabled: true\nMinitest/LiteralAsActualArgument:\n  Enabled: true\nMinitest/MultipleAssertions:\n  Enabled: false\nMinitest/RefuteInDelta:\n  Enabled: true\nMinitest/RefuteKindOf:\n  Enabled: true\nMinitest/RefutePathExists:\n  Enabled: true\nMinitest/TestMethodName:\n  Enabled: true\nMinitest/UnreachableAssertion:\n  Enabled: true\nMinitest/UnspecifiedException:\n  Enabled: true\n"
  },
  {
    "path": ".ruby-version",
    "content": "ruby-2.5.3\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\nThis 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.\n\n## Mission\n\nOur 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.\n\n## Objective\n\nOur 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.\n\n## Standards\n\n- 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.\n\n- **Mission focus**: We will focus our efforts on accomplishing the project’s mission, so that it is fulfilled to its highest potential.\n\n- **Creative conflict**: We acknowledge that discussions and disagreements are a natural part of problem-solving and should happen in a constructive way.\n\n- **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.\n\n- **Principle of charity**: We assume positive intentions of members, contributors, and policies.\n\n## Prohibited Behavior\n\n- Harassment, i.e., excessive, hostile, or bad-faith communication directed at another member or contributor  \n- Attacks based on personal characteristics  \n- Threats of violence  \n- Threatening to publish or publishing people’s personal information without their consent  \n- Repeatedly attempting to involve the project in issues outside of its mission  \n- Posting illegal content  \n- Abuse of the system for reporting code of conduct violations  \n- Promoting any behavior listed above  \n\n## Responsibilities  \n\nIn 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.\n\n## Conflict Resolution\n\n### Reporting\n\nWe 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.\n\nProject leaders will make reasonable efforts to adjudicate incidents shortly after they are brought to their attention.\n\n### Enforcement\n\nProject leaders shall take all reasonable actions to ensure the successful execution of the mission statement and the maximum effectiveness of the project.\n\nAll 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.\n\n## Scope\n\nThis 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.\n\n## Licensing\n\nThe 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."
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to `uffizzi_app`\n\nThanks for your interest! We are actively working to release `uffizzi_app` and define how you can contribute - please follow for updates!\n\nUffizzi welcomes contributions from everyone!\n\n## Communication:\n\nIf 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.\n\n## Releases\n\nWe're using Semantic Versioning 2.0.0 to name our release tags: https://semver.org/\n\nBe 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`.\n\n## Helm Chart Releases\n\nWhen 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.\n\nWhen 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.\n\n# Procedures for outside collaborators:\n\n- Fork the project on Github.\n\n- Make any changes you want to `uffizzi_app`, commit them, and push them to your fork.\n\n- 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!\n\n# Procedures for Uffizzi team members:\n\n1. Clone the repository and checkout to `develop` branch.\n\n2. Pull repository to ensure you have the latest changes.\n\n```bash\ngit pull --rebase develop\n```\n\n3. Start new branch from `develop`\n\n```bash\ngit checkout -b feature/ISSUE_NUMBER_short_issue_description (e.g. feature/53_add_domain_settings)\n```\n\n4. Make changes you need for the feature, commit them to the repo\n\n```bash\ngit add .\ngit commit -m '[#ISSUE_NUMBER] short commit description' (e.g. git commit -m '[#53] added domain settings')\ngit push origin BRANCH_NAME\n```\n\n5. 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\n\n6. Merge your feature to `qa` branch and push. Ensure your pipeline is successful.\n\n```bash\ngit checkout qa\ngit pull --rebase qa\ngit merge --no-ff BRANCH_NAME\ngit push origin qa\n```\n\n# Running linter\n\n```bash\ndocker-compose run --rm web bundle exec rubocop -A\n```\n\n# Running test\n\n```bash\ndocker-compose run --rm core bash\nbin/rails test\n```\n\n# Migrations\n\nIn order to add a new migration do the following steps:\n\n1. Add a new migration inside the core\n2. Run the command inside the `web` container\n\n```bash\nrake uffizzi_core:install:migrations\n```\n\nThis command copies the new migration to the `db/migrate` folder\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM ruby:2.7.5-slim\n\nARG RAILS_ROOT=/app\nRUN mkdir -p $RAILS_ROOT\nWORKDIR $RAILS_ROOT\n\nRUN apt-get update \\\n  && apt-get install -qq -y --no-install-recommends \\\n  vim-tiny \\\n  python2-dev \\\n  libpq-dev \\\n  build-essential \\\n  curl \\\n  less \\\n  tzdata \\\n  git \\\n  postgresql-client \\\n  bash \\\n  screen \\\n  shared-mime-info \\\n  && apt-get autoremove -y \\\n  && rm -rf /var/lib/apt/lists/*\n\nRUN gem install bundler:2.3.9\n\nCOPY Gemfile Gemfile.lock $RAILS_ROOT/\nCOPY core/lib/uffizzi_core/version.rb $RAILS_ROOT/core/lib/uffizzi_core/version.rb\nCOPY core/uffizzi_core.gemspec $RAILS_ROOT/core/uffizzi_core.gemspec\nCOPY core/Gemfile* $RAILS_ROOT/core/\n\nRUN bundle install --jobs 5\n\nCOPY . $RAILS_ROOT\n\nENV PATH $RAILS_ROOT/bin:$PATH\n\nEXPOSE 7000\n\nCMD /bin/bash -c \"bundle exec rails db:create db:migrate && bundle exec puma -C config/puma.rb\"\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\ngit_source(:github) { |repo| \"https://github.com/#{repo}.git\" }\n\nruby '2.7.5'\n\ngem 'bcrypt', '~> 3.1.7'\ngem 'bootsnap', '>= 1.4.2', require: false\ngem 'config'\ngem 'health_check'\ngem 'pg', '>= 0.18', '< 2.0'\ngem 'puma', '~> 4.1'\ngem 'rack-cors'\ngem 'rails', '~> 6.1.0'\ngem 'sidekiq'\ngem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]\ngem 'uffizzi_core', path: './core'\n\ngroup :development, :test do\n  gem 'awesome_print'\n  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]\nend\n\ngroup :development do\n  gem 'rubocop'\n  gem 'rubocop-minitest'\n  gem 'rubocop-rake'\nend\n"
  },
  {
    "path": "INSTALL.md",
    "content": "# Installation overview\n\nSee 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.\n\nOnce 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).\n\n## Uffizzi Architecture\n<img src=\"docs/images/uffizzi-architecture.png\" description=\"Uffizzi Architecture\" width=\"320\"/>\n\nUffizzi consists of the following components:\n\n* Uffizzi App (this repository) - The primary REST API for creating and managing Previews\n* [Uffizzi Controller](https://github.com/UffizziCloud/uffizzi_controller) - A smart proxy service that handles requests from Uffizzi App to the Kubernetes API\n* [Uffizzi CLI](https://github.com/UffizziCloud/uffizzi_cli) - A command-line interface for Uffizzi App\n\nUffizzi App requires the following external dependencies:\n\n * Kubernetes (k8s) cluster\n * PostgreSQL database\n * Redis cache\n\n## Controller Design\n\nThis `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.\n\n## Uffizzi App Environment Variables\n\n- `RAILS_SECRET_KEY_BASE` - secret_key_base of Rails::Application\n- `DATABASE_HOST` - the database hostname (default: 127.0.0.1)\n- `DATABASE_USER` - the database username (default: postgres)\n- `DATABASE_PASSWORD` - the database password\n- `DATABASE_PORT` - the database port (default: 5432)\n- `BUNDLE_PATH` - the location of gems for `bundle install` command (not required)\n- `GEM_PATH` - the location where gems can be found (not required)\n- `GEM_HOME` - where gems will be installed (not required)\n- `RAILS_WORKERS_COUNT` - the number of `puma` workers (default: 18)\n- `RAILS_THREADS_COUNT` - the number of `puma` threads (default: 5)\n- `RAILS_PORT` - the `puma` port (default: 7000)\n- `RAILS_ENV` - the rails environment (default: development)\n- `SIDEKIQ_CONCURRENCY` - sidekiq concurrency (default: 5)\n- `ALLOWED_HOSTS` - allowed hosts for rails app used for Rack::Cors (default: [])\n- `APP_URL` - URL of the application\n- `REDIS_URL` - URL of a Redis server\n- `CONTROLLER_URL` - URL of the controller application (default: http://controller:8080)\n- `CONTROLLER_LOGIN` - the login of the controller application (default: '')\n- `CONTROLLER_PASSWORD` - the password of the controller application (default: '')\n\n# Test Uffizzi App Locally\n\nIf 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.\n\n## Prepare\n\n```bash\ndocker-compose run --rm web bash -c \"bundle install && bundle exec rails db:setup\"\ndocker-compose up\n```\n\n## Create a new user\n\nRun the following command and follow instructions:\n\n```bash\ndocker-compose run --rm web bash -c \"rake uffizzi_core:create_user\"\n```\n\nor run the command with environment variables:\n\n- `UFFIZZI_USER_EMAIL` - user's email\n- `UFFIZZI_USER_PASSWORD` - user's password\n- `UFFIZZI_PROJECT_NAME` - user's project name\n\n```bash\ndocker-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\"\n```\n\n## Connect from uffizzi-cli to the app\n\n```bash\ndocker-compose run --rm gem bash\nbundle exec uffizzi login --hostname http://web:7000 -u admin@uffizzi.com\n```\n\n## API Documentation\n\n* [Development](http://lvh.me:7000/api-docs/index.html)\n\nRebuild documentation locally:\n\n```bash\ndocker-compose run --rm core bash\nbundle exec rake core:generate_docs\n```\n\n# Health checks\n\nThe 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\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [2021] [Uffizzi]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: release release_patch release_minor release_major test\n\nNEXT_PATCH=$(shell docker compose run --rm core bash -c \"bundle exec bump show-next patch\")\nNEXT_MINOR=$(shell docker compose run --rm core bash -c \"bundle exec bump show-next minor\")\nNEXT_MAJOR=$(shell docker compose run --rm core bash -c \"bundle exec bump show-next major\")\n\nrelease_patch: export VERSION=${NEXT_PATCH}\nrelease_patch:\n\tmake release\n\nrelease_minor: export VERSION=${NEXT_MINOR}\nrelease_minor:\n\tmake release\n\nrelease_major: export VERSION=${NEXT_MAJOR}\nrelease_major:\n\tmake release\n\nrelease:\n\tgit checkout develop\n\t@echo 'Set a new version'\n\tdocker compose run --rm core bash -c \"bundle exec bump set ${VERSION}\"\n\tdocker compose run --rm core bash -c \"bundle update uffizzi_core --conservative \"\n\tdocker compose run --rm web bash -c \"bundle update uffizzi_core --conservative \"\n\tgit commit -am \"Change version to ${VERSION}\"\n\t@echo 'Update remote origin'\n\tgit push origin develop\n\tgit checkout main\n\tgit pull origin --rebase main\n\tgit merge --no-ff --no-edit develop\n\tgit push origin main\n\t@echo 'Create a new tag'\n\tgit tag core_v${VERSION}\n\tgit push origin core_v${VERSION}\n\ntest:\n\tdocker compose run --rm core bash -c \"bundle exec rails test\"\n\nlint:\n\tdocker compose run --rm web bash -c \"bundle exec rubocop -A\"\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\" style=\"border-bottom: none\">\n  <h1>\n    <div>\n        <a href=\"https://www.uffizzi.com\">\n            <img src=\"misc/uffizzi-icon.png\" width=\"80\" />\n            <br>\n            Uffizzi\n        </a>\n    </div>\n    Environments-as-a-Service <br>\n    <a href=\"https://opensource.org/licenses/Apache-2.0\">\n        <img src=\"https://img.shields.io/badge/License-Apache%202.0-blue.svg\">\n    </a>\n  </h1>\n    <p align=\"center\">\n        <a href=\"http://www.uffizzi.com\"><b>Website</b></a> •\n        <a href=\"https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A\"><b>Slack</b></a> •\n        <a href=\"https://uffizzi.com/blog\"><b>Blog</b></a> •\n        <a href=\"https://twitter.com/_Uffizzi\"><b>Twitter</b></a> •\n        <a href=\"https://docs.uffizzi.com/\"><b>Documentation</b></a>\n    </p>\n</div align=\"center\" style=\"border-bottom: none\">\n\nUffizzi 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.\n\nUffizzi 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.  \n&nbsp;  \n&nbsp;  \n\n<hr>\n\n<h3 align=\"center\" style=\"border-bottom: none\">\n <div>\n   Trusted by top teams\n </div>  \n</h3>\n   <p align=\"center\">\n    <a href=\"https://backstage.spotify.com\"><b>Backstage</b></a> •\n    <a href=\"https://www.nocodb.com\"><b>NocoDB</b></a> •\n    <a href=\"https://www.forem.com\"><b>Forem</b></a> •\n    <a href=\"https://github.com/jesseduffield/lazygit\"><b>Lazygit</b></a> •\n    <a href=\"https://d2iq.com\"><b>D2IQ</b></a> •\n    <a href=\"https://github.com/parse-community/parse-dashboard\"><b>ParseDashboard</b></a> •\n    <a href=\"https://fonoster.com/\"><b>Fonoster</b></a>\n   </p>\n\n  <p align=\"center\">\n    <a href=\"https://answer.dev/\"><b>Answer</b></a> •\n    <a href=\"https://www.windmill.dev/\"><b>Windmill</b></a> •\n    <a href=\"https://flagsmith.com/\"><b>Flagsmith</b></a> •\n    <a href=\"https://usememos.com/\"><b>Memos</b></a> •\n    <a href=\"https://craterapp.com/\"><b>Crater</b></a> •\n    <a href=\"https://livebook.dev/\"><b>Livebook</b></a> •\n    <a href=\"https://online-go.com/\"><b>OnlineGo</b></a> •\n    <a href=\"https://boxyhq.com/\"><b>BoxyHQ</b></a>\n  </p>\n\n&nbsp;\nTeams 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)\n\n<hr>\n\n![github-banner](https://user-images.githubusercontent.com/7218230/191119628-4d39c65d-465f-4011-9370-d53d7b54d8cc.png)\n\n\n## Quickstart (~2 minute)\n\nGo to the [Quickstart Guide](https://docs.uffizzi.com/quickstart) to get started creating ephemeral environments.\n\n## How it works\nSpin 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.  \n\nUffizzi's modular design works with GitHub, GitLab, BitBucket, and any CI provider.\n\n<img width=\"600\" alt=\"preview-url\" src=\"https://user-images.githubusercontent.com/7218230/194924634-391aff82-8adf-473b-800e-a20dcdab82dd.png\">\n\n## Give us a star ⭐️\nIf you're interested in Uffizzi, give us a star. It helps others discover the project.\n\n## Use cases\n\nUffizzi is designed to integrate with any CI platform as a step in your pipeline. You can use Uffizzi to rapidly create:  \n\n- Cloud dev environments with hot reloading of deployed services\n- On-demand test environments for Kubernetes applications\n- Pull request environments  \n- Debugging environments  \n- Hotfix environments  \n- Demo environments  \n- Release environments\n- Staging environments  \n\n## What types of apps are supported by Uffizzi?\n\nUffizzi 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.\n\n## Why Uffizzi?\n\nUffizzi 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.\n\nUffizzi 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).\n\n## Set up ephemeral environments for your application\n\n(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.)  \n\nThere are three options to get Uffizzi:  \n\n1. **[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. \n2. **[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.   \n3. **[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).\n\n## Documentation\n\n- [Main documentation](https://docs.uffizzi.com)\n- [Docker Compose for Uffizzi ](https://docs.uffizzi.com/compose)\n- [Quickstart guide](https://docs.uffizzi.com/quickstart)\n\n## Community\n\n- [Slack channel](https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A) - Get support or discuss the project  \n- [Subscribe to our newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7011448505391042560) - Receive monthly updates about new features and special events  \n- [Contributing to Uffizzi](CONTRIBUTING.md) - Start here if you want to contribute\n- [Code of Conduct](CODE_OF_CONDUCT.md) - Let's keep it professional\n\n## FAQs\n\n<details><summary><b>My team tests locally. Why do I need Ephemeral Environments?</b></summary>\n<ol>\n  <li>Ephemeral Environments <a href=\"https://docs.uffizzi.com/core-concepts/production-like\">more closely resemble production</a>. 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.</li>\n  <li>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.</li>\n  <li>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.</li>\n</ol>\n</details>\n\n<details><summary><b>How is Uffizzi different from Codespaces, Gitpod, etc.?</b></summary>\n<p>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.</p>\n\n<p>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.</p>\n\nSee <a href=\"https://docs.uffizzi.com\">our documentation</a> for other common uses and guides.\n\n</details>\n\n<details><summary><b>How is Uffizzi different from GitHub Actions (or other CI providers)?</b></summary>\nUffizzi 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 <a href=\"https://docs.uffizzi.com/ci\">CI Recipes</a> for configuration help.\n</details>\n\n<details><summary><b>What about my database?</b></summary>\n<p>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 <i>image</i> in your configuration (See our <a href=\"https://docs.uffizzi.com/handbook/database-seeding\">Ephemeral Environments Handbook</a> for strategies on managing stateful services on Uffizzi.</p>\n</details>\n\n<details><summary><b>What do you mean by \"environments\"?</b></summary>\nSee <a href=\"https://docs.uffizzi.com/core-concepts/ephemeral-environments\">our documentaion</a> for what we mean.\n</details>\n\n<details><summary><b>Does Uffizzi support monorepos/polyrepos?</b></summary>\nYes. 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 <a href=\"https://docs.uffizzi.com/compose/reference\">Uffizzi Compose reference</a> for details.\n</details>\n\n<details><summary><b>Does Uffizzi support _____________?</b></summary>\nIn 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.  \n</details>\n\n<details><summary><b>How can my application services communicate?</b></summary>\nSee <a href=\"https://docs.uffizzi.com/architecture/networking\">Uffizzi Networking</a> for details.\n</details>\n\n<details><summary><b>Is Uffizzi open source?</b></summary>\nYes. If you have access to a Kubernetes cluster, you can install Uffizzi via Helm. Follow the <a href=\"INSTALL.md\">self-hosted installation guide</a>.\n</details>\n\n## License\n\nThis library is licensed under the [Apache License, Version 2.0](LICENSE).\n\n## Security\n\nIf 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`.\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative 'config/application'\n\nRails.application.load_tasks\n"
  },
  {
    "path": "app/assets/config/manifest.js",
    "content": "//= link_tree ../images\n//= link_directory ../stylesheets .css\n"
  },
  {
    "path": "app/assets/fonts/mtiFontTrackingCode.js",
    "content": "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, {}))"
  },
  {
    "path": "app/assets/images/.keep",
    "content": ""
  },
  {
    "path": "app/assets/stylesheets/application.css",
    "content": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's\n * vendor/assets/stylesheets directory can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the bottom of the\n * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS\n * files in this directory. Styles in this file should be added after the last require_* statement.\n * It is generally better to create a new file per style scope.\n *\n *= require_tree .\n *= require_self\n */\n"
  },
  {
    "path": "app/channels/application_cable/channel.rb",
    "content": "# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Channel < ActionCable::Channel::Base\n  end\nend\n"
  },
  {
    "path": "app/channels/application_cable/connection.rb",
    "content": "# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Connection < ActionCable::Connection::Base\n  end\nend\n"
  },
  {
    "path": "app/controllers/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationController < ActionController::Base\nend\n"
  },
  {
    "path": "app/controllers/concerns/.keep",
    "content": ""
  },
  {
    "path": "app/helpers/application_helper.rb",
    "content": "# frozen_string_literal: true\n\nmodule ApplicationHelper\nend\n"
  },
  {
    "path": "app/javascript/channels/consumer.js",
    "content": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocket features live using the `rails generate channel` command.\n\nimport { createConsumer } from \"@rails/actioncable\"\n\nexport default createConsumer()\n"
  },
  {
    "path": "app/javascript/channels/index.js",
    "content": "// Load all the channels within this directory and all subdirectories.\n// Channel files must be named *_channel.js.\n\nconst channels = require.context('.', true, /_channel\\.js$/)\nchannels.keys().forEach(channels)\n"
  },
  {
    "path": "app/javascript/packs/application.js",
    "content": "// This file is automatically compiled by Webpack, along with any other files\n// present in this directory. You're encouraged to place your actual application logic in\n// a relevant structure within app/javascript and only use these pack files to reference\n// that code so it'll be compiled.\n\nrequire(\"@rails/ujs\").start()\nrequire(\"turbolinks\").start()\nrequire(\"@rails/activestorage\").start()\nrequire(\"channels\")\n\n\n// Uncomment to copy all static images under ../images to the output folder and reference\n// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)\n// or the `imagePath` JavaScript helper below.\n//\n// const images = require.context('../images', true)\n// const imagePath = (name) => images(name, true)\n"
  },
  {
    "path": "app/jobs/application_job.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecord::Deadlocked\n\n  # Most jobs are safe to ignore if the underlying records are no longer available\n  # discard_on ActiveJob::DeserializationError\nend\n"
  },
  {
    "path": "app/mailers/application_mailer.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationMailer < ActionMailer::Base\n  default from: 'from@example.com'\n  layout 'mailer'\nend\n"
  },
  {
    "path": "app/models/application_record.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationRecord < ActiveRecord::Base\n  self.abstract_class = true\nend\n"
  },
  {
    "path": "app/models/concerns/.keep",
    "content": ""
  },
  {
    "path": "app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Uffizzi App</title>\n    <%= csrf_meta_tags %>\n    <%= csp_meta_tag %>\n\n    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>\n    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <style>\n      /* Email styles need to be inline */\n    </style>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "bin/rails",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nAPP_PATH = File.expand_path('../config/application', __dir__)\nrequire_relative '../config/boot'\nrequire 'rails/commands'\n"
  },
  {
    "path": "bin/rake",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire_relative '../config/boot'\nrequire 'rake'\nRake.application.run\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'fileutils'\n\n# path to your application root.\nAPP_ROOT = File.expand_path('..', __dir__)\n\ndef system!(*args)\n  system(*args) || abort(\"\\n== Command #{args} failed ==\")\nend\n\nFileUtils.chdir(APP_ROOT) do\n  # This script is a way to setup or update your development environment automatically.\n  # This script is idempotent, so that you can run it at anytime and get an expectable outcome.\n  # Add necessary setup steps to this file.\n\n  puts '== Installing dependencies =='\n  system! 'gem install bundler --conservative'\n  system('bundle check') || system!('bundle install')\n\n  # Install JavaScript dependencies\n  # system('bin/yarn')\n\n  # puts \"\\n== Copying sample files ==\"\n  # unless File.exist?('config/database.yml')\n  #   FileUtils.cp 'config/database.yml.sample', 'config/database.yml'\n  # end\n\n  puts \"\\n== Preparing database ==\"\n  system! 'bin/rails db:prepare'\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! 'bin/rails log:clear tmp:clear'\n\n  puts \"\\n== Restarting application server ==\"\n  system! 'bin/rails restart'\nend\n"
  },
  {
    "path": "bin/yarn",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nAPP_ROOT = File.expand_path('..', __dir__)\nDir.chdir(APP_ROOT) do\n  exec('yarnpkg', *ARGV)\nrescue Errno::ENOENT\n  warn('Yarn executable was not detected in the system.')\n  warn('Download Yarn at https://yarnpkg.com/en/docs/install')\n  exit(1)\nend\n"
  },
  {
    "path": "charts/uffizzi-app/Chart.yaml",
    "content": "apiVersion: v2\nname: uffizzi-app\nversion: 1.3.0\nkubeVersion: \">= 1.21.0-0\" # https://issuetracker.google.com/issues/77503699\ndescription: \"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.\"\ntype: application\nkeywords:\n  - devops\n  - uffizzi\n  - continuous-previews\n  - ephemeral\n  - environments\n  - pull-request\n  - merge-request\n  - on-demand\n  - ci\n  - cd\n  - cp\n  - idp\nhome: https://uffizzi.com/\nsources:\n  - https://github.com/UffizziCloud/uffizzi\ndependencies:\n  - name: uffizzi-controller\n    version: \"^2\"\n    repository: https://uffizzicloud.github.io/uffizzi_controller/\n  - name: postgresql\n    version: \"~13\"\n    repository: https://charts.bitnami.com/bitnami\n    condition: postgresql.enabled\n  - name: redis\n    version: \"~18\"\n    repository: https://charts.bitnami.com/bitnami\n    condition: redis.enabled\nmaintainers:\n  - name: Uffizzi\n    email: info@uffizzi.com\n    url: https://uffizzi.com\n  - name: Adam Vollrath\n    email: adam.vollrath@uffizzi.com\n    url: https://github.com/axisofentropy\nicon: https://app.uffizzi.com/favicon.png\nappVersion: \"2.3.0\"\ndeprecated: false\nannotations:\n  # Use this annotation to indicate that this chart version is a pre-release.\n  # https://artifacthub.io/docs/topics/annotations/helm/\n  artifacthub.io/prerelease: \"false\"\n"
  },
  {
    "path": "charts/uffizzi-app/README.md",
    "content": "# Helm chart guide\n\nThis chart installs Uffizzi.\n\n## Requirements\n\nThis chart requires a Kubernetes Cluster. While it will likely function on k8s >= 1.19, we have only tested upon k8s 1.21 - 1.23.\n\nThe Cluster must be capable of provisioning `Ingress` resources that obtain public IP addresses and/or hostnames.\n\nWe've tested Uffizzi on:\n\n- Google Kubernetes Engine (GKE)\n- Azure Kubernetes Service (AKS)\n- Amazon Elastic Kubernetes Service (EKS)\n\n### Dependencies\n\nThis chart depends upon three subcharts:\n\n- [`bitnami/postgresql`](https://artifacthub.io/packages/helm/bitnami/postgresql)\n- [`bitnami/redis`](https://artifacthub.io/packages/helm/bitnami/redis)\n- [`uffizzi-controller`](https://artifacthub.io/packages/helm/uffizzi-controller/uffizzi-controller)\n\nYou can disable the `bitnami` subcharts if you want to manage your own datastores.\n\n## Configuration\n\nThis 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/\n\n### Controller\n\nSee the [Controller's Helm Chart](https://artifacthub.io/packages/helm/uffizzi-controller/uffizzi-controller) for its configuration, including its certificate authority.\n\nThe controller itself depends upon two other popular Helm charts:\n\n- [`ingress-nginx`](https://kubernetes.github.io/ingress-nginx/)\n- [`cert-manager`](https://cert-manager.io/docs/)\n\nIf 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.\n\n### Secrets\n\nWhen 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.\n\n### Example Helm Values File\nExample values file with required values:\n\n```yaml\nglobal:\n  postgresql:\n    auth:\n      postgresPassword: ChangeMeNow\n      password: ChangeMeNow\n  redis:\n    password: ChangeMeNow\n  uffizzi:\n    firstUser:\n      email: user@example.com\n      password: ChangeMeNow\n    controller:\n      password: ChangeMeNow\napp_url: https://uffizzi.example.com\nwebHostname: uffizzi.example.com\nallowed_hosts: uffizzi.example.com\nmanaged_dns_zone_dns_name: uffizzi.example.com\nuffizzi-controller:\n  ingress:\n    hostname: controller.uffizzi.example.com\n  clusterIssuer: \"letsencrypt\"\n  certEmail: admin@example.com\n```\n\nEdit these values and save them in a file named `myvals.yaml` or similar.\n\n## Installation\n\nIf this is your first time using Helm, consult their documentation: https://helm.sh/docs/intro/quickstart/\n\nBegin by adding our Helm repository:\n\n```\nhelm repo add uffizzi https://uffizzicloud.github.io/uffizzi/\n```\n\nThen install the lastest version as a new release using the values you specified earlier. We recommend isolating Uffizzi in its own Namespace.\n\n```\nhelm install my-uffizzi uffizzi/uffizzi-app --values myvals.yaml --namespace uffizzi --create-namespace\n```\n\nIf 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).\n\nYou should then see the release is installed:\n```\nhelm list --namespace uffizzi\n```\n\n### DNS\n\nAfter 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`:\n\n```\nkubectl get ingress --namespace uffizzi\n```\n\nBe 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`.\n\n### Provisioning users\n\nYou'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.\n\nIf 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:\n\n```\nkubectl exec -it deploy/my-uffizzi-web --namespace uffizzi -- rake uffizzi_core:create_user\nEnter User Email (default: user@example.com): user@example.com\nEnter Password:\nEnter Project Name (default: default):\n```\n\n### Troubleshooting\n\nWhen installing this chart, you may see errors like this:\n```\nclusterroles.rbac.authorization.k8s.io \"my-uffizzi-controller-flux-default-source-controller-helmchart\" already exists\n```\n\nThis 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.\n\n## Usage\n\nIf 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.\n\n## More Info\n\nSee this project's main repository here: https://github.com/UffizziCloud/uffizzi\n\nAnd explore Uffizzi https://uffizzi.com\n"
  },
  {
    "path": "charts/uffizzi-app/templates/configmap-common.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: uffizzi-web-common-envs\ndata:\n  DATABASE_POOL: \"16\"\n  DATABASE_PORT: {{ .Values.global.postgresql.service.ports.postgresql | quote }}\n  DATABASE_HOST: {{ default (print .Release.Name \"-postgresql\") .Values.db_host | quote }}\n  RAILS_ENV: {{ .Values.env | quote }}\n  APP_URL: {{ .Values.app_url }}\n  CONTROLLER_URL: {{ default (print \"http://\" .Release.Name \"-controller:8080\") .Values.controller_url }}\n  EMAIL_DELIVERY_ENABLED: {{ .Values.feature_email_delivery | quote }}\n  MANAGED_DNS_ZONE_DNS_NAME: {{ .Values.managed_dns_zone_dns_name | quote }}\n  UFFIZZI_USER_EMAIL: {{ .Values.global.uffizzi.firstUser.email }}\n  UFFIZZI_USER_PASSWORD: {{ .Values.global.uffizzi.firstUser.password | quote }}\n  UFFIZZI_PROJECT_NAME: {{ .Values.global.uffizzi.firstUser.projectName | quote }}\n"
  },
  {
    "path": "charts/uffizzi-app/templates/configmap-sidekiq.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: uffizzi-web-sidekiq-envs\n\ndata:\n  SIDEKIQ_CONCURRENCY: \"10\"\n"
  },
  {
    "path": "charts/uffizzi-app/templates/configmap-web.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: uffizzi-web-service-envs\n\ndata:\n  ALLOWED_HOSTS: {{ .Values.allowed_hosts | quote }}\n  RAILS_PORT: \"7000\"\n  RAILS_SERVE_STATIC_FILES: \"true\"\n  RAILS_THREADS_COUNT: \"8\"\n  RAILS_WORKERS_COUNT: \"2\"\n"
  },
  {
    "path": "charts/uffizzi-app/templates/secret-web.yaml",
    "content": "apiVersion: v1\nkind: Secret\nmetadata:\n  name: uffizzi-web-secret-envs\ntype: Opaque\ndata:\n    DATABASE_USER: {{ .Values.global.postgresql.auth.username | b64enc }}\n    DATABASE_PASSWORD: {{ .Values.global.postgresql.auth.password | b64enc }}\n    DATABASE_NAME: {{ .Values.global.postgresql.auth.database | b64enc }}\n    RAILS_SECRET_KEY_BASE: {{ .Values.rails_secret_key_base | default (printf \"%x\" (randAscii 64))| b64enc }}\n    REDIS_URL: {{ default (print \"redis://:\" .Values.global.redis.password \"@\" .Release.Name \"-redis-master\") .Values.redis_url | b64enc }}\n    APP_LOGIN: {{ .Values.basic_auth_login | b64enc }}\n    APP_PASSWORD: {{ .Values.basic_auth_password | b64enc }}\n    SIDEKIQ_LOGIN: {{ .Values.basic_auth_login | b64enc }}\n    SIDEKIQ_PASSWORD: {{ .Values.basic_auth_password | b64enc }}\n    CONTROLLER_LOGIN: {{ .Values.global.uffizzi.controller.username | b64enc }}\n    CONTROLLER_PASSWORD: {{ .Values.global.uffizzi.controller.password | b64enc }}\n    GITHUB_APP_ID: {{ .Values.github_app_id | b64enc }}\n    GITHUB_APP_SLUG: {{ .Values.github_app_slug | b64enc }}\n    GITHUB_CLIENT_ID: {{ .Values.github_client_id | b64enc }}\n    GITHUB_CLIENT_SECRET: {{ .Values.github_client_secret | b64enc }}\n    GITHUB_PRIVATE_KEY: {{ .Values.github_private_key | b64enc }}\n    GITHUB_WEBHOOK_SECRET: {{ .Values.github_webhook_secret | b64enc }}\n"
  },
  {
    "path": "charts/uffizzi-app/templates/sidekiq-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Release.Name }}-sidekiq\n  labels:\n    app: uffizzi-sidekiq\n\nspec:\n  replicas: {{ .Values.sidekiq_replicas }}\n  selector:\n    matchLabels:\n      app: uffizzi-sidekiq\n\n  template:\n    metadata:\n      labels:\n        app: uffizzi-sidekiq\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - name: uffizzi-sidekiq\n        image: {{ .Values.image }}\n        command:\n        - /bin/bash\n        - -c\n        args:\n        - bundle exec sidekiq -C /app/config/sidekiq.yml\n        envFrom:\n        - secretRef:\n            name: uffizzi-web-secret-envs\n            optional: false\n        - configMapRef:\n            name: uffizzi-web-common-envs\n            optional: false\n        - configMapRef:\n            name: uffizzi-web-sidekiq-envs\n            optional: false\n        imagePullPolicy: Always\n        resources:\n          requests:\n            cpu: 400m\n            memory: 800Mi\n"
  },
  {
    "path": "charts/uffizzi-app/templates/web-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Release.Name }}-web\n  labels:\n    app: uffizzi-web\n\nspec:\n  replicas: {{ .Values.web_replicas }}\n  selector:\n    matchLabels:\n      app: uffizzi-web\n\n  template:\n    metadata:\n      labels:\n        app: uffizzi-web\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - name: uffizzi-web\n        image: {{ .Values.image }}\n        command:\n        - /bin/bash\n        - -c\n        args:\n        - bundle exec rails db:create db:migrate && bundle exec rake uffizzi_core:create_user && bundle exec puma -C config/puma.rb\n        envFrom:\n        - secretRef:\n            name: uffizzi-web-secret-envs\n            optional: false\n        - configMapRef:\n            name: uffizzi-web-common-envs\n            optional: false\n        - configMapRef:\n            name: uffizzi-web-service-envs\n            optional: false\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 7000\n          protocol: TCP\n#        readinessProbe:\n#          failureThreshold: 3\n#          httpGet:\n#            path: /health_check\n#            port: 7000\n#            scheme: HTTP\n#          periodSeconds: 10\n#          successThreshold: 1\n#          timeoutSeconds: 1\n        resources:\n          requests:\n            cpu: 150m\n            memory: 800Mi\n"
  },
  {
    "path": "charts/uffizzi-app/templates/web-ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: {{ .Release.Name }}-web-ingress\n  annotations:\n    cert-manager.io/cluster-issuer: {{ print .Release.Name \"-\" (index (.Values) \"uffizzi-controller\" \"clusterIssuer\") }}\n    kubernetes.io/ingress.class: nginx\nspec:\n  rules:\n  - host: {{ .Values.webHostname }}\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: {{ .Release.Name }}-web-service\n            port:\n              number: 80\n  tls:\n  - secretName: {{ .Values.webHostname }}\n    hosts:\n    - {{ .Values.webHostname }}\n"
  },
  {
    "path": "charts/uffizzi-app/templates/web-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Release.Name }}-web-service\n\nspec:\n  type: NodePort\n\n  selector:\n    app: uffizzi-web\n\n  ports:\n  - name: http\n    port: 80\n    protocol: TCP\n    targetPort: 7000\n\n  sessionAffinity: ClientIP\n"
  },
  {
    "path": "charts/uffizzi-app/values.yaml",
    "content": "global:\n  postgresql:\n    auth:\n      postgresPassword: ChangeMeNow\n      username: uffizzi-user\n      password: ChangeMeNow\n      database: uffizzi-app\n    service:\n      ports:\n        postgresql: \"5432\"\n  redis:\n    password: ChangeMeNow\n  uffizzi:\n    firstUser:\n      email: \"\"\n      password: \"\"\n      projectName: default\n    controller:\n      username: username\n      password: ChangeMeNow\n\nenv: production\napp_url: https://uffizzi.example.com\nwebHostname: uffizzi.example.com\nimage: uffizzi/app\nweb-replicas: 3\nsidekiq-replicas: 1\nhostname: localhost\ncontroller_url: \"\"\nallowed_hosts: \"\"\ndb_host: \"\" # default to dependent postgresql\nrails_secret_key_base: \"\" # default to half-random string\nredis_url: \"\" # default to dependent redis\nmanaged_dns_zone_dns_name: uffizzi.example.com\nbasic_auth_login: username\nbasic_auth_password: ChangeMeNow\ngithub_app_id: ChangeMeNow\ngithub_app_slug: ChangeMeNow\ngithub_client_id: ChangeMeNow\ngithub_client_secret: ChangeMeNow\ngithub_private_key: ChangeMeNow\ngithub_webhook_secret: ChangeMeNow\nsql_credentials_secret_name: example\nfeature_mailchimp: false\nfeature_email_delivery: false\nfeature_google_maps: false\nfeature_sentry: false\nfeature_stripe: false\n\nuffizzi-controller: #dependency\n  clusterIssuer: \"letsencrypt\"\n  image: uffizzi/controller:latest\n  ingress:\n    hostname: uffizzi.example.com\n  podCidr: 10.0.0.0/8\n  zerossl:\n    eab:\n      hmacKey: foo\n      keyId: bar\n  env: \"production\"\n  sandbox: \"false\"\n  certEmail: \"user@example.com\"\n  cert-manager: # dependency of dependency\n    installCRDs: true\n\npostgresql: #dependency\n  enabled: true\n\nredis: # dependency\n  enabled: true\n"
  },
  {
    "path": "ci/github-actions/README.md",
    "content": "# Use Uffizzi with GitHub Actions\n\nYou 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.\n\n## Example usage\n\nThe following example application demonstrates how to use Uffizzi with GitHub Actions:\n\n[Example voting app](https://github.com/UffizziCloud/example-voting-app/blob/main/.github/workflows/uffizzi-previews.yml)\n\n"
  },
  {
    "path": "ci/gitlab/README.md",
    "content": "# Use Uffizzi with GitLab CI\n\nYou 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.\n\n"
  },
  {
    "path": "config/application.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'boot'\n\nrequire 'rails/all'\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nrequire 'uffizzi_core'\n\nmodule UffizziApp\n  class Application < Rails::Application\n    config.load_defaults(6.0)\n\n    config.hosts = Settings.allowed_hosts\n\n    config.middleware.insert_before(0, Rack::Cors) do\n      allow do\n        origins do |source|\n          uri = URI.parse(source)\n          Settings.allowed_hosts.any? { |host| uri.host == host || uri.host.ends_with?(host) }\n        end\n        resource '*', headers: :any, methods: [:get, :post, :options, :put, :patch, :delete], credentials: true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "config/boot.rb",
    "content": "# frozen_string_literal: true\n\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)\n\nrequire 'bundler/setup' # Set up gems listed in the Gemfile.\nrequire 'bootsnap/setup' # Speed up boot time by caching expensive operations.\n"
  },
  {
    "path": "config/cable.yml",
    "content": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n  channel_prefix: uffizzi_app_production\n"
  },
  {
    "path": "config/credentials.yml.enc",
    "content": "/panGyOWYw7RDiAdnBU58UQcCf5LmUrtROWE1pQpIAlZ1BUUsCat6oE8MVO5TDTc4q0XOGjndQ28i+ffjpHCob1TPHlTwGmAWcaKawSOQeLPOgHHvtzumHfS5erwYLMn6DgKyUNqobtk0+0PFTiwY5Cpbukri+gwb7pc8rjweYSRilreS++X/pf7zm606NvRiIKgoVMciie25CVR9g1ll1jgjE/L750rrROrGsobLuMp6XEBdzjqI1O2P9fiyCFND3a2g4kq3ARnK7b/T2yk3NG31UABfSkNoGJk72vyxW+gottHKe1RhZ4kYWQvegycS1FrxSj2bEXcdjnWzOxs1lCzJgj2ImaYm/sN7pyDZ1zXiyYYqWpRWDbGtER0IOUVUg1bAxDK8BfFFj8kX3W4en8VkmmVcJWv7lDX--f9oik+xRebXs6ijD--rZd8unbXjXvCf6ar117b4Q=="
  },
  {
    "path": "config/database.yml",
    "content": "default: &default\n  adapter: postgresql\n  encoding: unicode\n  host: <%= ENV.fetch(\"DATABASE_HOST\") {\"127.0.0.1\"} %>\n  port: <%= ENV.fetch(\"DATABASE_PORT\") {5432} %>\n  pool: <%= ENV.fetch(\"DATABASE_POOL\") {5} %>\n  username: <%= ENV.fetch(\"DATABASE_USER\") {\"postgres\"} %>\n  password: <%= ENV.fetch(\"DATABASE_PASSWORD\") {\"\"} %>\n\ndevelopment:\n  <<: *default\n  database: uffizzi_development\n\ntest:\n  <<: *default\n  database: uffizzi_test\n\nproduction:\n  <<: *default\n  database: uffizzi_production\n"
  },
  {
    "path": "config/environment.rb",
    "content": "# frozen_string_literal: true\n\n# Load the Rails application.\nrequire_relative 'application'\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "config/environments/development.rb",
    "content": "# frozen_string_literal: true\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # In the development environment your application's code is reloaded on\n  # every request. This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.cache_classes = false\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable/disable caching. By default caching is disabled.\n  # Run rails dev:cache to toggle caching.\n  if Rails.root.join('tmp', 'caching-dev.txt').exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n\n    config.cache_store = :memory_store\n    config.public_file_server.headers = {\n      'Cache-Control' => \"public, max-age=#{2.days.to_i}\",\n    }\n  else\n    config.action_controller.perform_caching = false\n\n    config.cache_store = :null_store\n  end\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.raise_delivery_errors = false\n\n  config.action_mailer.perform_caching = false\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  config.logger = Logger.new($stdout)\n  config.log_level = :debug\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Debug mode disables concatenation and preprocessing of assets.\n  # This option may cause significant delays in view rendering with a large\n  # number of complex assets.\n  config.assets.debug = true\n\n  # Suppress logger output for asset requests.\n  config.assets.quiet = true\n\n  # Raises error for missing translations.\n  # config.action_view.raise_on_missing_translations = true\n\n  # Use an evented file watcher to asynchronously detect changes in source code,\n  # routes, locales, etc. This feature depends on the listen gem.\n  config.file_watcher = ActiveSupport::FileUpdateChecker\nend\n"
  },
  {
    "path": "config/environments/production.rb",
    "content": "# frozen_string_literal: true\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.cache_classes = true\n\n  # Eager load code on boot. This eager loads most of Rails and\n  # your application in memory, allowing both threaded web servers\n  # and those relying on copy on write to perform better.\n  # Rake tasks automatically ignore this option for performance.\n  config.eager_load = true\n\n  # Full error reports are disabled and caching is turned on.\n  config.consider_all_requests_local       = false\n  config.action_controller.perform_caching = true\n\n  # Ensures that a master key has been made available in either ENV[\"RAILS_MASTER_KEY\"]\n  # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).\n  # config.require_master_key = true\n\n  # Disable serving static files from the `/public` folder by default since\n  # Apache or NGINX already handles this.\n  config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?\n\n  # Compress CSS using a preprocessor.\n  # config.assets.css_compressor = :sass\n\n  # Do not fallback to assets pipeline if a precompiled asset is missed.\n  config.assets.compile = false\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.action_controller.asset_host = 'http://assets.example.com'\n\n  # Specifies the header that your server uses for sending files.\n  # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache\n  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Mount Action Cable outside main process or domain.\n  # config.action_cable.mount_path = nil\n  # config.action_cable.url = 'wss://example.com/cable'\n  # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\\/\\/example.*/ ]\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  # config.force_ssl = true\n\n  # Use the lowest log level to ensure availability of diagnostic information\n  # when problems arise.\n  config.log_level = :debug\n\n  # Prepend all log lines with the following tags.\n  config.log_tags = [:request_id]\n\n  # Use a different cache store in production.\n  # config.cache_store = :mem_cache_store\n\n  # Use a real queuing backend for Active Job (and separate queues per environment).\n  # config.active_job.queue_adapter     = :resque\n  # config.active_job.queue_name_prefix = \"uffizzi_app_production\"\n\n  config.action_mailer.perform_caching = false\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Send deprecation notices to registered listeners.\n  config.active_support.deprecation = :notify\n\n  # Use default logging formatter so that PID and timestamp are not suppressed.\n  config.log_formatter = ::Logger::Formatter.new\n\n  # Use a different logger for distributed setups.\n  # require 'syslog/logger'\n  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')\n\n  if ENV['RAILS_LOG_TO_STDOUT'].present?\n    logger           = ActiveSupport::Logger.new($stdout)\n    logger.formatter = config.log_formatter\n    config.logger    = ActiveSupport::TaggedLogging.new(logger)\n  end\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Inserts middleware to perform automatic connection switching.\n  # The `database_selector` hash is used to pass options to the DatabaseSelector\n  # middleware. The `delay` is used to determine how long to wait after a write\n  # to send a subsequent read to the primary.\n  #\n  # The `database_resolver` class is used by the middleware to determine which\n  # database is appropriate to use based on the time delay.\n  #\n  # The `database_resolver_context` class is used by the middleware to set\n  # timestamps for the last write to the primary. The resolver uses the context\n  # class timestamps to determine how long to wait before reading from the\n  # replica.\n  #\n  # By default Rails will store a last write timestamp in the session. The\n  # DatabaseSelector middleware is designed as such you can define your own\n  # strategy for connection switching and pass that into the middleware through\n  # these configuration options.\n  # config.active_record.database_selector = { delay: 2.seconds }\n  # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver\n  # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session\nend\n"
  },
  {
    "path": "config/environments/test.rb",
    "content": "# frozen_string_literal: true\n\n# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  config.cache_classes = false\n  config.action_view.cache_template_loading = true\n\n  # Do not eager load code on boot. This avoids loading your whole application\n  # just for the purpose of running a single test. If you are using a tool that\n  # preloads Rails for running tests, you may have to set it to true.\n  config.eager_load = false\n\n  # Configure public file server for tests with Cache-Control for performance.\n  config.public_file_server.enabled = true\n  config.public_file_server.headers = {\n    'Cache-Control' => \"public, max-age=#{1.hour.to_i}\",\n  }\n\n  # Show full error reports and disable caching.\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n  config.cache_store = :null_store\n\n  # Raise exceptions instead of rendering exception templates.\n  config.action_dispatch.show_exceptions = false\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  config.action_mailer.perform_caching = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raises error for missing translations.\n  # config.action_view.raise_on_missing_translations = true\nend\n"
  },
  {
    "path": "config/initializers/application_controller_renderer.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# ActiveSupport::Reloader.to_prepare do\n#   ApplicationController.renderer.defaults.merge!(\n#     http_host: 'example.org',\n#     https: false\n#   )\n# end\n"
  },
  {
    "path": "config/initializers/assets.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\nRails.application.config.assets.version = '1.0'\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n# Add Yarn node_modules folder to the asset load path.\nRails.application.config.assets.paths << Rails.root.join('node_modules')\n\n# Precompile additional assets.\n# application.js, application.css, and all non-JS/CSS in the app/assets\n# folder are already added.\n# Rails.application.config.assets.precompile += %w( admin.js admin.css )\n"
  },
  {
    "path": "config/initializers/backtrace_silencers.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.\n# Rails.backtrace_cleaner.remove_silencers!\n"
  },
  {
    "path": "config/initializers/content_security_policy.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy\n# For further information see the following documentation\n# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n\n# Rails.application.config.content_security_policy do |policy|\n#   policy.default_src :self, :https\n#   policy.font_src    :self, :https, :data\n#   policy.img_src     :self, :https, :data\n#   policy.object_src  :none\n#   policy.script_src  :self, :https\n#   policy.style_src   :self, :https\n#   # If you are using webpack-dev-server then specify webpack-dev-server host\n#   policy.connect_src :self, :https, \"http://localhost:3035\", \"ws://localhost:3035\" if Rails.env.development?\n\n#   # Specify URI for violation reports\n#   # policy.report_uri \"/csp-violation-report-endpoint\"\n# end\n\n# If you are using UJS then enable automatic nonce generation\n# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }\n\n# Set the nonce only to specific directives\n# Rails.application.config.content_security_policy_nonce_directives = %w(script-src)\n\n# Report CSP violations to a specified URI\n# For further information see the following documentation:\n# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only\n# Rails.application.config.content_security_policy_report_only = true\n"
  },
  {
    "path": "config/initializers/cookies_serializer.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Specify a serializer for the signed and encrypted cookie jars.\n# Valid options are :json, :marshal, and :hybrid.\nRails.application.config.action_dispatch.cookies_serializer = :json\n"
  },
  {
    "path": "config/initializers/filter_parameter_logging.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Configure sensitive parameters which will be filtered from the log file.\nRails.application.config.filter_parameters += [:password]\n"
  },
  {
    "path": "config/initializers/health_check.rb",
    "content": "# frozen_string_literal: true\n\nHealthCheck.setup do |config|\n  config.uri = ENV['HEALTH_CHECK_URI'] || 'health_check'\n  config.success = 'success'\n  config.http_status_for_error_text = 500\n  config.http_status_for_error_object = 500\n  config.standard_checks = ['database', 'migrations']\n  config.full_checks = ['database', 'migrations']\nend\n"
  },
  {
    "path": "config/initializers/inflections.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, '\\1en'\n#   inflect.singular /^(ox)en/i, '\\1'\n#   inflect.irregular 'person', 'people'\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym 'RESTful'\n# end\n"
  },
  {
    "path": "config/initializers/mime_types.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\n"
  },
  {
    "path": "config/initializers/sidekiq.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'sidekiq/web'\nrequire 'sidekiq-unique-jobs'\n\nSidekiq.configure_client do |config|\n  config.redis = { url: ENV['REDIS_URL'], size: 2 }\n\n  config.client_middleware do |chain|\n    chain.add(SidekiqUniqueJobs::Middleware::Client)\n  end\nend\n\nSidekiq.configure_server do |config|\n  config.redis = { url: ENV['REDIS_URL'], size: 20 }\n\n  config.client_middleware do |chain|\n    chain.add(SidekiqUniqueJobs::Middleware::Client)\n  end\n\n  config.server_middleware do |chain|\n    chain.add(SidekiqUniqueJobs::Middleware::Server)\n  end\n\n  SidekiqUniqueJobs::Server.configure(config)\nend\n\nif Settings.sidekiq.login.present?\n  Sidekiq::Web.use(Rack::Auth::Basic) do |username, password|\n    valid_login = ActiveSupport::SecurityUtils.secure_compare(\n      ::Digest::SHA256.hexdigest(username),\n      ::Digest::SHA256.hexdigest(Settings.sidekiq.login),\n    )\n    valid_password = ActiveSupport::SecurityUtils.secure_compare(\n      ::Digest::SHA256.hexdigest(password),\n      ::Digest::SHA256.hexdigest(Settings.sidekiq.password),\n    )\n    valid_login & valid_password\n  end\nend\n\nSidekiqUniqueJobs.configure do |config|\n  config.enabled = !Rails.env.test?\nend\n"
  },
  {
    "path": "config/initializers/wrap_parameters.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsWrapper which\n# is enabled by default.\n\n# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.\nActiveSupport.on_load(:action_controller) do\n  wrap_parameters format: [:json]\nend\n\n# To enable root element in JSON for ActiveRecord objects.\n# ActiveSupport.on_load(:active_record) do\n#   self.include_root_in_json = true\n# end\n"
  },
  {
    "path": "config/locales/en.yml",
    "content": "# Files in the config/locales directory are used for internationalization\n# and are automatically loaded by Rails. If you want to use locales other\n# than English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t 'hello'\n#\n# In views, this is aliased to just `t`:\n#\n#     <%= t('hello') %>\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# The following keys must be escaped otherwise they will not be retrieved by\n# the default I18n backend:\n#\n# true, false, on, off, yes, no\n#\n# Instead, surround them with single quotes.\n#\n# en:\n#   'true': 'foo'\n#\n# To learn more, please read the Rails Internationalization guide\n# available at https://guides.rubyonrails.org/i18n.html.\n\nen:\n"
  },
  {
    "path": "config/puma.rb",
    "content": "# frozen_string_literal: true\n\nthreads_count = ENV.fetch('RAILS_THREADS_COUNT', 5)\nthreads threads_count, threads_count\n\nport ENV.fetch('RAILS_PORT', 7000)\nenvironment ENV.fetch('RAILS_ENV', 'development')\n\nworkers ENV.fetch('RAILS_WORKERS_COUNT', 18)\n\npreload_app! if ENV.fetch('RAILS_WORKERS_COUNT').to_i.positive?\n\nbefore_fork do\n  ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)\nend\n\non_worker_boot do\n  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)\nend\n"
  },
  {
    "path": "config/routes.rb",
    "content": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n  mount UffizziCore::Engine => '/'\n  health_check_routes\nend\n"
  },
  {
    "path": "config/secrets.yml",
    "content": "default: &default\n  secret_key_base: <%=Settings.rails.secret_key_base%>\n\ndevelopment:\n  <<: *default\n\ntest:\n  <<: *default\n\nproduction:\n  <<: *default\n"
  },
  {
    "path": "config/settings.yml",
    "content": "rails:\n  secret_key_base: <%= ENV['RAILS_SECRET_KEY_BASE'] %>\napp:\n  ttl_reset_password_token: 900\n  host: <%= ENV['APP_URL'] %>\n  login:  <%= ENV['APP_LOGIN'] || '' %>\n  password:  <%= ENV['APP_PASSWORD'] || '' %>\n  managed_dns_zone: <%= ENV['MANAGED_DNS_ZONE_DNS_NAME'] %>\ngithub:\n  app_id: <%= ENV['GITHUB_APP_ID'] %>\n  app_slug: <%= ENV['GITHUB_APP_SLUG'] %>\n  client_id: <%= ENV['GITHUB_CLIENT_ID'] %>\n  client_secret: <%= ENV['GITHUB_CLIENT_SECRET'] %>\n  private_key: \"<%= ENV['GITHUB_PRIVATE_KEY'] %>\"\n  webhook_secret: <%= ENV['GITHUB_WEBHOOK_SECRET'] %>\ndocker_hub:\n  registry_url: 'https://index.docker.io/v1/'\n  public_namespace: 'library'\nsidekiq:\n  login:  <%= ENV['SIDEKIQ_LOGIN'] || '' %>\n  password:  <%= ENV['SIDEKIQ_PASSWORD'] || '' %>\ncontroller:\n  url:  <%= ENV['CONTROLLER_URL'] || 'http://controller:8080' %>\n  login:  <%= ENV['CONTROLLER_LOGIN'] || '' %>\n  password:  <%= ENV['CONTROLLER_PASSWORD'] || '' %>\n  connection:\n    retires_count: 1\n    next_retry_timeout_seconds: 1\n    timeout: 7\n    open_timeout: 5\n  limits:\n    cpu: '200m'\n  namespace_prefix: 'app-'\n  resource_create_retry_time: <%= 15.seconds %>\n  resource_update_retry_count: 60\nvcluster_controller:\n  url:  <%= ENV['VCLUSTER_CONTROLLER_URL'] || 'http://controller:8080' %>\n  login:  <%= ENV['VCLUSTER_CONTROLLER_LOGIN'] || '' %>\n  password:  <%= ENV['VCLUSTER_CONTROLLER_PASSWORD'] || '' %>\n  managed_dns_zone: <%= ENV['VCLUSTER_MANAGED_DNS_ZONE_DNS_NAME'] %>\n  connection:\n    retires_count: 1\n    next_retry_timeout_seconds: 1\n    timeout: 15\n    open_timeout: 10\nbilling_cycle:\n  hours_count: 720\nslack:\n  invite_url: https://join.slack.com/t/uffizzi/shared_invite/zt-ffr4o3x0-J~0yVT6qgFV~wmGm19Ux9A\nallowed_hosts: <%= ENV['ALLOWED_HOSTS']&.split(',') || [] %>\ndomain: <%= ENV['APP_URL'] %>\nsentry:\n  dsn: <%= ENV['SENTRY_DSN'] %>\n  release: <%= ENV['SENTRY_RELEASE'] %>\n  env: <%= Rails.env %>\nfeatures:\n  email_delivery_enabled: <%= !!ActiveModel::Type::Boolean.new.cast(ENV['EMAIL_DELIVERY_ENABLED']) %>\n  stripe_enabled: <%= !!ActiveModel::Type::Boolean.new.cast(ENV['STRIPE_ENABLED']) %>\nplatform_cluster:\n  project_id: <%= ENV['PLATFORM_PROJECT_ID'] %>\ngoogle:\n  registry_url: 'https://gcr.io/'\ngithub_container_registry:\n  registry_url: 'https://ghcr.io/'\ncompose:\n  default_memory: 125\n  memory_postfixes: <%= ['b', 'k', 'm', 'g'] %>\n  memory_values: <%= [125, 250, 500, 1000, 2000, 4000] %>\n  port_min_value: 1\n  port_max_value: 65535\n  delete_after_postfixes: <%= ['h'] %>\n  delete_after_min_value: 1\n  delete_after_max_value: 720\n  default_tag: latest\n  dockerfile_default_path: Dockerfile\n  default_branch: 'master'\ncontinuous_preview:\n  default_delete_preview_after: 72\ndeployment:\n  max_memory_limit: 8000\n  subdomain:\n    length_limit: 63\ndefault_job_retry_count: 5\nvcluster:\n  max_creation_retry_count: 5\n"
  },
  {
    "path": "config/sidekiq.yml",
    "content": "---\n:concurrency: <%= ENV[\"SIDEKIQ_CONCURRENCY\"].nil? ? 5 : ENV[\"SIDEKIQ_CONCURRENCY\"].to_i %>\n:verbose: true\n:queues:\n  - [default, 5]\n  - [active_storage_analysis, 5]\n  - [active_storage_purge, 5]\n  - [mailers, 5]\n  - [billing, 6]\n  - [deployments, 10]\n  - [config_files, 10]\n  - [compose_files, 10]\n  - [resources, 10]\n  - [databases, 10]\n  - [projects, 10]\n  - [accounts, 10]\n  - [containers, 10]\n  - [github, 10]\n  - [disable_deployments, 5]\n  - [clusters, 10]\n"
  },
  {
    "path": "config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n"
  },
  {
    "path": "config.ru",
    "content": "# frozen_string_literal: true\n\n# This file is used by Rack-based servers to start the application.\n\nrequire_relative 'config/environment'\n\nrun Rails.application\n"
  },
  {
    "path": "core/.gitignore",
    "content": "/.bundle/\n/doc/\n/log/*.log\n/pkg/\n/tmp/\n/test/dummy/db/*.sqlite3\n/test/dummy/db/*.sqlite3-*\n/test/dummy/log/*.log\n/test/dummy/storage/\n/test/dummy/tmp/\n.byebug_history\n"
  },
  {
    "path": "core/CHANGELOG.md",
    "content": "## [0.1.0] - 2022-04-05\n\n- Initial release\n"
  },
  {
    "path": "core/Dockerfile",
    "content": "FROM ruby:3.0.2-alpine3.14\n\nRUN apk update && apk upgrade && apk add bash curl-dev ruby-dev build-base git gcompat \\\n                                 curl ruby-json openssl postgresql-dev postgresql-client tzdata\n\nRUN mkdir -p /gem\nWORKDIR /gem\n\nENV GEM_HOME=\"/usr/local/bundle\"\n\nCOPY lib/uffizzi_core/version.rb /gem/lib/uffizzi_core/\nCOPY uffizzi_core.gemspec /gem/\nCOPY Gemfile* /gem/\nRUN bundle install --jobs 4\n\nCOPY . /gem\n\nENV PATH /gem/bin:$GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH\n"
  },
  {
    "path": "core/Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\ngit_source(:github) { |repo| \"https://github.com/#{repo}.git\" }\n\ngem 'iri'\n\ngemspec\n"
  },
  {
    "path": "core/LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "core/Makefile",
    "content": "release_gem:\n\tmkdir -p ${HOME}/.gem\n\ttouch ${HOME}/.gem/credentials\n\tchmod 0600 ${HOME}/.gem/credentials\n\tprintf -- \"---\\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\\n\" > ${HOME}/.gem/credentials\n\tgem build *.gemspec\n\tgem push *.gem\n"
  },
  {
    "path": "core/README.md",
    "content": "# Uffizzi Core\n\n**Uffizzi CLI API, Models, Services and core libraries**\n\n## Uffizzi Overview\n\nUffizzi 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.\n\n## Getting started with Uffizzi\n\nThe 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. \n\nAlternatively, 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.\n\n## Uffizzi Architecture\n\nUffizzi consists of the following components:\n\n* [Uffizzi App](https://github.com/UffizziCloud/uffizzi_app) - The primary REST API for creating and managing Previews\n* [Uffizzi Controller](https://github.com/UffizziCloud/uffizzi_controller) - A smart proxy service that handles requests from Uffizzi App to the Kubernetes API\n* Uffizzi CLI (this repository) - A command-line interface for Uffizzi App\n* [Uffizzi Dashboard](https://app.uffizzi.com) - A graphical user interface for Uffizzi App, available as a paid service at https://uffizzi.com\n\nTo host Uffizzi yourself, you will also need the following external dependencies:\n\n * Kubernetes (k8s) cluster\n * Postgres database\n * Redis cache\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'uffizzi_core'\n```\n\nAnd then execute:\n```bash\n$ bundle\n```\n\nOr install it yourself as:\n```bash\n$ gem install uffizzi_core\n```\n"
  },
  {
    "path": "core/Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/setup'\n\nAPP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)\nload 'rails/tasks/engine.rake'\n\nload 'rails/tasks/statistics.rake'\n\nrequire 'bundler/gem_tasks'\n\nrequire 'rake/testtask'\n\nRake::TestTask.new(:test) do |t|\n  t.libs << 'test'\n  t.pattern = 'test/**/*_test.rb'\n  t.verbose = false\nend\n\ntask default: :test\n\nnamespace :core do\n  desc 'Generate api docs'\n  task generate_docs: :environment do\n    SwaggerYard.register_custom_yard_tags!\n\n    spec = SwaggerYard::Swagger.new\n\n    File.open('swagger/v1/swagger.json', 'w') { |f| f << JSON.pretty_generate(spec.to_h) }\n  end\nend\n"
  },
  {
    "path": "core/app/assets/config/uffizzi_core_manifest.js",
    "content": "//= link_directory ../stylesheets/uffizzi_core .css\n"
  },
  {
    "path": "core/app/assets/images/uffizzi_core/.keep",
    "content": ""
  },
  {
    "path": "core/app/assets/stylesheets/uffizzi_core/application.css",
    "content": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,\n * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the bottom of the\n * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS\n * files in this directory. Styles in this file should be added after the last require_* statement.\n * It is generally better to create a new file per style scope.\n *\n *= require_tree .\n *= require_self\n */\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/amazon_registry_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::AmazonRegistryClient\n  attr_accessor :client, :token, :registry_url\n\n  def initialize(region:, access_key_id:, secret_access_key:)\n    credentials = Aws::Credentials.new(access_key_id, secret_access_key)\n    @client = Aws::ECR::Client.new(region: region, credentials: credentials)\n  end\n\n  def authorization_token\n    client.get_authorization_token({})\n  end\n\n  def batch_get_image(image:, tag:)\n    client.batch_get_image({ image_ids: [{ image_tag: tag }], repository_name: image })\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/azure_registry_client/request_result.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::AzureRegistryClient::RequestResult < Hashie::Mash\n  disable_warnings :key\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/azure_registry_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::AzureRegistryClient\n  attr_accessor :connection, :token, :registry_url\n\n  def initialize(registry_url:, username:, password:)\n    @registry_url = registry_url\n    @connection = build_connection(registry_url, username, password)\n    @token = oauth2_token&.result&.access_token\n  end\n\n  def manifests(image:, tag:)\n    url = \"/v2/#{image}/manifests/#{tag}\"\n    response = connection.get(url)\n\n    RequestResult.quiet.new(result: response.body, headers: response.headers)\n  end\n\n  def oauth2_token\n    service = URI.parse(registry_url).hostname\n    url = \"/oauth2/token?service=#{service}\"\n\n    response = connection.get(url, {})\n\n    RequestResult.new(result: response.body)\n  end\n\n  def authenticated?\n    token.present?\n  end\n\n  private\n\n  def build_connection(registry_url, username, password)\n    connection = Faraday.new(registry_url) do |faraday|\n      faraday.request(:basic_auth, username, password)\n      faraday.request(:json)\n      faraday.response(:json)\n      faraday.response(:raise_error)\n      faraday.adapter(Faraday.default_adapter)\n    end\n\n    connection.extend(UffizziCore::ContainerRegistryRequestDecorator)\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/container_registry_request_decorator.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ContainerRegistryRequestDecorator\n  [:get, :post, :head].each do |method|\n    define_method(method) do |url, params_or_body = nil, headers = nil, &block|\n      super(url, params_or_body, headers, &block)\n    rescue Faraday::ClientError => e\n      raise UffizziCore::ContainerRegistryError.new(e.response)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/controller_client/request_result.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ControllerClient::RequestResult < Hashie::Mash\n  disable_warnings :key\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/controller_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ControllerClient\n  class ConnectionError < StandardError; end\n\n  attr_accessor :connection\n\n  def initialize(connection_settings)\n    @connection = build_connection(connection_settings)\n  end\n\n  def apply_config_file(deployment_id:, config_file_id:, body:)\n    connection.post(\"/deployments/#{deployment_id}/config_files/#{config_file_id}\", body)\n  end\n\n  def deployment_containers(deployment_id:)\n    get(\"/deployments/#{deployment_id}/containers\")\n  end\n\n  def deploy_containers(deployment_id:, body:)\n    connection.post(\"/deployments/#{deployment_id}/containers\", body)\n  end\n\n  def deployment_containers_metrics(deployment_id:)\n    get(\"/deployments/#{deployment_id}/containers/metrics\")\n  end\n\n  def deployment_container_logs(deployment_id:, container_name:, limit:, previous:)\n    get(\"/deployments/#{deployment_id}/containers/#{container_name}/logs?limit=#{limit}&previous=#{previous}\")\n  end\n\n  def deployment_containers_events(deployment_id:)\n    events = get(\"/deployments/#{deployment_id}/containers/events\").result.items\n    pods_events = events.select { |event| event.involved_object.kind == 'Pod' }\n    pods_events.map do |event|\n      { first_timestamp: event.first_timestamp, last_timestamp: event.last_timestamp, reason: event.reason, message: event.message }\n    end\n  end\n\n  def nodes\n    get('/nodes')\n  end\n\n  def apply_credential(deployment_id:, body:)\n    connection.post(\"/deployments/#{deployment_id}/credentials\", body)\n  end\n\n  def delete_credential(deployment_id:, credential_id:)\n    connection.delete(\"/deployments/#{deployment_id}/credentials/#{credential_id}\")\n  end\n\n  def get_deployments_usage_metrics_containers(deployment_ids:, begin_at:, end_at:)\n    query_params = {\n      deployment_ids: deployment_ids,\n      begin_at: begin_at,\n      end_at: end_at,\n    }\n    get('/deployments/usage_metrics/containers', query_params)\n  end\n\n  def create_namespace(body:)\n    post('/namespaces', body)\n  end\n\n  def namespace(namespace:)\n    get(\"/namespaces/#{namespace}\")\n  end\n\n  def delete_namespace(namespace:)\n    connection.delete(\"/namespaces/#{namespace}\")\n  end\n\n  def create_cluster(namespace:, body:)\n    post(\"/namespaces/#{namespace}/cluster\", body)\n  end\n\n  def show_cluster(namespace:, name:)\n    get(\"/namespaces/#{namespace}/cluster/#{name}\")\n  end\n\n  def patch_cluster(name:, namespace:, body:)\n    patch(\"/namespaces/#{namespace}/cluster/#{name}\", body)\n  end\n\n  def ingresses(namespace:)\n    get(\"/namespaces/#{namespace}/ingresses\")\n  end\n\n  private\n\n  def get(url, params = {})\n    make_request(:get, url, params)\n  end\n\n  def post(url, params = {})\n    make_request(:post, url, params)\n  end\n\n  def patch(url, params = {})\n    make_request(:patch, url, params)\n  end\n\n  def make_request(method, url, params)\n    response = connection.send(method, url, params)\n    body = response.body\n    underscored_body = UffizziCore::Converters.deep_underscore_keys(body)\n\n    RequestResult.quiet.new(code: response.status, result: underscored_body)\n  rescue Faraday::ServerError\n    raise ConnectionError\n  end\n\n  def build_connection(settings)\n    connection = settings.connection\n    handled_exceptions = Faraday::Request::Retry::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]\n\n    Faraday.new(settings.url) do |conn|\n      conn.options.timeout = connection.timeout\n      conn.options.open_timeout = connection.open_timeout\n      conn.request(:basic_auth, settings.login, settings.password)\n      conn.request(:json)\n      conn.request(:retry,\n                   max: connection.retires_count,\n                   interval: connection.next_retry_timeout_seconds,\n                   exceptions: handled_exceptions)\n      conn.response(:json)\n      conn.response(:raise_error)\n      conn.adapter(Faraday.default_adapter)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/docker_hub_client/not_authorized_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DockerHubClient\n  class NotAuthorizedError < StandardError\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/docker_hub_client/request_result.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DockerHubClient\n  class RequestResult < Hashie::Mash\n    disable_warnings :key\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/docker_hub_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DockerHubClient\n  attr_accessor :connection, :jwt, :credential\n\n  BASE_URL = 'https://hub.docker.com'\n\n  def initialize(credential = nil)\n    @connection = build_connection\n    @credential = credential\n    return unless credential\n\n    @jwt = authenticate\n  end\n\n  def authenticate\n    params = { username: credential.username, password: credential.password }\n    url = \"#{BASE_URL}/v2/users/login/\"\n    response = connection.post(url, params)\n    request_result = RequestResult.new(result: response.body)\n    request_result.result.token\n  rescue NoMethodError\n    nil\n  end\n\n  def repository(namespace:, image:)\n    url = \"#{BASE_URL}/v2/repositories/#{namespace}/#{image}\"\n\n    response = connection.get(url) do |request|\n      request.headers['Authorization'] = \"JWT #{jwt}\"\n    end\n    RequestResult.new(status: response.status, result: response.body)\n  end\n\n  def public_images(q:, page: 1, per_page: 25)\n    url = \"#{BASE_URL}/api/content/v1/products/search\"\n    params = { page_size: per_page, q: q, type: :image, page: page }\n    response = connection.get(url, params) do |request|\n      request.headers['Search-Version'] = 'v3'\n    end\n    RequestResult.new(result: response.body)\n  end\n\n  def private_images(account:, page: 1, per_page: 25)\n    raise NotAuthorizedError if !authenticated? || account.empty?\n\n    url =  BASE_URL + \"/v2/repositories/#{account}/\"\n    params = { page_size: per_page, page: page }\n    response = connection.get(url, params) do |request|\n      request.headers['Authorization'] = \"JWT #{jwt}\"\n    end\n    RequestResult.new(result: response.body)\n  end\n\n  def accounts\n    raise NotAuthorizedError if !authenticated?\n\n    url = \"#{BASE_URL}/v2/repositories/namespaces/\"\n    response = connection.get(url) do |request|\n      request.headers['Authorization'] = \"JWT #{jwt}\"\n    end\n    RequestResult.new(result: response.body)\n  end\n\n  def metadata(namespace:, image:)\n    url = BASE_URL + \"/v2/repositories/#{namespace}/#{image}/\"\n    response = connection.get(url) do |request|\n      request.headers['Authorization'] = \"JWT #{jwt}\"\n    end\n    RequestResult.quiet.new(result: response.body)\n  end\n\n  def tags(namespace:, image:, q: '', page: 1, per_page: 10)\n    url = BASE_URL + \"/v2/repositories/#{namespace}/#{image}/tags\"\n    params = { page_size: per_page, page: page, name: q }\n    response = connection.get(url, params) do |request|\n      request.headers['Authorization'] = \"JWT #{jwt}\"\n    end\n    RequestResult.quiet.new(result: response.body)\n  end\n\n  def digest(image:, tag:, token:)\n    url = \"https://index.docker.io/v2/#{image}/manifests/#{tag}\"\n    response = connection.get(url) do |request|\n      request.headers['Accept'] = 'application/vnd.docker.distribution.manifest.v2+json'\n      request.headers['Authorization'] = \"Bearer #{token}\"\n    end\n\n    RequestResult.quiet.new(result: response.body, headers: response.headers)\n  end\n\n  def get_token(repository)\n    params = { username: credential.username, password: credential.password }\n    url = \"https://auth.docker.io/token?service=registry.docker.io&scope=repository:#{repository}:pull\"\n    response = connection.get(url, params)\n    RequestResult.new(result: response.body)\n  end\n\n  def authenticated?\n    jwt.present?\n  end\n\n  private\n\n  def build_connection\n    connection = Faraday.new do |faraday|\n      faraday.request(:json)\n      faraday.response(:json)\n      faraday.response(:raise_error)\n      faraday.adapter(Faraday.default_adapter)\n    end\n\n    connection.extend(UffizziCore::ContainerRegistryRequestDecorator)\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/docker_registry_client/request_result.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DockerRegistryClient::RequestResult < Hashie::Mash\n  disable_warnings :key\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/docker_registry_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DockerRegistryClient\n  ACCEPTED_TYPES = [\n    'application/vnd.oci.image.index.v1+json',\n    'application/vnd.oci.image.manifest.v1+json',\n    'application/vnd.docker.distribution.manifest.v1+json',\n    'application/vnd.docker.distribution.manifest.v2+json',\n    'application/vnd.docker.distribution.manifest.list.v2+json',\n    '*/*',\n  ].freeze\n\n  def initialize(registry_url:, username: nil, password: nil)\n    @registry_url = registry_url\n    @connection = build_connection(username, password)\n  end\n\n  def authenticated?\n    @connection.get(\"#{@registry_url}/v2/\")\n\n    true\n  end\n\n  def manifests(image:, tag:, namespace: nil)\n    full_image = [namespace, image].compact.join('/')\n    url = \"#{@registry_url}/v2/#{full_image}/manifests/#{tag}\"\n    response = @connection.get(url)\n\n    RequestResult.new(status: response.status, result: response.body)\n  end\n\n  private\n\n  def build_connection(username, password)\n    # initializing Faraday with the registry_url will trim the trailing slash required for the /v2/ request\n    connection = Faraday.new do |faraday|\n      faraday.headers['Accept'] = ACCEPTED_TYPES\n      faraday.request(:basic_auth, username, password) if username.present? && password.present?\n      faraday.request(:json)\n      faraday.response(:json)\n      faraday.response(:follow_redirects)\n      faraday.response(:raise_error)\n      faraday.adapter(Faraday.default_adapter)\n    end\n\n    connection.extend(UffizziCore::ContainerRegistryRequestDecorator)\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/github_container_registry_client/request_result.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::GithubContainerRegistryClient\n  class RequestResult < Hashie::Mash\n    disable_warnings :key\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/github_container_registry_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::GithubContainerRegistryClient\n  attr_accessor :token, :registry_url\n\n  def initialize(registry_url:, username:, password:)\n    @registry_url = registry_url\n    @username = username\n    @password = password\n    @token = access_token&.result&.token\n  end\n\n  def access_token\n    service = URI.parse(registry_url).hostname\n    url = \"/token?service=#{service}\"\n\n    response = connection.get(url, {})\n\n    RequestResult.new(result: response.body)\n  end\n\n  def authenticated?\n    token.present?\n  end\n\n  def manifests(image:, tag:)\n    url = \"/v2/#{@username}/#{image}/manifests/#{tag}\"\n    response = token_connection.get(url)\n\n    RequestResult.quiet.new(result: response.body, headers: response.headers)\n  end\n\n  private\n\n  def connection\n    connection = Faraday.new(registry_url) do |faraday|\n      faraday.request(:basic_auth, @username, @password)\n      faraday.request(:json)\n      faraday.response(:json)\n      faraday.response(:raise_error)\n      faraday.adapter(Faraday.default_adapter)\n    end\n\n    connection.extend(UffizziCore::ContainerRegistryRequestDecorator)\n  end\n\n  def token_connection\n    connection = Faraday.new(registry_url) do |faraday|\n      faraday.request(:authorization, 'Bearer', token)\n      faraday.request(:json)\n      faraday.response(:json)\n      faraday.response(:raise_error)\n      faraday.adapter(Faraday.default_adapter)\n    end\n\n    connection.extend(UffizziCore::ContainerRegistryRequestDecorator)\n  end\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/google_registry_client/request_result.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::GoogleRegistryClient::RequestResult < Hashie::Mash\n  disable_warnings :key\nend\n"
  },
  {
    "path": "core/app/clients/uffizzi_core/google_registry_client.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::GoogleRegistryClient\n  attr_accessor :connection, :token, :registry_url\n\n  def initialize(registry_url:, username:, password:)\n    @registry_url = registry_url\n    @connection = build_connection(registry_url, username, password)\n    @token = access_token&.result&.token\n  end\n\n  def manifests(image:, tag:)\n    url = \"/v2/#{image}/manifests/#{tag}\"\n    response = connection.get(url)\n\n    RequestResult.quiet.new(result: response.body, headers: response.headers)\n  end\n\n  def access_token\n    service = URI.parse(registry_url).hostname\n    url = \"/v2/token?service=#{service}\"\n\n    response = connection.get(url, {})\n\n    RequestResult.new(result: response.body)\n  end\n\n  def authenticated?\n    token.present?\n  end\n\n  private\n\n  def build_connection(registry_url, username, password)\n    connection = Faraday.new(registry_url) do |faraday|\n      faraday.request(:basic_auth, username, password)\n      faraday.request(:json)\n      faraday.response(:json)\n      faraday.response(:raise_error)\n      faraday.adapter(Faraday.default_adapter)\n    end\n\n    connection.extend(UffizziCore::ContainerRegistryRequestDecorator)\n  end\nend\n"
  },
  {
    "path": "core/app/contexts/uffizzi_core/account_context.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::AccountContext\n  attr_reader :user, :user_access_module, :account, :params\n\n  def initialize(user, user_access_module, account, params)\n    @user = user\n    @user_access_module = user_access_module\n    @account = account\n    @params = params\n  end\nend\n"
  },
  {
    "path": "core/app/contexts/uffizzi_core/base_context.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::BaseContext\n  attr_reader :user, :user_access_module, :params\n\n  def initialize(user, user_access_module, params)\n    @user = user\n    @user_access_module = user_access_module\n    @params = params\n  end\nend\n"
  },
  {
    "path": "core/app/contexts/uffizzi_core/project/cluster_context.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Project::ClusterContext\n  attr_reader :user, :user_access_module, :project, :cluster, :params\n\n  def initialize(user, project, user_access_module, cluster, params)\n    @user = user\n    @user_access_module = user_access_module\n    @project = project\n    @cluster = cluster\n    @params = params\n  end\nend\n"
  },
  {
    "path": "core/app/contexts/uffizzi_core/project_context.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ProjectContext\n  attr_reader :user, :user_access_module, :project, :account, :params\n\n  def initialize(user, user_access_module, project, account, params)\n    @user = user\n    @user_access_module = user_access_module\n    @account = account\n    @project = project\n    @params = params\n  end\nend\n"
  },
  {
    "path": "core/app/contexts/uffizzi_core/webhooks_context.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::WebhooksContext\n  attr_reader :request\n\n  def initialize(request)\n    @request = request\n  end\nend\n"
  },
  {
    "path": "core/app/controller_modules/uffizzi_core/api/cli/v1/accounts_controller_module.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Api::Cli::V1::AccountsControllerModule\n  def update; end\nend\n"
  },
  {
    "path": "core/app/controller_modules/uffizzi_core/api/cli/v1/projects/clusters_controller_module.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Api::Cli::V1::Projects::ClustersControllerModule\n  private\n\n  def update_show_trial_quota_exceeded_warning; end\n\n  def check_account_quota; end\n\n  def check_current_plan; end\nend\n"
  },
  {
    "path": "core/app/controller_modules/uffizzi_core/api/cli/v1/projects/deployments_controller_module.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerModule\n  private\n\n  def check_account_quota; end\n\n  def update_show_trial_quota_exceeded_warning; end\n\n  def check_current_plan; end\nend\n"
  },
  {
    "path": "core/app/controller_modules/uffizzi_core/api/cli/v1/projects_controller_module.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Api::Cli::V1::ProjectsControllerModule\n  private\n\n  def update_show_trial_quota_exceeded_warning; end\nend\n"
  },
  {
    "path": "core/app/controllers/concerns/uffizzi_core/auth_management.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::AuthManagement\n  def sign_in(user)\n    session[:user_id] = user.id\n  end\n\n  def sign_out\n    session[:user_id] = @current_user = nil\n  end\n\n  def signed_in?\n    session[:user_id].present? && current_user.present?\n  end\n\n  def current_user\n    @current_user ||= UffizziCore::User.find_by(id: current_user_id)\n  end\n\n  def auth_token\n    header = request.headers['Authorization']\n    header&.split(' ')&.last\n  end\n\n  def current_user_id\n    return session[:user_id] if session[:user_id].present?\n    return unless auth_token.present?\n\n    decoded_token = UffizziCore::TokenService.decode(auth_token)\n    return unless decoded_token\n    return if decoded_token.first['expires_at'] < DateTime.now\n\n    decoded_token.first['user_id']\n  end\n\n  def authenticate_request!\n    current_user ? true : head(:unauthorized)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/concerns/uffizzi_core/authorization_concern.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::AuthorizationConcern\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :init_authorize\n  end\n\n  def init_authorize\n    return unless self.class.ancestors.include?(UffizziCore::ApplicationController)\n\n    self.class.send(:define_method, policy_method_name) { send(:authorize, policy_method_params) }\n  end\n\n  def pundit_user\n    policy_context\n  end\n\n  private\n\n  def policy_method_name\n    [:authorize, policy_name].join('_')\n  end\n\n  def policy_name\n    controller_class = self.class.to_s\n\n    controller_class.gsub(/::|Controller/, '').underscore\n  end\n\n  def policy_method_params\n    controller_class = self.class.to_s\n\n    params = controller_class.gsub(/Controller/, '').split('::')\n    params.map(&:underscore).map(&:downcase).map(&:to_sym)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/concerns/uffizzi_core/dependency_injection_concern.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::DependencyInjectionConcern\n  extend ActiveSupport::Concern\n\n  class_methods do\n    def include_module_if_exists(module_name)\n      include(Object.const_get(module_name)) if Object.const_defined?(module_name)\n    end\n\n    def prepend_module_if_exists(module_name)\n      prepend(Object.const_get(module_name)) if Object.const_defined?(module_name)\n    end\n  end\n\n  def user_access_module\n    return unless module_exists?(:rbac)\n\n    UffizziCore::UserAccessService.new(module_class(:rbac))\n  end\n\n  def find_build_parser_module\n    module_class(:build_parser)\n  end\n\n  def find_volume_parser_module\n    module_class(:volume_parser)\n  end\n\n  def ci_module\n    return unless module_exists?(:ci_module)\n\n    module_class(:ci_module)\n  end\n\n  def ci_session\n    return unless module_exists?(:ci_session)\n\n    module_class(:ci_session)\n  end\n\n  def password_protection_module\n    return unless module_exists?(:password_protection)\n\n    module_class(:password_protection)\n  end\n\n  def find_ingress_parser_module\n    module_class(:ingress_parser)\n  end\n\n  def notification_module\n    return unless module_exists?(:notification_module)\n\n    module_class(:notification_module)\n  end\n\n  def domain_module\n    return unless module_exists?(:domain_module)\n\n    module_class(:domain_module)\n  end\n\n  def deployment_memory_module\n    return unless module_exists?(:deployment_memory_module)\n\n    module_class(:deployment_memory_module)\n  end\n\n  def template_memory_module\n    return unless module_exists?(:template_memory_module)\n\n    module_class(:template_memory_module)\n  end\n\n  def controller_settings_service\n    return unless module_exists?(:controller_settings)\n\n    module_class(:controller_settings)\n  end\n\n  private\n\n  def module_exists?(module_name)\n    module_class(module_name).present?\n  end\n\n  def module_class(module_name)\n    UffizziCore.dependencies[module_name]&.constantize\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/accounts/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ApplicationController < UffizziCore::Api::Cli::V1::ApplicationController\n  def resource_account\n    @resource_account ||= current_user.accounts.find(params[:account_id])\n  end\n\n  def policy_context\n    UffizziCore::AccountContext.new(current_user, user_access_module, resource_account, params)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/accounts/clusters_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ClustersController < UffizziCore::Api::Cli::V1::Accounts::ApplicationController\n  include UffizziCore::DependencyInjectionConcern\n  before_action :authorize_uffizzi_core_api_cli_v1_accounts_clusters\n\n  def index\n    return respond_with(clusters_by_account.includes(:project)) if valid_request_from_ci_workflow?\n\n    clusters = clusters_by_user.or(clusters_by_admin_projects)\n    respond_with(clusters.includes(:project))\n  end\n\n  private\n\n  def valid_request_from_ci_workflow?\n    ci_module.valid_request_from_ci_workflow?(params)\n  end\n\n  def clusters_by_admin_projects\n    projects = UffizziCore::Project\n      .active\n      .joins(:user_projects)\n      .where(account: resource_account)\n      .where(user_projects: { role: UffizziCore::UserProject.role.admin, user: current_user })\n\n    UffizziCore::Cluster.enabled.where(project_id: projects.select(:id))\n  end\n\n  def clusters_by_user\n    UffizziCore::Cluster.enabled.by_projects(account_projects).deployed_by_user(current_user)\n  end\n\n  def clusters_by_account\n    UffizziCore::Cluster.enabled.by_projects(account_projects)\n  end\n\n  def account_projects\n    UffizziCore::Project.active.where(account: resource_account)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/accounts/credentials_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Account/Credential\nclass UffizziCore::Api::Cli::V1::Accounts::CredentialsController < UffizziCore::Api::Cli::V1::Accounts::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_accounts_credentials\n\n  # Get a list of accounts credential\n  #\n  # @path [GET] /api/cli/v1/account/credentials\n  #\n  # @parameter credential(required,body) [object<username:string, password: string, type:string>]\n  def index\n    credentials = resource_account.credentials.pluck(:type)\n\n    render json: { credentials: credentials }, status: :ok\n  end\n\n  # Create account credential\n  #\n  # @path [POST] /api/cli/v1/account/credentials\n  #\n  # @parameter credential(required,body) [object<username:string, password: string, type:string>]\n  # @response [object<id:integer, username:string, password:string, type:string, state:string>] 201 Created successfully\n  # @response [object<errors>] 422 Unprocessable entity\n  #\n  # @example\n  #    Possible types:\n  #    UffizziCore::Credential::Amazon, UffizziCore::Credential::Azure, UffizziCore::Credential::DockerHub,\n  #    UffizziCore::Credential::DockerRegistry, UffizziCore::Credential::Google, UffizziCore::Credential::GithubContainerRegistry\n  def create\n    credential_form = UffizziCore::Api::Cli::V1::Account::Credential::CreateForm.new\n    credential_form.assign_attributes(credential_params)\n    credential_form.account = resource_account\n    credential_form.registry_url = registry_url(credential_form)\n    credential_form.username = '_json_key' if credential_form.google?\n    credential_form.activate\n\n    UffizziCore::Account::CreateCredentialJob.perform_async(credential_form.id) if credential_form.save\n\n    respond_with credential_form\n  end\n\n  # Update existing credential of the given type\n  #\n  # @path [PUT] /api/cli/v1/account/credentials/{type}\n  #\n  # @parameter type(required,path) [string] Credential type\n  # @parameter credential(required,body) [object<type:string>]\n  # @response [object<id:integer, registry_url:string, username:string, password:string>] 200 OK\n  # @response [object<errors>] 422 Unprocessable entity\n  def update\n    credential = resource_account.credentials.find_by!(type: params[:type])\n    # Called every pipeline run from CLI with the --update-if-exists-option\n    return respond_with credential unless credential_changed?(credential, credential_params)\n\n    credential_form = credential.becomes(UffizziCore::Api::Cli::V1::Account::Credential::UpdateForm)\n    credential_form.assign_attributes(credential_params)\n\n    if credential_form.save\n      UffizziCore::Account::UpdateCredentialJob.perform_async(credential_form.id)\n      respond_with credential_form\n    else\n      respond_with credential_form, status: :unprocessable_entity\n    end\n  end\n\n  # Check if credential of the type already exists in the account\n  #\n  # @path [GET] /api/cli/v1/account/credentials/{type}/check_credential\n  #\n  # @parameter credential(required,body) [object<type:string>]\n  # @response 422 Unprocessable entity\n  # @response 200 OK\n  def check_credential\n    credential_form = UffizziCore::Api::Cli::V1::Account::Credential::CheckCredentialForm.new\n    credential_form.type = params[:type]\n    credential_form.account = resource_account\n    if credential_form.valid?\n      respond_with credential_form\n    else\n      respond_with credential_form.errors, status: :unprocessable_entity\n    end\n  end\n\n  # Delete account credential\n  #\n  # @path [DELETE] /api/cli/v1/account/credentials/{type}\n  #\n  # @parameter type(required,path) [string] Type of the credential\n  # @response 204 No Content\n  # @response 401 Not authorized\n  # @response [object<errors: object<title: string>>] 404 Not found\n  def destroy\n    credential = resource_account.credentials.find_by!(type: params[:type])\n    credential.destroy\n  end\n\n  private\n\n  def credential_params\n    params.require(:credential)\n  end\n\n  def registry_url(credential_form)\n    if credential_form.docker_hub?\n      Settings.docker_hub.registry_url\n    elsif credential_form.google?\n      Settings.google.registry_url\n    elsif credential_form.github_container_registry?\n      Settings.github_container_registry.registry_url\n    else\n      credential_form.registry_url\n    end\n  end\n\n  def credential_changed?(credential, credential_params)\n    credential.password != credential_params[:password] ||\n      credential.username != credential_params[:username] ||\n      credential.registry_url != credential_params[:registry_url]\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/accounts/projects_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Project\n\nclass UffizziCore::Api::Cli::V1::Accounts::ProjectsController < UffizziCore::Api::Cli::V1::Accounts::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_accounts_projects\n\n  def index\n    projects = resource_account.projects.active\n\n    respond_with projects, each_serializer: UffizziCore::Api::Cli::V1::ShortProjectSerializer\n  end\n\n  # Create a project\n  #\n  # @path [POST] /api/cli/v1/accounts/{account_id}/projects\n  # @parameter params(required,body) [object<name: string, slug: string, description: string>]\n  #\n  # @response <object< project: Project>> 200 OK\n  # @response 404 Not Found\n  # @response 401 Not authorized\n  # @response [object<errors: object<password: string >>] 422 Unprocessable entity\n\n  def create\n    project_form = UffizziCore::Api::Cli::V1::Project::CreateForm.new(project_params)\n    project_form.account = resource_account\n    UffizziCore::ProjectService.add_users_to_project!(project_form, project_form.account) if project_form.save\n\n    respond_with project_form\n  end\n\n  private\n\n  def project_params\n    params.require(:project)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/accounts_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Project\n\nclass UffizziCore::Api::Cli::V1::AccountsController < UffizziCore::Api::Cli::V1::ApplicationController\n  include UffizziCore::Api::Cli::V1::AccountsControllerModule\n\n  before_action :authorize_uffizzi_core_api_cli_v1_accounts\n\n  # Get accounts of current user\n  #\n  # @path [GET] /api/cli/v1/accounts\n  #\n  # @response [object<accounts: Array<object<id: integer, name: string>> >] 200 OK\n  # @response 401 Not authorized\n  def index\n    accounts = current_user.accounts.order(name: :desc)\n\n    respond_with accounts\n  end\n\n  # Get account by name\n  #\n  # @path [GET] /api/cli/v1/accounts/{name}\n  #\n  # @response [object<account: <object<id: integer, name: string, projects: Array<object<id: integer, slug: string>>>> >] 200 OK\n  # @response 401 Not authorized\n  def show\n    respond_with resource_account\n  end\n\n  private\n\n  def policy_context\n    account = resource_account || current_user.default_account\n\n    UffizziCore::AccountContext.new(current_user, user_access_module, account, params)\n  end\n\n  def resource_account\n    @resource_account ||= if params[:name]\n      current_user.accounts.find_by!(name: params[:name])\n    else\n      current_user.default_account\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ApplicationController < UffizziCore::ApplicationController\n  before_action :authenticate_request!\n\n  def resource_project\n    @resource_project ||= current_user.projects.find_by!(slug: params[:slug])\n  end\n\n  def resource_account\n    @resource_account ||= resource_project.account\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/ci/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Ci::ApplicationController < UffizziCore::Api::Cli::V1::ApplicationController\n  before_action :authenticate_request!\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/ci/sessions_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Uffizzi\n\nclass UffizziCore::Api::Cli::V1::Ci::SessionsController < UffizziCore::Api::Cli::V1::Ci::ApplicationController\n  skip_before_action :authenticate_request!, only: [:create]\n\n  # Create session\n  #\n  # @path [POST] /api/cli/v1/github/session\n  #\n  # @parameter user(required,body)   [object<token: string >]\n  # @response [object<account_id: string, project_slug: string>] 201 Created successfully\n  # @response [object<errors: object<token: string >>] 422 Unprocessable entity\n  def create\n    return render json: { errors: { title: [I18n.t('session.unsupported_login_type')] } }, status: :unprocessable_entity unless ci_session\n\n    session_data, errors = ci_session.session_data_from_ci(user_params)\n    return render json: { errors: errors }, status: :unprocessable_entity if errors.present?\n\n    sign_in(session_data[:user])\n\n    data = {\n      account_id: session_data[:account_id],\n      project_slug: session_data[:project_slug],\n    }\n\n    render json: data, status: :created\n  end\n\n  private\n\n  def user_params\n    params.require(:user).permit(:token, :github_access_token)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ApplicationController < UffizziCore::Api::Cli::V1::ApplicationController\n  def resource_project\n    @resource_project ||= current_user.projects.find_by!(slug: params[:project_slug])\n  end\n\n  def resource_account\n    @resource_account ||= resource_project.account\n  end\n\n  def policy_context\n    UffizziCore::ProjectContext.new(current_user, user_access_module, resource_project, resource_account, params)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/clusters/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Clusters::ApplicationController < UffizziCore::Api::Cli::V1::Projects::ApplicationController\n  def resource_cluster\n    @resource_cluster ||= if request_by_admin? || valid_request_from_ci_workflow?\n      active_project_clusters.find_by!(name: params[:cluster_name])\n    else\n      active_project_clusters.deployed_by_user(current_user).find_by!(name: params[:cluster_name])\n    end\n  end\n\n  private\n\n  def active_project_clusters\n    @active_project_clusters ||= resource_project.clusters.enabled\n  end\n\n  def request_by_admin?\n    current_user.admin_access_to_project?(resource_project)\n  end\n\n  def valid_request_from_ci_workflow?\n    ci_module.valid_request_from_ci_workflow?(params)\n  end\n\n  def policy_context\n    UffizziCore::Project::ClusterContext.new(current_user, resource_project, user_access_module, resource_cluster, params)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/clusters/ingresses_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Clusters::IngressesController <\n  UffizziCore::Api::Cli::V1::Projects::Clusters::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_clusters_ingresses\n\n  def index\n    hosts = UffizziCore::ControllerService.ingress_hosts(resource_cluster)\n    user_hosts = UffizziCore::ClusterService.filter_user_ingress_host(resource_cluster, hosts)\n\n    data = {\n      ingresses: user_hosts,\n    }\n\n    respond_with data\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/clusters_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ClustersController < UffizziCore::Api::Cli::V1::Projects::ApplicationController\n  include UffizziCore::Api::Cli::V1::Projects::ClustersControllerModule\n\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_clusters\n  before_action :check_account_quota, only: [:create]\n  before_action :check_current_plan\n  after_action :update_show_trial_quota_exceeded_warning, only: [:create, :destroy]\n\n  def index\n    clusters = resource_project.clusters.enabled\n    return respond_with clusters if request_by_admin? || valid_request_from_ci_workflow?\n\n    respond_with clusters.deployed_by_user(current_user), each_serializer: UffizziCore::Api::Cli::V1::Projects::ShortClusterSerializer\n  end\n\n  def create\n    version = cluster_params[:k8s_version]\n    kubernetes_distribution = find_kubernetes_distribution(version)\n    return render_distribution_version_error(version) if kubernetes_distribution.blank?\n\n    cluster_form = UffizziCore::Api::Cli::V1::Cluster::CreateForm.new(cluster_params)\n    cluster_form.project = resource_project\n    cluster_form.deployed_by = current_user\n    cluster_form.kubernetes_distribution = kubernetes_distribution\n    return respond_with cluster_form unless cluster_form.save\n\n    UffizziCore::ClusterService.start_deploy(cluster_form)\n\n    respond_with cluster_form\n  end\n\n  def scale_down\n    if resource_cluster.deployed?\n      UffizziCore::ClusterService.scale_down!(resource_cluster)\n      return respond_with resource_cluster\n    end\n\n    return render_scale_error(I18n.t('cluster.already_asleep', name: resource_cluster.name)) if resource_cluster.scaled_down?\n\n    if resource_cluster.deploying_namespace? || resource_cluster.deploying?\n      render_scale_error(I18n.t('cluster.deploy_in_process', name: resource_cluster.name))\n    end\n  rescue AASM::InvalidTransition, UffizziCore::ClusterScaleError => e\n    render_scale_error(e.message)\n  end\n\n  def scale_up\n    if resource_cluster.scaled_down?\n      UffizziCore::ClusterService.scale_up!(resource_cluster)\n      return respond_with resource_cluster\n    end\n\n    return render_scale_error(I18n.t('cluster.already_awake', name: resource_cluster.name)) if resource_cluster.deployed?\n  rescue AASM::InvalidTransition, UffizziCore::ClusterScaleError => e\n    render_scale_error(e.message)\n  end\n\n  def show\n    respond_with resource_cluster\n  end\n\n  def sync\n    cluster_form = resource_cluster.becomes(UffizziCore::Api::Cli::V1::Cluster::SyncForm)\n    cluster_form.sync_status\n    cluster_form.save\n\n    respond_with cluster_form\n  end\n\n  def destroy\n    resource_cluster.disable!\n\n    head(:no_content)\n  end\n\n  private\n\n  def resource_cluster\n    active_project_clusters = resource_project.clusters.enabled\n    @resource_cluster ||= if request_by_admin? || valid_request_from_ci_workflow?\n      active_project_clusters.find_by!(name: params[:name])\n    else\n      active_project_clusters.deployed_by_user(current_user).find_by!(name: params[:name])\n    end\n  end\n\n  def request_by_admin?\n    current_user.admin_access_to_project?(resource_project)\n  end\n\n  def valid_request_from_ci_workflow?\n    ci_module.valid_request_from_ci_workflow?(params)\n  end\n\n  def cluster_params\n    params.require(:cluster)\n  end\n\n  def render_scale_error(message)\n    render json: { errors: { state: [message] } }, status: :unprocessable_entity\n  end\n\n  def find_kubernetes_distribution(version)\n    return UffizziCore::KubernetesDistribution.default if version.blank?\n\n    UffizziCore::KubernetesDistribution.find_by(version: version)\n  end\n\n  def render_distribution_version_error(version)\n    available_versions = UffizziCore::KubernetesDistribution.pluck(:version).join(', ')\n    message = I18n.t('kubernetes_distribution.not_available', version: version, available_versions: available_versions)\n    render json: { errors: { kubernetes_distribution: [message] } }, status: :unprocessable_entity\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/compose_files_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource ComposeFile\nclass UffizziCore::Api::Cli::V1::Projects::ComposeFilesController < UffizziCore::Api::Cli::V1::Projects::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_compose_files\n\n  # Get the compose file for the project\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/compose_file\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  #\n  # @response [ComposeFile] 200 OK\n  # @response 401 Not authorized\n  # @response [object<errors: object<title: string>>] 404 Not found\n  def show\n    respond_with compose_file\n  end\n\n  # Create a compose file for the project\n  #\n  # @path [POST] /api/cli/v1/projects/{project_slug}/compose_file\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  # @parameter params(required,body) [object <\n  #    compose_file: object<path: string, source: string, content: string>,\n  #    dependencies: Array<object<path: string, source: string, content: string, use_kind: string, is_file: boolean>>>]\n  #\n  # @response [ComposeFile] 201 OK\n  # @response 422 A compose file already exists for this project\n  # @response [ComposeFile] 422 Invalid compose file\n  # @response 401 Not authorized\n  def create\n    params = {\n      project: resource_project,\n      user: current_user,\n      compose_file_params: compose_file_params,\n      dependencies: dependencies_params[:dependencies] || [],\n    }\n\n    compose_file_form, errors = create_or_update_compose_file(params)\n    return render_errors(errors) if errors.present?\n\n    respond_with compose_file_form\n  end\n\n  # Delete the compose file for the project\n  #\n  # @path [DELETE] /api/cli/v1/projects/{project_slug}/compose_file\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  #\n  # @response 204 No Content\n  # @response 401 Not authorized\n  def destroy\n    compose_file.destroy\n\n    head :no_content\n  end\n\n  private\n\n  def compose_file\n    compose_file = resource_project.compose_file\n    raise ActiveRecord::RecordNotFound.new(\"Couldn't find UffizziCore::ComposeFile\", UffizziCore::ComposeFile) if compose_file.blank?\n\n    compose_file\n  end\n\n  def compose_file_params\n    params.require(:compose_file)\n  end\n\n  def dependencies_params\n    params.permit(dependencies: [:path, :source, :content, :use_kind, :is_file])\n  end\n\n  def create_or_update_compose_file(params)\n    existing_compose_file = resource_project.compose_file\n    if existing_compose_file.present?\n      UffizziCore::ComposeFileService.update(existing_compose_file, params)\n    else\n      kind = UffizziCore::ComposeFile.kind.main\n      UffizziCore::ComposeFileService.create(params, kind)\n    end\n  end\n\n  def render_errors(errors)\n    json = { errors: errors }\n\n    render json: json, status: :unprocessable_entity\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/activity_items_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemsController <\n  UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments_activity_items\n\n  # Get activity items for a deployment\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/deployment/{deployment_id}/actiivity_items\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  # @parameter deployment_d(required,path) [integer] The id of the deployment\n  #\n  # @response [ActivtyItem] 200 OK\n  # @response 401 Not authorized\n  # @response 404 Not found\n  def index\n    deployment = resource_project.deployments.enabled.find(params[:deployment_id])\n\n    unless deployment.active?\n      return render json: { errors: { title: [I18n.t('deployment.invalid_state', id: deployment.id, state: deployment.state)] } },\n                    status: :unprocessable_entity\n    end\n\n    activity_items = deployment\n      .activity_items\n      .page(page)\n      .per(per_page)\n      .order(updated_at: :desc)\n      .ransack(q_param)\n      .result\n\n    meta = meta(activity_items)\n    activity_items = activity_items.map do |activity_item|\n      UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemSerializer.new(activity_item).as_json\n    end\n\n    render json: {\n      activity_items: activity_items,\n      meta: meta,\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController < UffizziCore::Api::Cli::V1::Projects::ApplicationController\n  def resource_deployment\n    @resource_deployment ||= resource_project.deployments.active.find(params[:deployment_id])\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::ApplicationController <\n  UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController\n  def resource_container\n    @resource_container ||= resource_deployment.active_containers.find_by!(service_name: params[:container_name])\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers/logs_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Project/Deployment/Container/Log\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::LogsController <\n  UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::ApplicationController\n  # @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers/{container_name}/logs\n  #\n  # @parameter project_slug(required,path) [string] The slug of the project\n  # @parameter deployment_id(required,path) [integer] The id of the deployment\n  # @parameter container_name(required,path) [integer] The name of the container\n  #\n  # @response [object <logs: Array<object<insert_id: string, payload: string>>>] 200 OK\n  # @response [object<errors: object<title: string>>] 404 Not found\n  # @response 401 Not authorized\n  def index\n    response = UffizziCore::LogsService.fetch_container_logs(resource_container, logs_params)\n\n    render json: response\n  end\n\n  private\n\n  def logs_params\n    params.permit(:limit, :previous)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Container\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ContainersController <\n  UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments_containers\n\n  # Get a list of container services for a deployment\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  # @parameter deployment_id(required,path) [integer] The id of the deployment\n  #\n  # @response [Container] 200 OK\n  # @response 401 Not authorized\n  # @response 404 Not found\n  def index\n    containers = resource_deployment.containers.active\n\n    respond_with containers\n  end\n\n  def k8s_container_description\n    deployment ||= resource_project.deployments.enabled.find(params[:deployment_id])\n    container = deployment.containers.active.find_by!(service_name: params[:container_name])\n    last_state = UffizziCore::ContainerService.last_state(container)\n\n    render json: { last_state: last_state }\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments/events_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Event\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::EventsController <\n  UffizziCore::Api::Cli::V1::Projects::Deployments::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments_events\n\n  # Get the events associated with deployment\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/events\n  #\n  # @parameter project_slug(required,path) [string] The project_slug for the project\n  # @parameter deployment_id(required,path) [integer] The id of the deployment\n  #\n  # @response [object<\n  #   events: Array<object\n  #     <first_timestamp: string, last_timestamp: string, reason: string, message: string>\n  #   > >] 200 OK\n  #\n  # @response 401 Not authorized\n  # @response 404 Not found\n  def index\n    response = UffizziCore::ControllerService.fetch_deployment_events(resource_deployment)\n\n    events = { events: response }\n\n    render json: events\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Deployment\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsController < UffizziCore::Api::Cli::V1::Projects::ApplicationController\n  include UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerModule\n\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_deployments\n  before_action :check_account_quota, only: :create\n  before_action :check_current_plan\n  after_action :update_show_trial_quota_exceeded_warning, only: :destroy\n\n  # Get a list of active deployements for a project\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/deployments\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  #\n  # @response [Array<Deployment>] 200 OK\n  # @response 401 Not authorized\n  def index\n    search_labels = JSON.parse(q_param)\n    filtered_deployments = deployments.with_labels(search_labels)\n\n    respond_with filtered_deployments, each_serializer: UffizziCore::Api::Cli::V1::Projects::DeploymentsSerializer\n  end\n\n  # Get deployment information by id\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/deployments/{id}\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  #\n  # @response [Deployment] 200 OK\n  # @response [object<errors: object<title: string>>] 404 Not found\n  # @response 401 Not authorized\n  def show\n    respond_with deployment\n  end\n\n  # Create a deployment from a compose file\n  #\n  # @path [POST] /api/cli/v1/projects/{project_slug}/deployments\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  # @parameter params(required,body)   [object<\n  #    compose_file: object<path: string, source: string, content: string>,\n  #    dependencies: Array<object<path: string, source: string, content: string, use_kind: string, is_file: boolean>>>]\n  #\n  # @response [Deployment] 201 OK\n  # @response [object<errors: object<state: string>>] 422 Unprocessable Entity\n  # @response [object<errors: object<title: string>>] 404 Not found\n  # @response 401 Not authorized\n  def create\n    return render_deployment_exists_error if deployments.with_metadata.with_labels(metadata_params).exists?\n\n    compose_file, errors = find_or_create_compose_file\n    return render_invalid_file if compose_file.invalid_file?\n    return render_errors(errors) if errors.present?\n\n    errors = check_credentials(compose_file)\n    return render_errors(errors) if errors.present?\n\n    params = {\n      metadata: metadata_params,\n      creation_source: creation_source_params,\n    }\n\n    deployment = UffizziCore::DeploymentService.create_from_compose(compose_file, resource_project, current_user, params)\n\n    respond_with deployment\n  end\n\n  # Update the deployment with new compose file\n  #\n  # @path [PUT] /api/cli/v1/projects/{project_slug}/deployments/{id}\"\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  # @parameter params(required,body)   [object<\n  #    compose_file: object<path: string, source: string, content: string>,\n  #    dependencies: Array<object<path: string, source: string, content: string, use_kind: string, is_file: boolean>>>]\n  #\n  # @response [Deployment] 201 OK\n  # @response [object<errors: object<state: string>>] 422 Unprocessable Entity\n  # @response [object<errors: object<title: string>>] 404 Not found\n  # @response 401 Not authorized\n  def update\n    compose_file, errors = UffizziCore::ComposeFileService.create_temporary_compose(\n      resource_project,\n      current_user,\n      compose_file_params,\n      dependencies_params[:dependencies],\n    )\n    return render_invalid_file if compose_file.invalid_file?\n    return render_errors(errors) if errors.present?\n\n    errors = check_credentials(compose_file)\n    return render_errors(errors) if errors.present?\n\n    deployment = deployments.find(params[:id])\n    updated_deployment = UffizziCore::DeploymentService.update_from_compose(compose_file, resource_project, current_user, deployment,\n                                                                            metadata_params)\n\n    respond_with updated_deployment\n  end\n\n  # @path [POST] /api/cli/v1/projects/{project_slug}/deployments/{id}/deploy_containers\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  # @parameter id(required,path) [string] The id of the deployment\n  #\n  # @response 204 No Content\n  # @response [object<errors: object<title: string>>] 404 Not found\n  # @response 401 Not authorized\n  def deploy_containers\n    deployment = resource_project.deployments.active.find(params[:id])\n\n    deployment.update(deployed_by: current_user)\n\n    resource_project.config_files.by_deployment(deployment).each do |config_file|\n      UffizziCore::ConfigFile::ApplyJob.perform_async(deployment.id, config_file.id)\n    end\n\n    UffizziCore::Deployment::DeployContainersJob.perform_async(deployment.id)\n  end\n\n  # Disable deployment by id\n  #\n  # @path [DELETE] /api/cli/v1/projects/{project_slug}/deployments/{id}\n  #\n  # @parameter project_slug(required,path) [string] The project slug\n  #\n  # @response 204 No Content\n  # @response 401 Not authorized\n  def destroy\n    UffizziCore::DeploymentService.disable!(deployment)\n    deployment.deployment_events.create!(deployment_state: deployment.state, message: 'Destroyed by CLI')\n\n    head :no_content\n  end\n\n  private\n\n  def deployment\n    @deployment ||= deployments.find(params[:id])\n  end\n\n  def find_or_create_compose_file\n    existing_compose_file = resource_project.compose_file\n    if compose_file_params.present?\n      UffizziCore::ComposeFileService.create_temporary_compose(\n        resource_project,\n        current_user,\n        compose_file_params,\n        dependencies_params[:dependencies],\n      )\n    else\n      raise ActiveRecord::RecordNotFound if existing_compose_file.blank?\n\n      errors = []\n      [existing_compose_file, errors]\n    end\n  end\n\n  def check_credentials(compose_file)\n    credentials = resource_project.account.credentials\n    check_credentials_form = UffizziCore::Api::Cli::V1::ComposeFile::CheckCredentialsForm.new\n    check_credentials_form.compose_file = compose_file\n    check_credentials_form.credentials = credentials\n    return check_credentials_form.errors if check_credentials_form.invalid?\n\n    nil\n  end\n\n  def deployments\n    @deployments ||= resource_project.deployments.enabled\n  end\n\n  def deployment_params\n    params.required(:deployment)\n  end\n\n  def compose_file_params\n    params[:compose_file]\n  end\n\n  def dependencies_params\n    params.permit(dependencies: [:path, :source, :content, :use_kind, :is_file])\n  end\n\n  def metadata_params\n    params[:metadata]\n  end\n\n  def creation_source_params\n    params[:creation_source]\n  end\n\n  def render_invalid_file\n    render json: { errors: { state: [I18n.t('compose.invalid_compose')] } }, status: :unprocessable_entity\n  end\n\n  def render_deployment_exists_error\n    render json: { errors: { state: [I18n.t('deployment.already_exists')] } }, status: :unprocessable_entity\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects/secrets_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Project/Secrets\nclass UffizziCore::Api::Cli::V1::Projects::SecretsController < UffizziCore::Api::Cli::V1::Projects::ApplicationController\n  before_action :authorize_uffizzi_core_api_cli_v1_projects_secrets\n\n  # Get secrets for the project\n  #\n  # @path [GET] /api/cli/v1/projects/{project_slug}/secrets\n  # @parameter project_slug(required,path) [string]\n  # @response [object<secrets: Array<object<name: string, created_at: date, updated_at: date>>>] 200 OK\n  # @response 401 Not authorized\n  def index\n    respond_with resource_project.secrets, root: :secrets\n  end\n\n  # Add secret to project\n  #\n  # @path [POST] /api/cli/v1/projects/{project_slug}/secrets/bulk_create\n  # @parameter project_slug(required,path) [string]\n  # @parameter secrets(required,body) [object<secrets: Array<object <name: string, value: string>>>]\n  # @response [object<secrets: Array<object<name: string, created_at: date, updated_at: date>>>] 201 Created\n  # @response 422 A compose file already exists for this project\n  # @response 401 Not authorized\n  def bulk_create\n    secrets_form = UffizziCore::Api::Cli::V1::Secret::BulkAssignForm.new\n    secrets_form.secrets = resource_project.secrets\n    secrets_form.assign_secrets(secrets_params)\n    return respond_with secrets_form unless secrets_form.valid?\n\n    resource_project.secrets.replace(secrets_form.secrets)\n\n    UffizziCore::ProjectService.update_compose_secrets(resource_project)\n\n    respond_with resource_project.secrets, root: :secrets\n  end\n\n  # Delete a secret from project by secret name\n  #\n  # @path [DELETE] /api/cli/v1/projects/{project_slug}/secrets/{secret_name}\n  # @parameter project_slug(required,path) [string]\n  # @response [Project] 200 OK\n  # @response 404\n  # @response 401 Not authorized\n  def destroy\n    secret_name = CGI.unescape(params[:id])\n    secret = resource_project.secrets.find_by!(name: secret_name)\n\n    UffizziCore::ProjectService.update_compose_secret_errors(resource_project, secret)\n\n    secret.destroy\n\n    head :no_content\n  end\n\n  private\n\n  def secrets_params\n    params.require(:secrets)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/projects_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Project\n\nclass UffizziCore::Api::Cli::V1::ProjectsController < UffizziCore::Api::Cli::V1::ApplicationController\n  include UffizziCore::Api::Cli::V1::ProjectsControllerModule\n\n  before_action :authorize_uffizzi_core_api_cli_v1_projects\n  after_action :update_show_trial_quota_exceeded_warning, only: :destroy\n\n  # Get projects of current user\n  #\n  # @path [GET] /api/cli/v1/projects\n  #\n  # @response [object<projects: Array<object<slug: string, name: string>> >] 200 OK\n  # @response 401 Not authorized\n  def index\n    projects = current_user.projects.active.order(updated_at: :desc)\n\n    respond_with projects, each_serializer: UffizziCore::Api::Cli::V1::ShortProjectSerializer\n  end\n\n  # Get a project by slug\n  #\n  # @path [GET] /api/cli/v1/projects/{slug}\n  #\n  # @response <object< project: Project>> 200 OK\n  # @response 404 Not Found\n  # @response 401 Not authorized\n  def show\n    respond_with resource_project\n  end\n\n  # Delete a project\n  #\n  # @path [DELETE] /api/cli/v1/projects/{slug}\n  #\n  # @response 204 No content\n  # @response 404 Not Found\n  # @response 401 Not authorized\n\n  def destroy\n    resource_project.disable!\n\n    head :no_content\n  end\n\n  private\n\n  def project_params\n    params.require(:project)\n  end\n\n  def policy_context\n    current_project = current_user.projects.find_by(slug: params[:slug])\n    account = current_project&.account || current_user.default_account\n\n    UffizziCore::AccountContext.new(current_user, user_access_module, account, params)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/api/cli/v1/sessions_controller.rb",
    "content": "# frozen_string_literal: true\n\n# @resource Uffizzi\n\nclass UffizziCore::Api::Cli::V1::SessionsController < UffizziCore::Api::Cli::V1::ApplicationController\n  skip_before_action :authenticate_request!, only: [:create]\n\n  # Create session\n  #\n  # @path [POST] /api/cli/v1/session\n  #\n  # @parameter user(required,body)   [object<email: string, password: string >]\n  # @response [object<user: object<accounts: Array<object<id: integer, state: string>> >>] 201 Created successfully\n  # @response [object<errors: object<password: string >>] 422 Unprocessable entity\n  def create\n    session_form = UffizziCore::Api::Cli::V1::SessionCreateForm.new(session_params)\n\n    if session_form.valid?\n      sign_in(session_form.user)\n\n      return respond_with session_form.user\n    end\n\n    respond_with session_form\n  end\n\n  # Destroy session\n  #\n  # @path [DELETE] /api/cli/v1/session\n  #\n  # @response 204 No Content\n  def destroy\n    sign_out\n\n    head :no_content\n  end\n\n  private\n\n  def session_params\n    params.require(:user).permit(:email, :password)\n  end\nend\n"
  },
  {
    "path": "core/app/controllers/uffizzi_core/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ApplicationController < ActionController::Base\n  include Pundit::Authorization\n  include UffizziCore::ResponseService\n  include UffizziCore::AuthManagement\n  include UffizziCore::AuthorizationConcern\n  include UffizziCore::DependencyInjectionConcern\n\n  DEFAULT_PAGE = 1\n  DEFAULT_PER_PAGE = 20\n\n  protect_from_forgery with: :exception\n  RESCUABLE_EXCEPTIONS = [RuntimeError, TypeError, NameError, ArgumentError, SyntaxError].freeze\n  unless Rails.env.test?\n    rescue_from *RESCUABLE_EXCEPTIONS do |exception|\n      render_server_error(exception)\n    end\n  end\n  rescue_from ActiveRecord::RecordNotFound do |exception|\n    render_not_found(exception)\n  end\n\n  rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized\n\n  before_action :authenticate_request!\n  skip_before_action :verify_authenticity_token\n  respond_to :json\n\n  def render_not_authorized\n    render json: { errors: { title: [I18n.t('session.unauthorized')] } }, status: :forbidden\n  end\n\n  def policy_context\n    UffizziCore::BaseContext.new(current_user, user_access_module, params)\n  end\n\n  def self.responder\n    UffizziCore::JsonResponder\n  end\n\n  def render_not_found(exception)\n    resource = exception.model || 'Resource'\n    render json: { errors: { title: [\"#{resource} Not Found\"] } }, status: :not_found\n  end\n\n  def render_server_error(error)\n    render json: { errors: { title: [error] } }, status: :internal_server_error\n  end\n\n  def render_errors(errors)\n    json = { errors: errors }\n\n    render json: json, status: :unprocessable_entity\n  end\n\n  def q_param\n    params[:q] || ActionController::Parameters.new\n  end\n\n  def page\n    params[:page] || DEFAULT_PAGE\n  end\n\n  def per_page\n    params[:per_page] || DEFAULT_PER_PAGE\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/cluster_scale_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ClusterScaleError < StandardError\n  def initialize(action)\n    message = I18n.t('cluster.scaling_failed', action: action)\n\n    super(message)\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/compose_file/build_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::BuildError < UffizziCore::ComposeFileError\n  def initialize(message, extra_errors = {})\n    error_key = UffizziCore::ComposeFile::ErrorsService::TEMPLATE_BUILD_ERROR_KEY\n\n    super(message, error_key, extra_errors)\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/compose_file/credential_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::CredentialError < StandardError\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/compose_file/parse_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::ParseError < StandardError\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/compose_file/secrets_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::SecretsError < UffizziCore::ComposeFileError\n  def initialize(message, extra_errors = {})\n    error_key = UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY\n\n    super(message, error_key, extra_errors)\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/compose_file_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFileError < StandardError\n  attr_reader :errors\n\n  def initialize(message, error_key = nil, extra_errors = {})\n    if [NilClass, String].exclude?(error_key.class)\n      raise StandardError.new(\"#{self.class} arg 'error_key' should be a #{String} or #{NilClass}\")\n    end\n\n    unless extra_errors.is_a?(Hash)\n      raise StandardError.new(\"#{self.class} arg 'extra_errors' should be a #{Hash}\")\n    end\n\n    @errors = error_key.nil? ? {} : { error_key => message.to_s }.merge(extra_errors)\n\n    super(message)\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/container_registry_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistryError < StandardError\n  attr_reader :errors, :error_key\n\n  def initialize(response)\n    prepared_errors = prepare_errors(response[:body], response[:status])\n    @error_key = UffizziCore::ComposeFile::ErrorsService::DOCKER_REGISTRY_CONTAINER_ERROR_KEY\n    @errors = { @error_key => prepared_errors }\n\n    super(prepared_errors.to_json)\n  end\n\n  private\n\n  def prepare_errors(body, status)\n    parsed_body = JSON.parse!(body.to_s)\n\n    parsed_body.fetch('errors', parsed_body)\n  rescue JSON::ParserError, TypeError\n    msg = body.empty? ? I18n.t('registry.error', code: status) : body\n\n    { message: msg }\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/deployment/image_pull_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::ImagePullError < StandardError\n  attr_reader :deployment_id\n\n  def initialize(deployment_id)\n    super\n    @deployment_id = deployment_id\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/deployment_not_found_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DeploymentNotFoundError < StandardError\n  attr_reader :deployment_id\n\n  def initialize(deployment_id)\n    super\n    @deployment_id = deployment_id\n  end\nend\n"
  },
  {
    "path": "core/app/errors/uffizzi_core/registry_not_supported_error.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::RegistryNotSupportedError < StandardError\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/account/credential/check_credential_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Account::Credential::CheckCredentialForm\n  include UffizziCore::ApplicationFormWithoutActiveRecord\n\n  attribute :type\n  attribute :account\n\n  validate :credential_exists?\n\n  private\n\n  def credential_exists?\n    errors.add(:type, :exist) if account.credentials.by_type(type).exists?\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/account/credential/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Account::Credential::CreateForm < UffizziCore::Credential\n  include UffizziCore::ApplicationForm\n\n  permit :type, :registry_url, :username, :password\n\n  validates :password, presence: { message: :password_blank }\n  validate :check_registry_url, if: -> { errors[:password].empty? }\n  validate :check_credential_correctness, if: -> { errors[:password].empty? }\n  validate :credential_exists?, if: -> { errors[:password].empty? }\n\n  private\n\n  def check_registry_url\n    errors.add(:registry_url, :invalid_scheme) if URI.parse(registry_url).scheme.nil?\n  end\n\n  def check_credential_correctness\n    errors.add(:username, :incorrect, type: type.text) unless correct?\n  end\n\n  def credential_exists?\n    errors.add(:type, :exist) if account.credentials.by_type(type).exists?\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/account/credential/update_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Account::Credential::UpdateForm < UffizziCore::Credential\n  include UffizziCore::ApplicationForm\n\n  permit :registry_url, :username, :password\n\n  validates :password, presence: { message: :password_blank }\n  validate :check_registry_url, if: -> { errors[:password].empty? }\n  validate :check_credential_correctness, if: -> { errors[:password].empty? }\n\n  private\n\n  def check_registry_url\n    errors.add(:registry_url, :invalid_scheme) if URI.parse(registry_url).scheme.nil?\n  end\n\n  def check_credential_correctness\n    errors.add(:username, :incorrect) unless correct?\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/cluster/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Cluster::CreateForm < UffizziCore::Cluster\n  include UffizziCore::ApplicationForm\n\n  permit :name, :manifest, :creation_source\n\n  validate :check_manifest, if: -> { manifest.present? }\n\n  private\n\n  def check_manifest\n    YAML.load_stream(manifest)\n  rescue Psych::SyntaxError => e\n    err = [e.problem, e.context].compact.join(' ')\n\n    errors.add(:manifest, err)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/cluster/sync_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Cluster::SyncForm < UffizziCore::Cluster\n  include UffizziCore::ApplicationForm\n\n  permit :state\n\n  def sync_status\n    cluster_data = UffizziCore::ControllerService.show_cluster(self)\n\n    asleep_in_cluster = cluster_data.status.sleep\n    return unless deployed? || scaled_down?\n    return if actual_status?(asleep_in_cluster)\n\n    self.state = asleep_in_cluster ? UffizziCore::Cluster::STATE_SCALED_DOWN : UffizziCore::Cluster::STATE_DEPLOYED\n\n    self\n  end\n\n  private\n\n  def actual_status?(asleep_in_cluster)\n    (asleep_in_cluster && scaled_down?) || (!asleep_in_cluster && deployed?)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/compose_file/check_credentials_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ComposeFile::CheckCredentialsForm\n  include UffizziCore::ApplicationFormWithoutActiveRecord\n\n  attr_reader :type\n\n  attribute :compose_file\n  attribute :credentials\n\n  validate :check_containers_credentials\n\n  private\n\n  def check_containers_credentials\n    compose_content = Base64.decode64(compose_file.content)\n    compose_payload = { compose_file: compose_file }\n    compose_data = UffizziCore::ComposeFileService.parse(compose_content, compose_payload)\n\n    containers = compose_data[:containers]\n    containers.each do |container|\n      container_registry_service = UffizziCore::ContainerRegistryService.init_by_container(container, credentials)\n      @type = container_registry_service.type\n      next if container_registry_service.image_available?(credentials)\n\n      raise UffizziCore::ComposeFile::CredentialError.new(I18n.t('compose.unprocessable_image', value: type))\n    end\n  rescue UffizziCore::ContainerRegistryError => e\n    errors.add(:credentials, I18n.t('compose.unprocessable_image', value: type))\n    errors.add(e.error_key, e.message)\n  rescue UffizziCore::ComposeFile::CredentialError => e\n    errors.add(:credentials, e.message)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/compose_file/cli_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ComposeFile::CliForm\n  include UffizziCore::ApplicationFormWithoutActiveRecord\n\n  attribute :compose_content_data, Hash\n  attribute :compose_data, Hash\n  attribute :compose_dependencies, Array\n  attribute :compose_repositories, Array\n  attribute :content, String\n  attribute :source_kind, Symbol\n  attribute :compose_file, UffizziCore::ComposeFile\n\n  validates :content, presence: true\n  validates :compose_file, presence: true\n  validate :check_compose_parsed_data, if: -> { errors[:content].empty? }\n\n  def check_compose_parsed_data\n    compose_content = Base64.decode64(content)\n    compose_payload = { compose_file: compose_file }\n    self.compose_data = UffizziCore::ComposeFileService.parse(compose_content, compose_payload)\n  rescue UffizziCore::ComposeFile::ParseError => e\n    errors.add(:content, e.message)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/compose_file/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ComposeFile::CreateForm < UffizziCore::ComposeFile\n  include UffizziCore::ApplicationForm\n\n  permit :source, :path, :content\n\n  validates :source, presence: true\n  validates :path, presence: true\n  validates :content, presence: true\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/compose_file/template_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ComposeFile::TemplateForm\n  include UffizziCore::ApplicationFormWithoutActiveRecord\n\n  attribute :credentials\n  attribute :project, UffizziCore::Project\n  attribute :user, UffizziCore::User\n  attribute :compose_data, Hash\n  attribute :source, String\n  attribute :template_attributes, Hash\n  attribute :template_build_error, String\n  attribute :compose_dependencies, Array\n  attribute :compose_repositories, Array\n\n  validate :check_template_attributes\n\n  def assign_template_attributes!\n    self.template_attributes = UffizziCore::ComposeFileService.build_template_attributes(\n      compose_data,\n      source,\n      credentials,\n      project,\n      compose_dependencies,\n      compose_repositories,\n    )\n  rescue StandardError => e\n    self.template_build_error = e\n  end\n\n  private\n\n  def check_template_attributes\n    readable_errors = [\n      UffizziCore::ComposeFile::SecretsError,\n      UffizziCore::ComposeFile::BuildError,\n      UffizziCore::ContainerRegistryError,\n    ]\n\n    if readable_errors.include?(template_build_error.class)\n      template_build_error.errors.each { |k, v| errors.add(k, v) }\n\n      return\n    end\n\n    raise template_build_error if template_build_error.is_a?(StandardError)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/compose_file/update_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ComposeFile::UpdateForm < UffizziCore::ComposeFile\n  include UffizziCore::ApplicationForm\n\n  permit :content, :source, :path\n\n  validates :content, presence: true\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/config_file/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ConfigFile::CreateForm < UffizziCore::ConfigFile\n  include UffizziCore::ApplicationForm\n\n  permit :filename, :kind, :payload\n\n  validates :filename, presence: true\n  validates :kind, presence: true\n  validates :payload, presence: true\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/deployment/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Deployment::CreateForm < UffizziCore::Deployment\n  include UffizziCore::ApplicationForm\n  include UffizziCore::DependencyInjectionConcern\n\n  permit :creation_source,\n         :metadata,\n         containers_attributes: [\n           :image,\n           :service_name,\n           :tag,\n           :full_image_name,\n           :port,\n           :public,\n           :memory_limit,\n           :memory_request,\n           :entrypoint,\n           :command,\n           :receive_incoming_requests,\n           :continuously_deploy,\n           { variables: [:name, :value],\n             additional_subdomains: [],\n             secret_variables: [:name, :value],\n             volumes: [:source, :target, :type, :read_only],\n             healthcheck: [:test, :interval, :timeout, :retries, :start_period, :disable, { test: [] }],\n             repo_attributes: [\n               :namespace,\n               :name,\n               :slug,\n               :type,\n               :description,\n               :is_private,\n               :repository_id,\n               :branch,\n               :kind,\n               :dockerfile_path,\n               :dockerfile_context_path,\n               :deploy_preview_when_pull_request_is_opened,\n               :delete_preview_when_pull_request_is_closed,\n               :deploy_preview_when_image_tag_is_created,\n               :delete_preview_when_image_tag_is_updated,\n               :share_to_github,\n               :delete_preview_after,\n               { args: [:name, :value] },\n             ],\n             container_config_files_attributes: [\n               :config_file_id,\n               :mount_path,\n             ],\n             container_host_volume_files_attributes: [\n               :host_volume_file_id,\n               :source_path,\n             ] },\n         ]\n\n  validate :check_all_containers_have_unique_ports\n  validate :check_exists_ingress_container\n  validate :check_secrets_exist_in_database\n  validate :check_max_memory_limit\n\n  def assign_dependences!(project, user)\n    self.project = project\n\n    self.containers = containers.map do |container|\n      container.repo.project = project if !container.repo.nil?\n\n      container\n    end\n\n    self.deployed_by = user\n\n    self\n  end\n\n  private\n\n  def check_all_containers_have_unique_ports\n    active_containers = containers.select(&:active?)\n\n    errors.add(:containers, :duplicate_ports) unless UffizziCore::DeploymentService.all_containers_have_unique_ports?(active_containers)\n  end\n\n  def check_exists_ingress_container\n    active_containers = containers.select(&:active?)\n\n    errors.add(:containers, :incorrect_ingress_container) unless UffizziCore::DeploymentService.ingress_container?(active_containers)\n  end\n\n  def check_secrets_exist_in_database\n    return if compose_file.nil?\n\n    existing_secrets = project.secrets.pluck('name')\n    compose_file.template.payload['containers_attributes']\n      .map { |container| container['secret_variables'].map { |secret| secret['name'] } }.flatten.uniq\n      .select { |secret| existing_secrets.exclude?(secret) }\n      .each do |secret|\n      error_message = I18n.t('compose.project_secret_not_found', secret: secret)\n      errors.add(:secret_variables, error_message)\n    end\n  end\n\n  def check_max_memory_limit\n    return if deployment_memory_module.valid_memory_limit?(self)\n\n    deployment_memory_module.memory_limit_error_message(self)\n    errors.add(:containers, message)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/deployment/update_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Deployment::UpdateForm < UffizziCore::Deployment\n  include UffizziCore::ApplicationForm\n  include UffizziCore::DependencyInjectionConcern\n\n  permit :metadata,\n         containers_attributes: [\n           :image,\n           :service_name,\n           :tag,\n           :port,\n           :full_image_name,\n           :public,\n           :memory_limit,\n           :memory_request,\n           :entrypoint,\n           :command,\n           :receive_incoming_requests,\n           :continuously_deploy,\n           { variables: [:name, :value],\n             additional_subdomains: [],\n             secret_variables: [:name, :value],\n             volumes: [:source, :target, :type, :read_only],\n             healthcheck: [:test, :interval, :timeout, :retries, :start_period, :disable, { test: [] }],\n             repo_attributes: [\n               :namespace,\n               :name,\n               :slug,\n               :type,\n               :description,\n               :is_private,\n               :repository_id,\n               :branch,\n               :kind,\n               :dockerfile_path,\n               :dockerfile_context_path,\n               :deploy_preview_when_pull_request_is_opened,\n               :delete_preview_when_pull_request_is_closed,\n               :deploy_preview_when_image_tag_is_created,\n               :delete_preview_when_image_tag_is_updated,\n               :share_to_github,\n               :delete_preview_after,\n               { args: [:name, :value] },\n             ],\n             container_config_files_attributes: [\n               :config_file_id,\n               :mount_path,\n             ],\n             container_host_volume_files_attributes: [\n               :host_volume_file_id,\n               :source_path,\n             ] },\n         ]\n\n  validate :check_all_containers_have_unique_ports\n  validate :check_exists_ingress_container\n  validate :check_max_memory_limit\n\n  def assign_dependences!(project, user)\n    self.project = project\n\n    self.containers = containers.map do |container|\n      container.repo.project = project if !container.repo.nil?\n\n      container\n    end\n\n    self.deployed_by = user\n\n    self\n  end\n\n  private\n\n  def check_all_containers_have_unique_ports\n    active_containers = containers.select(&:active?)\n\n    errors.add(:containers, :duplicate_ports) unless UffizziCore::DeploymentService.all_containers_have_unique_ports?(active_containers)\n  end\n\n  def check_exists_ingress_container\n    active_containers = containers.select(&:active?)\n\n    errors.add(:containers, :incorrect_ingress_container) unless UffizziCore::DeploymentService.ingress_container?(active_containers)\n  end\n\n  def check_max_memory_limit\n    return if deployment_memory_module.valid_memory_limit?(self)\n\n    deployment_memory_module.memory_limit_error_message(self)\n    errors.add(:containers, message)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/project/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Project::CreateForm < UffizziCore::Project\n  include UffizziCore::ApplicationForm\n\n  permit :name, :slug, :description\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/project/update_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Project::UpdateForm < UffizziCore::Project\n  include UffizziCore::ApplicationForm\n\n  permit :name, :slug, :description\n\n  validates :name, presence: true, uniqueness: { scope: :account }\n  validates :slug, presence: true, uniqueness: true\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/secret/bulk_assign_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Secret::BulkAssignForm\n  include UffizziCore::ApplicationFormWithoutActiveRecord\n  MAX_SECRET_KEY_LENGTH = 256\n\n  attribute :secrets, Array\n  validate :check_duplicates\n  validate :check_length\n\n  def assign_secrets(new_secrets)\n    return if new_secrets.blank?\n\n    new_secrets.each do |new_secret|\n      secret = UffizziCore::Secret.new(name: new_secret['name'], value: new_secret['value'])\n      secrets.append(secret)\n    end\n  end\n\n  private\n\n  def check_duplicates\n    duplicates = []\n    groupped_secrets = secrets.group_by { |secret| secret['name'] }\n    groupped_secrets.each_pair do |key, value|\n      duplicates << key if value.size > 1\n    end\n\n    error_message = I18n.t('secrets.duplicates_exists', secrets: duplicates.join(', '))\n    errors.add(:secrets, error_message) if duplicates.present?\n  end\n\n  def check_length\n    secrets_with_invalid_key_length = secrets.select { |secret| secret['name'].length > MAX_SECRET_KEY_LENGTH }\n\n    error_message = I18n.t('secrets.invalid_key_length')\n    errors.add(:secrets, error_message) if secrets_with_invalid_key_length.present?\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/session_create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::SessionCreateForm\n  include UffizziCore::ApplicationFormWithoutActiveRecord\n\n  attribute :email, String\n  attribute :password, String\n\n  validates :email, :password, presence: true\n  validate :check_authenticate, if: :email\n\n  def user\n    @user ||= UffizziCore::User.active.by_email(email).first\n  end\n\n  def check_authenticate\n    return unless wrong_email_or_password?\n\n    errors.add(:password, 'Email or password is incorrect.')\n  end\n\n  private\n\n  def wrong_email_or_password?\n    return true if user.nil?\n\n    !user.authenticate(password)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/api/cli/v1/template/create_form.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Template::CreateForm < UffizziCore::Template\n  include UffizziCore::DependencyInjectionConcern\n\n  validate :check_max_memory_limit\n\n  def check_max_memory_limit\n    return if template_memory_module.valid_memory_limit?(self)\n\n    message = template_memory_module.memory_limit_error_message(self)\n    errors.add(:payload, message)\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/application_form.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ApplicationForm\n  extend ActiveSupport::Concern\n\n  include UffizziCore::MassAssignmentControlConcern\n\n  class_methods do\n    delegate :model_name, :name, to: :superclass\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/application_form_without_active_record.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ApplicationFormWithoutActiveRecord\n  extend ActiveSupport::Concern\n\n  included do\n    include ActiveModel::Validations\n    include ActiveModel::Conversion\n    include ActiveModel::Serialization\n    include ActiveModel::Validations::Callbacks\n    include ::Virtus.model\n  end\n\n  def persisted?\n    false\n  end\nend\n"
  },
  {
    "path": "core/app/forms/uffizzi_core/mass_assignment_control_concern.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::MassAssignmentControlConcern\n  extend ActiveSupport::Concern\n\n  class_methods do\n    def permit(*args)\n      @_args = args\n    end\n\n    def _args\n      @_args\n    end\n  end\n\n  def assign_attributes(attrs = ActionController::Parameters.new)\n    attrs = ActionController::Parameters.new if attrs.nil?\n\n    new_attrs = attrs.permit(*self.class._args)\n    super(new_attrs)\n  end\nend\n"
  },
  {
    "path": "core/app/helpers/uffizzi_core/application_helper.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore\n  module ApplicationHelper\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/account/create_credential_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Account::CreateCredentialJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :accounts, retry: 5\n\n  def perform(id)\n    credential = UffizziCore::Credential.find(id)\n    UffizziCore::AccountService.create_credential(credential)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/account/update_credential_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Account::UpdateCredentialJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :accounts, retry: 5\n\n  def perform(id)\n    credential = UffizziCore::Credential.find(id)\n    UffizziCore::AccountService.update_credential(credential)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/activity_item/docker/update_digest_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ActivityItem::Docker::UpdateDigestJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: 5\n\n  def perform(id)\n    activity_item = UffizziCore::ActivityItem.find(id)\n\n    UffizziCore::ActivityItemService.update_docker_digest!(activity_item)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/application_job.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore\n  class ApplicationJob\n    include Sidekiq::Worker\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/cluster/delete_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Cluster::DeleteJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :clusters,\n                  lock: :until_executed,\n                  retry: Settings.default_job_retry_count\n\n  def perform(id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS cluster_id=#{id} DeleteJob\")\n\n    cluster = UffizziCore::Cluster.find(id)\n    UffizziCore::ControllerService.delete_namespace(cluster)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/cluster/deploy_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Cluster::DeployJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count\n\n  def perform(id)\n    cluster = UffizziCore::Cluster.find(id)\n\n    UffizziCore::ClusterService.deploy_cluster(cluster)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/cluster/manage_deploying_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Cluster::ManageDeployingJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count\n\n  def perform(id, try = 1)\n    cluster = UffizziCore::Cluster.find(id)\n\n    UffizziCore::ClusterService.manage_deploying(cluster, try)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/cluster/manage_scaling_down_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Cluster::ManageScalingDownJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count\n\n  def perform(id)\n    cluster = UffizziCore::Cluster.find(id)\n\n    UffizziCore::ClusterService.manage_scale_down(cluster)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/cluster/manage_scaling_up_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Cluster::ManageScalingUpJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :clusters, retry: Settings.default_job_retry_count\n\n  def perform(id, try = 1)\n    cluster = UffizziCore::Cluster.find(id)\n\n    UffizziCore::ClusterService.manage_scale_up(cluster, try)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/config_file/apply_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ConfigFile::ApplyJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :config_files, retry: Settings.controller.resource_create_retry_count\n\n  sidekiq_retry_in do |count, exception|\n    case exception\n    when UffizziCore::DeploymentNotFoundError\n      Rails.logger.info(\"DEPLOYMENT_PROCESS ApplyJob retry deployment_id=#{exception.deployment_id} count=#{count}\")\n      Settings.controller.resource_create_retry_time\n    else\n      if [Settings.default_job_retry_count, Settings.controller.resource_create_retry_count].include?(count)\n        Sentry.capture_exception(exception)\n        :kill\n      else\n        Settings.controller.resource_create_retry_time\n      end\n    end\n  end\n\n  def perform(deployment_id, config_file_id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment_id} Apply ConfigFile(config_file_id:#{config_file_id})\")\n\n    deployment = UffizziCore::Deployment.find(deployment_id)\n    config_file = UffizziCore::ConfigFile.find(config_file_id)\n    if deployment.disabled?\n      Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled stop config file applying\")\n      return\n    end\n\n    unless UffizziCore::ControllerService.namespace_exists?(deployment)\n      raise UffizziCore::DeploymentNotFoundError,\n            deployment_id\n    end\n\n    UffizziCore::ControllerService.apply_config_file(deployment, config_file)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/create_credential_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::CreateCredentialJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: Settings.controller.resource_create_retry_count\n\n  sidekiq_retry_in do |count, exception|\n    case exception\n    when UffizziCore::DeploymentNotFoundError\n      Rails.logger.info(\"DEPLOYMENT_PROCESS CreateCredentialJob retry deployment_id=#{exception.deployment_id} count=#{count}\")\n      Settings.controller.resource_create_retry_time\n    else\n      if [Settings.default_job_retry_count, Settings.controller.resource_create_retry_count].include?(count)\n        Sentry.capture_exception(exception)\n        :kill\n      else\n        Settings.controller.resource_create_retry_time\n      end\n    end\n  end\n\n  def perform(deployment_id, credential_id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment_id} CreateCredentialJob(cred_id:#{credential_id})\")\n\n    deployment = UffizziCore::Deployment.find(deployment_id)\n    credential = UffizziCore::Credential.find(credential_id)\n\n    if deployment.disabled?\n      Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled stop creating credential\")\n      return\n    end\n\n    unless UffizziCore::ControllerService.namespace_exists?(deployment)\n      raise UffizziCore::DeploymentNotFoundError,\n            deployment_id\n    end\n\n    UffizziCore::ControllerService.apply_credential(deployment, credential)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/create_credentials_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::CreateCredentialsJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: 5\n\n  def perform(deployment_id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment_id} CreateCredentialsJob\")\n\n    deployment = UffizziCore::Deployment.find(deployment_id)\n\n    credentials = deployment.project.account.credentials.deployable\n\n    credentials.each do |credential|\n      UffizziCore::Deployment::CreateCredentialJob.perform_async(deployment.id, credential.id)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/create_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::CreateJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: 5\n\n  def perform(id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{id} CreateJob\")\n\n    deployment = UffizziCore::Deployment.find(id)\n\n    UffizziCore::ControllerService.create_namespace(deployment)\n\n    UffizziCore::Deployment::CreateCredentialsJob.perform_async(deployment.id)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/delete_credential_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::DeleteCredentialJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: 5\n\n  def perform(deployment_id, credential_id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment_id} DeleteCredentialJob(cred_id:#{credential_id})\")\n\n    deployment = UffizziCore::Deployment.find(deployment_id)\n    credential = UffizziCore::Credential.find(credential_id)\n    UffizziCore::ControllerService.delete_credential(deployment, credential)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/delete_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::DeleteJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :disable_deployments,\n                  lock: :until_executed,\n                  retry: Settings.default_job_retry_count\n\n  def perform(id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{id} DeleteJob\")\n\n    deployment = UffizziCore::Deployment.find(id)\n    UffizziCore::ControllerService.delete_namespace(deployment)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/deploy_containers_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::DeployContainersJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: Settings.controller.resource_create_retry_count\n\n  sidekiq_retry_in do |count, exception|\n    case exception\n    when UffizziCore::DeploymentNotFoundError\n      Rails.logger.info(\"DEPLOYMENT_PROCESS DeployContainersJob retry deployment_id=#{exception.deployment_id} count=#{count}\")\n      Settings.controller.resource_create_retry_time\n    else\n      if [Settings.default_job_retry_count, Settings.controller.resource_create_retry_count].include?(count)\n        Sentry.capture_exception(exception)\n        :kill\n      else\n        Settings.controller.resource_create_retry_time\n      end\n    end\n  end\n\n  def perform(id, repeated = false)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{id} DeployContainersJob\")\n\n    deployment = UffizziCore::Deployment.find(id)\n    if deployment.disabled?\n      Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled stop deploying\")\n      return\n    end\n\n    raise UffizziCore::DeploymentNotFoundError, id unless UffizziCore::ControllerService.namespace_exists?(deployment)\n\n    UffizziCore::DeploymentService.deploy_containers(deployment, repeated)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/manage_deploy_activity_item_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::ManageDeployActivityItemJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: Settings.default_job_retry_count\n\n  sidekiq_retry_in do |count, exception|\n    case exception\n    when UffizziCore::Deployment::ImagePullError\n      Rails.logger.info(\"DEPLOYMENT_PROCESS ManageDeployActivityItemJob retry deployment_id=#{exception.deployment_id} count=#{count}\")\n      Settings.controller.resource_create_retry_time\n    when ActiveRecord::RecordNotFound\n      :kill if exception.model.constantize == UffizziCore::ActivityItem\n    else\n      if count == Settings.default_job_retry_count\n        Sentry.capture_exception(exception)\n        :kill\n      else\n        Settings.controller.resource_create_retry_time\n      end\n    end\n  end\n\n  sidekiq_retries_exhausted do |msg, exception|\n    case exception\n    when UffizziCore::Deployment::ImagePullError\n      Rails.logger.info(\"DEPLOYMENT_PROCESS ManageDeployActivityItemJob exhausted #{msg.inspect} #{exception.inspect}\")\n\n      activity_item_id = msg['args'].first\n      activity_item = UffizziCore::ActivityItem.find(activity_item_id)\n      UffizziCore::ActivityItemService.fail_deployment!(activity_item)\n    end\n  end\n\n  def perform(activity_item_id)\n    activity_item = UffizziCore::ActivityItem.find(activity_item_id)\n    container = activity_item.container\n\n    if container.disabled?\n      logger_message = \"DEPLOYMENT_PROCESS deployment_id=#{container.deployment_id} activity_item_id=#{activity_item.id}\n      deployment was disabled stop monitoring\"\n      Rails.logger.info(logger_message)\n      return\n    end\n\n    UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)\n  end\nend\n"
  },
  {
    "path": "core/app/jobs/uffizzi_core/deployment/update_credential_job.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::UpdateCredentialJob < UffizziCore::ApplicationJob\n  sidekiq_options queue: :deployments, retry: Settings.controller.resource_update_retry_count\n\n  sidekiq_retry_in do |count, exception|\n    case exception\n    when UffizziCore::DeploymentNotFoundError\n      Rails.logger.info(\"DEPLOYMENT_PROCESS UpdateCredentialJob retry deployment_id=#{exception.deployment_id} count=#{count}\")\n      Settings.controller.resource_update_retry_time\n    else\n      if [Settings.default_job_retry_count, Settings.controller.resource_update_retry_count].include?(count)\n        Sentry.capture_exception(exception)\n        :kill\n      else\n        Settings.controller.resource_create_retry_time\n      end\n    end\n  end\n\n  def perform(deployment_id, credential_id)\n    Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment_id} UpdateCredentialJob(cred_id:#{credential_id})\")\n\n    deployment = UffizziCore::Deployment.find(deployment_id)\n    credential = UffizziCore::Credential.find(credential_id)\n\n    if deployment.disabled?\n      Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment was disabled. Stop updating credential\")\n      return\n    end\n\n    unless UffizziCore::ControllerService.namespace_exists?(deployment)\n      raise UffizziCore::DeploymentNotFoundError,\n            deployment_id\n    end\n\n    UffizziCore::ControllerService.apply_credential(deployment, credential)\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/account.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Account\n  extend ActiveSupport::Concern\n\n  # rubocop:disable Metrics/BlockLength\n  included do\n    include AASM\n    include UffizziCore::AccountRepo\n    include UffizziCore::StateMachineConcern\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:accounts]\n\n    enumerize :kind, in: [:personal, :organizational], scope: true, predicates: true\n    validates :kind, presence: true\n    validates :domain, uniqueness: true, if: :domain\n\n    has_many :memberships, dependent: :destroy\n    has_many :users, through: :memberships\n    has_many :credentials, dependent: :destroy\n\n    has_many :projects, dependent: :destroy\n    has_many :deployments, through: :projects\n    has_many :payments, dependent: :destroy\n    has_many :clusters, through: :projects\n\n    aasm(:sso_state) do\n      state :connection_not_configured, initial: true\n      state :connection_disabled\n      state :connection_active\n\n      event :activate_connection do\n        transitions from: [:connection_not_configured, :connection_disabled], to: :connection_active\n      end\n\n      event :deactivate_connection do\n        transitions from: [:connection_active], to: :connection_disabled\n      end\n\n      event :reset_connection do\n        transitions from: [:connection_active, :connection_disabled], to: :connection_not_configured\n      end\n    end\n\n    def update_payment_issue_date\n      update(payment_issue_at: DateTime.current)\n    end\n\n    def active_projects\n      projects.active\n    end\n\n    def disable_projects\n      active_projects.each(&:disable_deployments)\n    end\n\n    #  This method is deprecated. Don't use it.\n    def user\n      users.find_by(memberships: { role: UffizziCore::Membership.role.admin })\n    end\n  end\n  # rubocop:enable Metrics/BlockLength\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/activity_item.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::ActivityItem\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::ActivityItemRepo\n\n    self.table_name = UffizziCore.table_names[:activity_items]\n\n    belongs_to :deployment\n    belongs_to :container\n    belongs_to :build, optional: true\n\n    has_many :events, dependent: :destroy\n\n    def docker?\n      type == UffizziCore::ActivityItem::Docker.name\n    end\n\n    def image\n      [namespace, name].compact.join('/')\n    end\n\n    def full_image\n      return \"#{image}:#{tag}\" if docker?\n\n      ''\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/cluster.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Cluster\n  extend ActiveSupport::Concern\n  include UffizziCore::ClusterRepo\n\n  NAMESPACE_PREFIX = 'c'\n\n  # rubocop:disable Metrics/BlockLength\n  included do\n    include AASM\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:clusters]\n\n    belongs_to :project, class_name: UffizziCore::Project.name\n    belongs_to :deployed_by, class_name: UffizziCore::User.name, foreign_key: :deployed_by_id, optional: true\n    validates_uniqueness_of :name, conditions: -> { enabled }, scope: :project_id\n    validates :name, presence: true, format: { with: /\\A[a-zA-Z0-9-]*\\z/ }\n\n    enumerize :creation_source, in: UffizziCore.cluster_creation_sources, scope: true, predicates: true\n    attribute :creation_source, :string, default: :manual\n    validates :creation_source, presence: true\n    belongs_to :kubernetes_distribution, optional: true\n\n    aasm(:state) do\n      state :deploying_namespace, initial: true\n      state :failed_deploy_namespace\n      state :deploying\n      state :deployed\n      state :scaling_down\n      state :scaled_down\n      state :scaling_up\n      state :failed_scale_up\n      state :failed\n      state :disabled\n\n      event :start_deploying do\n        transitions from: [:deploying_namespace], to: :deploying\n      end\n\n      event :fail_deploy_namespace do\n        transitions from: [:deploying_namespace], to: :failed_deploy_namespace\n      end\n\n      event :finish_deploy do\n        transitions from: [:deploying], to: :deployed\n      end\n\n      event :start_scaling_down do\n        transitions from: [:deployed], to: :scaling_down\n      end\n\n      event :scale_down do\n        transitions from: [:scaling_down], to: :scaled_down\n      end\n\n      event :start_scaling_up do\n        transitions from: [:scaled_down, :failed_scale_up], to: :scaling_up\n      end\n\n      event :scale_up do\n        transitions from: [:scaling_up], to: :deployed\n      end\n\n      event :fail_scale_up do\n        transitions from: [:scaling_up], to: :failed_scale_up\n      end\n\n      event :fail do\n        transitions from: [:deploying], to: :failed\n      end\n\n      event :disable, after: :after_disable do\n        transitions from: [\n          :deploying_namespace,\n          :failed_deploy_namespace,\n          :deploying,\n          :deployed,\n          :scaling_down,\n          :scaling_up,\n          :scaled_down,\n          :failed,\n        ], to: :disabled\n      end\n    end\n\n    def after_disable\n      UffizziCore::Cluster::DeleteJob.perform_async(id)\n    end\n\n    def namespace\n      [NAMESPACE_PREFIX, id].join\n    end\n  end\n  # rubocop:enable Metrics/BlockLength\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/comment.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Comment\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::CommentRepo\n\n    self.table_name = UffizziCore.table_names[:comments]\n\n    has_ancestry(cache_depth: true)\n    const_set(:MAX_DEPTH_LEVEL, 1)\n\n    belongs_to :user\n    belongs_to :commentable, polymorphic: true\n\n    validates :content, length: { maximum: 1500 }, presence: true\n    validates :ancestry_depth, numericality: { less_than_or_equal_to: self::MAX_DEPTH_LEVEL, only_integer: true }\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/compose_file.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::ComposeFile\n  extend ActiveSupport::Concern\n\n  LOCAL_SOURCE = :local\n\n  included do\n    include UffizziCore::ComposeFileRepo\n    include AASM\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:compose_files]\n\n    belongs_to :project\n    belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id, optional: true\n\n    has_one :template, dependent: :destroy\n    has_many :config_files, dependent: :destroy\n    has_many :host_volume_files, dependent: :destroy\n    has_many :deployments, dependent: :nullify\n\n    enumerize :kind, in: UffizziCore.compose_file_kinds, predicates: true, scope: :shallow, default: :main\n\n    validates :source, presence: true\n    validate :main_compose_file_uniqueness, on: :create, if: -> { kind.main? }\n\n    aasm(:auto_deploy) do\n      state :disabled, initial: true\n      state :enabled\n\n      event :enable do\n        transitions from: [:disabled], to: :enabled\n      end\n\n      event :disable do\n        transitions from: [:enabled], to: :disabled\n      end\n    end\n\n    aasm(:state) do\n      state :valid_file, initial: true\n      state :invalid_file\n\n      event :set_valid do\n        transitions from: [:invalid_file], to: :valid_file\n      end\n\n      event :set_invalid do\n        transitions from: [:valid_file], to: :invalid_file\n      end\n    end\n\n    def local_source?\n      repository_id.nil? && branch.nil?\n    end\n\n    def source_kind\n      return LOCAL_SOURCE if local_source?\n    end\n\n    private\n\n    def main_compose_file_uniqueness\n      return unless project.compose_files.main.exists?\n\n      errors.add(:compose_file, 'A compose file already exist for this project')\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/config_file.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::ConfigFile\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::ConfigFileRepo\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:config_files]\n\n    belongs_to :project\n    belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id, optional: true\n    belongs_to :compose_file, optional: true\n\n    has_many :container_config_files, dependent: :destroy\n\n    enumerize :kind, in: [:config_map, :secret], default: :config_map, predicates: true\n    enumerize :creation_source, in: [:manual, :compose_file, :system], predicates: true, scope: true\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/container.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Container\n  extend ActiveSupport::Concern\n  REQUEST_MEMORY_RATIO = 4\n\n  # rubocop:disable Metrics/BlockLength\n  included do\n    include UffizziCore::ContainerRepo\n    include AASM\n    include UffizziCore::StateMachineConcern\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:containers]\n\n    enumerize :kind, in: [:internal, :user], predicates: true\n\n    belongs_to :deployment, touch: true\n    belongs_to :repo, optional: true\n\n    has_many :activity_items, dependent: :destroy\n    has_many :container_config_files, dependent: :destroy\n    has_many :config_files, through: :container_config_files\n    has_many :container_host_volume_files, dependent: :destroy\n    has_many :host_volume_files, through: :container_host_volume_files\n\n    attribute :public, :boolean, default: false\n    attribute :port, :integer, default: nil\n\n    enumerize :source, in: [:github], skip_validations: true\n    validates :port,\n              presence: true,\n              inclusion: { in: 0..65_535 },\n              uniqueness: { scope: :deployment_id },\n              if: :should_check_port\n\n    after_commit :check_target_port, on: :create\n\n    before_save :update_target_port_value, if: :will_save_change_to_port?\n    before_create :set_defaults\n\n    accepts_nested_attributes_for :repo\n    accepts_nested_attributes_for :container_config_files, allow_destroy: true\n    accepts_nested_attributes_for :container_host_volume_files, allow_destroy: true\n\n    validates :variables, 'uffizzi_core/environment_variable_list': true, allow_nil: true\n    validates :secret_variables, 'uffizzi_core/environment_variable_list': true, allow_nil: true\n    validates :entrypoint, 'uffizzi_core/image_command_args': true, allow_nil: true\n    validates :command, 'uffizzi_core/image_command_args': true, allow_nil: true\n    validates :tag, presence: true\n\n    aasm :continuously_deploy, column: :continuously_deploy, namespace: :cd do\n      state :disabled, initial: true\n      state :enabled\n    end\n\n    aasm :state, column: :state do\n      state :active, initial: true\n      state :disabled\n\n      event :activate do\n        transitions from: [:disabled], to: :active\n      end\n\n      event :disable do\n        transitions from: [:active], to: :disabled\n      end\n    end\n\n    def image_name\n      \"#{image}:#{tag}\"\n    end\n\n    private\n\n    def should_check_port\n      public && active?\n    end\n\n    def set_defaults\n      update_target_port_value\n    end\n\n    def update_target_port_value\n      self.target_port = UffizziCore::ContainerService.target_port_value(self)\n    end\n\n    def check_target_port\n      if target_port && deployment.containers.where(target_port: target_port).size > 1\n        update(target_port: UffizziCore::DeploymentService.find_unused_port(deployment))\n      end\n    end\n  end\n  # rubocop:enable Metrics/BlockLength\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/container_config_file.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::ContainerConfigFile\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:container_config_files]\n\n    belongs_to :container\n    belongs_to :config_file\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/container_host_volume_file.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::ContainerHostVolumeFile\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:container_host_volume_files]\n\n    belongs_to :container\n    belongs_to :host_volume_file\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/coupon.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Coupon\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:coupons]\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/credential.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Credential\n  extend ActiveSupport::Concern\n\n  # rubocop:disable Metrics/BlockLength\n  included do\n    include AASM\n    include UffizziCore::CredentialRepo\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:credentials]\n\n    const_set(:CREDENTIAL_TYPES, [\n                'UffizziCore::Credential::Amazon',\n                'UffizziCore::Credential::Azure',\n                'UffizziCore::Credential::DockerHub',\n                'UffizziCore::Credential::DockerRegistry',\n                'UffizziCore::Credential::GithubContainerRegistry',\n                'UffizziCore::Credential::Google',\n              ])\n\n    belongs_to :account\n\n    before_destroy :remove_token\n\n    validates :registry_url, presence: true\n\n    aasm :state, column: :state do\n      state :not_connected, initial: true\n      state :active\n      state :unauthorized\n\n      event :activate do\n        transitions from: [:not_connected, :unauthorized], to: :active\n      end\n\n      event :unauthorize do\n        transitions from: [:not_connected, :active], to: :unauthorized\n      end\n\n      event :disconnect do\n        transitions from: [:active, :unauthorized], to: :not_connected\n      end\n    end\n\n    UffizziCore::ContainerRegistryService.sources.each do |t|\n      define_method :\"#{t}?\" do\n        type == \"UffizziCore::Credential::#{t.to_s.camelize}\"\n      end\n    end\n\n    def correct?\n      credential = self\n      return false unless credential\n\n      container_registry_service = UffizziCore::ContainerRegistryService.init_by_subclass(credential.type)\n      status = container_registry_service.credential_correct?(credential)\n\n      if credential.persisted? && credential.active? && !status\n        Rails.logger.warn(\"Wrong credential: credential_correct? credential_id=#{credential.id}\")\n      end\n\n      status\n    end\n\n    private\n\n    def remove_token\n      account.projects.find_each do |project|\n        project.deployments.find_each do |deployment|\n          containers = deployment.containers\n          attributes = { continuously_deploy: UffizziCore::Container::STATE_CD_DISABLED }\n\n          containers.with_docker_hub_repo.update_all(attributes) if docker_hub?\n        end\n      end\n    end\n  end\n  # rubocop:enable Metrics/BlockLength\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/deployment.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Deployment\n  extend ActiveSupport::Concern\n\n  # rubocop:disable Metrics/BlockLength\n  included do\n    include AASM\n    include UffizziCore::StateMachineConcern\n    include UffizziCore::DeploymentRepo\n    extend Enumerize\n    include UffizziCore::DependencyInjectionConcern\n\n    self.table_name = UffizziCore.table_names[:deployments]\n\n    enumerize :kind, in: [:standard], predicates: true, default: :standard\n\n    belongs_to :project, touch: true\n    belongs_to :deployed_by, class_name: UffizziCore::User.name, foreign_key: :deployed_by_id, optional: true\n    belongs_to :template, optional: true\n    belongs_to :compose_file, optional: true\n\n    has_many :credentials, through: :project\n    has_many :containers, dependent: :destroy, index_errors: true\n    has_many :activity_items, dependent: :destroy\n    has_many :deployment_events, dependent: :destroy\n\n    has_one :ingress_container, -> { where(receive_incoming_requests: true) }, class_name: UffizziCore::Container.name\n    validates :kind, presence: true\n\n    enumerize :creation_source, in: [:manual, :demo, :continuous_preview, :compose_file_manual, :compose_file_continuous_preview,\n                                     :github_actions, :gitlab_actions], predicates: true, scope: true, default: :manual\n\n    accepts_nested_attributes_for :containers, allow_destroy: true\n\n    after_destroy_commit :clean\n\n    def active_containers\n      containers.active\n    end\n\n    aasm(:state) do\n      state :active, initial: true\n      state :failed\n      state :disabled\n\n      event :activate, after: :after_activate do\n        transitions from: [:disabled, :failed], to: :active\n      end\n\n      event :fail do\n        transitions from: [:active], to: :failed\n      end\n\n      event :disable, after: :after_disable do\n        transitions from: [:active, :failed], to: :disabled\n      end\n    end\n\n    def after_activate\n      update!(disabled_at: nil)\n    end\n\n    def after_disable\n      clean\n      update!(disabled_at: Time.now)\n    end\n\n    def clean\n      active_containers.each(&:disable!)\n      UffizziCore::Deployment::DeleteJob.perform_async(id)\n    end\n\n    def preview_url\n      managed_dns_zone = controller_settings_service.deployment_settings_by_deployment(self).managed_dns_zone\n      \"#{subdomain}.#{managed_dns_zone}\"\n    end\n\n    def namespace\n      \"deployment-#{id}\"\n    end\n  end\n  # rubocop:enable Metrics/BlockLength\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/deployment_event.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::DeploymentEvent\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:deployment_events]\n\n    belongs_to :deployment\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/event.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Event\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::EventRepo\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:events]\n\n    enumerize :state, in: UffizziCore.event_states, predicates: true, scope: true\n\n    belongs_to :activity_item, touch: true\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/host_volume_file.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::HostVolumeFile\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::HostVolumeFileRepo\n\n    self.table_name = UffizziCore.table_names[:host_volume_files]\n\n    belongs_to :project\n    belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id, optional: true\n    belongs_to :compose_file, optional: true\n\n    has_many :container_host_volume_files, dependent: :destroy\n\n    validates :source, presence: true\n    validates :path, presence: true\n    validates :payload, presence: true\n    validates :is_file, inclusion: [true, false]\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/kubernetes_distribution.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::KubernetesDistribution\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:kubernetes_distributions]\n\n    has_many :clusters\n\n    def self.default\n      find_by(default: true)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/membership.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Membership\n  extend ActiveSupport::Concern\n\n  included do\n    include AASM\n    include UffizziCore::StateMachineConcern\n    include UffizziCore::MembershipRepo\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:memberships]\n\n    enumerize :role, in: [:admin, :developer, :viewer], predicates: true\n    validates :role, presence: true\n\n    belongs_to :account\n    belongs_to :user\n\n    validates :role, presence: true\n\n    aasm(:state) do\n      state :active, initial: true\n      state :blocked\n      state :inactive\n\n      event :activate do\n        transitions from: [:blocked, :inactive], to: :active\n      end\n\n      event :deactivate do\n        transitions from: [:active], to: :inactive\n      end\n\n      event :block do\n        transitions from: [:active], to: :blocked\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/payment.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Payment\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:payments]\n\n    belongs_to :account\n\n    scope :succeeded, -> { where(status: :succeeded) }\n    scope :pending, -> { where(status: :pending) }\n    scope :failed, -> { where(status: :failed) }\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/price.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Price\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::PriceRepo\n\n    self.table_name = UffizziCore.table_names[:prices]\n\n    belongs_to :product\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/product.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Product\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::ProductRepo\n\n    self.table_name = UffizziCore.table_names[:products]\n\n    has_one :price, dependent: :destroy\n\n    UffizziCore::Product.inheritance_column = :sti\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/project.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Project\n  extend ActiveSupport::Concern\n\n  # rubocop:disable Metrics/BlockLength\n  included do\n    include AASM\n    include UffizziCore::StateMachineConcern\n    include UffizziCore::ProjectRepo\n\n    self.table_name = UffizziCore.table_names[:projects]\n\n    belongs_to :account\n\n    has_many :repos, dependent: :destroy\n    has_many :deployments, dependent: :destroy\n    has_many :user_projects, dependent: :destroy\n    has_many :users, through: :user_projects\n    has_many :config_files, dependent: :destroy\n    has_many :templates, dependent: :destroy\n    has_many :credentials, through: :account\n    has_many :compose_files, dependent: :destroy\n    has_many :secrets, dependent: :destroy, as: :resource\n    has_many :host_volume_files, dependent: :destroy\n    has_many :clusters, dependent: :destroy\n\n    validates :name, presence: true\n    validates_uniqueness_of :name, conditions: -> { where(state: :active) }, scope: :account_id\n    validates :slug, presence: true, uniqueness: true\n\n    aasm(:state) do\n      state :active, initial: true\n      state :disabled\n\n      event :activate do\n        transitions from: [:disabled], to: :active\n      end\n\n      event :disable, after: :after_disable do\n        transitions from: [:active], to: :disabled\n      end\n    end\n\n    def after_disable\n      disable_deployments\n      disable_clusters\n    end\n\n    def active_deployments\n      deployments.enabled\n    end\n\n    def active_clusters\n      clusters.enabled\n    end\n\n    def disable_deployments\n      active_deployments.each do |deployment|\n        UffizziCore::DeploymentService.disable!(deployment)\n        deployment.deployment_events.create!(deployment_state: deployment.state, message: 'Disabled after project was disabled')\n      end\n    end\n\n    def disable_clusters\n      active_clusters.each(&:disable!)\n    end\n\n    def compose_file\n      compose_files.main.first\n    end\n  end\n  # rubocop:enable Metrics/BlockLength\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/rating.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Rating\n  extend ActiveSupport::Concern\n\n  included do\n    include AASM\n\n    self.table_name = UffizziCore.table_names[:ratings]\n\n    aasm(:state) do\n      state :active, initial: true\n      state :disabled\n\n      event :activate do\n        transitions from: [:disabled], to: :active\n      end\n\n      event :disable do\n        transitions from: [:active], to: :disabled\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Repo\n  extend ActiveSupport::Concern\n\n  included do\n    extend Enumerize\n    include UffizziCore::RepoRepo\n\n    self.table_name = UffizziCore.table_names[:repos]\n\n    enumerize :kind, in: [:buildpacks18, :dockerfile, :dotnet, :gatsby, :barestatic], predicates: true\n\n    belongs_to :project\n    has_one :container, inverse_of: :repo, dependent: :destroy\n\n    validates :dockerfile_path, presence: true, if: :dockerfile?\n    validates :delete_preview_after, numericality: { greater_than: 0, only_integer: true }, allow_nil: true\n\n    def docker_hub?\n      type == UffizziCore::Repo::DockerHub.name\n    end\n\n    def azure?\n      type == UffizziCore::Repo::Azure.name\n    end\n\n    def google?\n      type == UffizziCore::Repo::Google.name\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/role.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Role\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:roles]\n\n    has_and_belongs_to_many :users, join_table: UffizziCore.table_names[:users_roles]\n\n    belongs_to :resource,\n               polymorphic: true,\n               optional: true\n\n    validates :resource_type,\n              inclusion: { in: Rolify.resource_types },\n              allow_nil: true\n\n    scopify\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/secret.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Secret\n  extend ActiveSupport::Concern\n\n  included do\n    self.table_name = UffizziCore.table_names[:secrets]\n\n    belongs_to :resource, polymorphic: true\n\n    validates :name, presence: true, uniqueness: { scope: :resource }\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/template.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::Template\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::TemplateRepo\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:templates]\n\n    belongs_to :added_by, class_name: UffizziCore::User.name, foreign_key: :added_by_id\n    belongs_to :project, touch: true\n    belongs_to :compose_file, optional: true\n\n    has_many :deployments, dependent: :nullify\n\n    enumerize :creation_source, in: [:manual, :compose_file, :system], predicates: true, scope: true\n\n    validates :name, presence: true\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/user.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::User\n  extend ActiveSupport::Concern\n\n  included do\n    include AASM\n    include ActiveModel::Validations\n    include UffizziCore::StateMachineConcern\n    include UffizziCore::HashidConcern\n    include UffizziCore::UserRepo\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:users]\n\n    rolify({ role_cname: UffizziCore::Role.name, role_join_table_name: UffizziCore.table_names[:users_roles] })\n\n    has_many :memberships, dependent: :destroy\n    has_many :accounts, through: :memberships\n    has_many :user_projects, dependent: :destroy\n    has_many :projects, through: :user_projects\n    has_many :deployments, class_name: UffizziCore::Deployment.name, foreign_key: :deployed_by_id, dependent: :nullify\n    has_many :clusters, foreign_key: :deployed_by_id\n\n    has_one_attached :avatar\n\n    enumerize :creation_source, in: UffizziCore.user_creation_sources, predicates: true\n\n    def default_account\n      personal_account\n    end\n\n    def personal_account\n      accounts.personal.find_by(owner_id: id)\n    end\n\n    def full_name\n      \"#{first_name} #{last_name}\"\n    end\n\n    aasm(:state) do\n      state :initial, initial: true\n      state :active\n      state :disabled\n\n      event :activate do\n        transitions from: [:initial, :disabled], to: :active\n      end\n\n      event :disable do\n        transitions from: [:initial, :active], to: :disabled\n      end\n    end\n\n    def admin_access_to_project?(project)\n      project.user_projects.where(user_id: id, role: UffizziCore::UserProject.role.admin).exists?\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/concerns/models/user_project.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Concerns::Models::UserProject\n  extend ActiveSupport::Concern\n\n  included do\n    extend Enumerize\n\n    self.table_name = UffizziCore.table_names[:user_projects]\n\n    enumerize :role, in: UffizziCore.user_project_roles, predicates: true, scope: true\n    validates :role, presence: true\n\n    belongs_to :user\n    belongs_to :project\n    belongs_to :invited_by, class_name: UffizziCore::User.name, foreign_key: :invited_by_id, optional: true\n  end\nend\n"
  },
  {
    "path": "core/app/lib/uffizzi_core/rbac/user_access_service.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Rbac::UserAccessService\n  class << self\n    def admin_access_to_account?(_user, _account)\n      true\n    end\n\n    def any_access_to_account?(_user, _account)\n      true\n    end\n\n    def admin_or_developer_access_to_account?(_user, _account)\n      true\n    end\n\n    def admin_or_developer_access_to_project?(_user, _project)\n      true\n    end\n\n    def any_access_to_project?(_user, _project)\n      true\n    end\n\n    def admin_access_to_project?(_user, _project)\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/mailers/uffizzi_core/application_mailer.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore\n  class ApplicationMailer < ActionMailer::Base\n    default from: 'from@example.com'\n    layout 'mailer'\n  end\nend\n"
  },
  {
    "path": "core/app/models/concerns/uffizzi_core/hashid_concern.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::HashidConcern\n  extend ActiveSupport::Concern\n\n  class_methods do\n    def hashid_service\n      @hashid_service ||= Hashids.new(\"#{Rails.application.secrets.secret_key_base}-#{name}\")\n    end\n\n    def find_by_hashid(hashid)\n      id = hashid_service.decode(hashid.to_s).first\n      find_by(id: id)\n    end\n\n    def find_by_hashid!(hashid)\n      id = hashid_service.decode(hashid.to_s).first\n      find(id)\n    end\n  end\n\n  def hashid\n    self.class.hashid_service.encode(id)\n  end\nend\n"
  },
  {
    "path": "core/app/models/concerns/uffizzi_core/state_machine_concern.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::StateMachineConcern\n  extend ActiveSupport::Concern\n\n  class_methods do\n    def aasm(attribute, *args)\n      attr_accessor(:\"#{attribute}_event\")\n\n      define_method(\"#{attribute}_event=\") do |value|\n        send(value)\n      end\n      super(attribute, *args)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/account.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Account < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Account\n\n  belongs_to :owner, class_name: UffizziCore::User.name, foreign_key: :owner_id\n\n  aasm(:state) do\n    state :active, initial: true\n    state :payment_issue\n    state :disabled\n    state :draft\n\n    event :activate do\n      transitions from: [:payment_issue, :disabled, :draft], to: :active\n    end\n\n    event :raise_payment_issue, before_success: :update_payment_issue_date do\n      transitions from: [:active, :disabled], to: :payment_issue\n    end\n\n    event :disable, after: :disable_projects do\n      transitions from: [:active, :payment_issue], to: :disabled\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/activity_item/docker.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ActivityItem::Docker < UffizziCore::ActivityItem\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/activity_item/github.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ActivityItem::Github < UffizziCore::ActivityItem\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/activity_item/memory_limit.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ActivityItem::MemoryLimit < UffizziCore::ActivityItem\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/activity_item.rb",
    "content": "# frozen_string_literal: true\n\n# @model\n#\n# @property id [integer]\n# @property namespace [string]\n# @property name(required) [string]\n# @property tag [string]\n# @property branch [string]\n# @property type [string]\n# @property container_id [string]\n# @property commit [string]\n# @property commit_message [string]\n# @property build_id [integer]\n# @property created_at [date]\n# @property updated_at [date]\n# @property data [object]\n# @property digest [string]\n# @property meta [object]\n\nclass UffizziCore::ActivityItem < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::ActivityItem\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/application_record.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore\n  class ApplicationRecord < ActiveRecord::Base\n    self.abstract_class = true\n  end\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/cluster.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Cluster < ApplicationRecord\n  include UffizziCore::Concerns::Models::Cluster\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/comment.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Comment < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Comment\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/compose_file.rb",
    "content": "# frozen_string_literal: true\n\n# @model ComposeFile\n# @property id [integer]\n# @property source [string]\n# @property path [string]\n# @property content(required) [string]\n# @property auto_deploy [boolean]\n# @property state [string]\n# @property payload [string]\n\nclass UffizziCore::ComposeFile < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::ComposeFile\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/config_file.rb",
    "content": "# frozen_string_literal: true\n\n# @model\n#\n# @property filename [string]\n# @property kind [string]\n# @property payload [string]\n# @property source [string]\n\nclass UffizziCore::ConfigFile < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::ConfigFile\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/container.rb",
    "content": "# frozen_string_literal: true\n\n# @model Container\n#\n# @property id [integer]\n# @property name [string]\n# @property memory_limit [integer]\n# @property memory_request [integer]\n# @property continuously_deploy [string]\n# @property variables [object]\n# @property secret_variables [object]\n# @property container_config_files [ConfigFile]\n\nclass UffizziCore::Container < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Container\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/container_config_file.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerConfigFile < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::ContainerConfigFile\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/container_host_volume_file.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerHostVolumeFile < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::ContainerHostVolumeFile\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/continuous_preview.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContinuousPreview < UffizziCore::ApplicationRecord\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/coupon.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Coupon < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Coupon\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential/amazon.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential::Amazon < UffizziCore::Credential\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential/azure.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential::Azure < UffizziCore::Credential\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential/docker_hub.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential::DockerHub < UffizziCore::Credential\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential/docker_registry.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential::DockerRegistry < UffizziCore::Credential\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential/github_container_registry.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential::GithubContainerRegistry < UffizziCore::Credential\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential/google.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential::Google < UffizziCore::Credential\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/credential.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Credential < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Credential\n\n  enumerize :type,\n            in: self::CREDENTIAL_TYPES, i18n_scope: ['enumerize.credential.type']\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/database.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Database < UffizziCore::ApplicationRecord\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/database_offering.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DatabaseOffering < UffizziCore::ApplicationRecord\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/deployment.rb",
    "content": "# frozen_string_literal: true\n\n# @model Deployment\n#\n# @property id [integer]\n# @property project_id [integer]\n# @property kind [string]\n# @property state [string]\n# @property preview_url [string]\n# @property tag [string]\n# @property branch [string]\n# @property commit [string]\n# @property image_id [string]\n# @property created_at [date-time]\n# @property updated_at [date-time]\n# @property ingress_container_ready [boolean]\n# @property ingress_container_state [string]\n# @property creation_source [string]\n# @property contaners [array]\n# @property deployed_by [object]\n\nclass UffizziCore::Deployment < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Deployment\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/deployment_event.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DeploymentEvent < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::DeploymentEvent\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/event.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Event < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Event\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/host_volume_file.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::HostVolumeFile < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::HostVolumeFile\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/kubernetes_distribution.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::KubernetesDistribution < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::KubernetesDistribution\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/membership.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Membership < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Membership\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/payment.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Payment < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Payment\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/price.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Price < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Price\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/product.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Product < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Product\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/project.rb",
    "content": "# frozen_string_literal: true\n\n# @model Project\n# @property slug [string]\n# @property name [string]\n# @property description [string]\n# @property created_at [date-time]\n# @property secrets [string]\n# @property default_compose [object<source: string>]\n# @property deployments [object<id: integer, domain: string>]\n# @property account [object<id: integer, kind: string, state: string>]\n\nclass UffizziCore::Project < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Project\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/rating.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Rating < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Rating\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo/amazon.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo::Amazon < UffizziCore::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo/azure.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo::Azure < UffizziCore::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo/docker_hub.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo::DockerHub < UffizziCore::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo/docker_registry.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo::DockerRegistry < UffizziCore::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo/github_container_registry.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo::GithubContainerRegistry < UffizziCore::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo/google.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo::Google < UffizziCore::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/repo.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Repo < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Repo\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/role.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Role < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Role\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/secret.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Secret < ApplicationRecord\n  include UffizziCore::Concerns::Models::Secret\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/template.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Template < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::Template\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/user.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::User < ActiveRecord::Base\n  include UffizziCore::Concerns::Models::User\n\n  has_secure_password\n\n  validates :email, presence: true, 'uffizzi_core/email': true, uniqueness: { case_sensitive: false }\n  validates :password, allow_nil: true, length: { minimum: 8 }, on: :update\nend\n"
  },
  {
    "path": "core/app/models/uffizzi_core/user_project.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::UserProject < UffizziCore::ApplicationRecord\n  include UffizziCore::Concerns::Models::UserProject\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/accounts/clusters_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ClustersPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_account?(context.user, context.account)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/accounts/credentials_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::CredentialsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.admin_access_to_account?(context.user, context.account)\n  end\n\n  def create?\n    context.user_access_module.admin_access_to_account?(context.user, context.account)\n  end\n\n  def update?\n    context.user_access_module.admin_access_to_account?(context.user, context.account)\n  end\n\n  def check_credential?\n    context.user_access_module.admin_access_to_account?(context.user, context.account)\n  end\n\n  def destroy?\n    context.user_access_module.admin_access_to_account?(context.user, context.account)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/accounts/projects_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ProjectsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_account?(context.user, context.account)\n  end\n\n  def create?\n    context.user_access_module.admin_or_developer_access_to_account?(context.user, context.account)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/accounts_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::AccountsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user.present?\n  end\n\n  def show?\n    context.user_access_module.any_access_to_account?(context.user, context.account)\n  end\n\n  def update?\n    false\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/clusters/ingresses_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Clusters::IngressesPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    return true if context.user_access_module.admin_access_to_project?(context.user, context.project)\n\n    context.cluster.deployed_by_id == context.user.id\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/clusters_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ClustersPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def show?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def create?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def scale_down?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def scale_up?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def sync?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def destroy?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/compose_files_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ComposeFilesPolicy < UffizziCore::ApplicationPolicy\n  def show?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def create?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def destroy?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/deployments/activity_items_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/deployments/containers_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ContainersPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def k8s_container_description?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/deployments/events_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::EventsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/deployments_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def show?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def create?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def update?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def destroy?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def deploy_containers?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects/secrets_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::SecretsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_project?(context.user, context.project)\n  end\n\n  def bulk_create?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\n\n  def destroy?\n    context.user_access_module.admin_or_developer_access_to_project?(context.user, context.project)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/api/cli/v1/projects_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ProjectsPolicy < UffizziCore::ApplicationPolicy\n  def index?\n    context.user_access_module.any_access_to_account?(context.user, context.account)\n  end\n\n  def show?\n    context.user_access_module.any_access_to_account?(context.user, context.account)\n  end\n\n  def destroy?\n    context.user_access_module.admin_or_developer_access_to_account?(context.user, context.account)\n  end\nend\n"
  },
  {
    "path": "core/app/policies/uffizzi_core/application_policy.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ApplicationPolicy\n  attr_reader :context, :record\n\n  def initialize(context, record)\n    raise Pundit::NotAuthorizedError, 'must be logged in' if !context.instance_of?(UffizziCore::WebhooksContext) && context.user.blank?\n\n    @record = record\n    @context = context\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/account_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::AccountRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_kind, ->(kind) { where(kind: kind) }\n    scope :personal, -> { by_kind(UffizziCore::Account.kind.personal) }\n    scope :organizational, -> { by_kind(UffizziCore::Account.kind.organizational) }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/activity_item_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ActivityItemRepo\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::BasicOrderRepo\n\n    scope :docker, -> {\n      where(type: UffizziCore::ActivityItem::Docker.name)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/basic_order_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::BasicOrderRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :order_by_id, ->(order = :asc) {\n      order(id: order)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/cluster_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ClusterRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :deployed, -> { where(state: UffizziCore::Cluster::STATE_DEPLOYED) }\n    scope :enabled, -> { where.not(state: UffizziCore::Cluster::STATE_DISABLED) }\n    scope :deployed_by_user, ->(user) { where(deployed_by: user) }\n    scope :by_projects, ->(projects) { where(project_id: projects.select(:id)) }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/comment_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::CommentRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_user, ->(user) {\n      where(user: user)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/compose_file_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ComposeFileRepo\n  extend ActiveSupport::Concern\n\n  included do\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/config_file_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ConfigFileRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_deployment, ->(deployment) {\n      select(:id)\n        .joins(:container_config_files)\n        .where(container_config_files: { container: deployment.containers })\n        .distinct\n    }\n\n    scope :by_source, ->(source) {\n      where(source: source)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/container_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ContainerRepo\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::BasicOrderRepo\n\n    scope :with_amazon_repo, -> { includes(:repo).where(repo: { type: UffizziCore::Repo::Amazon.name }) }\n    scope :with_docker_hub_repo, -> { includes(:repo).where(repo: { type: UffizziCore::Repo::DockerHub.name }) }\n\n    scope :with_public_access, -> {\n      where(public: true)\n    }\n\n    scope :by_repo_type, ->(type) {\n      includes(:repo).where(repo: { type: type })\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/credential_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::CredentialRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_type, ->(type) { where(type: type) }\n    scope :docker_hub, -> { by_type(UffizziCore::Credential::DockerHub.name) }\n    scope :docker_registry, -> { by_type(UffizziCore::Credential::DockerRegistry.name) }\n    scope :azure, -> { by_type(UffizziCore::Credential::Azure.name) }\n    scope :google, -> { by_type(UffizziCore::Credential::Google.name) }\n    scope :amazon, -> { by_type(UffizziCore::Credential::Amazon.name) }\n    scope :github_container_registry, -> { by_type(UffizziCore::Credential::GithubContainerRegistry.name) }\n    scope :deployable, -> {\n      by_type([\n                UffizziCore::Credential::DockerHub.name,\n                UffizziCore::Credential::DockerRegistry.name,\n                UffizziCore::Credential::Azure.name,\n                UffizziCore::Credential::Google.name,\n                UffizziCore::Credential::Amazon.name,\n                UffizziCore::Credential::GithubContainerRegistry.name,\n              ])\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/deployment_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::DeploymentRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :with_name, ->(name) {\n      where(name: name)\n    }\n    scope :with_amazon_repos, -> { includes(containers: [:repo]).where(containers: { repos: { type: UffizziCore::Repo::Amazon.name } }) }\n    scope :enabled, -> { where(state: [:active, :failed]) }\n    scope :active_for_credential_id, ->(credential_id) {\n      active.joins(project: :credentials).merge(UffizziCore::Project.active).where(credentials: { id: credential_id })\n    }\n    scope :with_metadata, -> { where.not(metadata: {}) }\n    scope :with_labels, ->(labels) {\n      where(\"#{UffizziCore::Deployment.table_name}.metadata @> ?\", labels.to_json)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/event_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::EventRepo\n  extend ActiveSupport::Concern\n\n  included do\n    include UffizziCore::BasicOrderRepo\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/host_volume_file_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::HostVolumeFileRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_deployment, ->(deployment) {\n      joins(:container_host_volume_files)\n        .where(container_host_volume_files: { container: deployment.containers })\n        .distinct\n    }\n\n    scope :by_source, ->(source) {\n      where(source: source)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/membership_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::MembershipRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_role_admin, -> { by_role(UffizziCore::Membership.role.admin) }\n    scope :by_account, ->(account) { where(account_id: account.id) }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/price_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::PriceRepo\n  extend ActiveSupport::Concern\n\n  class_methods do\n    def container_memory_for(env)\n      find_by(slug: env.kind)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/product_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ProductRepo\n  extend ActiveSupport::Concern\n\n  class_methods do\n    def container_memory\n      find_by(slug: :container_memory)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/project_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ProjectRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_ids, ->(ids) { where(id: ids) }\n    scope :by_accounts, ->(account_ids) { where(account_id: account_ids) }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/repo_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::RepoRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :docker_hub, -> { where(type: UffizziCore::Repo::DockerHub.name) }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/template_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::TemplateRepo\n  extend ActiveSupport::Concern\n\n  included do\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/usage_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::UsageRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_timestamp, ->(direction = :asc) { order(\"timestamp #{direction}\") }\n  end\nend\n"
  },
  {
    "path": "core/app/repositories/uffizzi_core/user_repo.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::UserRepo\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_email, ->(email) {\n      where('lower(email) = ?', email.downcase)\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/responders/uffizzi_core/json_responder.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::JsonResponder < ActionController::Responder\n  def api_behavior(*args, &block)\n    if post?\n      display(resource, status: :created)\n    elsif put? || patch?\n      display(resource, status: :ok)\n    else\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/account_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::AccountSerializer < UffizziCore::BaseSerializer\n  include UffizziCore::DependencyInjectionConcern\n  include_module_if_exists('UffizziCore::Api::Cli::V1::AccountSerializerModule')\n\n  type :account\n\n  has_many :projects\n\n  attributes :id, :name, :api_url, :vclusters_controller_url\n\n  def api_url\n    Settings.domain\n  end\n\n  def vclusters_controller_url\n    controller_settings_service.vcluster_settings_by_account(object).url\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/accounts/cluster_serializer/project_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ClusterSerializer::ProjectSerializer < UffizziCore::BaseSerializer\n  type :project\n\n  attributes :name\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/accounts/cluster_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ClusterSerializer < UffizziCore::BaseSerializer\n  type :cluster\n\n  belongs_to :project\n\n  attributes :name\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/accounts/credential_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::CredentialSerializer < UffizziCore::BaseSerializer\n  attributes :id, :username, :password, :type, :state\n\n  def password\n    anonymize(object.password)\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/accounts/project_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Accounts::ProjectSerializer < UffizziCore::BaseSerializer\n  type :project\n\n  attributes :name,\n             :slug,\n             :account_id,\n             :description,\n             :created_at\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/project_serializer/account_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ProjectSerializer::AccountSerializer < UffizziCore::BaseSerializer\n  attributes :id, :kind, :state, :name\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/project_serializer/compose_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ProjectSerializer::ComposeFileSerializer < UffizziCore::BaseSerializer\n  type :compose_file\n\n  attributes :source\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/project_serializer/deployment_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ProjectSerializer::DeploymentSerializer < UffizziCore::BaseSerializer\n  type :deployment\n\n  attributes :id,\n             :preview_url,\n             :state\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/project_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ProjectSerializer < UffizziCore::BaseSerializer\n  type :project\n  has_many :deployments\n  has_many :secrets\n  has_one :default_compose\n  belongs_to :account\n\n  attributes :name,\n             :slug,\n             :description,\n             :created_at\n\n  def default_compose\n    object.compose_files.main.first\n  end\n\n  def deployments\n    object.deployments.active.map do |deployment|\n      deployment.state = if UffizziCore::DeploymentService.failed?(deployment)\n        UffizziCore::Deployment::STATE_FAILED\n      else\n        UffizziCore::Deployment::STATE_ACTIVE\n      end\n      deployment\n    end\n  end\n\n  def secrets\n    return [] unless object.secrets\n\n    object.secrets.map(&:name)\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/cluster_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ClusterSerializer < UffizziCore::BaseSerializer\n  type :cluster\n\n  attributes :id, :name, :state, :kubeconfig, :created_at, :host, :k8s_version\n\n  def k8s_version\n    object.kubernetes_distribution&.version || UffizziCore::KubernetesDistribution.default.version\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/compose_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ComposeFileSerializer < UffizziCore::BaseSerializer\n  type :compose_file\n\n  attributes :id, :source, :path, :auto_deploy, :state, :payload, :content\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer/container_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentSerializer::ContainerSerializer < UffizziCore::BaseSerializer\n  type :deployment\n\n  attributes :id,\n             :kind,\n             :image,\n             :tag,\n             :variables,\n             :secret_variables,\n             :created_at,\n             :updated_at,\n             :memory_limit,\n             :memory_request,\n             :entrypoint,\n             :command,\n             :port,\n             :public,\n             :repo_id,\n             :continuously_deploy,\n             :receive_incoming_requests,\n             :healthcheck,\n             :volumes,\n             :service_name\n\n  def secret_variables\n    return unless object.secret_variables.present?\n\n    object.secret_variables.map do |var|\n      { name: var['name'], value: anonymize(var['value']) }\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer/user_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentSerializer::UserSerializer < UffizziCore::BaseSerializer\n  type :user\n\n  attributes :id, :kind, :email\n\n  def kind\n    :internal\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentSerializer < UffizziCore::BaseSerializer\n  include UffizziCore::DependencyInjectionConcern\n  include_module_if_exists('UffizziCore::Api::Cli::V1::Projects::DeploymentSerializerModule')\n\n  type :deployment\n\n  attributes :id,\n             :project_id,\n             :state,\n             :preview_url\n\n  has_many :containers\n\n  belongs_to :deployed_by\n\n  def containers\n    object.containers.active\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/activity_item_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemSerializer < UffizziCore::BaseSerializer\n  type :activity_item\n\n  attributes :id, :namespace, :name, :tag, :type, :branch, :commit, :commit_message, :state, :created_at,\n             :updated_at, :build_id, :data, :container_id, :digest\n\n  def type\n    return :github if object.type == UffizziCore::ActivityItem::Github.name\n    return :docker if object.type == UffizziCore::ActivityItem::Docker.name\n    return :memory_limit if object.type == UffizziCore::ActivityItem::MemoryLimit.name\n\n    nil\n  end\n\n  def commit\n    object.commit.to_s.slice(0..6)\n  end\n\n  def state\n    object.events.order_by_id.last&.state\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer/container_config_file_serializer/config_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ContainerSerializer::ContainerConfigFileSerializer::ConfigFileSerializer <\n  UffizziCore::BaseSerializer\n  attributes :id, :filename, :kind, :payload\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer/container_config_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ContainerSerializer::ContainerConfigFileSerializer < UffizziCore::BaseSerializer\n  attributes :id, :mount_path\n\n  belongs_to :config_file\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ContainerSerializer < UffizziCore::BaseSerializer\n  attributes :id, :name, :memory_limit, :memory_request, :continuously_deploy, :variables, :secret_variables, :healthcheck\n\n  has_many :container_config_files\n\n  type :container\n\n  def name\n    image_name = object.image.split('/').pop\n    \"#{image_name}:#{object.tag}\"\n  end\n\n  def secret_variables\n    return unless object.secret_variables.present?\n\n    object.secret_variables.map do |var|\n      { name: var['name'], value: anonymize(var['value']) }\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments_serializer/user_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsSerializer::UserSerializer < UffizziCore::BaseSerializer\n  type :user\n\n  attributes :email\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/deployments_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsSerializer < UffizziCore::BaseSerializer\n  type :deployment\n\n  attributes :id,\n             :created_at,\n             :updated_at,\n             :state,\n             :preview_url,\n             :metadata\n\n  belongs_to :deployed_by\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/secret_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::SecretSerializer < UffizziCore::BaseSerializer\n  attributes :name, :created_at, :updated_at\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/projects/short_cluster_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::Projects::ShortClusterSerializer < UffizziCore::BaseSerializer\n  type :cluster\n\n  attributes :id, :name\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/short_project_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::ShortProjectSerializer < UffizziCore::BaseSerializer\n  type :project\n  belongs_to :account, serializer: UffizziCore::Api::Cli::V1::ProjectSerializer::AccountSerializer\n\n  # account_id supports CLI versions < 2.0.5\n  attributes :name, :slug, :account_id\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/user_serializer/account_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::UserSerializer::AccountSerializer < UffizziCore::BaseSerializer\n  attributes :id, :kind, :state, :name\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/api/cli/v1/user_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Api::Cli::V1::UserSerializer < UffizziCore::BaseSerializer\n  type :user\n\n  attributes :default_account\n\n  def default_account\n    UffizziCore::Api::Cli::V1::UserSerializer::AccountSerializer.new(object.default_account)\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/base_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::BaseSerializer < ActiveModel::Serializer\n  def anonymize(field)\n    '*' * field.length\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/apply_config_file/config_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::ApplyConfigFile::ConfigFileSerializer < UffizziCore::BaseSerializer\n  attributes :id, :filename, :kind, :payload\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/create_cluster/cluster_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::CreateCluster::ClusterSerializer < UffizziCore::BaseSerializer\n  include UffizziCore::DependencyInjectionConcern\n  include_module_if_exists('UffizziCore::Controller::CreateCluster::ClusterSerializerModule')\n\n  attributes :name, :manifest, :base_ingress_host, :distro, :image, :account_id\n\n  def base_ingress_host\n    managed_dns_zone = controller_settings_service.vcluster_settings_by_vcluster(object).managed_dns_zone\n\n    [object.namespace, managed_dns_zone].join('.')\n  end\n\n  def image\n    kubernetes_distribution.image\n  end\n\n  def distro\n    kubernetes_distribution.distro\n  end\n\n  def account_id\n    object.project.account_id\n  end\n\n  private\n\n  def kubernetes_distribution\n    @kubernetes_distribution ||= object.kubernetes_distribution\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/create_credential/credential_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::CreateCredential::CredentialSerializer < UffizziCore::BaseSerializer\n  attributes :id, :registry_url, :username, :password\n\n  def username\n    return 'AWS' if object.amazon?\n\n    object.username\n  end\n\n  def password\n    if object.amazon?\n      UffizziCore::ContainerRegistry::AmazonService.access_token(object)\n    else\n      object.password\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/create_deployment/deployment_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::CreateDeployment::DeploymentSerializer < UffizziCore::BaseSerializer\n  attributes :kind\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/compose_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::DeployContainers::ComposeFileSerializer < UffizziCore::BaseSerializer\n  attributes :source_kind\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer/container_config_file_serializer/config_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\n# rubocop:disable Layout/LineLength\nclass UffizziCore::Controller::DeployContainers::ContainerSerializer::ContainerConfigFileSerializer::ConfigFileSerializer < UffizziCore::BaseSerializer\n  # rubocop:enable Layout/LineLength\n\n  attributes :id, :filename, :kind, :payload\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer/container_config_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::DeployContainers::ContainerSerializer::ContainerConfigFileSerializer < UffizziCore::BaseSerializer\n  attributes :mount_path\n\n  belongs_to :config_file\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer/container_host_volume_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::DeployContainers::ContainerSerializer::ContainerHostVolumeFileSerializer < UffizziCore::BaseSerializer\n  attributes :host_volume_file_id, :source_path\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::DeployContainers::ContainerSerializer < UffizziCore::BaseSerializer\n  attributes :id,\n             :kind,\n             :full_image_name,\n             :variables,\n             :secret_variables,\n             :memory_limit,\n             :memory_request,\n             :entrypoint,\n             :command,\n             :port,\n             :target_port,\n             :public,\n             :controller_name,\n             :receive_incoming_requests,\n             :healthcheck,\n             :volumes,\n             :service_name,\n             :additional_subdomains\n\n  has_many :container_config_files\n  has_many :container_host_volume_files\n\n  def entrypoint\n    object.entrypoint.blank? ? nil : JSON.parse(object.entrypoint)\n  end\n\n  def command\n    object.command.blank? ? nil : JSON.parse(object.command)\n  end\n\n  def healthcheck\n    return {} if object.healthcheck.blank?\n\n    command = object.healthcheck['test']\n    new_command = if command.is_a?(Array)\n      items_to_remove = ['CMD', 'CMD-SHELL']\n      command.select { |item| items_to_remove.exclude?(item) }\n    elsif object.healthcheck['test'].is_a?(String)\n      command.split\n    end\n\n    object.healthcheck.merge('test' => new_command)\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/credential_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::DeployContainers::CredentialSerializer < UffizziCore::BaseSerializer\n  attributes :id\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/deploy_containers/host_volume_file_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::DeployContainers::HostVolumeFileSerializer < UffizziCore::BaseSerializer\n  attributes :id, :source, :path, :payload, :is_file\n\n  def payload\n    Base64.encode64(object.payload)\n  end\nend\n"
  },
  {
    "path": "core/app/serializers/uffizzi_core/controller/update_cluster/cluster_serializer.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Controller::UpdateCluster::ClusterSerializer < UffizziCore::BaseSerializer\n  include UffizziCore::DependencyInjectionConcern\n  include_module_if_exists('UffizziCore::Controller::UpdateCluster::ClusterSerializerModule')\n\n  attributes :name, :manifest, :base_ingress_host\n\n  def base_ingress_host\n    managed_dns_zone = controller_settings_service.vcluster_settings_by_vcluster(object).managed_dns_zone\n\n    [object.namespace, managed_dns_zone].join('.')\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/account_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::AccountService\n  class << self\n    def create_credential(credential)\n      UffizziCore::Deployment.active_for_credential_id(credential.id).pluck(:id).each do |deployment_id|\n        UffizziCore::Deployment::CreateCredentialJob.perform_async(deployment_id, credential.id)\n      end\n    end\n\n    def update_credential(credential)\n      UffizziCore::Deployment.active_for_credential_id(credential.id).pluck(:id).each do |deployment_id|\n        UffizziCore::Deployment::UpdateCredentialJob.perform_async(deployment_id, credential.id)\n      end\n    end\n\n    def delete_credential(credential)\n      UffizziCore::Deployment.active_for_credential_id(credential.id).pluck(:id).each do |deployment_id|\n        UffizziCore::Deployment::DeleteCredentialJob.perform_async(deployment_id, credential.id)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/activity_item_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ActivityItemService\n  COMPLETED_STATES = ['deployed', 'failed', 'cancelled'].freeze\n\n  class << self\n    include UffizziCore::DependencyInjectionConcern\n\n    def create_docker_item!(repo, container)\n      activity_item_attributes = {\n        namespace: repo.namespace,\n        name: repo.name,\n        container: container,\n        deployment_id: container.deployment_id,\n        type: UffizziCore::ActivityItem::Docker.name,\n        tag: container.tag,\n      }\n\n      create_item!(activity_item_attributes)\n    end\n\n    def fail_deployment!(activity_item)\n      deployment = activity_item.container.deployment\n      last_event = activity_item.events.order_by_id.last\n\n      activity_item.events.create(state: UffizziCore::Event.state.failed) unless last_event&.failed?\n\n      UffizziCore::DeploymentService.fail!(deployment)\n    end\n\n    def update_docker_digest!(activity_item)\n      container = activity_item.container\n      repo = container.repo\n      credential = UffizziCore::RepoService.credential(repo)\n      container_registry_service = UffizziCore::ContainerRegistryService.init_by_subclass(repo.type)\n      digest = container_registry_service.digest(credential, activity_item.image, activity_item.tag)\n\n      activity_item.update!(digest: digest)\n\n      activity_item\n    end\n\n    def manage_deploy_activity_item(activity_item)\n      container = activity_item.container\n      deployment = container.deployment\n      service = UffizziCore::ManageActivityItemsService.new(deployment)\n      container_status_item = service.container_status_item(container)\n      return if container_status_item.nil?\n\n      status = container_status_item[:status]\n      last_event = activity_item.events.order_by_id.last\n      activity_item.events.create(state: status) if last_event&.state != status\n\n      return handle_failed_status(activity_item, deployment) if failed?(status)\n\n      if deployed?(status) && UffizziCore::ContainerService.ingress_container?(container)\n        deployment.update!(last_deploy_at: last_event.created_at)\n        deployment.deployment_events.create!(deployment_state: status)\n\n        return\n      end\n\n      return unless [UffizziCore::Event.state.building, UffizziCore::Event.state.deploying].include?(status)\n\n      UffizziCore::Deployment::ManageDeployActivityItemJob.perform_in(5.seconds, activity_item.id)\n    end\n\n    private\n\n    def handle_failed_status(activity_item, deployment)\n      UffizziCore::ActivityItemService.fail_deployment!(activity_item)\n      notification_module.notify_about_failed_deployment(deployment) if notification_module.present?\n    end\n\n    def create_item!(activity_item_attributes)\n      activity_item = UffizziCore::ActivityItem.find_by(activity_item_attributes)\n      return activity_item unless completed?(activity_item)\n\n      UffizziCore::ActivityItem.create!(activity_item_attributes)\n    end\n\n    def completed?(activity_item)\n      return true if activity_item.nil?\n\n      last_event = activity_item.events.order_by_id.last\n      COMPLETED_STATES.include?(last_event.state)\n    end\n\n    def failed?(status)\n      status == UffizziCore::Event.state.failed\n    end\n\n    def deployed?(status)\n      status == UffizziCore::Event.state.deployed\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/ci_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::CiService\n  class << self\n    def valid_request_from_ci_workflow?(_params)\n      false\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/cluster_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ClusterService\n  class << self\n    def start_deploy(cluster)\n      UffizziCore::Cluster::DeployJob.perform_async(cluster.id)\n    end\n\n    def deploy_cluster(cluster)\n      begin\n        UffizziCore::ControllerService.create_namespace(cluster)\n      rescue UffizziCore::ControllerClient::ConnectionError\n        return cluster.fail_deploy_namespace!\n      end\n\n      cluster.start_deploying!\n\n      begin\n        UffizziCore::ControllerService.create_cluster(cluster)\n      rescue UffizziCore::ControllerClient::ConnectionError\n        return cluster.fail!\n      end\n\n      UffizziCore::Cluster::ManageDeployingJob.perform_in(5.seconds, cluster.id)\n    end\n\n    def scale_up!(cluster)\n      cluster.start_scaling_up!\n      UffizziCore::ControllerService.patch_cluster(cluster, sleep: false)\n      UffizziCore::Cluster::ManageScalingUpJob.perform_in(5.seconds, cluster.id)\n    end\n\n    def manage_scale_up(cluster, try)\n      return cluster.fail_scale_up! if try > Settings.vcluster.max_scale_up_retry_count\n      return cluster.scale_up! if ready?(cluster)\n\n      UffizziCore::Cluster::ManageScalingUpJob.perform_in(5.seconds, cluster.id, ++try)\n    end\n\n    def scale_down!(cluster)\n      cluster.start_scaling_down!\n      UffizziCore::ControllerService.patch_cluster(cluster, sleep: true)\n\n      UffizziCore::Cluster::ManageScalingDownJob.perform_in(5.seconds, cluster.id)\n    end\n\n    def manage_scale_down(cluster)\n      return cluster.scale_down! unless awake?(cluster)\n\n      UffizziCore::Cluster::ManageScalingDownJob.perform_in(5.seconds, cluster.id)\n    end\n\n    def manage_deploying(cluster, try)\n      return if cluster.disabled?\n      return cluster.fail! if try > Settings.vcluster.max_creation_retry_count\n\n      deployed_cluster = UffizziCore::ControllerService.show_cluster(cluster)\n\n      if deployed_cluster.status.ready && deployed_cluster.status.kube_config.present?\n        cluster.finish_deploy\n        cluster.kubeconfig = deployed_cluster.status.kube_config\n        cluster.host = deployed_cluster.status.host\n        cluster.save!\n\n        return\n      end\n\n      UffizziCore::Cluster::ManageDeployingJob.perform_in(5.seconds, cluster.id, ++try)\n    end\n\n    def filter_user_ingress_host(cluster, ingress_hosts)\n      ingress_hosts.reject { |h| h == cluster.host }\n    end\n\n    private\n\n    def awake?(cluster)\n      data = UffizziCore::ControllerService.show_cluster(cluster)\n\n      !data.status.sleep\n    end\n\n    def ready?(cluster)\n      data = UffizziCore::ControllerService.show_cluster(cluster)\n\n      data.status.ready\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/builders/container_builder_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Builders::ContainerBuilderService\n  attr_accessor :credentials, :project, :repositories\n\n  def initialize(credentials, project, repositories = [])\n    @credentials = credentials\n    @project = project\n    @repositories = repositories\n  end\n\n  # rubocop:disable Metrics/PerceivedComplexity\n  def build_attributes(container_data, ingress_data, continuous_preview_global_data, compose_dependencies)\n    image_data = container_data[:image] || {}\n    build_data = container_data[:build] || {}\n    environment = container_data[:environment] || []\n    deploy_data = container_data[:deploy] || {}\n    configs_data = container_data[:configs] || []\n    secrets = container_data[:secrets] || []\n    container_name = container_data[:container_name]\n    healthcheck_data = container_data[:healthcheck] || {}\n    volumes_data = container_data[:volumes] || []\n\n    github_deps_service = UffizziCore::ComposeFile::GithubDependenciesService\n\n    env_file_dependencies = github_deps_service.env_file_dependencies_for_container(compose_dependencies, container_name)\n    configs_dependencies = github_deps_service.configs_dependencies_for_container(compose_dependencies, container_name)\n    host_volumes_dependencies = github_deps_service.host_volumes_dependencies_for_container(compose_dependencies, container_name)\n    is_ingress = ingress_container?(container_name, ingress_data)\n    repo_attributes = repo_attributes(container_data, continuous_preview_global_data)\n    additional_subdomains = is_ingress ? ingress_data.fetch(:additional_subdomains, []) : []\n    memory_limit = memory_limit(deploy_data)\n\n    {\n      tag: tag(image_data, repo_attributes),\n      port: port(container_name, ingress_data),\n      full_image_name: full_image_name(image_data, build_data, repo_attributes),\n      image: image(container_data, image_data, build_data, credentials),\n      public: is_ingress,\n      entrypoint: entrypoint(container_data),\n      command: command(container_data),\n      variables: variables(environment, env_file_dependencies),\n      secret_variables: secret_variables(secrets),\n      memory_limit: memory_limit,\n      memory_request: memory_request(memory_limit),\n      repo_attributes: repo_attributes,\n      continuously_deploy: continuously_deploy(deploy_data),\n      receive_incoming_requests: is_ingress,\n      container_config_files_attributes: container_config_files_attributes(configs_data, configs_dependencies),\n      service_name: container_name,\n      name: container_name,\n      healthcheck: healthcheck_data,\n      volumes: volumes_data,\n      additional_subdomains: additional_subdomains,\n      container_host_volume_files_attributes: container_host_volume_files_attributes(volumes_data, host_volumes_dependencies),\n    }\n  end\n  # rubocop:enable Metrics/PerceivedComplexity\n\n  private\n\n  def container_registry(container_data)\n    @container_registry ||= UffizziCore::ContainerRegistryService.init_by_container(container_data, @credentials)\n  end\n\n  def repo_attributes(container_data, continuous_preview_global_data)\n    repo_attributes = build_repo_attributes(container_data)\n    continuous_preview_container_data = container_data[:'x-uffizzi-continuous-preview'] || container_data[:'x-uffizzi-continuous-previews']\n\n    set_continuous_preview_attributes_to_repo(repo_attributes, continuous_preview_global_data.to_h, continuous_preview_container_data.to_h)\n  end\n\n  def build_repo_attributes(container_data)\n    container_registry = container_registry(container_data)\n    repo_type = container_registry.repo_type.name\n    raise UffizziCore::ComposeFile::BuildError, I18n.t('compose.invalid_repo_type') if repo_type.blank?\n\n    image_data = container_registry.image_data\n    if container_registry.image_available?(credentials)\n      docker_repo_builder = UffizziCore::ComposeFile::Builders::DockerRepoBuilderService.new(repo_type)\n      return docker_repo_builder.build_attributes(image_data)\n    end\n\n    UffizziCore::ComposeFile::ErrorsService.raise_build_error!(container_registry.type)\n  rescue UffizziCore::ContainerRegistryError => e\n    UffizziCore::ComposeFile::ErrorsService.raise_build_error!(container_registry.type, e.errors)\n  end\n\n  def set_continuous_preview_attributes_to_repo(repo_attributes, global_data, container_data)\n    condition_attributes = [\n      :deploy_preview_when_pull_request_is_opened,\n      :delete_preview_when_pull_request_is_closed,\n      :deploy_preview_when_image_tag_is_created,\n      :delete_preview_when_image_tag_is_updated,\n      :share_to_github,\n    ]\n\n    condition_attributes.each do |attribute|\n      repo_attributes[attribute] = select_continuous_preview_attribute(global_data[attribute], container_data[attribute], false)\n    end\n\n    global = global_data.dig(:delete_preview_after, :value)\n    local = container_data.dig(:delete_preview_after, :value)\n    repo_attributes[:delete_preview_after] = select_continuous_preview_attribute(global, local, nil)\n\n    repo_attributes\n  end\n\n  def select_continuous_preview_attribute(global_attribute, local_attribute, default_attribute)\n    return local_attribute if local_attribute.present?\n    return global_attribute if global_attribute.present?\n\n    default_attribute\n  end\n\n  def tag(image_data, repo_attributes)\n    image_data[:tag] || repo_attributes[:branch]\n  end\n\n  def port(container_name, ingress)\n    return nil unless ingress_container?(container_name, ingress)\n\n    ingress[:port]\n  end\n\n  def image(container_data, image_data, build_data, credentials)\n    if image_data.present?\n      container_registry(container_data).image_name(credentials)\n    else\n      \"#{build_data[:account_name]}/#{build_data[:repository_name]}\"\n    end\n  end\n\n  def image_name(container_data)\n    container_registry = container_registry(container_data)\n    container_registry.image_name(credentials)\n  end\n\n  def full_image_name(image_data, build_data, repo_attributes)\n    return image_data[:full_image_name] if image_data.present?\n\n    \"#{build_data[:account_name]}/#{build_data[:repository_name]}:#{repo_attributes[:branch]}\"\n  end\n\n  def ingress_container?(container_name, ingress)\n    ingress[:container_name] == container_name\n  end\n\n  def entrypoint(container_data)\n    entrypoint = container_data[:entrypoint]\n    entrypoint.present? ? entrypoint.to_s : nil\n  end\n\n  def command(container_data)\n    command = container_data[:command]\n    command.present? ? command.to_s : nil\n  end\n\n  def command_args(command_data)\n    return nil if command_data[:command_args].blank?\n\n    command_data[:command_args].to_s\n  end\n\n  def memory_limit(deploy_data)\n    memory = deploy_data[:memory]\n    return Settings.compose.default_memory if memory.nil?\n\n    memory_value = case memory[:postfix]\n                   when 'b'\n                     memory[:value] / 1_000_000\n                   when 'k'\n                     memory[:value] / 1000\n                   when 'm'\n                     memory[:value]\n                   when 'g'\n                     memory[:value] * 1000\n    end\n\n    unless Settings.compose.memory_values.include?(memory_value)\n      raise UffizziCore::ComposeFile::BuildError,\n            I18n.t('compose.invalid_memory')\n    end\n\n    memory_value\n  end\n\n  def memory_request(memory)\n    memory / UffizziCore::Container::REQUEST_MEMORY_RATIO\n  end\n\n  def continuously_deploy(deploy_data)\n    return :disabled if deploy_data[:auto] == false\n\n    :enabled\n  end\n\n  def variables(variables_data, dependencies)\n    variables_builder.build_attributes(variables_data, dependencies)\n  end\n\n  def secret_variables(secrets)\n    variables_builder.build_secret_attributes(secrets)\n  end\n\n  def container_config_files_attributes(config_files_data, dependencies)\n    UffizziCore::ComposeFile::Builders::ContainerConfigFilesBuilderService.build_attributes(config_files_data, dependencies, project)\n  end\n\n  def container_host_volume_files_attributes(volumes_data, host_volumes_dependencies)\n    host_volumes_data = volumes_data.select do |v|\n      v[:type] == UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::HOST_VOLUME_TYPE\n    end\n\n    UffizziCore::ComposeFile::Builders::ContainerHostVolumeFilesBuilderService\n      .build_attributes(host_volumes_data, host_volumes_dependencies, project)\n  end\n\n  def variables_builder\n    @variables_builder ||= UffizziCore::ComposeFile::Builders::VariablesBuilderService.new(project)\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/builders/container_config_files_builder_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Builders::ContainerConfigFilesBuilderService\n  class << self\n    def build_attributes(config_files_data, dependencies, project)\n      return [] if config_files_data.empty?\n\n      config_file_sources = dependencies.pluck(:source)\n      config_files = project.config_files.with_creation_source(UffizziCore::ConfigFile.creation_source.compose_file)\n        .by_source(config_file_sources)\n\n      config_files_data.map do |config_file_data|\n        detected_dependency = dependencies.detect { |dependency| dependency[:path] == config_file_data[:source] }\n        detected_config_file = config_files.detect { |config_file| config_file.source == detected_dependency[:source] }\n\n        if detected_config_file.nil?\n          raise UffizziCore::ComposeFile::BuildError, I18n.t('compose.config_file_not_found', name: config_file_data[:source])\n        end\n\n        {\n          mount_path: config_file_data[:target],\n          config_file_id: detected_config_file.id,\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/builders/container_host_volume_files_builder_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Builders::ContainerHostVolumeFilesBuilderService\n  class << self\n    def build_attributes(container_host_volumes_data, host_volumes_dependencies, project)\n      return [] if container_host_volumes_data.empty?\n\n      host_volume_files = project\n        .host_volume_files\n        .by_source(host_volumes_dependencies.pluck(:source))\n\n      container_host_volumes_data.map do |container_host_volume_data|\n        detected_dependency = host_volumes_dependencies.detect do |dependency|\n          dependency[:raw_source] == container_host_volume_data[:source]\n        end\n        detected_host_volume_file = host_volume_files.detect { |host_volume_file| host_volume_file.source == detected_dependency[:source] }\n\n        if detected_host_volume_file.nil?\n          raise UffizziCore::ComposeFile::BuildError,\n                I18n.t('compose.host_volume_file_not_found', name: container_host_volume_data[:source])\n        end\n\n        {\n          source_path: container_host_volume_data[:source],\n          host_volume_file_id: detected_host_volume_file.id,\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/builders/docker_repo_builder_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Builders::DockerRepoBuilderService\n  attr_accessor :type\n\n  def initialize(type)\n    @type = type\n  end\n\n  def build_attributes(image_data)\n    {\n      kind: nil,\n      name: image_data[:name],\n      slug: image_data[:name],\n      type: type,\n      branch: nil,\n      namespace: image_data[:namespace],\n      is_private: nil, # TODO: detect\n      description: '',\n      repository_id: nil,\n      dockerfile_path: '',\n      dockerfile_context_path: nil,\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/builders/template_builder_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Builders::TemplateBuilderService\n  attr_accessor :credentials, :project, :repositories\n\n  def initialize(credentials, project, repositories = [])\n    @credentials = credentials\n    @project = project\n    @repositories = repositories\n  end\n\n  def build_attributes(compose_data, compose_dependencies, source)\n    containers_data = compose_data[:containers]\n    ingress_data = compose_data[:ingress]\n    continuous_preview_global_data = compose_data[:continuous_preview]\n\n    containers_attributes = build_containers_attributes(\n      containers_data,\n      ingress_data,\n      continuous_preview_global_data,\n      compose_dependencies,\n    )\n\n    {\n      name: source,\n      payload: {\n        containers_attributes: containers_attributes,\n      },\n    }\n  end\n\n  private\n\n  def build_containers_attributes(containers_data, ingress_data, continuous_preview_global_data, compose_dependencies)\n    containers_data.map do |container_data|\n      builder = UffizziCore::ComposeFile::Builders::ContainerBuilderService.new(credentials, project, repositories)\n\n      builder.build_attributes(container_data, ingress_data, continuous_preview_global_data, compose_dependencies)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/builders/variables_builder_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Builders::VariablesBuilderService\n  attr_accessor :project\n\n  require 'dotenv'\n\n  def initialize(project)\n    @project = project\n  end\n\n  def build_attributes(variables_data, dependencies)\n    variables = variables_data\n    variables_from_dependencies = variables_from_dependencies(dependencies)\n\n    variables + variables_from_dependencies\n  end\n\n  def build_secret_attributes(secrets)\n    project_secrets = project.secrets || []\n\n    secrets.uniq.map do |secret|\n      detected_secret = project_secrets.detect { |project_secret| project_secret['name'] == secret }\n      error_message = I18n.t('compose.project_secret_not_found', secret: secret)\n      raise UffizziCore::ComposeFile::SecretsError, error_message if detected_secret.nil?\n\n      build_variable(detected_secret['name'], detected_secret['value'])\n    end\n  end\n\n  private\n\n  def variables_from_dependencies(dependencies)\n    variables = dependencies.map do |dependency|\n      variables_data = parse_variables_from_dependency(dependency)\n\n      variables_data.map { |variable_data| build_variable(variable_data.first, variable_data.last) }\n    end\n\n    variables.flatten\n  end\n\n  def parse_variables_from_dependency(dependency)\n    content = dependency[:content]\n    return [] if content.blank?\n\n    variables_content = UffizziCore::ComposeFile::GithubDependenciesService.content(dependency)\n    parser = Dotenv::Parser.new(variables_content)\n    parser.call.to_a\n  end\n\n  def build_variable(name, value)\n    {\n      name: name,\n      value: value,\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/config_files_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::ConfigFilesService\n  def initialize(compose_file_form)\n    @compose_file_form = compose_file_form\n    @repository_id = compose_file_form.repository_id\n    @branch = compose_file_form.branch\n    @path = compose_file_form.path\n    @user = compose_file_form.added_by\n    @project = compose_file_form.project\n  end\n\n  def create_config_files(compose_dependencies)\n    configs_dependencies = UffizziCore::ComposeFile::GithubDependenciesService.configs_dependencies(compose_dependencies)\n    errors = []\n    configs_dependencies.each do |config_dependency|\n      errors = create_config_file(config_dependency)\n      errors << errors if errors\n    end\n\n    errors\n  end\n\n  private\n\n  def create_config_file(config_dependency)\n    source = UffizziCore::ComposeFile::GithubDependenciesService.build_source_path(@path, config_dependency[:path], @repository_id, @branch)\n    config_file = @project.config_files.find_or_initialize_by(source: source)\n    attributes = {\n      filename: UffizziCore::ComposeFile::GithubDependenciesService.filename(config_dependency),\n      payload: UffizziCore::ComposeFile::GithubDependenciesService.content(config_dependency),\n    }\n\n    config_file.assign_attributes(attributes)\n    config_file_form = build_config_file_form(config_file)\n    return config_file_form.errors if config_file_form.invalid?\n\n    config_file_form.save\n\n    nil\n  end\n\n  def build_config_file_form(config_file)\n    config_file_form = config_file.becomes(UffizziCore::Api::Cli::V1::ConfigFile::CreateForm)\n    config_file_form.project = @project\n    config_file_form.added_by = @user\n    config_file_form.compose_file = @compose_file_form\n    config_file_form.creation_source = UffizziCore::ConfigFile.creation_source.compose_file\n\n    config_file_form\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/config_option_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::ConfigOptionService\n  class << self\n    def valid_option_format?(option)\n      if option.is_a?(TrueClass) || option.is_a?(FalseClass)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.boolean_option', value: option)\n      end\n\n      option.match(/^[\\w.-]+$/).present?\n    end\n\n    def config_options(compose_data)\n      compose_data.each_with_object([]) do |(key, value), keys|\n        if compose_data.equal?(value)\n          raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.infinite_recursion', key: key)\n        end\n\n        keys << key\n        keys.concat(config_options(value)) if value.is_a?(Hash)\n      end\n    end\n\n    def prepare_file_path_value(file_path)\n      pathname = Pathname.new(file_path)\n\n      pathname.cleanpath.to_s.strip.delete_prefix('/')\n    end\n\n    def ingress_option(compose_data)\n      compose_data.dig('x-uffizzi', 'ingress').presence || compose_data['x-uffizzi-ingress'].presence\n    end\n\n    def continuous_preview_option(compose_data)\n      compose_data.dig('x-uffizzi', 'continuous_preview').presence ||\n        compose_data.dig('x-uffizzi', 'continuous_previews').presence ||\n        compose_data['x-uffizzi-continuous-preview'].presence ||\n        compose_data['x-uffizzi-continuous-previews'].presence\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/container_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::ContainerService\n  class << self\n    def has_secret?(container, secret)\n      container['secret_variables'].any? { |container_secret| container_secret['name'] == secret['name'] }\n    end\n\n    def update_secret(container, secret)\n      secret_index = container['secret_variables'].find_index { |container_secret| container_secret['name'] == secret['name'] }\n      container['secret_variables'][secret_index] = secret\n\n      container\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/dependencies_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::DependenciesService\n  ENV_FILE_TYPE = 'env_file'\n  CONFIG_TYPE = 'config'\n  VOLUME_TYPE = 'volume'\n  DEPENDENCY_CONFIG_USE_KIND = 'config_map'\n  DEPENDENCY_VOLUME_USE_KIND = 'volume'\n\n  class << self\n    def build_dependencies(compose_data, compose_path, dependencies_params)\n      config_dependencies_params = dependencies_params.select { |d| d[:use_kind] == DEPENDENCY_CONFIG_USE_KIND }\n      volume_dependencies_params = dependencies_params.select { |d| d[:use_kind] == DEPENDENCY_VOLUME_USE_KIND }\n\n      dependencies = compose_data[:containers].map do |container|\n        env_file_dependencies = build_env_files_dependencies(container, compose_path, config_dependencies_params)\n        configs_dependencies = build_configs_dependencies(container, compose_path, config_dependencies_params)\n        volumes_dependencies = build_volumes_dependencies(container, compose_path, volume_dependencies_params)\n\n        env_file_dependencies + configs_dependencies + volumes_dependencies\n      end\n\n      dependencies.compact.flatten\n    end\n\n    def build_env_files_dependencies(container, compose_path, dependencies_params)\n      env_files = container[:env_file]\n      return [] unless env_files.present?\n\n      env_files.map do |path|\n        dependency = dependencies_params.detect { |item| item[:path] == path }\n        source = build_source_path(compose_path, path)\n\n        base_file_params(dependency, container).merge(source: source, type: ENV_FILE_TYPE)\n      end\n    end\n\n    def build_configs_dependencies(container, compose_path, dependencies_params)\n      configs = container[:configs]\n      return [] unless configs.present?\n\n      configs.map do |config|\n        dependency = dependencies_params.detect { |item| item[:path] == config[:source] }\n        source = build_source_path(compose_path, dependency[:path])\n\n        base_file_params(dependency, container).merge(source: source, type: CONFIG_TYPE)\n      end\n    end\n\n    def build_volumes_dependencies(container, compose_path, raw_dependencies)\n      container_volumes = container[:volumes]\n      return [] unless container_volumes.present?\n\n      container_volumes\n        .select { |c| c[:type] == UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::HOST_VOLUME_TYPE }\n        .map do |container_volume|\n          detected_raw_dependency = raw_dependencies.detect { |raw_dependency| raw_dependency[:source] == container_volume[:source] }\n          builded_source = build_source_path(compose_path, detected_raw_dependency[:source])\n\n          {\n            content: detected_raw_dependency[:content],\n            path: detected_raw_dependency[:path],\n            container_name: container[:container_name],\n            source: builded_source,\n            raw_source: detected_raw_dependency[:source],\n            type: VOLUME_TYPE,\n            is_file: detected_raw_dependency[:is_file],\n          }\n        end\n    end\n\n    def base_file_params(dependency, container)\n      {\n        content: dependency[:content],\n        path: dependency[:path],\n        container_name: container[:container_name],\n      }\n    end\n\n    def build_source_path(compose_path, dependency_path)\n      prepared_compose_path = Pathname.new(compose_path).basename.to_s\n      \"#{prepared_compose_path}/#{dependency_path}\"\n    end\n\n    def host_volume_binary_content(dependency)\n      Base64.decode64(dependency[:content])\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/errors_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::ErrorsService\n  SECRETS_ERROR_KEY = 'secret_variables'\n  TEMPLATE_BUILD_ERROR_KEY = 'template_build_error'\n  DOCKER_REGISTRY_CONTAINER_ERROR_KEY = 'docker_registry_container_error'\n\n  class << self\n    def has_error?(compose_file, error_code)\n      error = compose_file.payload.dig('errors', error_code)\n\n      error.present?\n    end\n\n    def has_errors?(compose_file)\n      compose_file.payload['errors'].present?\n    end\n\n    def update_compose_errors!(compose_file, errors, invalid_content)\n      compose_file.payload['errors'] = errors\n      compose_file.set_invalid if compose_file.valid_file?\n      compose_file.content = invalid_content\n\n      compose_file.save!\n\n      compose_file\n    end\n\n    def reset_compose_errors!(compose_file)\n      compose_file.payload['errors'] = nil\n      compose_file.set_valid\n\n      compose_file.save!\n\n      compose_file\n    end\n\n    def reset_error!(compose_file, error_code)\n      errors = compose_file.payload['errors']\n      return if errors.nil?\n\n      new_errors = errors.except(error_code)\n      compose_file.payload['errors'] = new_errors\n      compose_file.save!\n\n      compose_file\n    end\n\n    def raise_build_error!(type, extra_errors = {})\n      msg = I18n.t('compose.unprocessable_image', value: type)\n      raise UffizziCore::ComposeFile::BuildError.new(msg, extra_errors)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/github_dependencies_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::GithubDependenciesService\n  ENV_FILE_TYPE = 'env_file'\n  CONFIG_TYPE = 'config'\n  VOLUME_TYPE = 'volume'\n\n  class << self\n    def filename(dependency)\n      pathname = Pathname.new(dependency[:path])\n\n      pathname.basename.to_s\n    end\n\n    def content(dependency)\n      Base64.decode64(dependency[:content])\n    end\n\n    def env_file_dependencies_for_container(dependencies, container_name)\n      dependencies.select { |dependency| dependency[:type] == ENV_FILE_TYPE && dependency[:container_name] == container_name }\n    end\n\n    def configs_dependencies_for_container(dependencies, container_name)\n      configs_dependencies(dependencies).select { |dependency| dependency[:container_name] == container_name }\n    end\n\n    def host_volumes_dependencies_for_container(dependencies, container_name)\n      dependencies.select { |dependency| dependency[:type] == VOLUME_TYPE && dependency[:container_name] == container_name }\n    end\n\n    def configs_dependencies(dependencies)\n      dependencies.select { |dependency| dependency[:type] == CONFIG_TYPE }\n    end\n\n    def select_dependencies_by_type(dependencies, type)\n      dependencies.select { |dependency| dependency[:type].to_s == type.to_s }\n    end\n\n    def build_source_path(compose_path, dependency_path, repository_id, branch)\n      prepared_compose_path = Pathname.new(compose_path).basename.to_s\n      base_source = \"#{prepared_compose_path}/#{dependency_path}\"\n      return base_source if repository_id.blank?\n\n      \"#{repository_id}/#{branch}/#{base_source}\"\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/host_volume_files_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::HostVolumeFilesService\n  class << self\n    def bulk_create(compose_file_form, compose_dependencies)\n      volumes_dependencies = UffizziCore::ComposeFile::GithubDependenciesService\n        .select_dependencies_by_type(compose_dependencies, UffizziCore::ComposeFile::DependenciesService::VOLUME_TYPE)\n\n      errors = []\n      volumes_dependencies.each do |volume_dependency|\n        new_errors = create(compose_file_form, volume_dependency)\n        errors << new_errors if new_errors\n      end\n\n      errors\n    end\n\n    def create(compose_file_form, volume_dependency)\n      source = volume_dependency[:source]\n      host_volume_file = compose_file_form.project.host_volume_files.find_or_initialize_by(source: source)\n      attributes = {\n        payload: UffizziCore::ComposeFile::DependenciesService.host_volume_binary_content(volume_dependency),\n        source: source,\n        path: volume_dependency[:path],\n        is_file: volume_dependency[:is_file],\n      }\n\n      host_volume_file.assign_attributes(attributes)\n      host_volume_file.project = compose_file_form.project\n      host_volume_file.added_by = compose_file_form.added_by\n      host_volume_file.compose_file = compose_file_form\n\n      return host_volume_file.errors if host_volume_file.invalid?\n\n      host_volume_file.save\n\n      nil\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/configs_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::ConfigsParserService\n  class << self\n    def parse(configs_data)\n      return [] if configs_data.nil?\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :configs) unless configs_data.is_a?(Hash)\n\n      configs = []\n      configs_data.each_pair do |config_name, config_data|\n        if config_data['file'].blank?\n          raise UffizziCore::ComposeFile::ParseError,\n                I18n.t('compose.config_file_option_empty', config_name: config_name)\n        end\n\n        configs << {\n          config_name: config_name,\n          config_file: config_data['file'],\n        }\n      end\n\n      configs\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/continuous_preview_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::ContinuousPreviewParserService\n  class << self\n    def parse(continuous_preview_data)\n      return {} if continuous_preview_data.nil?\n\n      {\n        deploy_preview_when_pull_request_is_opened: trigger_value(continuous_preview_data, 'deploy_preview_when_pull_request_is_opened'),\n        delete_preview_when_pull_request_is_closed: trigger_value(continuous_preview_data, 'delete_preview_when_pull_request_is_closed'),\n        deploy_preview_when_image_tag_is_created: trigger_value(continuous_preview_data, 'deploy_preview_when_image_tag_is_created'),\n        delete_preview_when_image_tag_is_updated: trigger_value(continuous_preview_data, 'delete_preview_when_image_tag_is_updated'),\n        delete_preview_after: delete_preview_after_value(continuous_preview_data['delete_preview_after']),\n        share_to_github: trigger_value(continuous_preview_data, 'share_to_github'),\n      }\n    end\n\n    private\n\n    def trigger_value(continuous_preview_data, field)\n      value = continuous_preview_data[field]\n      return nil if value.nil?\n      return value if value.in?([true, false])\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_bool_value', field: field, value: value)\n    end\n\n    def delete_preview_after_value(value)\n      return {} if value.blank?\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_string', option: :delete_preview_after) unless value.is_a?(String)\n\n      hours, postfix = value.scan(/^([0-9]+)([a-zA-Z])$/).flatten\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_integer', option: :delete_preview_after) if hours.nil?\n\n      formatted_hours = hours.to_i\n      if formatted_hours < Settings.compose.delete_after_min_value\n        raise UffizziCore::ComposeFile::ParseError,\n              I18n.t('compose.invalid_delete_after_min', value: Settings.compose.delete_after_min_value)\n      end\n\n      if formatted_hours > Settings.compose.delete_after_max_value\n        raise UffizziCore::ComposeFile::ParseError,\n              I18n.t('compose.invalid_delete_after_max', value: Settings.compose.delete_after_max_value)\n      end\n\n      if postfix.nil? || !Settings.compose.delete_after_postfixes.include?(postfix.downcase)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_delete_after_postfix')\n      end\n\n      {\n        value: formatted_hours,\n        postfix: postfix.downcase,\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/ingress_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::IngressParserService\n  class << self\n    def parse(ingress_data, services_data)\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.no_ingress') if ingress_data.nil?\n\n      container_name = container_name(ingress_data, services_data)\n      port = port(ingress_data)\n      additional_attributes = build_additional_attributes(ingress_data, services_data)\n\n      {\n        container_name: container_name,\n        port: port,\n      }.merge(additional_attributes)\n    end\n\n    private\n\n    def container_name(ingress_data, services_data)\n      container_name = ingress_data['service']\n\n      if container_name.nil?\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.ingress_service_not_found')\n      end\n\n      unless services_data.keys.include?(container_name)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_ingress_service', value: container_name)\n      end\n\n      container_name\n    end\n\n    def port(ingress_data)\n      port = ingress_data['port']\n\n      if port.nil?\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.ingress_port_not_specified')\n      end\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_integer', option: :port) unless port.is_a?(Integer)\n\n      port_min = Settings.compose.port_min_value\n      port_max = Settings.compose.port_max_value\n      if port < port_min || port > port_max\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.port_out_of_range', port_min: port_min, port_max: port_max)\n      end\n\n      port\n    end\n\n    def build_additional_attributes(*)\n      {}\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/named_volumes_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::NamedVolumesParserService\n  VALID_VOLUME_NAME_REGEX = /^[a-zA-Z0-9._-]+$/.freeze\n\n  class << self\n    def parse(volumes_data)\n      return [] if volumes_data.nil?\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :volumes) unless volumes_data.is_a?(Hash)\n\n      volume_names = volumes_data.keys\n      volume_names.each do |volume_name|\n        unless volume_name.match?(VALID_VOLUME_NAME_REGEX)\n          raise UffizziCore::ComposeFile::ParseError,\n                I18n.t('compose.volume_invalid_name', name: volume_name)\n        end\n      end\n\n      volume_names\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/secrets_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::SecretsParserService\n  class << self\n    def parse(secrets_data)\n      return [] if secrets_data.nil?\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :secrets) unless secrets_data.is_a?(Hash)\n\n      secrets = []\n      secrets_data.each_pair do |secret_name, secret_data|\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.secret_name_blank', option: secret_name) if secret_data['name'].blank?\n\n        if secret_data['external'] != true\n          raise UffizziCore::ComposeFile::ParseError,\n                I18n.t('compose.secret_external', secret: secret_name)\n        end\n\n        secrets << {\n          secret_name: secret_name,\n          secret_variable: secret_data['name'],\n        }\n      end\n\n      secrets\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/command_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::CommandParserService\n  class << self\n    def parse(command_data)\n      return nil if command_data.blank?\n\n      case command_data\n      when String\n        Shellwords.split(command_data)\n      when Array\n        command_data\n      else\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :command)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/configs_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::ConfigsParserService\n  class << self\n    def parse(configs, global_configs_data)\n      return [] if configs.nil?\n\n      configs.map do |config|\n        config_data = case config\n                      when String\n                        process_short_syntax(config, global_configs_data)\n                      when Hash\n                        process_long_syntax(config, global_configs_data)\n                      else\n                        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :configs)\n        end\n\n        config_data\n      end\n    end\n\n    private\n\n    def process_short_syntax(config_name, global_configs_data)\n      global_config = find_global_config!(config_name, global_configs_data)\n\n      {\n        source: UffizziCore::ComposeFile::ConfigOptionService.prepare_file_path_value(global_config[:config_file]),\n        target: global_config[:config_file],\n      }\n    end\n\n    def process_long_syntax(config_data, global_configs_data)\n      global_config = find_global_config!(config_data['source'], global_configs_data)\n      target = config_data['target'].blank? ? global_config[:config_file] : config_data['target']\n\n      {\n        source: UffizziCore::ComposeFile::ConfigOptionService.prepare_file_path_value(global_config[:config_file]),\n        target: target,\n      }\n    end\n\n    def find_global_config!(config_name, global_configs_data)\n      detected_config = global_configs_data.detect { |global_config_data| global_config_data[:config_name] == config_name }\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.global_config_not_found', config: config_name) if detected_config.nil?\n\n      detected_config\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/deploy_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::DeployParserService\n  class << self\n    def parse(deploy_data)\n      return {} if deploy_data.blank?\n\n      auto = prepare_deploy_auto(deploy_data)\n      memory = prepare_memory(deploy_data)\n\n      {\n        auto: auto,\n        memory: memory,\n      }\n    end\n\n    private\n\n    def prepare_deploy_auto(deploy_data)\n      auto = deploy_data['x-uffizzi-auto-deploy-updates']\n      return auto if auto.nil? || auto.in?([true, false])\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_bool_value', field: :auto, value: auto)\n    end\n\n    def prepare_memory(deploy_data)\n      memory = deploy_data.dig('resources', 'limits', 'memory')\n      return nil if memory.blank?\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_memory_type', value: memory) unless memory.is_a?(String)\n\n      value, postfix = memory.scan(/^([0-9]+)([a-zA-Z]+)$/).flatten\n\n      if postfix.nil? || !Settings.compose.memory_postfixes.include?(postfix.downcase)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_memory_postfix', value: memory)\n      end\n\n      {\n        value: value.to_i,\n        postfix: postfix.downcase,\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/entrypoint_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::EntrypointParserService\n  class << self\n    def parse(entrypoint_data)\n      return nil if entrypoint_data.blank?\n\n      case entrypoint_data\n      when String\n        [entrypoint_data]\n      when Array\n        entrypoint_data\n      else\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :entrypoint)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/env_file_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::EnvFileParserService\n  class << self\n    def parse(env_file)\n      env_files = case env_file\n                  when String\n                    [prepare_file_path(env_file)]\n                  when Array\n                    env_file.map { |env_file_path| prepare_file_path(env_file_path) }\n                  else\n                    raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :env_file)\n      end\n\n      check_duplicates(env_files)\n\n      env_files\n    end\n\n    private\n\n    def prepare_file_path(env_file_path)\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.empty_env_file') if env_file_path.blank?\n\n      UffizziCore::ComposeFile::ConfigOptionService.prepare_file_path_value(env_file_path)\n    end\n\n    def check_duplicates(env_files)\n      return if env_files.uniq.length == env_files.length\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.env_file_duplicates', values: env_files)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/environment_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::EnvironmentParserService\n  extend UffizziCore::ComposeFile::Parsers::VariablesParserService\n\n  class << self\n    def parse(environment)\n      return [] if environment.blank?\n\n      case environment\n      when Array\n        environment.map { |variable| parse_variable_from_string(variable) }\n      when Hash\n        environment.to_a.map { |variable| parse_variable_from_array(variable) }\n      else\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :environment)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/healthcheck_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::HealthcheckParserService\n  REQUIRED_START_COMMANDS = ['NONE', 'CMD', 'CMD-SHELL'].freeze\n  REQUIRED_OPTIONS = ['test', 'disable'].freeze\n\n  class << self\n    def parse(healthcheck_data)\n      return {} if healthcheck_data.blank?\n\n      unless required_options_any?(healthcheck_data)\n        raise UffizziCore::ComposeFile::ParseError,\n              I18n.t('compose.healthcheck_missing_required_option',\n                     required_options: REQUIRED_OPTIONS.join(', '))\n      end\n\n      command = parse_command(healthcheck_data) if healthcheck_data['test'].present?\n\n      {\n        test: command,\n        interval: parse_time(:interval, healthcheck_data['interval']),\n        timeout: parse_time(:timeout, healthcheck_data['timeout']),\n        retries: parse_retries(healthcheck_data['retries']),\n        start_period: parse_time(:start_period, healthcheck_data['start_period']),\n        disable: parse_disable_option(healthcheck_data['disable'], command),\n      }\n    end\n\n    private\n\n    def parse_command(healthcheck_data)\n      command = healthcheck_data['test']\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.string_or_array_error', option: :test) if command.nil?\n\n      case command\n      when Array\n        start_command = command.first\n\n        unless REQUIRED_START_COMMANDS.include?(start_command)\n          raise UffizziCore::ComposeFile::ParseError,\n                I18n.t('compose.required_start_commands', available_commands: REQUIRED_START_COMMANDS.join(', '))\n        end\n\n        command\n      when String\n        command\n      else\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :test)\n      end\n    end\n\n    def parse_retries(value)\n      return if value.nil?\n\n      unless value.is_a?(Integer)\n        raise UffizziCore::ComposeFile::ParseError,\n              I18n.t('compose.invalid_retries', value: value)\n      end\n\n      value\n    end\n\n    def parse_time(key, value)\n      return if value.nil?\n\n      error_message = I18n.t('compose.invalid_time_interval', key: key, value: value)\n      raise UffizziCore::ComposeFile::ParseError, error_message if value.is_a?(Integer)\n\n      tokens = {\n        's' => 1,\n        'm' => 60,\n        'h' => (60 * 60),\n        'd' => (60 * 60 * 24),\n      }\n\n      time_parts = value.scan(/(\\d+)(\\w)/).compact\n\n      time_parts.reduce(0) do |acc, part|\n        amount, measure = part\n        acc += amount.to_i * tokens[measure]\n\n        acc\n      rescue StandardError\n        raise UffizziCore::ComposeFile::ParseError, error_message\n      end\n    end\n\n    def parse_disable_option(value, command)\n      return true if command.is_a?(Array) && command.first == 'NONE'\n      return false if value.nil?\n      return value if value.in?([true, false])\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_bool_value', field: 'disable', value: value)\n    end\n\n    def required_options_any?(healthcheck_data)\n      (REQUIRED_OPTIONS & healthcheck_data.keys).present?\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/image_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'docker_distribution'\n\nclass UffizziCore::ComposeFile::Parsers::Services::ImageParserService\n  DEFAULT_TAG = Settings.compose.default_tag\n  class << self\n    def parse(value)\n      return {} if value.blank?\n\n      parsed_image = DockerDistribution::Normalize.parse_docker_ref(value)\n      image_path = parsed_image.path\n      namespace, name = get_namespace_and_name(image_path)\n      full_image_name = \"#{[parsed_image.domain, parsed_image.path].compact.join('/')}:#{parsed_image.tag}\"\n\n      {\n        registry_url: parsed_image.domain,\n        namespace: namespace,\n        name: name,\n        tag: parsed_image.tag,\n        full_image_name: full_image_name,\n      }\n    rescue DockerDistribution::NameContainsUppercase\n      raise_parse_error(I18n.t('compose.image_name_contains_uppercase_value', value: value))\n    rescue DockerDistribution::ReferenceInvalidFormat, DockerDistribution::ParseNormalizedNamedError\n      raise_parse_error(I18n.t('compose.invalid_image_value', value: value))\n    end\n\n    private\n\n    def raise_parse_error(message)\n      raise UffizziCore::ComposeFile::ParseError, message\n    end\n\n    def get_namespace_and_name(image_path)\n      return [nil, image_path] unless image_path.index('/').present?\n\n      image_path.split('/')\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/secrets_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::SecretsParserService\n  class << self\n    def parse(secrets, global_secrets_data)\n      return [] if secrets.nil?\n\n      secrets.map do |secret|\n        variable_name = if secret.is_a?(String)\n          process_short_syntax(secret, global_secrets_data)\n        else\n          raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :secrets)\n        end\n\n        variable_name\n      end\n    end\n\n    private\n\n    def process_short_syntax(secret_name, global_secrets_data)\n      global_secret = find_global_secret!(secret_name, global_secrets_data)\n\n      global_secret[:secret_variable]\n    end\n\n    def find_global_secret!(secret_name, global_secrets_data)\n      detected_secret = global_secrets_data.detect { |global_secret_data| global_secret_data[:secret_name] == secret_name }\n      error_message = I18n.t('compose.global_secret_not_found', secret: secret_name)\n      raise UffizziCore::ComposeFile::ParseError, error_message if detected_secret.nil?\n\n      detected_secret\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services/volumes_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::Services::VolumesParserService\n  HOST_VOLUME_TYPE = :host\n  NAMED_VOLUME_TYPE = :named\n  ANONYMOUS_VOLUME_TYPE = :anonymous\n  READONLY_OPTION = 'ro'\n  READ_WRITE_OPTION = 'rw'\n\n  class << self\n    def parse(volumes, additional_data)\n      return [] if volumes.blank?\n\n      if volumes.is_a?(String)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volumes_should_be_array', volumes: volumes)\n      end\n\n      volumes.map do |volume|\n        volume_data = case volume\n                      when String\n                        process_short_syntax(volume, additional_data)\n                      when Hash\n                        process_long_syntax(volume, additional_data)\n                      else\n                        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_type', option: :volumes)\n        end\n\n        volume_data\n      end.uniq\n    end\n\n    private\n\n    def process_short_syntax(volume_data, additional_data)\n      volume_parts = volume_data.split(':').map(&:strip)\n      read_only = volume_parts.last.to_s.downcase == READONLY_OPTION\n      part1, part2 = volume_parts\n      source_path = part1\n      target_path = [READONLY_OPTION, READ_WRITE_OPTION].include?(part2.to_s.downcase) ? nil : part2\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_prop_is_required', prop_name: 'source') if source_path.blank?\n\n      build_volume_attributes(source_path, target_path, read_only, additional_data)\n    end\n\n    def process_long_syntax(volume_data, additional_data)\n      source_path = volume_data['source'].to_s.strip\n      target_path = volume_data['target'].to_s.strip\n      read_only = volume_data['read_only'].present?\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_prop_is_required', prop_name: 'source') if source_path.blank?\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_prop_is_required', prop_name: 'target') if target_path.blank?\n\n      build_volume_attributes(source_path, target_path, read_only, additional_data)\n    end\n\n    def build_volume_attributes(source_path, target_path, read_only, params = {})\n      volume_type = build_volume_type(source_path, target_path)\n\n      if volume_type == NAMED_VOLUME_TYPE\n        validate_named_volume(source_path, target_path, params[:named_volumes_names], params[:service_name])\n      end\n\n      if volume_type == ANONYMOUS_VOLUME_TYPE\n        validate_anonymous_volume(source_path)\n      end\n\n      {\n        source: source_path,\n        target: target_path,\n        type: volume_type,\n        read_only: read_only,\n      }\n    end\n\n    def build_volume_type(source_path, target_path)\n      return HOST_VOLUME_TYPE if path?(source_path) && path?(target_path)\n      return ANONYMOUS_VOLUME_TYPE if path?(source_path) && target_path.blank?\n      return NAMED_VOLUME_TYPE if source_path.present? && !path?(source_path) && path?(target_path)\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.volume_path_is_invalid', path: [source_path, target_path].join(':'))\n    end\n\n    def path?(path)\n      path.to_s.start_with?('/', './', '../')\n    end\n\n    def validate_named_volume(source_path, target_path, named_volumes_names, service_name)\n      if path_has_only_root?(target_path)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_volume_destination', spec: \"#{source_path}:#{target_path}\")\n      end\n\n      return if named_volumes_names.include?(source_path)\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.named_volume_not_exists', source_path: source_path,\n                                                                                            target_path: target_path,\n                                                                                            service_name: service_name)\n    end\n\n    def validate_anonymous_volume(path)\n      if path_has_only_root?(path)\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_volume_destination', spec: path)\n      end\n    end\n\n    def path_has_only_root?(path)\n      path.size == 1 && path.include?('/')\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/services_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::Parsers::ServicesParserService\n  class << self\n    include UffizziCore::DependencyInjectionConcern\n\n    def parse(services, global_configs_data, global_secrets_data, compose_payload, global_named_volume_names)\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.no_services') if services.nil? || services.keys.empty?\n\n      services.keys.map do |service|\n        unless valid_service_name?(service)\n          raise UffizziCore::ComposeFile::ParseError,\n                I18n.t('compose.invalid_service_name', value: service)\n        end\n\n        service_data = prepare_service_data(service, services.fetch(service), global_configs_data,\n                                            global_secrets_data, compose_payload, global_named_volume_names)\n\n        if service_data[:image].blank? && service_data[:build].blank?\n          raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.image_build_no_specified', value: service)\n        end\n\n        service_data\n      end\n    end\n\n    private\n\n    def prepare_service_data(service_name, service_data, global_configs_data,\n                             global_secrets_data, compose_payload, global_named_volume_names)\n      options_data = {}\n      service_data.each_pair do |key, value|\n        service_key = key.to_sym\n\n        options_data[service_key] = case service_key\n                                    when :image\n                                      UffizziCore::ComposeFile::Parsers::Services::ImageParserService.parse(value)\n                                    when :build\n                                      check_and_parse_build_option(value, compose_payload)\n                                    when :env_file\n                                      UffizziCore::ComposeFile::Parsers::Services::EnvFileParserService.parse(value)\n                                    when :environment\n                                      UffizziCore::ComposeFile::Parsers::Services::EnvironmentParserService.parse(value)\n                                    when :configs\n                                      UffizziCore::ComposeFile::Parsers::Services::ConfigsParserService.parse(value, global_configs_data)\n                                    when :secrets\n                                      UffizziCore::ComposeFile::Parsers::Services::SecretsParserService.parse(value, global_secrets_data)\n                                    when :deploy\n                                      UffizziCore::ComposeFile::Parsers::Services::DeployParserService.parse(value)\n                                    when :entrypoint\n                                      UffizziCore::ComposeFile::Parsers::Services::EntrypointParserService.parse(value)\n                                    when :command\n                                      UffizziCore::ComposeFile::Parsers::Services::CommandParserService.parse(value)\n                                    when :healthcheck\n                                      UffizziCore::ComposeFile::Parsers::Services::HealthcheckParserService.parse(value)\n                                    when :'x-uffizzi-continuous-preview', :'x-uffizzi-continuous-previews'\n                                      UffizziCore::ComposeFile::Parsers::ContinuousPreviewParserService.parse(value)\n                                    when :volumes\n                                      parse_volumes(value, named_volumes_names: global_named_volume_names,\n                                                           service_name: service_name,\n                                                           compose_payload: compose_payload)\n        end\n      end\n\n      options_data[:container_name] = service_name\n\n      options_data\n    end\n\n    def check_and_parse_build_option(value, compose_payload)\n      build_parser_module = find_build_parser_module\n\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.build_not_implemented') unless build_parser_module\n\n      build_parser_module.parse(value, compose_payload)\n    end\n\n    def parse_volumes(volumes, additional_data)\n      volume_parser_module = find_volume_parser_module\n\n      return UffizziCore::ComposeFile::Parsers::Services::VolumesParserService.parse(volumes, additional_data) unless volume_parser_module\n\n      volume_parser_module.parse(volumes, additional_data)\n    end\n\n    # All services are added as Additional Hosts in Kubernetes and must be RFC 123 compliant\n    def valid_service_name?(name)\n      rfc_123_pattern = /\\A(?!-)[a-z0-9-]{1,63}(?<!-)\\z/\n      name.match?(rfc_123_pattern)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/parsers/variables_parser_service.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ComposeFile::Parsers::VariablesParserService\n  def parse_variable_from_string(str)\n    variable_parts = str.split('=', 2)\n\n    parse_variable_from_array(variable_parts)\n  end\n\n  def parse_variable_from_array(arr)\n    name = arr.first.to_s\n    value = arr.last.to_s\n\n    build_variable(name, value)\n  end\n\n  def build_variable(name, value)\n    raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.no_variable_name', name: name, value: value) if name.blank?\n\n    {\n      name: name,\n      value: value,\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file/template_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFile::TemplateService\n  def initialize(cli_form, project, user)\n    @project = project\n    @user = user\n    @compose_dependencies = cli_form.compose_dependencies\n    @compose_data = cli_form.compose_data\n    @compose_repositories = cli_form.compose_repositories\n  end\n\n  def create_template(compose_file_form)\n    compose_file_template_form = build_compose_file_template_form(compose_file_form)\n    return compose_file_template_form.errors if compose_file_template_form.invalid?\n\n    template_form = build_template_form(compose_file_form, compose_file_template_form)\n    return template_form.errors if template_form.invalid?\n\n    template_form.save\n\n    nil\n  end\n\n  private\n\n  def build_compose_file_template_form(compose_file_form)\n    credentials = @project.account.credentials\n    compose_file_template_form = UffizziCore::Api::Cli::V1::ComposeFile::TemplateForm.new\n    compose_file_template_form.compose_data = @compose_data\n    compose_file_template_form.source = compose_file_form.source\n    compose_file_template_form.credentials = credentials\n    compose_file_template_form.project = @project\n    compose_file_template_form.user = @user\n    compose_file_template_form.compose_dependencies = @compose_dependencies\n    compose_file_template_form.compose_repositories = @compose_repositories\n    compose_file_template_form.assign_template_attributes!\n\n    compose_file_template_form\n  end\n\n  def build_template_form(compose_file_form, compose_file_template_form)\n    attributes = compose_file_template_form.template_attributes\n    source = compose_file_template_form.source\n    template = compose_file_form.template\n    template = @project.templates.find_or_initialize_by(name: source) if template.blank?\n    template.assign_attributes(attributes)\n    template_form = template.becomes(UffizziCore::Api::Cli::V1::Template::CreateForm)\n    template_form.project = @project\n    template_form.added_by = @user\n    template_form.compose_file = compose_file_form\n    template_form.creation_source = UffizziCore::Template.creation_source.compose_file\n\n    template_form\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/compose_file_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ComposeFileService\n  class << self\n    include UffizziCore::DependencyInjectionConcern\n\n    def create(params, kind)\n      compose_file_form = create_compose_form(params, kind)\n\n      process_compose_file(compose_file_form, params)\n    end\n\n    def update(compose_file, params)\n      compose_file_form = create_update_compose_form(compose_file, params)\n\n      process_compose_file(compose_file_form, params)\n    end\n\n    def parse(compose_content, compose_payload = {})\n      compose_data = load_compose_data(compose_content)\n      check_config_options_format(compose_data)\n      configs_data = UffizziCore::ComposeFile::Parsers::ConfigsParserService.parse(compose_data['configs'])\n      secrets_data = UffizziCore::ComposeFile::Parsers::SecretsParserService.parse(compose_data['secrets'])\n      named_volume_names = UffizziCore::ComposeFile::Parsers::NamedVolumesParserService.parse(compose_data['volumes'])\n      containers_data = UffizziCore::ComposeFile::Parsers::ServicesParserService.parse(\n        compose_data['services'],\n        configs_data,\n        secrets_data,\n        compose_payload,\n        named_volume_names,\n      )\n      continuous_preview_option = UffizziCore::ComposeFile::ConfigOptionService.continuous_preview_option(compose_data)\n      continuous_preview_data = UffizziCore::ComposeFile::Parsers::ContinuousPreviewParserService.parse(continuous_preview_option)\n\n      ingress_option = UffizziCore::ComposeFile::ConfigOptionService.ingress_option(compose_data)\n      ingress_data = parse_ingress(ingress_option, compose_data)\n\n      {\n        containers: containers_data,\n        ingress: ingress_data,\n        continuous_preview: continuous_preview_data,\n      }\n    end\n\n    def parse_ingress(ingress_option, compose_data)\n      ingress_parser_module = find_ingress_parser_module\n\n      unless ingress_parser_module\n        return UffizziCore::ComposeFile::Parsers::IngressParserService.parse(ingress_option,\n                                                                             compose_data['services'])\n      end\n\n      ingress_parser_module.parse(ingress_option, compose_data['services'])\n    end\n\n    def build_template_attributes(compose_data, source, credentials, project, compose_dependencies = [], compose_repositories = [])\n      builder = UffizziCore::ComposeFile::Builders::TemplateBuilderService.new(credentials, project, compose_repositories)\n\n      builder.build_attributes(compose_data, compose_dependencies, source)\n    end\n\n    def has_secret?(compose_file, secret)\n      containers = compose_file.template.payload['containers_attributes']\n\n      containers.any? { |container| UffizziCore::ComposeFile::ContainerService.has_secret?(container, secret) }\n    end\n\n    def update_secret!(compose_file, secret)\n      compose_file.template.payload['containers_attributes'].each do |container|\n        next unless UffizziCore::ComposeFile::ContainerService.has_secret?(container, secret)\n\n        UffizziCore::ComposeFile::ContainerService.update_secret(container, secret)\n        next if compose_file.payload['errors'].blank?\n\n        compose_file_errors = compose_file.payload['errors'].presence\n        secrets_errors = compose_file_errors[UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY].presence\n        new_secrets_errors = secrets_errors.reject { |secret_errors| secret_errors.include?(secret.name) }\n\n        if new_secrets_errors.present?\n          new_errors = { UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY => new_secrets_errors }\n          UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, compose_file_errors.merge(new_errors),\n                                                                         compose_file.content)\n          next\n        end\n\n        compose_file_errors.delete(['secret_variables'])\n        next UffizziCore::ComposeFile::ErrorsService.reset_compose_errors!(compose_file) if compose_file_errors.empty?\n\n        UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, compose_file_errors, compose_file.content)\n      end\n\n      compose_file.template.save!\n\n      compose_file\n    end\n\n    def secrets_valid?(compose_file, secrets)\n      secret_names = secrets.pluck('name')\n\n      compose_file.template.payload['containers_attributes'].all? do |container|\n        container['secret_variables'].all? { |secret| secret_names.include?(secret['name']) }\n      end\n    end\n\n    def create_temporary_compose(resource_project, current_user, compose_file_params, dependencies)\n      create_params = { project: resource_project, user: current_user, compose_file_params: compose_file_params,\n                        dependencies: dependencies || [] }\n      kind = UffizziCore::ComposeFile.kind.temporary\n      UffizziCore::ComposeFileService.create(create_params, kind)\n    end\n\n    private\n\n    def process_compose_file(compose_file_form, params)\n      cli_form = UffizziCore::Api::Cli::V1::ComposeFile::CliForm.new\n      cli_form.content = compose_file_form.content\n      cli_form.compose_file = compose_file_form.becomes(UffizziCore::ComposeFile)\n      return [compose_file_form, cli_form.errors] if cli_form.invalid?\n\n      dependencies = params[:dependencies].to_a\n      compose_data = cli_form.compose_data\n      compose_dependencies = build_compose_dependecies(compose_data, compose_file_form.path, dependencies)\n      cli_form.compose_dependencies = compose_dependencies\n\n      persist!(compose_file_form, cli_form)\n    end\n\n    def create_compose_form(params, kind)\n      compose_file_params = params[:compose_file_params]\n      compose_file_form = UffizziCore::Api::Cli::V1::ComposeFile::CreateForm.new(compose_file_params)\n      compose_file_form.project = params[:project]\n      compose_file_form.added_by = params[:user]\n      compose_file_form.content = compose_file_params[:content]\n      compose_file_form.kind = kind\n      payload_dependencies = prepare_compose_file_dependencies(params[:dependencies])\n      compose_file_form.payload['dependencies'] = payload_dependencies\n\n      compose_file_form\n    end\n\n    def create_update_compose_form(compose_file, params)\n      compose_file_form = compose_file.becomes(UffizziCore::Api::Cli::V1::ComposeFile::UpdateForm)\n      compose_file_params = params[:compose_file_params]\n      compose_file_form.assign_attributes(compose_file_params)\n      payload_dependencies = prepare_compose_file_dependencies(params[:dependencies])\n      compose_file_form.payload['dependencies'] = payload_dependencies\n\n      compose_file_form\n    end\n\n    def build_compose_dependecies(compose_data, compose_path, dependencies)\n      return [] if dependencies.empty?\n\n      UffizziCore::ComposeFile::DependenciesService.build_dependencies(compose_data, compose_path, dependencies)\n    end\n\n    def prepare_compose_file_dependencies(compose_dependencies)\n      compose_dependencies.map { |dependency| { path: dependency[:path] } }\n    end\n\n    def persist!(compose_file_form, cli_form)\n      errors = []\n      ActiveRecord::Base.transaction do\n        if !compose_file_form.save\n          errors = compose_file_form.errors\n          raise ActiveRecord::Rollback\n        end\n\n        config_files_service = UffizziCore::ComposeFile::ConfigFilesService.new(compose_file_form)\n        config_file_errors = config_files_service.create_config_files(cli_form.compose_dependencies)\n        host_volume_file_errors = UffizziCore::ComposeFile::HostVolumeFilesService\n          .bulk_create(compose_file_form, cli_form.compose_dependencies)\n        raise ActiveRecord::Rollback if config_file_errors.present? || host_volume_file_errors.present?\n\n        project = compose_file_form.project\n        user = compose_file_form.added_by\n        template_service = UffizziCore::ComposeFile::TemplateService.new(cli_form, project, user)\n        errors = template_service.create_template(compose_file_form)\n\n        raise ActiveRecord::Rollback if errors.present?\n      end\n\n      [compose_file_form, errors]\n    end\n\n    def load_compose_data(compose_content)\n      begin\n        compose_data = YAML.safe_load(compose_content, aliases: true)\n      rescue Psych::SyntaxError => e\n        err = [e.problem, e.context].compact.join(' ')\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_file', err: err, line: e.line, column: e.column)\n      end\n      raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.unsupported_file') if compose_data.nil? || compose_data.is_a?(String)\n\n      compose_data\n    end\n\n    def check_config_options_format(compose_data)\n      options = UffizziCore::ComposeFile::ConfigOptionService.config_options(compose_data)\n\n      options.each do |option|\n        next if UffizziCore::ComposeFile::ConfigOptionService.valid_option_format?(option)\n\n        raise UffizziCore::ComposeFile::ParseError, I18n.t('compose.invalid_config_option', value: option)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry/amazon_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistry::AmazonService\n  class << self\n    def digest(credential, image, tag)\n      response = client(credential).batch_get_image(image: image, tag: tag)\n      response.images[0].image_id.image_digest\n    rescue StandardError\n      nil\n    end\n\n    def get_region_from_registry_url(url)\n      parsed_url = URI.parse(url)\n      host = parsed_url.host\n      parsed_host = host.split('.')\n      parsed_host[3]\n    end\n\n    def image_available?(credential, _image_data)\n      credential.present?\n    end\n\n    def credential_correct?(credential)\n      access_token(credential).present?\n    end\n\n    def access_token(credential)\n      response = client(credential).authorization_token\n      base64_data = response.authorization_data[0].authorization_token\n      token_string = Base64.decode64(base64_data)\n      token_items = token_string.split(':')\n      token_items.pop\n    rescue Aws::ECR::Errors::UnrecognizedClientException, Aws::ECR::Errors::InvalidSignatureException\n      ''\n    end\n\n    private\n\n    def client(credential)\n      region = get_region_from_registry_url(credential.registry_url)\n\n      UffizziCore::AmazonRegistryClient.new(\n        region: region,\n        access_key_id: credential.username,\n        secret_access_key: credential.password,\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry/azure_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistry::AzureService\n  class << self\n    def image_available?(credential, _image_data)\n      credential.present?\n    end\n\n    def credential_correct?(credential)\n      client(credential).authenticated?\n    end\n\n    def digest(*); end\n\n    private\n\n    def client(c)\n      UffizziCore::AzureRegistryClient.new(registry_url: c.registry_url, username: c.username, password: c.password)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry/docker_hub_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistry::DockerHubService\n  class << self\n    def accounts(credential)\n      client = user_client(credential)\n      response = client.accounts\n      Rails.logger.info(\"DockerHubService accounts response=#{response.inspect} credential_id=#{credential.id}\")\n\n      accounts_response = response.result\n      accounts_response.nil? ? [] : accounts_response.namespaces\n    end\n\n    def image_available?(credential, image_data)\n      namespace = image_data[:namespace]\n      repo_name = image_data[:name]\n      client(credential).repository(namespace: namespace, image: repo_name)\n\n      true\n    end\n\n    def user_client(credential)\n      return @client if @client&.credential&.username == credential.username\n\n      @client = client(credential)\n\n      unless @client.authenticated?\n        Rails.logger.warn(\"broken credentials, DockerHubService credential_id=#{credential.id}\")\n        credential.unauthorize! unless credential.unauthorized?\n      end\n\n      @client\n    end\n\n    def digest(credential, image, tag)\n      docker_hub_client = client(credential)\n      token = docker_hub_client.get_token(image).result.token\n      response = docker_hub_client.digest(image: image, tag: tag, token: token)\n      response.headers['docker-content-digest']\n      # FIXME: can't get digest for private image: {\"code\":\"UNAUTHORIZED\",\"message\":\"authentication required\"...}\n    rescue UffizziCore::ContainerRegistryError\n      'unknown'\n    end\n\n    def credential_correct?(credential)\n      client(credential).authenticated?\n    end\n\n    private\n\n    def client(credential)\n      UffizziCore::DockerHubClient.new(credential)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry/docker_registry_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistry::DockerRegistryService\n  class << self\n    def image_available?(credential, image_data)\n      client_params = build_client_params(credential, image_data)\n      client = UffizziCore::DockerRegistryClient.new(**client_params)\n      client.manifests(namespace: image_data[:namespace], image: image_data[:name], tag: image_data[:tag])\n\n      true\n    end\n\n    def credential_correct?(credential)\n      client(credential).authenticated?\n    end\n\n    def digest(*); end\n\n    private\n\n    def client(credential)\n      params = {\n        registry_url: credential.registry_url,\n        username: credential.username,\n        password: credential.password,\n      }\n\n      UffizziCore::DockerRegistryClient.new(params)\n    end\n\n    def build_client_params(credential, image_data)\n      registry_url = credential&.registry_url || image_data[:registry_url]\n      new_registry_url = registry_url.start_with?('https://', 'http://') ? registry_url : \"https://#{registry_url}\"\n\n      {\n        registry_url: new_registry_url,\n        username: credential&.username,\n        password: credential&.password,\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry/github_container_registry_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistry::GithubContainerRegistryService\n  class << self\n    def image_available?(credential, _image_data)\n      credential.present?\n    end\n\n    def credential_correct?(credential)\n      client(credential).authenticated?\n    end\n\n    def digest(*); end\n\n    private\n\n    def client(c)\n      UffizziCore::GithubContainerRegistryClient.new(registry_url: c.registry_url, username: c.username, password: c.password)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry/google_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistry::GoogleService\n  class << self\n    def digest(credential, image, tag)\n      response = client(credential).manifests(image: image, tag: tag)\n\n      response.headers['docker-content-digest']\n    end\n\n    def image_available?(credential, _image_data)\n      credential.present?\n    end\n\n    def credential_correct?(credential)\n      client(credential).authenticated?\n    end\n\n    private\n\n    def client(c)\n      UffizziCore::GoogleRegistryClient.new(registry_url: c.registry_url, username: c.username, password: c.password)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_registry_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerRegistryService\n  attr_accessor :type, :container_data\n\n  class << self\n    def init_by_subclass(credential_type)\n      type = credential_type.demodulize.underscore\n      new(type.to_sym)\n    end\n\n    def init_by_container(container, credentials)\n      registry_url = container.dig(:image, :registry_url)\n\n      return new(:docker_hub, container) if registry_url.include?('docker.io')\n      return new(:azure, container) if registry_url.include?('azurecr.io')\n      return new(:google, container) if registry_url.include?('gcr.io')\n      return init_by_credentials(container, credentials) if registry_url.include?('amazonaws.com')\n      return new(:github_container_registry, container) if registry_url.include?('ghcr.io')\n      return new(:docker_registry, container) if docker_registry?(container)\n    end\n\n    def init_by_credentials(container, credentials)\n      return new(:docker_registry, container) if credentials && credentials.docker_registry.where(username: 'AWS').exists?\n\n      new(:amazon, container)\n    end\n\n    def docker_registry?(container)\n      registry_url = container.dig(:image, :registry_url)\n      return false if registry_url.nil?\n\n      registry_domain_regexp = /(\\w+\\.\\w{2,})(?::\\d+)?\\z/\n      registry_domain = registry_url.match(registry_domain_regexp)&.to_a&.last\n      return false if registry_domain.nil?\n\n      ['amazonaws.com', 'azurecr.io', 'gcr.io', 'ghcr.io'].exclude?(registry_domain)\n    end\n\n    def sources\n      [:azure, :google, :amazon, :github_container_registry, :docker_registry, :docker_hub, *additional_sources]\n    end\n\n    def additional_sources\n      []\n    end\n  end\n\n  def initialize(type, container_data = {})\n    @type = type\n    @container_data = container_data\n\n    raise ::UffizziCore::RegistryNotSupportedError unless self.class.sources.include?(type)\n  end\n\n  def digest(credential, image, tag)\n    service.digest(credential, image, tag)\n  end\n\n  def service\n    @service ||= \"UffizziCore::ContainerRegistry::#{type.to_s.camelize}Service\".safe_constantize\n  end\n\n  def repo_type\n    @repo_type ||= \"UffizziCore::Repo::#{type.to_s.camelize}\".safe_constantize\n  end\n\n  def credential_correct?(credential)\n    service.credential_correct?(credential)\n  rescue URI::InvalidURIError, Faraday::ConnectionFailed, UffizziCore::ContainerRegistryError\n    false\n  end\n\n  def image_data\n    @image_data ||= container_data[:image]\n  end\n\n  def image_name(credentials)\n    if image_data[:registry_url].present? && [:google, :github_container_registry, :docker_registry, :docker_hub].exclude?(type)\n      return image_data[:name]\n    end\n\n    if type == :docker_registry && credential(credentials).nil?\n      return [image_data[:registry_url], image_data[:namespace], image_data[:name]].compact.join('/')\n    end\n\n    \"#{image_data[:namespace]}/#{image_data[:name]}\"\n  end\n\n  def credential(credentials_scope)\n    credentials_scope.send(type).first\n  end\n\n  def image_available?(credentials_scope)\n    credential = credential(credentials_scope)\n    service.image_available?(credential, image_data)\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/container_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ContainerService\n  class << self\n    def pod_name(container)\n      return container.controller_name if container.controller_name.present?\n\n      formatted_name = container.image_name\n      formatted_name.parameterize.gsub('_', '-')\n    end\n\n    def target_port_value(container)\n      container.port\n    end\n\n    def defines_env?(container, name)\n      return false if container.variables.nil?\n\n      container.variables.select { |v| v['name'].downcase.to_sym == name.to_sym }.any?\n    end\n\n    def continuously_deploy_enabled?(container)\n      container.aasm(:continuously_deploy).current_state == UffizziCore::Container::STATE_CD_ENABLED\n    end\n\n    def last_state(container)\n      pods = pods_by_container(container)\n      container_status = container_status(container, pods)\n      return {} if container_status.blank? || container_status&.dig('last_state')&.blank?\n\n      container_status['last_state'].map do |code, state|\n        {\n          code: code,\n          reason: state.reason,\n          exit_code: state.exit_code,\n          started_at: state.started_at,\n          finished_at: state.finished_at,\n        }\n      end.first\n    end\n\n    def ingress_container?(container)\n      container.receive_incoming_requests?\n    end\n\n    private\n\n    def container_status(container, pods)\n      pods\n        .flat_map { |pod| pod&.status&.container_statuses }\n        .compact\n        .detect { |cs| cs.name.include?(container.controller_name) }\n    end\n\n    def pods_by_container(container)\n      UffizziCore::ControllerService.fetch_pods(container.deployment)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/controller_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ControllerService\n  class InvalidPublicPort < StandardError\n    def initialize(containers)\n      msg = \"Deployment with ID #{containers.first.deployment.id} does not have any public port\"\n\n      super(msg)\n    end\n  end\n\n  class << self\n    include UffizziCore::DependencyInjectionConcern\n\n    def apply_config_file(deployment, config_file)\n      body = {\n        config_file: UffizziCore::Controller::ApplyConfigFile::ConfigFileSerializer.new(config_file).as_json,\n      }\n\n      controller_client(deployment).apply_config_file(deployment_id: deployment.id, config_file_id: config_file.id, body: body)\n    end\n\n    def apply_credential(deployment, credential)\n      image = if credential.github_container_registry?\n        deployment.containers.by_repo_type(UffizziCore::Repo::GithubContainerRegistry.name).first&.image\n      end\n\n      options = { image: image }\n\n      body = UffizziCore::Controller::CreateCredential::CredentialSerializer.new(credential, options).as_json\n      controller_client(deployment).apply_credential(deployment_id: deployment.id, body: body)\n    end\n\n    def delete_credential(deployment, credential)\n      controller_client(deployment).delete_credential(deployment_id: deployment.id, credential_id: credential.id)\n    end\n\n    def deploy_containers(deployment, containers)\n      check_any_container_has_public_port(containers) do |exists|\n        UffizziCore::DeploymentService.disable!(deployment) unless exists\n      end\n\n      containers = containers.map do |container|\n        UffizziCore::Controller::DeployContainers::ContainerSerializer.new(container).as_json(include: '**')\n      end\n\n      credentials = deployment.credentials.deployable.map do |credential|\n        UffizziCore::Controller::DeployContainers::CredentialSerializer.new(credential).as_json\n      end\n\n      host_volume_files = UffizziCore::HostVolumeFile.by_deployment(deployment).map do |host_volume_file|\n        UffizziCore::Controller::DeployContainers::HostVolumeFileSerializer.new(host_volume_file).as_json\n      end\n\n      compose_file = if deployment.compose_file.present?\n        UffizziCore::Controller::DeployContainers::ComposeFileSerializer.new(deployment.compose_file).as_json\n      end\n\n      body = {\n        containers: containers,\n        credentials: credentials,\n        deployment_url: deployment.preview_url,\n        compose_file: compose_file,\n        host_volume_files: host_volume_files,\n      }\n\n      if password_protection_module.present?\n        body = password_protection_module.add_password_configuration(body, deployment.project_id)\n      end\n\n      controller_client(deployment).deploy_containers(deployment_id: deployment.id, body: body)\n    end\n\n    def namespace_exists?(deployable)\n      controller_client(deployable).namespace(namespace: deployable.namespace).code == 200\n    end\n\n    def fetch_deployment_events(deployment)\n      request_events(deployment) || []\n    end\n\n    def fetch_pods(deployment)\n      pods = controller_client(deployment).deployment_containers(deployment_id: deployment.id).result || []\n      pods.filter { |pod| pod.metadata.name.start_with?(Settings.controller.namespace_prefix) }\n    end\n\n    def fetch_namespace(deployable)\n      controller_client(deployable).namespace(namespace: deployable.namespace).result || nil\n    end\n\n    def create_namespace(deployable)\n      body = { namespace: deployable.namespace }\n      controller_client(deployable).create_namespace(body: body).result || nil\n    end\n\n    def delete_namespace(deployable)\n      controller_client(deployable).delete_namespace(namespace: deployable.namespace)\n    end\n\n    def create_cluster(cluster)\n      body = UffizziCore::Controller::CreateCluster::ClusterSerializer.new(cluster).as_json\n      controller_client(cluster).create_cluster(namespace: cluster.namespace, body: body).result\n    end\n\n    def show_cluster(cluster)\n      controller_client(cluster).show_cluster(namespace: cluster.namespace, name: cluster.name).result\n    end\n\n    def delete_cluster(cluster)\n      controller_client(cluster).delete_cluster(namespace: cluster.namespace)\n    end\n\n    def patch_cluster(cluster, sleep:)\n      body = UffizziCore::Controller::UpdateCluster::ClusterSerializer.new(cluster).as_json\n      body[:sleep] = sleep\n\n      controller_client(cluster).patch_cluster(name: cluster.name, namespace: cluster.namespace, body: body)\n    end\n\n    def ingress_hosts(cluster)\n      namespace = cluster.namespace\n\n      ingresses = controller_client(cluster).ingresses(namespace: namespace).result.items\n\n      ingresses.map do |ingress|\n        ingress.spec.rules.map(&:host)\n      end.flatten\n    end\n\n    private\n\n    def check_any_container_has_public_port(containers)\n      exists = containers.any? { |c| c.port.present? && c.public }\n      yield(exists) if block_given?\n\n      return if exists\n\n      raise InvalidPublicPort.new(containers)\n    end\n\n    def request_events(deployment)\n      controller_client(deployment).deployment_containers_events(deployment_id: deployment.id)\n    end\n\n    def controller_client(deployable)\n      settings = case deployable\n                 when UffizziCore::Deployment\n                   controller_settings_service.deployment_settings_by_deployment(deployable)\n                 when UffizziCore::Cluster\n                   controller_settings_service.vcluster_settings_by_vcluster(deployable)\n                 else\n                   raise StandardError, \"Deployable #{deployable.class.name} undefined\"\n      end\n\n      UffizziCore::ControllerClient.new(settings)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/controller_settings_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ControllerSettingsService\n  class << self\n    def vcluster_settings_by_vcluster(_cluster)\n      Settings.vcluster_controller\n    end\n\n    def vcluster_settings_by_account(_account)\n      Settings.vcluster_controller\n    end\n\n    def deployment_settings_by_deployment(_deployment)\n      Settings.controller.deep_dup.tap do |s|\n        s.managed_dns_zone = Settings.app.managed_dns_zone\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/deployment/domain_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::DomainService\n  class << self\n    include UffizziCore::DependencyInjectionConcern\n\n    def build_subdomain(deployment)\n      return domain_module.build_subdomain(deployment) if domain_module.present?\n      return build_docker_continuous_preview_subdomain(deployment) if deployment&.continuous_preview_payload&.fetch('docker', nil).present?\n\n      build_default_subdomain(deployment)\n    end\n\n    def update_subdomain!(deployment)\n      deployment.subdomain = build_subdomain(deployment)\n      deployment.save!\n    end\n\n    private\n\n    def build_docker_continuous_preview_subdomain(deployment)\n      project = deployment.project\n      continuous_preview_payload = deployment.continuous_preview_payload\n      docker_payload = continuous_preview_payload['docker']\n      repo_name = docker_payload['image'].split('/').last\n      image_tag = docker_payload['tag']\n      deployment_name = name(deployment)\n      subdomain = \"#{image_tag}-#{deployment_name}-#{repo_name}-#{project.slug}\"\n\n      format_subdomain(subdomain)\n    end\n\n    def build_default_subdomain(deployment)\n      deployment_name = name(deployment)\n      slug = deployment.project.slug.to_s\n      subdomain = \"#{deployment_name}-#{slug}\"\n\n      format_subdomain(subdomain)\n    end\n\n    def name(deployment)\n      \"deployment-#{deployment.id}\"\n    end\n\n    def format_subdomain(full_subdomain_name)\n      # Replace _ to - because RFC 1123 subdomain must consist of lower case alphanumeric characters,\n      # '-' or '.', and must start and end with an alphanumeric character\n      rfc_subdomain = full_subdomain_name.gsub('_', '-')\n      subdomain_length_limit = Settings.deployment.subdomain.length_limit\n      return rfc_subdomain if rfc_subdomain.length <= subdomain_length_limit\n\n      sliced_subdomain = rfc_subdomain.slice(0, subdomain_length_limit)\n      return sliced_subdomain.chop if sliced_subdomain.end_with?('-')\n\n      sliced_subdomain\n    end\n\n    def format_url_safe(name)\n      name.gsub(/ /, '-').gsub(/[^\\w-]+/, '-')\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/deployment/memory_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Deployment::MemoryService\n  class << self\n    def valid_memory_limit?(_deployment)\n      true\n    end\n\n    def memory_limit_error_message(_deployment); end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/deployment_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::DeploymentService\n  include UffizziCore::DependencyInjectionConcern\n  prepend_module_if_exists('UffizziCore::DeploymentServiceModule')\n\n  MIN_TARGET_PORT_RANGE = 37_000\n  MAX_TARGET_PORT_RANGE = 39_999\n\n  DEPLOYMENT_PROCESS_STATUSES = {\n    building: :building,\n    deploying: :deploying,\n    failed: :failed,\n    queued: :queued,\n  }.freeze\n\n  class << self\n    include UffizziCore::DependencyInjectionConcern\n\n    def create_from_compose(compose_file, project, user, params)\n      deployment_attributes = ActionController::Parameters.new(compose_file.template.payload)\n      deployment_form = UffizziCore::Api::Cli::V1::Deployment::CreateForm.new(deployment_attributes)\n      deployment_form.assign_dependences!(project, user)\n      deployment_form.compose_file = compose_file\n      deployment_form.creation_source = params[:creation_source] || UffizziCore::Deployment.creation_source.compose_file_manual\n      deployment_form.metadata = params[:metadata] || {}\n\n      run_deployment_creation_tasks(deployment_form) if deployment_form.save\n\n      deployment_form\n    end\n\n    def update_from_compose(compose_file, project, user, deployment, metadata)\n      deployment_attributes = ActionController::Parameters.new(compose_file.template.payload)\n\n      deployment_form = UffizziCore::Api::Cli::V1::Deployment::UpdateForm.new(deployment_attributes)\n      deployment_form.assign_dependences!(project, user)\n      deployment_form.compose_file = compose_file\n      deployment_form.metadata = metadata || {}\n\n      ActiveRecord::Base.transaction do\n        deployment.containers.destroy_all\n        deployment.compose_file.destroy! if deployment.compose_file&.kind&.temporary?\n        deployment.activate unless deployment.active?\n        new_params = params_for_update_deployment(deployment_form, compose_file)\n        deployment.update!(new_params)\n      end\n\n      deployment\n    end\n\n    def deploy_containers(deployment, repeated = false)\n      if !repeated\n        create_activity_items(deployment)\n        update_controller_container_names(deployment)\n      end\n\n      status = deployment_process_status(deployment)\n\n      case status\n      when DEPLOYMENT_PROCESS_STATUSES[:building]\n        Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} repeat deploy_containers\")\n        UffizziCore::Deployment::DeployContainersJob.perform_in(1.minute, deployment.id, true)\n        unless repeated\n          deployment.deployment_events.create!(deployment_state: status)\n        end\n      when DEPLOYMENT_PROCESS_STATUSES[:deploying]\n        Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} start deploying into controller\")\n\n        containers = deployment.active_containers\n        containers_with_variables = add_default_deployment_variables!(containers, deployment)\n\n        UffizziCore::ControllerService.deploy_containers(deployment, containers_with_variables)\n        deployment.deployment_events.create!(deployment_state: status)\n      else\n        Rails.logger.info(\"DEPLOYMENT_PROCESS deployment_id=#{deployment.id} deployment has builds errors, stopping\")\n      end\n    end\n\n    def disable!(deployment)\n      deployment.disable!\n      compose_file = deployment.compose_file || deployment.template&.compose_file\n      return unless compose_file&.kind&.temporary?\n\n      compose_file.destroy!\n    end\n\n    def fail!(deployment)\n      return if deployment.failed?\n\n      deployment.fail!\n      deployment.deployment_events.create!(deployment_state: deployment.state)\n      compose_file = deployment.compose_file || deployment.template&.compose_file\n      return unless compose_file&.kind&.temporary?\n\n      compose_file.destroy!\n    end\n\n    def all_containers_have_unique_ports?(containers)\n      ports = containers.map(&:port).compact\n      containers.empty? || ports.size == ports.uniq.size\n    end\n\n    def ingress_container?(containers)\n      containers.empty? || containers.map(&:receive_incoming_requests).count(true) == 1\n    end\n\n    def find_unused_port(deployment)\n      selected_port = nil\n\n      Timeout.timeout(20) do\n        loop do\n          selected_port = rand(MIN_TARGET_PORT_RANGE..MAX_TARGET_PORT_RANGE)\n\n          break if !deployment.containers.exists?(target_port: selected_port)\n        end\n      end\n\n      selected_port\n    end\n\n    def setup_ingress_container(deployment, ingress_container, port)\n      old_deployment_subdomain = deployment.subdomain\n\n      containers = deployment.containers.active\n\n      UffizziCore::Container.transaction do\n        containers.update_all(receive_incoming_requests: false, port: nil, public: false)\n        containers.find(ingress_container.id).update!(port: port, public: true, receive_incoming_requests: true)\n      end\n\n      deployment.reload\n\n      new_deployment_subdomain = DomainService.build_subdomain(deployment)\n\n      if new_deployment_subdomain != old_deployment_subdomain\n        deployment.update(subdomain: new_deployment_subdomain)\n      end\n\n      UffizziCore::Deployment::DeployContainersJob.perform_async(deployment.id)\n    end\n\n    def pull_request_payload_present?(deployment)\n      deployment.continuous_preview_payload.present? && deployment.continuous_preview_payload['pull_request'].present?\n    end\n\n    def failed?(deployment)\n      deployment_status = deployment_process_status(deployment)\n\n      deployment_status == DEPLOYMENT_PROCESS_STATUSES[:failed]\n    end\n\n    private\n\n    def run_deployment_creation_tasks(deployment)\n      UffizziCore::Deployment::DomainService.update_subdomain!(deployment)\n\n      UffizziCore::Deployment::CreateJob.perform_async(deployment.id)\n    end\n\n    def deployment_process_status(deployment)\n      containers = deployment.active_containers\n      activity_items = containers.map { |container| container.activity_items.order_by_id.last }.compact\n      events = activity_items.map { |activity_item| activity_item.events.order_by_id.last&.state }\n      events = events.flatten.uniq\n\n      return DEPLOYMENT_PROCESS_STATUSES[:queued] if events.empty?\n      return DEPLOYMENT_PROCESS_STATUSES[:failed] if events.include?(UffizziCore::Event.state.failed)\n      return DEPLOYMENT_PROCESS_STATUSES[:building] if events.include?(UffizziCore::Event.state.building)\n\n      DEPLOYMENT_PROCESS_STATUSES[:deploying]\n    end\n\n    def create_activity_items(deployment)\n      deployment.active_containers.each do |container|\n        repo = container.repo\n        activity_item = UffizziCore::ActivityItemService.create_docker_item!(repo, container)\n\n        create_default_activity_item_event(activity_item)\n\n        if UffizziCore::RepoService.credential(repo).present? && activity_item.docker?\n          UffizziCore::ActivityItem::Docker::UpdateDigestJob.perform_async(activity_item.id)\n        end\n        UffizziCore::Deployment::ManageDeployActivityItemJob.perform_in(5.seconds, activity_item.id)\n      end\n    end\n\n    def create_default_activity_item_event(activity_item)\n      activity_item.events.create(state: UffizziCore::Event.state.deploying) if activity_item.docker?\n    end\n\n    def update_controller_container_names(deployment)\n      deployment.active_containers.each do |container|\n        next if container.controller_name.present?\n\n        controller_name = generate_controller_container_name(container)\n        container.update!(controller_name: controller_name)\n      end\n    end\n\n    def generate_controller_container_name(container)\n      Digest::SHA256.hexdigest(\"#{container.id}:#{container.image}\")[0, 10]\n    end\n\n    def add_default_deployment_variables!(containers, deployment)\n      containers.each do |container|\n        envs = []\n        if container.port.present? && !UffizziCore::ContainerService.defines_env?(container, 'PORT')\n          envs.push('name' => 'PORT', 'value' => container.target_port.to_s)\n        end\n\n        envs.push('name' => 'UFFIZZI_URL', 'value' => \"https://#{deployment.preview_url}\")\n        envs.push('name' => 'UFFIZZI_DOMAIN', 'value' => deployment.preview_url)\n\n        preview_url = \"https://#{domain_module.build_preview_url(deployment)}\" if domain_module.present?\n        envs.push('name' => 'UFFIZZI_PREDICTABLE_URL', 'value' => preview_url || '')\n\n        container.variables = [] if container.variables.nil?\n\n        container.variables.push(*envs)\n      end\n    end\n\n    def params_for_update_deployment(deployment_form, compose_file)\n      {\n        containers: deployment_form.containers,\n        compose_file_id: compose_file.id,\n        metadata: deployment_form.metadata,\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/logs_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::LogsService\n  NOT_ALLOWED_SYMBOLS_IN_NAME_REGEX = /[^a-zA-Z0-9-]/.freeze\n  DEFAULT_LOGS_LIMIT = 1000\n\n  class << self\n    def fetch_container_logs(container, query = {})\n      response = request_logs(container, query).result || {}\n      response = Hashie::Mash.new(response)\n      logs = response.logs || []\n\n      {\n        logs: format_logs(logs),\n      }\n    end\n\n    private\n\n    def request_logs(container, query)\n      deployment = container.deployment\n\n      controller_client.deployment_container_logs(\n        deployment_id: deployment.id,\n        container_name: UffizziCore::ContainerService.pod_name(container),\n        limit: query[:limit] || DEFAULT_LOGS_LIMIT,\n        previous: query[:previous] || false,\n      )\n    end\n\n    def format_logs(logs)\n      logs.map do |item|\n        timestamp, *payload = item.split\n        formatted_timestamp = timestamp.present? ? timestamp.to_time(:utc).strftime('%Y-%m-%d %H:%M:%S.%L %Z') : nil\n        { timestamp: formatted_timestamp, payload: payload.join(' ') }\n      end\n    end\n\n    def controller_client\n      UffizziCore::ControllerClient.new(Settings.controller)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/manage_activity_items_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ManageActivityItemsService\n  attr_accessor :deployment, :containers, :pods, :namespace_data\n\n  def initialize(deployment)\n    @deployment = deployment\n    @containers = deployment.active_containers\n    @namespace_data = UffizziCore::ControllerService.fetch_namespace(deployment)\n    @pods = UffizziCore::ControllerService.fetch_pods(deployment)\n  end\n\n  def container_status_item(container)\n    container_status_items.detect { |container_statuses| container_statuses[:id] == container.id }\n  end\n\n  def container_status_items\n    build_container_status_items(build_network_connectivities, build_containers_replicas)\n  end\n\n  private\n\n  def build_network_connectivities\n    containers.with_public_access.map do |container|\n      { id: container.id, items: build_container_network_connectivity_items(container) }\n    end\n  end\n\n  def build_container_network_connectivity_items(container)\n    network_connectivities = container_network_connectivities(container)\n    return [] if network_connectivities.nil?\n\n    network_connectivities.map do |network_connectivity|\n      type, value = network_connectivity\n\n      { type: type, status: value.status }\n    end\n  end\n\n  def build_containers_replicas\n    containers.map do |container|\n      items = pods.map do |pod|\n        {\n          name: item_name(pod, container),\n          status: get_status(pod, container),\n        }\n      end\n\n      { id: container.id, items: items }\n    end\n  end\n\n  def build_container_status_items(network_connectivities, containers_replicas)\n    containers.map do |container|\n      network_connectivity = network_connectivities.detect { |item| item[:id] == container.id }\n      container_replicas = containers_replicas.detect { |item| item[:id] == container.id }\n\n      {\n        id: container.id,\n        status: build_container_status(container, network_connectivity, container_replicas),\n      }\n    end\n  end\n\n  def replicas_contains_status?(replicas, status)\n    replicas.any? { |replica| replica[:status] == status }\n  end\n\n  def build_container_status(container, network_connectivity, container_replicas)\n    error = replicas_contains_status?(container_replicas[:items], UffizziCore::Event.state.failed)\n    container_is_running = replicas_contains_status?(container_replicas[:items], UffizziCore::Event.state.deployed)\n    deployed = !error && container_is_running\n    return container_status(error, deployed) unless container.public?\n\n    network_connectivity[:items].each do |item|\n      status = item[:status].to_sym\n      error ||= status == :failed\n      deployed &&= status == :success\n    end\n\n    container_status(error, deployed)\n  end\n\n  def container_status(error, deployed)\n    return UffizziCore::Event.state.failed if error\n    return UffizziCore::Event.state.deployed if deployed\n\n    UffizziCore::Event.state.deploying\n  end\n\n  def item_name(pod, container)\n    hash = pod.metadata.name.split('-').last\n    \"#{container.image_name}-#{hash}\"\n  end\n\n  def get_status(pod, container)\n    pod_container = pod_container(pod, container)\n\n    return UffizziCore::Event.state.deploying if pod_container.nil? || pod_container[:state].to_h.empty?\n\n    pod_container_status = pod_container[:state].keys.first\n    state = pod_container[:state][pod_container_status]\n    reason = state&.reason\n\n    case pod_container_status.to_sym\n    when :running\n      UffizziCore::Event.state.deployed\n    when :waiting\n      raise UffizziCore::Deployment::ImagePullError, @deployment.id if ['ErrImagePull', 'ImagePullBackOff'].include?(reason)\n\n      UffizziCore::Event.state.deploying\n    else\n      UffizziCore::Event.state.deploying\n    end\n  end\n\n  def pod_container(pod, container)\n    pod_name = UffizziCore::ContainerService.pod_name(container)\n\n    pod&.status&.container_statuses&.detect { |cs| cs.name.include?(pod_name) }\n  end\n\n  def container_network_connectivities(container)\n    network_connectivity = Hashie::Mash.new(JSON.parse(deployment_network_connectivity))\n    containers_network_connectivity = network_connectivity&.containers\n\n    containers_network_connectivity[container.id.to_s] unless containers_network_connectivity.nil?\n  end\n\n  def deployment_network_connectivity\n    namespace_data&.metadata&.annotations&.network_connectivity.presence || '{}'\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/project_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ProjectService\n  class << self\n    def update_compose_secrets(project)\n      compose_file = project.compose_file\n      return if compose_file&.template.nil?\n\n      project.secrets.each do |secret|\n        if UffizziCore::ComposeFileService.has_secret?(compose_file, secret)\n          UffizziCore::ComposeFileService.update_secret!(compose_file, secret)\n        end\n      end\n\n      return unless UffizziCore::ComposeFileService.secrets_valid?(compose_file, project.secrets)\n\n      secrets_error_key = UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY\n      return unless UffizziCore::ComposeFile::ErrorsService.has_error?(compose_file, secrets_error_key)\n\n      UffizziCore::ComposeFile::ErrorsService.reset_error!(compose_file, secrets_error_key)\n      compose_file.set_valid! unless UffizziCore::ComposeFile::ErrorsService.has_errors?(compose_file)\n    end\n\n    def update_compose_secret_errors(project, secret)\n      compose_file = project.compose_file\n      return if compose_file.nil?\n      return unless UffizziCore::ComposeFileService.has_secret?(compose_file, secret)\n\n      error_message = I18n.t('compose.project_secret_not_found', secret: secret['name'])\n      compose_file_errors = compose_file.payload['errors'] || {}\n      secrets_errors = compose_file_errors[UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY].presence || []\n      new_secrets_errors = secrets_errors.append(error_message).uniq\n      error = { UffizziCore::ComposeFile::ErrorsService::SECRETS_ERROR_KEY => new_secrets_errors }\n      new_errors = compose_file_errors.merge(error)\n\n      UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, new_errors, compose_file.content)\n    end\n\n    def add_users_to_project!(project, account)\n      user_projects = account.memberships.map do |membership|\n        { project: project, user: membership.user, role: membership.role }\n      end\n\n      UffizziCore::UserProject.create!(user_projects)\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/repo_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::RepoService\n  class << self\n    def needs_target_port?(repo)\n      return false if repo.nil?\n\n      !repo.dockerfile?\n    end\n\n    def credential(repo)\n      container_registry_service = UffizziCore::ContainerRegistryService.init_by_subclass(repo.type)\n      credentials = repo.project.account.credentials\n      container_registry_service.credential(credentials)\n    end\n\n    def image_name(repo)\n      \"e#{repo.container.deployment_id}r#{repo.id}-#{Digest::SHA256.hexdigest(\"#{self.class}:#{repo.branch}:\n      #{repo.project_id}:#{repo.id}\")[0, 10]}\"\n    end\n\n    def image(repo)\n      repo_credential = credential(repo)\n\n      \"#{repo_credential.registry_url}/#{image_name(repo)}\"\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/response_service.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ResponseService\n  def meta(collection)\n    {\n      total_count: collection.total_count,\n      current_page: collection.current_page,\n      per_page: collection.limit_value,\n      count: collection.to_a.count,\n      total_pages: collection.total_pages,\n    }\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/template/memory_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::Template::MemoryService\n  class << self\n    def valid_memory_limit?(_template)\n      true\n    end\n\n    def memory_limit_error_message(_template); end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/token_service.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::TokenService\n  class << self\n    def encode(payload)\n      JWT.encode(payload, Settings.rails.secret_key_base, 'HS256')\n    end\n\n    def decode(token)\n      JWT.decode(token, Settings.rails.secret_key_base, true, algorithm: 'HS256')\n    rescue JWT::DecodeError\n      nil\n    end\n\n    def generate\n      SecureRandom.hex\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/user_access_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::UserAccessService\n  attr_accessor :user_access_module\n\n  delegate :admin_access_to_account?, :developer_access_to_account?, :viewer_access_to_account?,\n           :admin_or_developer_access_to_account?, :any_access_to_account?, :admin_access_to_project?,\n           :developer_access_to_project?, :viewer_access_to_project?, :admin_or_developer_access_to_project?,\n           :any_access_to_project?, :global_admin?, :valid_token?, to: :@user_access_module\n\n  def initialize(user_access_module)\n    @user_access_module = user_access_module\n  end\nend\n"
  },
  {
    "path": "core/app/services/uffizzi_core/user_generator_service.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::UserGeneratorService\n  DEFAULT_USER_EMAIL = 'user@example.com'\n  DEFAULT_PROJECT_NAME = 'default'\n  DEFAULT_ACCOUNT_NAME = 'default'\n\n  class << self\n    def safe_generate(email, password, project_name)\n      generate(email, password, project_name)\n    rescue ActiveRecord::RecordInvalid => e\n      puts e.message\n    end\n\n    def generate(email, password, project_name)\n      user_attributes = build_user_attributes(email, password)\n      project_attributes = build_project_attributes(project_name)\n\n      ActiveRecord::Base.transaction do\n        user = UffizziCore::User.create!(user_attributes)\n\n        account_attributes = build_account_attributes(user)\n        account = UffizziCore::Account.create!(account_attributes)\n        user.memberships.create!(account: account, role: UffizziCore::Membership.role.admin)\n        project = account.projects.create!(project_attributes)\n        project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)\n      end\n    end\n\n    private\n\n    def build_user_attributes(email, password)\n      user_attributes = {\n        state: UffizziCore::User::STATE_ACTIVE,\n        creation_source: UffizziCore::User.creation_source.system,\n      }\n\n      if email.present?\n        user_attributes[:email] = email\n      elsif IO::console.present?\n        IO::console.write(\"Enter User Email (default: #{DEFAULT_USER_EMAIL}): \")\n        user_attributes[:email] = IO::console.gets.strip.presence || DEFAULT_USER_EMAIL\n      end\n\n      user_attributes[:password] = if password.present?\n        password\n      elsif IO::console.present?\n        IO::console.getpass('Enter Password: ')\n      end\n\n      user_attributes\n    end\n\n    def build_project_attributes(project_name)\n      project_attributes = {\n        state: UffizziCore::Project::STATE_ACTIVE,\n      }\n      if project_name.present?\n        project_attributes[:name] = project_name\n      elsif IO::console.present?\n        IO::console.write(\"Enter Project Name (default: #{DEFAULT_PROJECT_NAME}): \")\n        project_attributes[:name] = IO::console.gets.strip.presence || DEFAULT_PROJECT_NAME\n      else\n        project_attributes[:name] = DEFAULT_PROJECT_NAME\n      end\n\n      project_attributes[:slug] = prepare_project_slug(project_attributes[:name])\n      project_attributes\n    end\n\n    def build_account_attributes(user)\n      {\n        owner: user,\n        name: DEFAULT_ACCOUNT_NAME,\n        state: UffizziCore::Account::STATE_ACTIVE,\n        kind: UffizziCore::Account.kind.personal,\n      }\n    end\n\n    def prepare_project_slug(project_name)\n      project_name.downcase.gsub(/[^A-Za-z0-9]/, '-')\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/utils/uffizzi_core/converters.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::Converters\n  class << self\n    include ActionView::Helpers::NumberHelper\n\n    def deep_lower_camelize_keys(object)\n      case object\n      when Array\n        object.map do |element|\n          element.deep_transform_keys { |key| key.to_s.camelize(:lower) }\n        end\n      when Hash\n        object.deep_transform_keys { |key| key.to_s.camelize(:lower) }\n      else\n        object\n      end\n    end\n\n    def deep_underscore_keys(object)\n      case object\n      when Array\n        object.map do |element|\n          element.deep_transform_keys { |key| key.to_s.underscore }\n        end\n      when Hash\n        object.deep_transform_keys { |key| key.to_s.underscore }\n      else\n        object\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/app/validators/uffizzi_core/email_validator.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::EmailValidator < ActiveModel::EachValidator\n  EMAIL_REGEXP = /\\A([^@\\s<>]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\z/i.freeze\n\n  def validate_each(record, attribute, value)\n    record.errors.add(attribute, (options[:message] || :invalid)) unless value&.match?(EMAIL_REGEXP)\n  end\nend\n"
  },
  {
    "path": "core/app/validators/uffizzi_core/environment_variable_list_validator.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::EnvironmentVariableListValidator < ActiveModel::EachValidator\n  def validate_each(record, attribute, variables)\n    record.errors.add(attribute, :invalid) if variables.class != Array || !valid_variables?(variables)\n  end\n\n  def valid_variables?(variables)\n    variables.each do |variable|\n      return false if variable.class != Hash || !variable.key?('name') || variable['name'].nil? || !variable.key?('value')\n    end\n\n    true\n  end\nend\n"
  },
  {
    "path": "core/app/validators/uffizzi_core/image_command_args_validator.rb",
    "content": "# frozen_string_literal: true\n\nclass UffizziCore::ImageCommandArgsValidator < ActiveModel::EachValidator\n  def validate_each(record, attribute, command_args)\n    record.errors.add(attribute, :invalid) if !command_args.nil? && !valid_command_args?(command_args)\n  end\n\n  def valid_command_args?(raw_command_args)\n    return true if raw_command_args.empty?\n\n    begin\n      command_args = JSON.parse(raw_command_args)\n    rescue JSON::ParserError\n      return false\n    end\n\n    return false if command_args.class != Array || command_args.empty?\n\n    command_args.all? { |item| item.instance_of?(String) }\n  end\nend\n"
  },
  {
    "path": "core/bin/rails",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# This command will automatically be run when you run \"rails\" with Rails gems\n# installed from the root of your application.\n\nENGINE_ROOT = File.expand_path('..', __dir__)\nENGINE_PATH = File.expand_path('../lib/uffizzi_core/engine', __dir__)\nAPP_PATH = File.expand_path('../test/dummy/config/application', __dir__)\n\n# Set up gems listed in the Gemfile.\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)\nrequire 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])\n\nrequire 'rails/all'\nrequire 'rails/engine/commands'\n"
  },
  {
    "path": "core/config/initializers/rswag_api.rb",
    "content": "# frozen_string_literal: true\n\nRswag::Api.configure do |c|\n  # Specify a root folder where Swagger JSON files are located\n  # This is used by the Swagger middleware to serve requests for API descriptions\n  # NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure\n  # that it's configured to generate files in the same folder\n  c.swagger_root = \"#{UffizziCore::Engine.root}/swagger\"\n\n  # Inject a lamda function to alter the returned Swagger prior to serialization\n  # The function will have access to the rack env for the current request\n  # For example, you could leverage this to dynamically assign the \"host\" property\n  #\n  # c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }\nend\n"
  },
  {
    "path": "core/config/initializers/rswag_ui.rb",
    "content": "# frozen_string_literal: true\n\nRswag::Ui.configure do |c|\n  # List the Swagger endpoints that you want to be documented through the swagger-ui\n  # The first parameter is the path (absolute or relative to the UI host) to the corresponding\n  # endpoint and the second is a title that will be displayed in the document selector\n  # NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON or YAML endpoints,\n  # then the list below should correspond to the relative paths for those endpoints\n\n  c.swagger_endpoint('/api-docs/v1/swagger.json', 'API V1 Docs')\n\n  # Add Basic Auth in case your API is private\n  # c.basic_auth_enabled = true\n  # c.basic_auth_credentials 'username', 'password'\nend\n"
  },
  {
    "path": "core/config/initializers/swagger_yard.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'swagger_yard'\n\nSwaggerYard.configure do |config|\n  config.api_version = '1.0'\n\n  config.title = 'Uffizzi docs'\n  config.description = 'Your API does this'\n\n  config.api_base_path = 'http://lvh.me:7000'\n\n  config.controller_path = File.expand_path('app/controllers/uffizzi_core/api/**/*', UffizziCore::Engine.root)\n  config.model_path = File.expand_path('app/models/uffizzi_core/**/*', UffizziCore::Engine.root)\n\n  config.include_private = false\nend\n"
  },
  {
    "path": "core/config/locales/en.activerecord.yml",
    "content": "en:\n  activerecord:\n    errors:\n      models:\n        uffizzi_core/project:\n          attributes:\n            secret:\n              not_found: There is no secret with name %{name}\n            name:\n              taken: \"A project with the name '%{value}' already exists.\"\n            slug:\n              taken: \"A project slug '%{value}' already taken.\"\n        uffizzi_core/credential:\n          attributes:\n            registry_url:\n              invalid_scheme: The protocol for the registry url is not specified\n            password:\n              password_blank: Password/Access Token can't be blank\n            username:\n              incorrect: \"We were unable to log you into %{type}. Please verify that your username and password are correct.\"\n            type:\n              exist: Credential of that type already exist.\n        uffizzi_core/deployment:\n          attributes:\n            containers:\n              max_memory_limit_error: \"Total memory limit of containers must be less than %{max}\"\n        uffizzi_core/user:\n          attributes:\n            email:\n              invalid: Invalid email address\n"
  },
  {
    "path": "core/config/locales/en.yml",
    "content": "en:\n  github:\n    preview_url_message: |\n      **This branch has been deployed using Uffizzi.**\n      Preview URL:\n      https://%{preview_url}\n      View deployment details here:\n      https://%{deployment_url}\n      This is an automated comment. To turn off commenting, visit uffizzi.com.\n    invalid_compose_message: |\n      :exclamation: **Preview failed: invalid compose**\n      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Uffizzi was unable to deploy a preview of this pull request because the compose file in this branch is invalid.\n\n  compose:\n    unsupported_file: Unsupported compose file\n    invalid_file: 'Syntax error: %{err} at line %{line} column %{column}'\n    invalid_compose: Invalid compose file\n    invalid_config_option: Invalid config option '%{value}' - only a-zA-Z0-9._- characters are allowed\n    no_services: Service services has neither an image nor a build context specified. At least one must be provided.\n    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\"\n    no_ingress: Service ingress has not been defined.\n    invalid_image_value: Invalid image value '%{value}'\n    image_name_contains_uppercase_value: Image name '%{value}' contains uppercase characters\n    unprocessable_image: Invalid credential '%{value} or image does not exist'\n    invalid_repo_type: Unsupported repo type\n    repo_not_found: The specified repository doesn't exist '%{name}'\n    ingress_port_not_specified: Ingress port not specified\n    ingress_service_not_found: Ingress service not found\n    invalid_ingress_service: Invalid ingress service '%{value}'\n    no_variable_name: Invalid environment variable 'name=%{name}' 'value=%{value}'\n    invalid_config: Invalid config value 'source=%{source}' 'target=%{target}'\n    port_out_of_range: Port should be specified between %{port_min} - %{port_max}\n    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\n    invalid_memory_type: Memory '%{value}' contains an invalid type, it should be a string\n    invalid_memory_value: Invalid memory value '%{value}'\n    invalid_memory: The memory should be one of the `125m` `250m` `500m` `1000m` `2000m` `4000m` values\n    image_build_no_specified: Service '%{value}' has neither an image nor a build context specified. At least one must be provided.\n    build_context_no_specified: The context option should be specified\n    config_file_not_found: Config file not found '%{name}'\n    host_volume_file_not_found: Host volume file not found '%{name}'\n    invalid_context: Invalid context value '%{value}'\n    invalid_bool_value: Invalid %{field} value '%{value}'. The value should be `true` or `false`\n    invalid_delete_after_postfix: The postfix of the delete_preview_after value should be `h`\n    invalid_integer: The specified value for %{option} should be an Integer type\n    invalid_string: The specified value for %{option} should be a String type\n    invalid_delete_after_min: Minimum delete_preview_after allowed is %{value}h\n    invalid_delete_after_max: Maximum delete_preview_after allowed is %{value}h\n    invalid_type: Unsupported type of '%{option}' option\n    empty_env_file: env_file contains an empty value\n    file_not_found: Couldn't find '%{path}' file\n    env_file_duplicates: env_file contains non-unique items '%{values}'\n    boolean_option: The service name %{value} must be a quoted string, i.e. '%{value}'.\n    config_file_option_empty: \"'%{config_name}' has an empty file\"\n    global_config_not_found: undefined config '%{config}'\n    invalid_branch: Branch '%{branch}' doesn't exist for repository '%{repository_url}'\n    repository_not_found: The specified repository doesn't exist '%{repository_url}'\n    build_context_unknown_repository: Compose repository is undefined\n    secret_name_blank: The specified name for '%{option}' secret can not be blank\n    secret_external: The specified secret '%{secret}' should be external\n    global_secret_not_found: undefined secret '%{secret}'\n    project_secret_not_found: Project secret '%{secret}' not found\n    continuous_preview_in_service_level: The option '%{option}' is not supported for service-level. Use 'x-uffizzi-continuous-preview' instead\n    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'\n    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.\"\n    invalid_retries: \"Invalid retries value: 'retries:%{value}'. The value should be an integer.\"\n    string_or_array_error: \"'%{option}' contains an invalid type, it should be a string, or an array\"\n    not_implemented: \"'%{option}' option is not implemented\"\n    infinite_recursion: \"Found infinite recursion for key '%{key}'\"\n    volume_path_is_invalid: The path '%{path}' is invalid\n    volume_prop_is_required: The '%{prop_name}' is a required property\n    volume_invalid_name: \"Volumes value '%{name}' does not match any of the regexes: '^[a-zA-Z0-9._-]+$'\"\n    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.\n    invalid_volume_destination: Invalid volume specification '%{spec}' destination can't be '/'\n    required_start_commands: \"When 'test' is a list the first item must be one of: '%{available_commands}'\"\n    volumes_should_be_array: Volumes '%{volumes}' should be an arra\n    healthcheck_missing_required_option: \"One of these options is required: %{required_options}\"\n    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/\"\n\n  secrets:\n    duplicates_exists: Secret with key %{secrets} already exist.\n    invalid_key_length: A secret key must be no longer than 256 characters.\n\n  deployment:\n    invalid_state: Preview with ID deployment-%{id} %{state}\n    already_exists: An active deployment already exists\n  \n  cluster:\n    already_asleep: The cluster %{name} is already asleep.\n    already_awake: The cluster %{name} is already awake.\n    scaling_failed: Failed to %{action} cluster.\n    deploy_in_process: Please wait until the cluster %{name} is deployed.\n\n  session:\n    unsupported_login_type: This type of login is not supported\n    unauthorized: Unauthorized\n\n  kubernetes_distribution:\n    not_available: \"The k8s version %{version} is not supported. Available version are: %{available_versions}.\"\n\n  enumerize:\n    credential:\n      type:\n        \"UffizziCore::Credential::GithubContainerRegistry\": \"Github Container Registry\"\n        \"UffizziCore::Credential::DockerHub\": \"DockerHub\"\n        \"UffizziCore::Credential::DockerRegistry\": \"Docker Registry\"\n        \"UffizziCore::Credential::Azure\": \"Azure\"\n        \"UffizziCore::Credential::Google\": \"Google\"\n        \"UffizziCore::Credential::Amazon\": \"Amazon\"\n\n  registry:\n    error: Container registry returned a %{code} error\n"
  },
  {
    "path": "core/config/routes.rb",
    "content": "# frozen_string_literal: true\n\nUffizziCore::Engine.routes.draw do\n  mount Rswag::Api::Engine => '/api-docs'\n  mount Rswag::Ui::Engine => '/api-docs'\n\n  namespace :api, defaults: { format: :json } do\n    namespace :cli do\n      namespace :v1 do\n        resources :projects, only: ['index', 'show', 'destroy'], param: :slug do\n          scope module: :projects do\n            resource :compose_file, only: ['show', 'create', 'destroy']\n            resources :clusters, only: [:index, :create, :show, :destroy], param: :name do\n              member do\n                put :scale_down\n                put :scale_up\n                put :sync\n              end\n              scope module: :clusters do\n                resources :ingresses, only: ['index']\n              end\n            end\n            resources :deployments, only: ['index', 'show', 'create', 'destroy', 'update'] do\n              post :deploy_containers, on: :member\n              scope module: :deployments do\n                resources :activity_items, only: ['index']\n                resources :events, only: ['index']\n                resources :containers, only: ['index'], param: :name do\n                  get :k8s_container_description\n                  scope module: :containers do\n                    resources :logs, only: ['index']\n                    resources :builds, only: [] do\n                      collection do\n                        get :logs\n                      end\n                    end\n                  end\n                end\n              end\n            end\n            resources :secrets, only: ['index', 'destroy'] do\n              collection do\n                post :bulk_create\n              end\n            end\n          end\n        end\n        resource :session, only: ['create', 'destroy']\n\n        namespace :ci do\n          resource :session, only: ['create']\n        end\n\n        resources :accounts, only: ['show', 'update'], param: :name\n\n        resources :accounts, only: ['index'] do\n          scope module: :accounts do\n            resources :projects, only: ['index', 'create']\n            resources :clusters, only: ['index']\n            resources :credentials, only: ['index', 'create', 'update', 'destroy'], param: :type do\n              member do\n                get :check_credential\n              end\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220218121438_create_uffizzi_core_tables.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateUffizziCoreTables < ActiveRecord::Migration[6.1]\n  def change\n    create_table('uffizzi_core_accounts', force: :cascade) do |t|\n      t.text('name')\n      t.text('kind', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('customer_token')\n      t.string('state')\n      t.string('subscription_token')\n      t.datetime('payment_issue_at')\n      t.string('domain')\n      t.boolean('sso_enabled', default: false)\n      t.bigint('owner_id')\n      t.integer('container_memory_limit')\n      t.string('workos_organization_id')\n      t.string('sso_state')\n      t.index(['customer_token'], name: 'index_accounts_on_customer_token', unique: true)\n      t.index(['domain'], name: 'index_accounts_on_domain', unique: true)\n      t.index(['subscription_token'], name: 'index_accounts_on_subscription_token', unique: true)\n    end\n\n    create_table('uffizzi_core_activity_items', force: :cascade) do |t|\n      t.bigint('deployment_id', null: false)\n      t.string('namespace')\n      t.string('name')\n      t.string('tag')\n      t.string('branch')\n      t.string('type')\n      t.bigint('container_id', null: false)\n      t.string('commit')\n      t.string('commit_message')\n      t.bigint('build_id')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.jsonb('data', default: {}, null: false)\n      t.string('digest')\n      t.index(['container_id'], name: 'index_activity_items_on_container_id')\n      t.index(['deployment_id'], name: 'index_activity_items_on_deployment_id')\n    end\n\n    create_table('uffizzi_core_builds', force: :cascade) do |t|\n      t.bigint('repo_id', null: false)\n      t.string('build_id')\n      t.string('repository')\n      t.string('branch')\n      t.string('commit')\n      t.string('committer')\n      t.string('message')\n      t.string('log_url')\n      t.integer('status')\n      t.datetime('started_at')\n      t.datetime('ended_at')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.boolean('deployed')\n      t.index(['build_id'], name: 'index_builds_on_build_id', unique: true)\n      t.index(['repo_id'], name: 'index_builds_on_repo_id')\n    end\n\n    create_table('uffizzi_core_comments', force: :cascade) do |t|\n      t.string('commentable_type')\n      t.bigint('commentable_id')\n      t.text('content')\n      t.bigint('user_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('ancestry')\n      t.integer('ancestry_depth', default: 0)\n      t.index(['ancestry'], name: 'index_comments_on_ancestry')\n      t.index(['commentable_type', 'commentable_id'], name: 'index_comments_on_commentable')\n      t.index(['user_id'], name: 'index_comments_on_user_id')\n    end\n\n    create_table('uffizzi_core_compose_files', force: :cascade) do |t|\n      t.string('source')\n      t.bigint('repository_id')\n      t.string('branch')\n      t.string('path')\n      t.string('auto_deploy')\n      t.bigint('added_by_id')\n      t.bigint('project_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('state')\n      t.jsonb('payload', default: {}, null: false)\n      t.text('content')\n      t.string('kind', default: 'main')\n      t.index(['project_id'], name: 'index_compose_files_on_project_id')\n    end\n\n    create_table('uffizzi_core_config_files', force: :cascade) do |t|\n      t.string('filename')\n      t.string('kind')\n      t.bigint('added_by_id')\n      t.text('payload')\n      t.bigint('project_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.bigint('compose_file_id')\n      t.string('creation_source')\n      t.string('source')\n      t.index(['compose_file_id'], name: 'index_config_files_on_compose_file_id')\n      t.index(['project_id'], name: 'index_config_files_on_project_id')\n    end\n\n    create_table('uffizzi_core_container_config_files', force: :cascade) do |t|\n      t.string('mount_path')\n      t.bigint('container_id', null: false)\n      t.bigint('config_file_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['config_file_id'], name: 'index_container_config_files_on_config_file_id')\n      t.index(['container_id'], name: 'index_container_config_files_on_container_id')\n    end\n\n    create_table('uffizzi_core_containers', force: :cascade) do |t|\n      t.string('image')\n      t.string('tag')\n      t.jsonb('variables')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.bigint('deployment_id')\n      t.boolean('public', default: false, null: false)\n      t.integer('port')\n      t.bigint('repo_id')\n      t.string('state')\n      t.string('continuously_deploy', null: false)\n      t.string('kind', default: 'user')\n      t.integer('target_port')\n      t.string('controller_name')\n      t.boolean('receive_incoming_requests')\n      t.integer('memory_request')\n      t.integer('memory_limit')\n      t.jsonb('secret_variables')\n      t.string('entrypoint')\n      t.string('command')\n      t.index(['deployment_id'], name: 'index_containers_on_deployment_id')\n      t.index(['repo_id'], name: 'index_containers_on_repo_id')\n    end\n\n    create_table('uffizzi_core_coupons', force: :cascade) do |t|\n      t.string('token')\n      t.string('name', null: false)\n      t.string('currency', null: false)\n      t.bigint('amount_off', null: false)\n      t.string('duration', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n    end\n\n    create_table('uffizzi_core_credentials', force: :cascade) do |t|\n      t.string('type')\n      t.string('username')\n      t.string('password')\n      t.bigint('project_id')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('provider_ref')\n      t.bigint('account_id')\n      t.string('state')\n      t.string('registry_url')\n      t.index(['account_id'], name: 'index_credentials_on_account_id')\n      t.index(['project_id'], name: 'index_credentials_on_project_id')\n      t.index(['provider_ref'], name: 'index_credentials_on_provider_ref')\n    end\n\n    create_table('uffizzi_core_deployments', force: :cascade) do |t|\n      t.bigint('project_id', null: false)\n      t.text('kind', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('creator_name')\n      t.string('subdomain')\n      t.string('state')\n      t.float('memory_limit')\n      t.bigint('deployed_by_id')\n      t.bigint('continuous_preview_id_deprecated')\n      t.jsonb('continuous_preview_payload')\n      t.string('creation_source')\n      t.bigint('compose_file_id')\n      t.bigint('template_id')\n      t.index(['compose_file_id'], name: 'index_deployments_on_compose_file_id')\n      t.index(['project_id'], name: 'index_deployments_on_project_id')\n      t.index(['template_id'], name: 'index_deployments_on_template_id')\n    end\n\n    create_table('uffizzi_core_events', force: :cascade) do |t|\n      t.bigint('activity_item_id', null: false)\n      t.string('state')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['activity_item_id'], name: 'index_events_on_activity_item_id')\n    end\n\n    create_table('uffizzi_core_invitations', force: :cascade) do |t|\n      t.text('email', null: false)\n      t.text('token', null: false)\n      t.string('status', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.bigint('invited_by_id', null: false)\n      t.bigint('entityable_id', null: false)\n      t.string('entityable_type', null: false)\n      t.string('role', null: false)\n      t.bigint('invitee_id')\n      t.index(['token'], name: 'index_invitations_on_token', unique: true)\n    end\n\n    create_table('uffizzi_core_memberships', force: :cascade) do |t|\n      t.bigint('user_id', null: false)\n      t.bigint('account_id', null: false)\n      t.text('role', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['account_id'], name: 'index_memberships_on_account_id')\n      t.index(['user_id', 'account_id'], name: 'index_memberships_on_user_id_and_account_id')\n      t.index(['user_id'], name: 'index_memberships_on_user_id')\n    end\n\n    create_table('uffizzi_core_payments', force: :cascade) do |t|\n      t.bigint('account_id', null: false)\n      t.string('charge_id')\n      t.string('status')\n      t.float('amount')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['account_id'], name: 'index_payments_on_account_id')\n    end\n\n    create_table('uffizzi_core_prices', force: :cascade) do |t|\n      t.string('token')\n      t.string('slug', null: false)\n      t.string('name', null: false)\n      t.float('units_price', null: false)\n      t.bigint('units_amount', null: false)\n      t.bigint('product_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['product_id'], name: 'index_prices_on_product_id')\n    end\n\n    create_table('uffizzi_core_products', force: :cascade) do |t|\n      t.string('token')\n      t.string('slug', null: false)\n      t.string('name', null: false)\n      t.string('type', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('kind')\n    end\n\n    create_table('uffizzi_core_projects', force: :cascade) do |t|\n      t.text('name', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.bigint('account_id', null: false)\n      t.string('state')\n      t.string('slug')\n      t.string('description')\n      t.jsonb('secrets')\n      t.index(['account_id', 'name'], name: 'index_projects_on_account_id_and_name', unique: true)\n      t.index(['account_id'], name: 'index_projects_on_account_id')\n    end\n\n    create_table('uffizzi_core_ratings', force: :cascade) do |t|\n      t.string('name')\n      t.string('state')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n    end\n\n    create_table('uffizzi_core_repos', force: :cascade) do |t|\n      t.string('namespace')\n      t.string('name')\n      t.string('tag')\n      t.string('type')\n      t.string('branch')\n      t.bigint('project_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('description')\n      t.boolean('is_private')\n      t.string('slug')\n      t.bigint('repository_id')\n      t.string('kind')\n      t.string('dockerfile_path')\n      t.jsonb('args')\n      t.string('dockerfile_context_path')\n      t.boolean('deploy_preview_when_pull_request_is_opened')\n      t.boolean('delete_preview_when_pull_request_is_closed')\n      t.boolean('deploy_preview_when_image_tag_is_created')\n      t.boolean('delete_preview_when_image_tag_is_updated')\n      t.boolean('share_to_github')\n      t.integer('delete_preview_after')\n      t.string('tag_pattern_deprecated')\n      t.index(['project_id'], name: 'index_repos_on_project_id')\n    end\n\n    create_table('uffizzi_core_roles', force: :cascade) do |t|\n      t.string('name')\n      t.string('resource_type')\n      t.bigint('resource_id')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['name', 'resource_type', 'resource_id'], name: 'index_roles_on_name_and_resource_type_and_resource_id')\n      t.index(['resource_type', 'resource_id'], name: 'index_roles_on_resource_type_and_resource_id')\n    end\n\n    create_table('uffizzi_core_templates', force: :cascade) do |t|\n      t.string('name')\n      t.bigint('added_by_id')\n      t.jsonb('payload', null: false)\n      t.bigint('project_id', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.bigint('compose_file_id')\n      t.string('creation_source')\n      t.index(['project_id'], name: 'index_templates_on_project_id')\n    end\n\n    create_table('uffizzi_core_user_projects', force: :cascade) do |t|\n      t.bigint('user_id', null: false)\n      t.bigint('project_id', null: false)\n      t.text('role', null: false)\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.bigint('invited_by_id')\n      t.index(['project_id'], name: 'index_user_projects_on_project_id')\n      t.index(['user_id', 'project_id'], name: 'index_user_projects_on_user_id_and_project_id')\n      t.index(['user_id'], name: 'index_user_projects_on_user_id')\n    end\n\n    create_table('uffizzi_core_users', force: :cascade) do |t|\n      t.string('first_name')\n      t.string('last_name')\n      t.string('email', default: '', null: false)\n      t.string('password_digest', default: '', null: false)\n      t.string('confirmation_token')\n      t.string('state')\n      t.string('phone')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.string('github')\n      t.string('website')\n      t.string('twitter')\n      t.string('linkedin')\n      t.string('devto')\n      t.string('facebook')\n      t.string('blog')\n      t.text('bio')\n      t.string('status')\n      t.string('availability')\n      t.string('primary_skills')\n      t.string('learning')\n      t.string('coding_for')\n      t.string('education')\n      t.string('title')\n      t.string('work')\n      t.string('primary_location')\n      t.string('creation_source')\n      t.index('lower((email)::text)', name: 'index_email_on_lower_email', unique: true)\n    end\n\n    create_table('uffizzi_core_users_roles', id: false, force: :cascade) do |t|\n      t.bigint('user_id')\n      t.bigint('role_id')\n      t.index(['role_id'], name: 'index_users_roles_on_role_id')\n      t.index(['user_id', 'role_id'], name: 'index_users_roles_on_user_id_and_role_id')\n      t.index(['user_id'], name: 'index_users_roles_on_user_id')\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220309110201_remove_secrets_from_projects.rb",
    "content": "# frozen_string_literal: true\n\nclass RemoveSecretsFromProjects < ActiveRecord::Migration[6.1]\n  def change\n    remove_column('uffizzi_core_projects', 'secrets')\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220310110150_create_project_secrets.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateProjectSecrets < ActiveRecord::Migration[6.1]\n  def change\n    create_table('uffizzi_core_project_secrets', force: :cascade) do |t|\n      t.bigint('project_id', null: false)\n      t.string('name')\n      t.string('value')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['project_id'], name: 'index_project_secrets_on_project_id')\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220325113342_add_name_to_uffizzi_containers.rb",
    "content": "# frozen_string_literal: true\n\nclass AddNameToUffizziContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :name, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220329123323_rename_project_secrets_to_secrets.rb",
    "content": "# frozen_string_literal: true\n\nclass RenameProjectSecretsToSecrets < ActiveRecord::Migration[6.1]\n  def change\n    rename_table(:uffizzi_core_project_secrets, :uffizzi_core_secrets)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220329124542_add_resource_to_secrets.rb",
    "content": "# frozen_string_literal: true\n\nclass AddResourceToSecrets < ActiveRecord::Migration[6.1]\n  def change\n    add_belongs_to(:uffizzi_core_secrets, :resource, polymorphic: true)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220329143241_remove_project_ref_from_secrets.rb",
    "content": "# frozen_string_literal: true\n\nclass RemoveProjectRefFromSecrets < ActiveRecord::Migration[6.1]\n  def change\n    remove_reference(:uffizzi_core_secrets, :project)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220419074956_add_health_check_to_containers.rb",
    "content": "# frozen_string_literal: true\n\nclass AddHealthCheckToContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column :uffizzi_core_containers, :healthcheck, :jsonb\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220422151523_add_volumes_to_uffizzi_core_containers.rb",
    "content": "# frozen_string_literal: true\n\nclass AddVolumesToUffizziCoreContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :volumes, :jsonb)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220525113412_rename_name_to_uffizzi_containers.rb",
    "content": "# frozen_string_literal: true\n\nclass RenameNameToUffizziContainers < ActiveRecord::Migration[6.1]\n  def change\n    rename_column(:uffizzi_core_containers, :name, :service_name)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220704135629_add_disabled_at_to_deployments.rb",
    "content": "# frozen_string_literal: true\n\nclass AddDisabledAtToDeployments < ActiveRecord::Migration[6.1]\n  def up\n    change_table :uffizzi_core_deployments do |t|\n      t.datetime :disabled_at\n    end\n    UffizziCore::Deployment.disabled.update_all('disabled_at = updated_at')\n  end\n\n  def down\n    change_table :uffizzi_core_deployments do |t|\n      t.remove :disabled_at\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220805164628_add_metadata_to_deployment.rb",
    "content": "# frozen_string_literal: true\n\nclass AddMetadataToDeployment < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_deployments, :metadata, :jsonb, default: {})\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220901110752_create_host_volume_files.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateHostVolumeFiles < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_host_volume_files do |t|\n      t.string :source\n      t.string :path\n      t.boolean :is_file\n      t.binary :payload\n      t.bigint :added_by_id\n      t.timestamps\n\n      t.references :project, null: false,\n                             foreign_key: true,\n                             index: { name: :index_host_volume_file_on_project_id },\n                             foreign_key: { to_table: :uffizzi_core_projects }\n      t.references :compose_file, null: false,\n                                  foreign_key: true,\n                                  index: { name: :index_host_volume_file_on_compose_file_id },\n                                  foreign_key: { to_table: :uffizzi_core_compose_files }\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220901165313_create_container_host_volume_files.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateContainerHostVolumeFiles < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_container_host_volume_files do |t|\n      t.string :source_path\n      t.timestamps\n      t.references :container, null: false,\n                               foreign_key: true,\n                               index: { name: :uf_core_cont_h_v_on_cont },\n                               foreign_key: { to_table: :uffizzi_core_containers }\n      t.references :host_volume_file, null: false,\n                                      foreign_key: true,\n                                      index: { name: :uf_core_cont_h_v_on_h_v_file },\n                                      foreign_key: { to_table: :uffizzi_core_host_volume_files }\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20220927113647_add_additional_subdomains_to_containers.rb",
    "content": "# frozen_string_literal: true\n\nclass AddAdditionalSubdomainsToContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :additional_subdomains, :string, array: true, default: [])\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230111000000_add_state_to_memberships.rb",
    "content": "# frozen_string_literal: true\n\nclass AddStateToMemberships < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_memberships, :state, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230306142513_add_last_deploy_at_to_deployments.rb",
    "content": "# frozen_string_literal: true\n\nclass AddLastDeployAtToDeployments < ActiveRecord::Migration[6.1]\n  def up\n    add_column :uffizzi_core_deployments, :last_deploy_at, :datetime\n\n    UffizziCore::Deployment.where(last_deploy_at: nil).update_all('last_deploy_at = updated_at')\n  end\n\n  def down\n    remove_column :uffizzi_core_deployments, :last_deploy_at\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230406154451_add_full_image_name_to_container.rb",
    "content": "# frozen_string_literal: true\n\nclass AddFullImageNameToContainer < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :full_image_name, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230531135739_create_deployment_events.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateDeploymentEvents < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_deployment_events do |t|\n      t.string :deployment_state\n      t.string :message\n      t.timestamps\n      t.references :deployment, null: false,\n                                index: { name: :uf_core_dep_events_on_dep },\n                                foreign_key: { to_table: :uffizzi_core_deployments }\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230613101901_create_clusters.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateClusters < ActiveRecord::Migration[6.1]\n  def change\n    create_table('uffizzi_core_clusters', force: :cascade) do |t|\n      t.references :project, null: false,\n                             foreign_key: true,\n                             index: { name: :index_cluster_on_project_id },\n                             foreign_key: { to_table: :uffizzi_core_projects }\n      t.bigint 'deployed_by_id', foreign_key: true\n      t.string 'state'\n      t.string 'name'\n      t.text 'manifest'\n      t.text 'kubeconfig'\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230711101901_add_host_to_clusters.rb",
    "content": "# frozen_string_literal: true\n\nclass AddHostToClusters < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :host, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230810140316_add_source_to_uffizzi_core_clusters.rb",
    "content": "# frozen_string_literal: true\n\nclass AddSourceToUffizziCoreClusters < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :creation_source, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20230824150022_update_name_constraint_to_projects.rb",
    "content": "# frozen_string_literal: true\n\nclass UpdateNameConstraintToProjects < ActiveRecord::Migration[6.1]\n  def change\n    remove_index(:uffizzi_core_projects, [:account_id, :name])\n    add_index(:uffizzi_core_projects, [:account_id, :name], unique: true,\n                                                            where: \"state = 'active'\",\n                                                            name: 'proj_uniq_name')\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20231009162719_create_uffizzi_core_kubernetes_distributions.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateUffizziCoreKubernetesDistributions < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_kubernetes_distributions do |t|\n      t.string :version\n      t.string :distro\n      t.string :image\n      t.boolean :default, default: false\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20231009182412_add_kubernetes_distribution_id_to_uffizzi_core_clusters.rb",
    "content": "# frozen_string_literal: true\n\nclass AddKubernetesDistributionIdToUffizziCoreClusters < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :kubernetes_distribution_id, :integer, foreign_key: true)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20240301200235_add_node_selector_to_cluster.rb",
    "content": "# frozen_string_literal: true\n\nclass AddNodeSelectorToCluster < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :node_selector, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/migrate/20240314170113_delete_node_selector_from_cluster.rb",
    "content": "# frozen_string_literal: true\n\nclass DeleteNodeSelectorFromCluster < ActiveRecord::Migration[6.1]\n  def change\n    remove_column(:uffizzi_core_clusters, :node_selector, :string)\n  end\nend\n"
  },
  {
    "path": "core/db/seeds.rb",
    "content": "# frozen_string_literal: true\n\nuser = UffizziCore::User.create!(\n  email: 'admin@uffizzi.com',\n  password: 'password',\n  state: UffizziCore::User::STATE_ACTIVE,\n  creation_source: UffizziCore::User.creation_source.system,\n)\naccount = UffizziCore::Account.create!(\n  owner: user, name: 'default',\n  state: UffizziCore::Account::STATE_ACTIVE,\n  kind: UffizziCore::Account.kind.personal\n)\nuser.memberships.create!(account: account, role: UffizziCore::Membership.role.admin)\nproject = account.projects.create!(name: 'default', slug: 'default', state: UffizziCore::Project::STATE_ACTIVE)\nproject.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)\n"
  },
  {
    "path": "core/lib/tasks/uffizzi_core_tasks.rake",
    "content": "# frozen_string_literal: true\n\nnamespace :uffizzi_core do\n  Rake::Task['install:migrations'].clear_comments\n\n  desc 'Copy over the migration needed to the application'\n  task install: :environment do\n    if Rake::Task.task_defined?('uffizzi_core:install:migrations')\n      Rake::Task['uffizzi_core:install:migrations'].invoke\n    else\n      Rake::Task['app:uffizzi_core:install:migrations'].invoke\n    end\n  end\n\n  desc 'Create a new user'\n  task create_user: :environment do\n    UffizziCore::UserGeneratorService.safe_generate(ENV['UFFIZZI_USER_EMAIL'], ENV['UFFIZZI_USER_PASSWORD'], ENV['UFFIZZI_PROJECT_NAME'])\n  end\nend\n"
  },
  {
    "path": "core/lib/uffizzi_core/engine.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'active_model_serializers'\n\nmodule UffizziCore\n  class Engine < ::Rails::Engine\n    isolate_namespace UffizziCore\n\n    config.before_initialize do\n      config.i18n.load_path += Dir[\"#{config.root}/config/locales/**/*.yml\"]\n    end\n\n    ActiveModelSerializers.config.adapter = :json\n  end\nend\n"
  },
  {
    "path": "core/lib/uffizzi_core/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore\n  VERSION = '2.4.10'\nend\n"
  },
  {
    "path": "core/lib/uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'uffizzi_core/version'\nrequire 'uffizzi_core/engine'\n\nrequire 'aasm'\nrequire 'active_model_serializers'\nrequire 'ancestry'\nrequire 'aws-sdk-ecr'\nrequire 'aws-sdk-eventbridge'\nrequire 'aws-sdk-iam'\nrequire 'config'\nrequire 'hashie'\nrequire 'faraday'\nrequire 'enumerize'\nrequire 'jwt'\nrequire 'kaminari'\nrequire 'octokit'\nrequire 'pg'\nrequire 'pundit'\nrequire 'ransack'\nrequire 'responders'\nrequire 'rolify'\nrequire 'rswag/api'\nrequire 'rswag/ui'\nrequire 'sidekiq'\nrequire 'virtus'\nrequire 'faraday/follow_redirects'\n\nmodule UffizziCore\n  mattr_accessor :dependencies, default: {\n    rbac: 'UffizziCore::Rbac::UserAccessService',\n    deployment_memory_module: 'UffizziCore::Deployment::MemoryService',\n    template_memory_module: 'UffizziCore::Template::MemoryService',\n    controller_settings: 'UffizziCore::ControllerSettingsService',\n    ci_module: 'UffizziCore::CiService',\n  }\n  mattr_accessor :table_names, default: {\n    accounts: :uffizzi_core_accounts,\n    activity_items: :uffizzi_core_activity_items,\n    builds: :uffizzi_core_builds,\n    clusters: :uffizzi_core_clusters,\n    comments: :uffizzi_core_comments,\n    compose_files: :uffizzi_core_compose_files,\n    config_files: :uffizzi_core_config_files,\n    container_config_files: :uffizzi_core_container_config_files,\n    containers: :uffizzi_core_containers,\n    coupons: :uffizzi_core_coupons,\n    credentials: :uffizzi_core_credentials,\n    deployments: :uffizzi_core_deployments,\n    events: :uffizzi_core_events,\n    invitations: :uffizzi_core_invitations,\n    memberships: :uffizzi_core_memberships,\n    payments: :uffizzi_core_payments,\n    prices: :uffizzi_core_prices,\n    products: :uffizzi_core_products,\n    projects: :uffizzi_core_projects,\n    ratings: :uffizzi_core_ratings,\n    repos: :uffizzi_core_repos,\n    roles: :uffizzi_core_roles,\n    secrets: :uffizzi_core_secrets,\n    templates: :uffizzi_core_templates,\n    user_projects: :uffizzi_core_user_projects,\n    users: :uffizzi_core_users,\n    users_roles: :uffizzi_core_users_roles,\n    host_volume_files: :uffizzi_core_host_volume_files,\n    container_host_volume_files: :uffizzi_core_container_host_volume_files,\n    deployment_events: :uffizzi_core_deployment_events,\n    kubernetes_distributions: :uffizzi_core_kubernetes_distributions,\n  }\n  mattr_accessor :user_creation_sources, default: [:system, :online_registration, :google, :sso]\n  mattr_accessor :user_project_roles, default: [:admin, :developer, :viewer]\n  mattr_accessor :account_sources, default: [:manual]\n  mattr_accessor :compose_file_kinds, default: [:main, :temporary]\n  mattr_accessor :event_states, default: [:waiting, :queued, :successful, :deployed, :failed, :building, :timeout, :cancelled, :deploying]\n  mattr_accessor :cluster_creation_sources, default: [:manual]\nend\n"
  },
  {
    "path": "core/swagger/v1/swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"Uffizzi docs\",\n    \"description\": \"Your API does this\",\n    \"version\": \"1.0\"\n  },\n  \"host\": \"lvh.me:7000\",\n  \"basePath\": \"/\",\n  \"schemes\": [\n    \"http\"\n  ],\n  \"paths\": {\n    \"/api/cli/v1/account/credentials\": {\n      \"get\": {\n        \"tags\": [\n          \"Account/Credential\"\n        ],\n        \"operationId\": \"Account/Credential-index\",\n        \"parameters\": [\n          {\n            \"name\": \"credential\",\n            \"description\": \"credential\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"password\": {\n                  \"type\": \"string\"\n                },\n                \"type\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"Get a list of accounts credential\"\n          }\n        },\n        \"description\": \"Get a list of accounts credential\",\n        \"summary\": \"Get a list of accounts credential\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/account/credentials\",\n        \"x-action\": \"index\"\n      },\n      \"post\": {\n        \"tags\": [\n          \"Account/Credential\"\n        ],\n        \"operationId\": \"Account/Credential-create\",\n        \"parameters\": [\n          {\n            \"name\": \"credential\",\n            \"description\": \"credential\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"password\": {\n                  \"type\": \"string\"\n                },\n                \"type\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"default\": {\n            \"description\": \"Create account credential\",\n            \"examples\": {\n              \"application/json\": \"Possible types:\\nUffizziCore::Credential::Amazon, UffizziCore::Credential::Azure, UffizziCore::Credential::DockerHub,\\nUffizziCore::Credential::DockerRegistry, UffizziCore::Credential::Google, UffizziCore::Credential::GithubContainerRegistry\"\n            }\n          },\n          \"201\": {\n            \"description\": \"Created successfully\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"integer\"\n                },\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"password\": {\n                  \"type\": \"string\"\n                },\n                \"type\": {\n                  \"type\": \"string\"\n                },\n                \"state\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"additionalProperties\": {\n                \"type\": \"errors\"\n              }\n            }\n          }\n        },\n        \"description\": \"Create account credential\",\n        \"summary\": \"Create account credential\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/account/credentials\",\n        \"x-action\": \"create\"\n      }\n    },\n    \"/api/cli/v1/account/credentials/{type}\": {\n      \"put\": {\n        \"tags\": [\n          \"Account/Credential\"\n        ],\n        \"operationId\": \"Account/Credential-update\",\n        \"parameters\": [\n          {\n            \"name\": \"credential\",\n            \"description\": \"credential\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"type\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          {\n            \"name\": \"type\",\n            \"description\": \"Credential type\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"integer\"\n                },\n                \"registry_url\": {\n                  \"type\": \"string\"\n                },\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"password\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"additionalProperties\": {\n                \"type\": \"errors\"\n              }\n            }\n          }\n        },\n        \"description\": \"Update existing credential of the given type\",\n        \"summary\": \"Update existing credential of the given type\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/account/credentials\",\n        \"x-action\": \"update\"\n      },\n      \"delete\": {\n        \"tags\": [\n          \"Account/Credential\"\n        ],\n        \"operationId\": \"Account/Credential-destroy\",\n        \"parameters\": [\n          {\n            \"name\": \"type\",\n            \"description\": \"Type of the credential\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          }\n        },\n        \"description\": \"Delete account credential\",\n        \"summary\": \"Delete account credential\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/account/credentials\",\n        \"x-action\": \"destroy\"\n      }\n    },\n    \"/api/cli/v1/account/credentials/{type}/check_credential\": {\n      \"get\": {\n        \"tags\": [\n          \"Account/Credential\"\n        ],\n        \"operationId\": \"Account/Credential-check_credential\",\n        \"parameters\": [\n          {\n            \"name\": \"credential\",\n            \"description\": \"credential\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"type\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          {\n            \"name\": \"type\",\n            \"description\": \"Scope response to type\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"422\": {\n            \"description\": \"Unprocessable entity\"\n          },\n          \"200\": {\n            \"description\": \"OK\"\n          }\n        },\n        \"description\": \"Check if credential of the type already exists in the account\",\n        \"summary\": \"Check if credential of the type already exists in the account\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/account/credentials\",\n        \"x-action\": \"check_credential\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/compose_file\": {\n      \"get\": {\n        \"tags\": [\n          \"ComposeFile\"\n        ],\n        \"operationId\": \"ComposeFile-show\",\n        \"parameters\": [\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/ComposeFile\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          }\n        },\n        \"description\": \"Get the compose file for the project\",\n        \"summary\": \"Get the compose file for the project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/compose_files\",\n        \"x-action\": \"show\"\n      },\n      \"post\": {\n        \"tags\": [\n          \"ComposeFile\"\n        ],\n        \"operationId\": \"ComposeFile-create\",\n        \"parameters\": [\n          {\n            \"name\": \"params\",\n            \"description\": \"params\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"compose_file\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"path\": {\n                      \"type\": \"string\"\n                    },\n                    \"source\": {\n                      \"type\": \"string\"\n                    },\n                    \"content\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                },\n                \"dependencies\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"path\": {\n                        \"type\": \"string\"\n                      },\n                      \"source\": {\n                        \"type\": \"string\"\n                      },\n                      \"content\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"201\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/ComposeFile\"\n            }\n          },\n          \"422\": {\n            \"description\": \"Invalid compose file\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/ComposeFile\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Create a compose file for the project\",\n        \"summary\": \"Create a compose file for the project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/compose_files\",\n        \"x-action\": \"create\"\n      },\n      \"delete\": {\n        \"tags\": [\n          \"ComposeFile\"\n        ],\n        \"operationId\": \"ComposeFile-destroy\",\n        \"parameters\": [\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Delete the compose file for the project\",\n        \"summary\": \"Delete the compose file for the project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/compose_files\",\n        \"x-action\": \"destroy\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers/{container_name}/logs\": {\n      \"get\": {\n        \"tags\": [\n          \"Project/Deployment/Container/Log\"\n        ],\n        \"operationId\": \"Project/Deployment/Container/Log-index\",\n        \"parameters\": [\n          {\n            \"name\": \"container_name\",\n            \"description\": \"The name of the container\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"integer\"\n          },\n          {\n            \"name\": \"deployment_id\",\n            \"description\": \"The id of the deployment\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"integer\"\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The slug of the project\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"logs\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"insert_id\": {\n                        \"type\": \"string\"\n                      },\n                      \"payload\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments/containers/logs\",\n        \"x-action\": \"index\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/containers\": {\n      \"get\": {\n        \"tags\": [\n          \"Container\"\n        ],\n        \"operationId\": \"Container-index\",\n        \"parameters\": [\n          {\n            \"name\": \"deployment_id\",\n            \"description\": \"The id of the deployment\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"integer\"\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Container\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          },\n          \"404\": {\n            \"description\": \"Not found\"\n          }\n        },\n        \"description\": \"Get a list of container services for a deployment\",\n        \"summary\": \"Get a list of container services for a deployment\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments/containers\",\n        \"x-action\": \"index\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments/{deployment_id}/events\": {\n      \"get\": {\n        \"tags\": [\n          \"Event\"\n        ],\n        \"operationId\": \"Event-index\",\n        \"parameters\": [\n          {\n            \"name\": \"deployment_id\",\n            \"description\": \"The id of the deployment\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"integer\"\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project_slug for the project\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"events\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"first_timestamp\": {\n                        \"type\": \"string\"\n                      },\n                      \"last_timestamp\": {\n                        \"type\": \"string\"\n                      },\n                      \"reason\": {\n                        \"type\": \"string\"\n                      },\n                      \"message\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          },\n          \"404\": {\n            \"description\": \"Not found\"\n          }\n        },\n        \"description\": \"Get the events associated with deployment\",\n        \"summary\": \"Get the events associated with deployment\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments/events\",\n        \"x-action\": \"index\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments\": {\n      \"get\": {\n        \"tags\": [\n          \"Deployment\"\n        ],\n        \"operationId\": \"Deployment-index\",\n        \"parameters\": [\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/Deployment\"\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Get a list of active deployements for a project\",\n        \"summary\": \"Get a list of active deployements for a project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments\",\n        \"x-action\": \"index\"\n      },\n      \"post\": {\n        \"tags\": [\n          \"Deployment\"\n        ],\n        \"operationId\": \"Deployment-create\",\n        \"parameters\": [\n          {\n            \"name\": \"params\",\n            \"description\": \"params\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"compose_file\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"path\": {\n                      \"type\": \"string\"\n                    },\n                    \"source\": {\n                      \"type\": \"string\"\n                    },\n                    \"content\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                },\n                \"dependencies\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"path\": {\n                        \"type\": \"string\"\n                      },\n                      \"source\": {\n                        \"type\": \"string\"\n                      },\n                      \"content\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"201\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Deployment\"\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable Entity\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"state\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Create a deployment from a compose file\",\n        \"summary\": \"Create a deployment from a compose file\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments\",\n        \"x-action\": \"create\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments/{id}\": {\n      \"get\": {\n        \"tags\": [\n          \"Deployment\"\n        ],\n        \"operationId\": \"Deployment-show\",\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"Scope response to id\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Deployment\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Get deployment information by id\",\n        \"summary\": \"Get deployment information by id\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments\",\n        \"x-action\": \"show\"\n      },\n      \"delete\": {\n        \"tags\": [\n          \"Deployment\"\n        ],\n        \"operationId\": \"Deployment-destroy\",\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"Scope response to id\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Disable deployment by id\",\n        \"summary\": \"Disable deployment by id\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments\",\n        \"x-action\": \"destroy\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments/{id}\\\"\": {\n      \"put\": {\n        \"tags\": [\n          \"Deployment\"\n        ],\n        \"operationId\": \"Deployment-update\",\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"Scope response to id\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"params\",\n            \"description\": \"params\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"compose_file\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"path\": {\n                      \"type\": \"string\"\n                    },\n                    \"source\": {\n                      \"type\": \"string\"\n                    },\n                    \"content\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                },\n                \"dependencies\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"path\": {\n                        \"type\": \"string\"\n                      },\n                      \"source\": {\n                        \"type\": \"string\"\n                      },\n                      \"content\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"201\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Deployment\"\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable Entity\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"state\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Update the deployment with new compose file\",\n        \"summary\": \"Update the deployment with new compose file\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments\",\n        \"x-action\": \"update\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/deployments/{id}/deploy_containers\": {\n      \"post\": {\n        \"tags\": [\n          \"Deployment\"\n        ],\n        \"operationId\": \"Deployment-deploy_containers\",\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"The id of the deployment\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"The project slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          },\n          \"404\": {\n            \"description\": \"Not found\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"title\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/deployments\",\n        \"x-action\": \"deploy_containers\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/secrets\": {\n      \"get\": {\n        \"tags\": [\n          \"Project/Secrets\"\n        ],\n        \"operationId\": \"Project/Secrets-index\",\n        \"parameters\": [\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"project_slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"secrets\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"name\": {\n                        \"type\": \"string\"\n                      },\n                      \"created_at\": {\n                        \"type\": \"string\",\n                        \"format\": \"date\"\n                      },\n                      \"updated_at\": {\n                        \"type\": \"string\",\n                        \"format\": \"date\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Get secrets for the project\",\n        \"summary\": \"Get secrets for the project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/secrets\",\n        \"x-action\": \"index\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/secrets/bulk_create\": {\n      \"post\": {\n        \"tags\": [\n          \"Project/Secrets\"\n        ],\n        \"operationId\": \"Project/Secrets-bulk_create\",\n        \"parameters\": [\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"project_slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"secrets\",\n            \"description\": \"secrets\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"secrets\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"name\": {\n                        \"type\": \"string\"\n                      },\n                      \"value\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"201\": {\n            \"description\": \"Created\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"secrets\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"name\": {\n                        \"type\": \"string\"\n                      },\n                      \"created_at\": {\n                        \"type\": \"string\",\n                        \"format\": \"date\"\n                      },\n                      \"updated_at\": {\n                        \"type\": \"string\",\n                        \"format\": \"date\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"A compose file already exists for this project\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Add secret to project\",\n        \"summary\": \"Add secret to project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/secrets\",\n        \"x-action\": \"bulk_create\"\n      }\n    },\n    \"/api/cli/v1/projects/{project_slug}/secrets/{secret_name}\": {\n      \"delete\": {\n        \"tags\": [\n          \"Project/Secrets\"\n        ],\n        \"operationId\": \"Project/Secrets-destroy\",\n        \"parameters\": [\n          {\n            \"name\": \"project_slug\",\n            \"description\": \"project_slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"secret_name\",\n            \"description\": \"Scope response to secret_name\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Project\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Delete a secret from project by secret name\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Delete a secret from project by secret name\",\n        \"summary\": \"Delete a secret from project by secret name\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects/secrets\",\n        \"x-action\": \"destroy\"\n      }\n    },\n    \"/api/cli/v1/projects\": {\n      \"get\": {\n        \"tags\": [\n          \"Project\"\n        ],\n        \"operationId\": \"Project-index\",\n        \"parameters\": [\n\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"projects\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                      \"slug\": {\n                        \"type\": \"string\"\n                      },\n                      \"name\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Get projects of current user\",\n        \"summary\": \"Get projects of current user\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects\",\n        \"x-action\": \"index\"\n      },\n      \"post\": {\n        \"tags\": [\n          \"Project\"\n        ],\n        \"operationId\": \"Project-create\",\n        \"parameters\": [\n          {\n            \"name\": \"params\",\n            \"description\": \"params\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"type\": \"string\"\n                },\n                \"slug\": {\n                  \"type\": \"string\"\n                },\n                \"description\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"project\": {\n                  \"$ref\": \"#/definitions/Project\"\n                }\n              }\n            }\n          },\n          \"404\": {\n            \"description\": \"Not Found\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"password\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          }\n        },\n        \"description\": \"Create a project\",\n        \"summary\": \"Create a project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects\",\n        \"x-action\": \"create\"\n      }\n    },\n    \"/api/cli/v1/projects/{slug}\": {\n      \"get\": {\n        \"tags\": [\n          \"Project\"\n        ],\n        \"operationId\": \"Project-show\",\n        \"parameters\": [\n          {\n            \"name\": \"slug\",\n            \"description\": \"Scope response to slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No content\"\n          },\n          \"404\": {\n            \"description\": \"Not Found\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Get a project by slug\",\n        \"summary\": \"Get a project by slug\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects\",\n        \"x-action\": \"show\"\n      },\n      \"delete\": {\n        \"tags\": [\n          \"Project\"\n        ],\n        \"operationId\": \"Project-destroy\",\n        \"parameters\": [\n          {\n            \"name\": \"slug\",\n            \"description\": \"Scope response to slug\",\n            \"required\": true,\n            \"in\": \"path\",\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"project\": {\n                  \"$ref\": \"#/definitions/Project\"\n                }\n              }\n            }\n          },\n          \"404\": {\n            \"description\": \"Not Found\"\n          },\n          \"401\": {\n            \"description\": \"Not authorized\"\n          }\n        },\n        \"description\": \"Delete a project\",\n        \"summary\": \"Delete a project\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/projects\",\n        \"x-action\": \"destroy\"\n      }\n    },\n    \"/api/cli/v1/session\": {\n      \"post\": {\n        \"tags\": [\n          \"Uffizzi\"\n        ],\n        \"operationId\": \"Uffizzi-create\",\n        \"parameters\": [\n          {\n            \"name\": \"user\",\n            \"description\": \"user\",\n            \"required\": true,\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"email\": {\n                  \"type\": \"string\"\n                },\n                \"password\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"201\": {\n            \"description\": \"Created successfully\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"user\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"accounts\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"type\": \"object\",\n                        \"properties\": {\n                          \"id\": {\n                            \"type\": \"integer\"\n                          },\n                          \"state\": {\n                            \"type\": \"string\"\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"errors\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"password\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n              }\n            }\n          }\n        },\n        \"description\": \"Create session\",\n        \"summary\": \"Create session\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/sessions\",\n        \"x-action\": \"create\"\n      },\n      \"delete\": {\n        \"tags\": [\n          \"Uffizzi\"\n        ],\n        \"operationId\": \"Uffizzi-destroy\",\n        \"parameters\": [\n\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          }\n        },\n        \"description\": \"Destroy session\",\n        \"summary\": \"Destroy session\",\n        \"x-controller\": \"uffizzi_core/api/cli/v1/sessions\",\n        \"x-action\": \"destroy\"\n      }\n    }\n  },\n  \"tags\": [\n    {\n      \"name\": \"Account/Credential\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"ComposeFile\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Container\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Deployment\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Event\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Project\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Project/Deployment/Container/Log\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Project/Secrets\",\n      \"description\": \"\"\n    },\n    {\n      \"name\": \"Uffizzi\",\n      \"description\": \"\"\n    }\n  ],\n  \"securityDefinitions\": {\n  },\n  \"definitions\": {\n    \"UffizziCore_ActivityItem\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\"\n        },\n        \"namespace\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"tag\": {\n          \"type\": \"string\"\n        },\n        \"branch\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        },\n        \"container_id\": {\n          \"type\": \"string\"\n        },\n        \"commit\": {\n          \"type\": \"string\"\n        },\n        \"commit_message\": {\n          \"type\": \"string\"\n        },\n        \"build_id\": {\n          \"type\": \"integer\"\n        },\n        \"created_at\": {\n          \"type\": \"string\",\n          \"format\": \"date\"\n        },\n        \"updated_at\": {\n          \"type\": \"string\",\n          \"format\": \"date\"\n        },\n        \"data\": {\n          \"type\": \"object\"\n        },\n        \"digest\": {\n          \"type\": \"string\"\n        },\n        \"meta\": {\n          \"type\": \"object\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"ComposeFile\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\"\n        },\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"path\": {\n          \"type\": \"string\"\n        },\n        \"content\": {\n          \"type\": \"string\"\n        },\n        \"auto_deploy\": {\n          \"type\": \"boolean\"\n        },\n        \"state\": {\n          \"type\": \"string\"\n        },\n        \"payload\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"content\"\n      ]\n    },\n    \"UffizziCore_ConfigFile\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"filename\": {\n          \"type\": \"string\"\n        },\n        \"kind\": {\n          \"type\": \"string\"\n        },\n        \"payload\": {\n          \"type\": \"string\"\n        },\n        \"source\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"Container\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"memory_limit\": {\n          \"type\": \"integer\"\n        },\n        \"memory_request\": {\n          \"type\": \"integer\"\n        },\n        \"continuously_deploy\": {\n          \"type\": \"string\"\n        },\n        \"variables\": {\n          \"type\": \"object\"\n        },\n        \"secret_variables\": {\n          \"type\": \"object\"\n        },\n        \"container_config_files\": {\n          \"$ref\": \"#/definitions/ConfigFile\"\n        }\n      }\n    },\n    \"Deployment\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\"\n        },\n        \"project_id\": {\n          \"type\": \"integer\"\n        },\n        \"kind\": {\n          \"type\": \"string\"\n        },\n        \"state\": {\n          \"type\": \"string\"\n        },\n        \"preview_url\": {\n          \"type\": \"string\"\n        },\n        \"tag\": {\n          \"type\": \"string\"\n        },\n        \"branch\": {\n          \"type\": \"string\"\n        },\n        \"commit\": {\n          \"type\": \"string\"\n        },\n        \"image_id\": {\n          \"type\": \"string\"\n        },\n        \"created_at\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"updated_at\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"ingress_container_ready\": {\n          \"type\": \"boolean\"\n        },\n        \"ingress_container_state\": {\n          \"type\": \"string\"\n        },\n        \"creation_source\": {\n          \"type\": \"string\"\n        },\n        \"contaners\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"deployed_by\": {\n          \"type\": \"object\"\n        }\n      }\n    },\n    \"Project\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"slug\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"description\": {\n          \"type\": \"string\"\n        },\n        \"created_at\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"secrets\": {\n          \"type\": \"string\"\n        },\n        \"default_compose\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"source\": {\n              \"type\": \"string\"\n            }\n          }\n        },\n        \"deployments\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"id\": {\n              \"type\": \"integer\"\n            },\n            \"domain\": {\n              \"type\": \"string\"\n            }\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/accounts/clusters_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Accounts::ClustersControllerTest < ActionController::TestCase\n  setup do\n    @developer = create(:user, :with_organizational_account)\n    sign_in(@developer)\n  end\n\n  test '#index' do\n    admin_user = create(:user, :with_organizational_account)\n    admin_user_org_account = admin_user.accounts.organizational.first\n    create(:membership, :developer, user: @developer, account: admin_user_org_account)\n    admin_project = create(:project, :with_members, account: admin_user_org_account, members: [admin_user, @developer])\n    create(:cluster, :deployed, project: admin_project, deployed_by_id: admin_user.id)\n    showable_cluster1 = create(:cluster, :deployed, project: admin_project, deployed_by_id: @developer.id)\n\n    admin_project2 = create(:project, :with_members, account: admin_user_org_account, members: [admin_user])\n    create(:user_project, :admin, user: @developer, project: admin_project2)\n    showable_cluster2 = create(:cluster, :deployed, project: admin_project2, deployed_by_id: admin_user.id)\n\n    other_user = create(:user, :with_organizational_account)\n    other_user_account = other_user.accounts.organizational.first\n    create(:membership, :developer, user: @developer, account: other_user_account)\n    other_project = create(:project, :with_members, account: other_user_account, members: [other_user, @developer])\n    create(:cluster, :deployed, project: other_project, deployed_by_id: other_user.id)\n    create(:cluster, :deployed, project: other_project, deployed_by_id: @developer.id)\n\n    get :index, params: { account_id: admin_user_org_account.id }, format: :json\n\n    assert_response(:success)\n\n    clusters_data = JSON.parse(response.body)['clusters']\n    assert_equal(2, clusters_data.count)\n    assert_equal([showable_cluster1.name, showable_cluster2.name], clusters_data.pluck('name').sort)\n\n    actual_project_name1 = clusters_data.min_by { |c| c['name'] }.dig('project', 'name')\n    actual_project_name2 = clusters_data.max_by { |c| c['name'] }.dig('project', 'name')\n    assert_equal(showable_cluster1.project.name, actual_project_name1)\n    assert_equal(showable_cluster2.project.name, actual_project_name2)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/accounts/credentials_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Accounts::CredentialsControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @account = @user.personal_account\n    @project = create(:project, :with_members, account: @account, members: [@user])\n\n    sign_in @user\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n  end\n\n  teardown do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#index returns a list of credentials' do\n    create(:credential, :docker_hub, account: @account)\n    create(:credential, :amazon, account: @account)\n\n    params = { account_id: @account.id }\n\n    get :index, params: params, format: :json\n\n    assert_response :success\n\n    data = JSON.parse(response.body)\n    assert_equal(['UffizziCore::Credential::DockerHub', 'UffizziCore::Credential::Amazon'], data['credentials'])\n  end\n\n  test '#create docker hub credential' do\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    attributes = attributes_for(:credential, :docker_hub)\n    params = { account_id: @account.id, credential: attributes }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    assert_requested(stubbed_dockerhub_login)\n  end\n\n  test '#create if docker hub auth is failed' do\n    data = json_fixture('files/dockerhub/login_fail.json')\n    stubbed_dockerhub_login = stub_dockerhub_login_fail(data)\n\n    attributes = attributes_for(:credential, :docker_hub)\n    params = { account_id: @account.id, credential: attributes }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :unprocessable_entity\n    assert_requested(stubbed_dockerhub_login)\n  end\n\n  test '#create azure credential' do\n    registry_url = generate(:url)\n    oauth2_token_response = { access_token: generate(:string) }\n\n    stubbed_azure_registry_oauth2_token = stub_azure_registry_oauth2_token(registry_url, oauth2_token_response)\n\n    attributes = attributes_for(:credential, :azure, registry_url: registry_url)\n    params = { account_id: @account.id, credential: attributes }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n    assert_requested stubbed_azure_registry_oauth2_token\n    assert_response :success\n  end\n\n  test '#create google credential' do\n    attributes = attributes_for(:credential, :google)\n    params = { account_id: @account.id, credential: attributes }\n    token_response = { token: generate(:string) }\n\n    stubbed_google_registry_token = stub_google_registry_token(token_response)\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n    assert_requested stubbed_google_registry_token\n    assert_response :success\n  end\n\n  test '#create amazon credential' do\n    attributes = attributes_for(:credential, :amazon)\n    params = { account_id: @account.id, credential: attributes }\n\n    UffizziCore::ContainerRegistry::AmazonService.expects(:credential_correct?).at_least(1).returns(true)\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create github_container_registry credential' do\n    attributes = attributes_for(:credential, :github_container_registry)\n    params = { account_id: @account.id, credential: attributes }\n    registry_url = attributes[:registry_url]\n    token_response = { token: generate(:string) }\n    stub_github_container_registry_access_token(registry_url, token_response)\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create docker registry credential to ecr' do\n    attributes = attributes_for(:credential, :docker_registry)\n    attributes[:registry_url] = attributes_for(:credential, :amazon)[:registry_url]\n    params = { account_id: @account.id, credential: attributes }\n\n    UffizziCore::ContainerRegistry::DockerRegistryService.expects(:credential_correct?).at_least(1).returns(true)\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#update when data changed' do\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    credential = create(:credential, :docker_hub, account: @account)\n    new_username = 'new username'\n    credential_attributes = attributes_for(:credential, :docker_hub, account: @account).merge(username: new_username)\n\n    params = { account_id: @account.id, credential: credential_attributes, type: credential_attributes[:type] }\n    differences = {\n      -> { UffizziCore::Credential.count } => 0,\n    }\n\n    assert_difference(differences) do\n      put :update, params: params, format: :json\n    end\n    assert_response(:success)\n    assert_equal(credential.reload.username, new_username)\n    assert_requested(stubbed_dockerhub_login)\n  end\n\n  test '#update when credential is the same' do\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    credential = create(:credential, :docker_hub, account: @account)\n\n    params = {\n      account_id: @account.id,\n      credential: {\n        password: credential.password,\n        username: credential.username,\n        registry_url: credential.registry_url,\n      },\n      type: credential.type,\n    }\n\n    put :update, params: params, format: :json\n\n    assert_response(:success)\n    refute_requested(stubbed_dockerhub_login)\n  end\n\n  test '#create duplicate credential' do\n    stub_dockerhub_login\n    stub_controller\n\n    credential = create(:credential, :docker_hub, account: @account)\n\n    differences = { -> { UffizziCore::Credential.count } => 0 }\n    attributes = attributes_for(:credential, :docker_hub, account: @account)\n    params = { account_id: @account.id, credential: attributes }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n    assert_response :unprocessable_entity\n    assert_equal UffizziCore::Credential.last.id, credential.id\n  end\n\n  test '#check_credential valid credential' do\n    stub_dockerhub_login\n    stub_controller\n\n    attributes = attributes_for(:credential, :docker_hub, account: @account)\n    params = { account_id: @account.id, type: attributes[:type] }\n\n    post :check_credential, params: params, format: :json\n\n    assert_response :success\n  end\n\n  test '#check_credential duplicate credential' do\n    stub_dockerhub_login\n    stub_controller\n\n    credential = create(:credential, :docker_hub, account: @account)\n\n    params = { account_id: @account.id, type: credential.type }\n\n    post :check_credential, params: params, format: :json\n\n    assert_response :unprocessable_entity\n  end\n\n  test '#destroy docker hub credential' do\n    credential = create(:credential, :docker_hub, account: @account)\n\n    params = { account_id: @account.id, type: credential.type }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#destroy azure credential' do\n    credential = create(:credential, :azure, account: @account)\n\n    params = { account_id: @account.id, type: credential.type }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#destroy google credential' do\n    credential = create(:credential, :google, account: @account)\n\n    params = { account_id: @account.id, type: credential.type }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#destroy amazon credential' do\n    credential = create(:credential, :amazon, account: @account)\n\n    params = { account_id: @account.id, type: credential.type }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#destroy github_container_registry credential' do\n    credential = create(:credential, :github_container_registry, account: @account)\n\n    params = { account_id: @account.id, type: credential.type }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#destroy unexisted credential' do\n    params = { account_id: @account.id, type: UffizziCore::Credential::DockerHub.name }\n\n    differences = {\n      -> { UffizziCore::Credential.count } => 0,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :not_found\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/accounts/projects_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Accounts::ProjectsControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_organizational_account)\n    @account = @admin.accounts.organizational.first\n    sign_in(@admin)\n  end\n\n  test '#index' do\n    create(:project, :with_members, account: @account, members: [@admin])\n\n    get :index, params: { account_id: @account.id }, format: :json\n\n    data = JSON.parse(response.body)\n    assert(data['projects'].first['account'].present?)\n    assert_response(:success)\n  end\n\n  test '#create' do\n    attributes = attributes_for(:project)\n    create(:user, :developer_in_organization, organization: @account)\n\n    differences = {\n      -> { UffizziCore::Project.count } => 1,\n      -> { UffizziCore::UserProject.count } => 2,\n    }\n\n    assert_difference differences do\n      post :create, params: { account_id: @account.id, project: attributes }, format: :json\n    end\n\n    assert_response(:success)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/accounts_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::AccountsControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    sign_in(@user)\n  end\n\n  test '#index' do\n    get :index, format: :json\n\n    assert_response(:success)\n  end\n\n  test '#show' do\n    get :show, params: { name: 'wrong' }, format: :json\n\n    assert_response(:not_found)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/clusters/ingresses_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::Clusters::IngressesControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, :with_members, account: @user.personal_account, members: [@user])\n    @cluster = create(:cluster, project: @project, deployed_by: @user)\n\n    sign_in @user\n  end\n\n  test '#index' do\n    data = json_fixture('files/controller/ingresses.json')\n    stubbed_get_ingresses_request = stub_get_ingresses(data)\n\n    params = { project_slug: @project.slug, cluster_name: @cluster.name }\n\n    get :index, params: params, format: :json\n\n    assert_response(:success)\n    assert_requested(stubbed_get_ingresses_request)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/clusters_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::ClustersControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_organizational_account)\n    @account = @admin.accounts.organizational.first\n    @project = create(:project, :with_members, members: [@admin], account: @account)\n\n    @developer = create(:user)\n    create(:membership, :developer, account: @account, user: @developer)\n    create(:user_project, :developer, project: @project, user: @developer)\n    create(:kubernetes_distribution, :default)\n  end\n\n  teardown do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#index lists all clusters to admins' do\n    sign_in(@admin)\n\n    create(:cluster, project: @project, deployed_by: @developer)\n\n    params = {\n      project_slug: @project.slug,\n    }\n    get :index, params: params, format: :json\n\n    assert_response(:success)\n    data = JSON.parse(response.body)\n    assert_equal(1, data['clusters'].count)\n  end\n\n  test '#index only shows clusters deployed by the same user for non-adminss' do\n    create(:cluster, project: @project, deployed_by: @admin)\n    create(:cluster, project: @project, deployed_by: @developer)\n    sign_in(@developer)\n\n    params = {\n      project_slug: @project.slug,\n    }\n    get :index, params: params, format: :json\n\n    assert_response(:success)\n    data = JSON.parse(response.body)\n    assert_equal(1, data['clusters'].count)\n  end\n\n  test '#create' do\n    sign_in(@admin)\n    cluster_creation_data = json_fixture('files/controller/cluster_not_ready.json')\n    params = {\n      project_slug: @project.slug,\n      cluster: {\n        name: cluster_creation_data[:name],\n      },\n    }\n\n    expected_request = {\n      name: cluster_creation_data[:name],\n      manifest: nil,\n      base_ingress_host: /#{UffizziCore::Cluster::NAMESPACE_PREFIX}\\d/,\n    }\n    stubbed_create_cluster_request = stub_create_cluster_request_with_expected(cluster_creation_data, expected_request)\n    stubbed_create_namespace_request = stub_create_namespace_request\n    cluster_show_data = json_fixture('files/controller/cluster_ready.json')\n    stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)\n\n    differences = {\n      -> { UffizziCore::Cluster.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response(:success)\n    assert(UffizziCore::Cluster.find_by(name: cluster_creation_data[:name]).creation_source.manual?)\n    assert_requested(stubbed_create_cluster_request)\n    assert_requested(stubbed_create_namespace_request)\n    assert_requested(stubbed_cluster_request)\n  end\n\n  test '#create with wrong k8s version' do\n    sign_in(@admin)\n\n    params = {\n      project_slug: @project.slug,\n      cluster: {\n        name: 'my cluster',\n        k8s_version: 'wrong.version',\n      },\n    }\n\n    differences = {\n      -> { UffizziCore::Cluster.count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response(:unprocessable_entity)\n  end\n\n  test '#create when enabled cluster with the same name exists' do\n    sign_in(@admin)\n    name = 'test'\n    create(:cluster, project: @project, deployed_by: @admin, name: name)\n\n    params = {\n      project_slug: @project.slug,\n      cluster: {\n        name: name,\n      },\n    }\n\n    differences = {\n      -> { UffizziCore::Cluster.count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response(:unprocessable_entity)\n  end\n\n  test '#create with manifest' do\n    sign_in(@admin)\n    manifest = File.read('test/fixtures/files/cluster/manifest.yml')\n    cluster_creation_data = json_fixture('files/controller/cluster_not_ready.json')\n    cluster_show_data = json_fixture('files/controller/cluster_ready.json')\n\n    params = {\n      project_slug: @project.slug,\n      cluster: {\n        name: cluster_creation_data[:name],\n        manifest: manifest,\n      },\n    }\n\n    stubbed_create_namespace_request = stub_create_namespace_request\n    expected_request = {\n      name: cluster_creation_data[:name],\n      manifest: manifest,\n      base_ingress_host: /#{UffizziCore::Cluster::NAMESPACE_PREFIX}\\d/,\n    }\n    stubbed_create_cluster_request = stub_create_cluster_request_with_expected(cluster_creation_data, expected_request)\n    stubbed_get_cluster_request = stub_get_cluster_request(cluster_show_data)\n\n    differences = {\n      -> { UffizziCore::Cluster.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response(:success)\n    assert_requested(stubbed_create_cluster_request)\n    assert_requested(stubbed_create_namespace_request)\n    assert_requested(stubbed_get_cluster_request)\n  end\n\n  test '#show shows cluster created by the same developer' do\n    cluster = create(:cluster, project: @project, deployed_by: @developer, name: 'test')\n    sign_in(@developer)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    get :show, params: params, format: :json\n\n    assert_response(:success)\n  end\n\n  test '#show does not show cluster created by a different user to developer' do\n    sign_in(@developer)\n\n    cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test')\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    get :show, params: params, format: :json\n\n    assert_response(:not_found)\n  end\n\n  test '#show shows clusters created by a different user to admin' do\n    sign_in(@admin)\n\n    cluster = create(:cluster, project: @project, deployed_by: @developer, name: 'test')\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    get :show, params: params, format: :json\n\n    assert_response(:success)\n  end\n\n  test '#scale_down' do\n    sign_in(@admin)\n\n    cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_DEPLOYED)\n    stubbed_scale_request = stub_scale_cluster_request\n\n    cluster_show_data = json_fixture('files/controller/cluster_asleep.json')\n    stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    put :scale_down, params: params, format: :json\n\n    assert_response(:success)\n    assert(cluster.reload.scaled_down?)\n    assert_requested(stubbed_scale_request)\n    assert_requested(stubbed_cluster_request)\n  end\n\n  test '#scale_up' do\n    sign_in(@admin)\n\n    cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_SCALED_DOWN)\n    stubbed_scale_request = stub_scale_cluster_request\n    cluster_show_data = json_fixture('files/controller/cluster_awake.json')\n    stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    put :scale_up, params: params, format: :json\n\n    assert_response(:success)\n    assert(cluster.reload.deployed?)\n    assert_requested(stubbed_scale_request)\n    assert_requested(stubbed_cluster_request)\n  end\n\n  test '#sync when the data is actual' do\n    sign_in(@admin)\n    cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_DEPLOYED)\n\n    cluster_show_data = json_fixture('files/controller/cluster_awake.json')\n    stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    put :sync, params: params, format: :json\n\n    assert_requested(stubbed_cluster_request)\n    assert(cluster.reload.deployed?)\n  end\n\n  test '#sync when the data needs to be updated' do\n    sign_in(@admin)\n    cluster = create(:cluster, project: @project, deployed_by: @admin, name: 'test', state: UffizziCore::Cluster::STATE_DEPLOYED)\n\n    cluster_show_data = json_fixture('files/controller/cluster_asleep.json')\n    stubbed_cluster_request = stub_get_cluster_request(cluster_show_data)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    put :sync, params: params, format: :json\n\n    assert_requested(stubbed_cluster_request)\n    assert(cluster.reload.scaled_down?)\n  end\n\n  test '#destroy developer can destroy a cluster created by him' do\n    sign_in(@developer)\n\n    cluster = create(:cluster, :deployed, project: @project, deployed_by: @developer, name: 'test')\n    stubbed_delete_namespace_request = stub_delete_namespace_request(cluster)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    delete :destroy, params: params, format: :json\n\n    assert_response(:success)\n    assert(cluster.reload.disabled?)\n    assert_requested(stubbed_delete_namespace_request)\n  end\n\n  test '#destroy developer cannot destroy a cluster created by other user' do\n    sign_in(@developer)\n\n    cluster = create(:cluster, :deployed, project: @project, deployed_by: @admin, name: 'test')\n    stubbed_delete_namespace_request = stub_delete_namespace_request(cluster)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    delete :destroy, params: params, format: :json\n\n    assert_response(:not_found)\n    refute_requested(stubbed_delete_namespace_request)\n  end\n\n  test '#destroy admin can destroy a cluster created by other user' do\n    sign_in(@admin)\n\n    cluster = create(:cluster, :deployed, project: @project, deployed_by: @developer, name: 'test')\n    stubbed_delete_namespace_request = stub_delete_namespace_request(cluster)\n\n    params = {\n      project_slug: @project.slug,\n      name: cluster.name,\n    }\n\n    delete :destroy, params: params, format: :json\n\n    assert_response(:success)\n    assert(cluster.reload.disabled?)\n    assert_requested(stubbed_delete_namespace_request)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/compose_files_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::ComposeFilesControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_personal_account)\n    @account = @admin.personal_account\n    @project = create(:project, :with_members, account: @account, members: [@admin])\n    @compose_file = create(:compose_file, project: @project, added_by: @admin)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin, payload: template_payload)\n  end\n\n  test '#show - admin gets compose file' do\n    sign_in @admin\n\n    params = { project_slug: @project.slug }\n\n    get :show, params: params, format: :json\n\n    assert_response :success\n  end\n\n  test '#show - returns 404 if compose file does not exist' do\n    sign_in @admin\n\n    @compose_file.destroy!\n\n    params = { project_slug: @project.slug }\n\n    get :show, params: params, format: :json\n\n    error_message = JSON.parse(response.body, symbolize_names: true)[:errors][:title].first\n\n    assert_response :not_found\n    assert_equal('UffizziCore::ComposeFile Not Found', error_message)\n  end\n\n  test '#create - docker compose file' do\n    sign_in @admin\n\n    @compose_file.destroy!\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-vote-app-docker.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n    dependency = {\n      path: 'configs/vote.conf',\n      source: 'vote.conf',\n      content: json_fixture('files/compose_dependencies/configs/vote_conf.json')[:content],\n      use_kind: UffizziCore::ComposeFile::DependenciesService::DEPENDENCY_CONFIG_USE_KIND,\n    }\n\n    stub_dockerhub_repository_any\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [dependency],\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.main.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create - main compose file when already exists' do\n    sign_in @admin\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/test-compose-success-without-dependencies.yml')\n    encoded_content = Base64.encode64(file_content)\n    stub_dockerhub_repository_any\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.main.count } => 0,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create - check amazon ecr creation' do\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :amazon, account: @account)\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-amazon.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    differences = {\n      -> { UffizziCore::Template.count } => 1,\n      -> { UffizziCore::ComposeFile.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create - check github registry creation' do\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :github_container_registry, account: @account)\n    create(:credential, :docker_hub, account: @account)\n\n    @compose_file.destroy!\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    content = json_fixture('files/github/compose_files/hello_world_compose_github_container_registry.json')[:content]\n    compose_file_attributes = base_attributes.merge(content: content)\n\n    differences = {\n      -> { UffizziCore::Template.count } => 1,\n      -> { UffizziCore::ComposeFile.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test 'create - ECR via Docker Registry' do\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    registry_url = 'https://323707565364.dkr.ecr.us-east-1.amazonaws.com'\n    credential = create(:credential, :docker_registry, username: 'AWS', registry_url: registry_url, account: @account)\n\n    @compose_file.destroy!\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-amazon.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content)\n    stubbed_docker_registry_manifests = stub_docker_registry_manifests(credential.registry_url, 'test-compose', 'latest')\n\n    differences = {\n      -> { UffizziCore::Template.count } => 1,\n      -> { UffizziCore::ComposeFile.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    assert_requested(stubbed_docker_registry_manifests)\n  end\n\n  test '#create - compose file with volumes' do\n    sign_in @admin\n\n    @compose_file.destroy!\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/compose_files/compose_with_volumes.yml')\n    stub_dockerhub_repository_any\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.main.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create - with yaml aliases' do\n    sign_in @admin\n\n    @compose_file.destroy!\n    stub_dockerhub_repository('library', 'nginx')\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-with_alias.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n    dependency = {\n      path: 'configs/vote.conf',\n      source: 'vote.conf',\n      content: json_fixture('files/compose_dependencies/configs/vote_conf.json')[:content],\n      use_kind: UffizziCore::ComposeFile::DependenciesService::DEPENDENCY_CONFIG_USE_KIND,\n    }\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [dependency],\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.main.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#create - string command' do\n    sign_in @admin\n\n    @compose_file.destroy!\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-vote-app-with-command-as-string.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    stub_dockerhub_repository_any\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.main.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    template = UffizziCore::Template.last\n    assert_equal('[\"postgres\", \"-c\", \"jit=off\"]', template.payload['containers_attributes'].first['command'])\n  end\n\n  test '#create - service name does not match RFC 123' do\n    sign_in @admin\n\n    @compose_file.destroy!\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-invalid-service-name.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.main.count } => 0,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response(:unprocessable_entity)\n  end\n\n  test '#destroy - deletes compose file' do\n    sign_in @admin\n\n    params = { project_slug: @project.slug }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.count } => -1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :no_content\n  end\n\n  test '#destroy - when compose_file not found' do\n    sign_in @admin\n\n    @compose_file.destroy!\n    params = { project_slug: @project.slug }\n\n    delete :destroy, params: params, format: :json\n\n    error_message = JSON.parse(response.body, symbolize_names: true)[:errors][:title].first\n\n    assert_response :not_found\n    assert_equal('UffizziCore::ComposeFile Not Found', error_message)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/activity_items_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ActivityItemsControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, :with_members, account: @user.personal_account, members: [@user])\n    @deployment = create(:deployment, project: @project)\n\n    sign_in @user\n  end\n\n  test '#index' do\n    container = create(:container, :with_public_port, deployment: @deployment)\n    create(:activity_item, :with_building_event, tag: container.tag, container: container, deployment: @deployment)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_response :success\n  end\n\n  test '#index - with failed deployment' do\n    @deployment.fail!\n    container = create(:container, :with_public_port, deployment: @deployment)\n    create(:activity_item, :with_building_event, tag: container.tag, container: container, deployment: @deployment)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_response :unprocessable_entity\n\n    response_body = JSON.parse(response.body)\n\n    assert_equal(\"Preview with ID deployment-#{@deployment.id} failed\", response_body['errors']['title'].first)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers/logs_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::Containers::LogsControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, :with_members, account: @user.personal_account, members: [@user])\n    @deployment = create(:deployment, project: @project)\n    @container = create(:container, deployment: @deployment)\n    @pod_name = UffizziCore::ContainerService.pod_name(@container)\n    @limit = 30\n    @previous = false\n\n    sign_in @user\n  end\n\n  test '#index' do\n    controller_response = json_fixture('files/controller/logs.json')\n\n    pod_name = UffizziCore::ContainerService.pod_name(@container)\n    stubbed_request = stub_container_log_request(@deployment.id, pod_name, @limit, @previous, controller_response)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n      container_name: @container.service_name,\n      limit: @limit,\n      previous: @previous,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_requested stubbed_request\n\n    assert_response :success\n\n    data = JSON.parse(response.body)\n\n    expected_result = {\n      'logs' => [\n        {\n          'timestamp' => '2022-11-14 11:46:55.474 UTC',\n          'payload' => '/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration',\n        },\n      ],\n    }\n    assert_equal(expected_result, data)\n  end\n\n  test '#index with empty logs info' do\n    controller_response = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        logs: [],\n      },\n    )\n\n    stubbed_request = stub_container_log_request(@deployment.id, @pod_name, @limit, @previous, controller_response)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n      container_name: @container.service_name,\n      limit: @limit,\n      previous: @previous,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_requested stubbed_request\n\n    assert_response :success\n  end\n\n  test '#index with controller error' do\n    controller_response = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        logs: [],\n      },\n    )\n    stubbed_request = stub_container_log_request(@deployment.id, @pod_name, @limit, @previous, controller_response)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n      container_name: @container.service_name,\n      limit: @limit,\n      previous: @previous,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_requested stubbed_request\n\n    assert_response :success\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/containers_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::ContainersControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    sign_in @user\n\n    @account = @user.personal_account\n    @project = create(:project, :with_members, account: @user.personal_account, members: [@user])\n    @deployment = create(:deployment, project: @project)\n  end\n\n  test '#index' do\n    repo = create(:repo, :docker_hub, project: @project)\n\n    create(\n      :container,\n      deployment: @deployment,\n      repo: repo,\n      image: 'uffizzitest/webhooks-test-app',\n      tag: 'latest',\n      secret_variables: [{ 'name' => 'test', 'value' => 'test' }],\n    )\n\n    params = { project_slug: @project.slug, deployment_id: @deployment.id }\n\n    get :index, params: params, format: :json\n\n    assert_response :success\n  end\n\n  test '#k8s_container_description - with last state' do\n    container = create(:container, deployment: @deployment, controller_name: 'f03d008a48')\n    controller_response = json_fixture('files/controller/deployment_containers_with_error.json')\n    stubbed_request = stub_controller_containers_request(@deployment, controller_response)\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n      container_name: container.service_name,\n    }\n\n    get :k8s_container_description, params: params, format: :json\n\n    assert_requested stubbed_request\n    assert_response :success\n\n    data = JSON.parse(response.body)\n    expected_result = {\n      'last_state' => {\n        'code' => 'terminated',\n        'reason' => 'Error',\n        'exit_code' => 127,\n        'started_at' => '2022-12-05T18:11:46Z',\n        'finished_at' => '2022-12-05T18:11:46Z',\n      },\n    }\n\n    assert_equal(expected_result, data)\n  end\n\n  test '#k8s_container_description - with empty last state' do\n    container = create(:container, deployment: @deployment, controller_name: 'f03d008a48')\n    controller_response = json_fixture('files/controller/deployment_containers.json')\n    stubbed_request = stub_controller_containers_request(@deployment, controller_response)\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n      container_name: container.service_name,\n    }\n\n    get :k8s_container_description, params: params, format: :json\n\n    assert_requested stubbed_request\n    assert_response :success\n\n    data = JSON.parse(response.body)\n    expected_result = { 'last_state' => {} }\n\n    assert_equal(expected_result, data)\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments/events_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::Deployments::EventsControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, :with_members, account: @user.personal_account, members: [@user])\n    @deployment = create(:deployment, project: @project)\n\n    sign_in @user\n  end\n\n  test '#index' do\n    first_timestamp = generate(:string)\n    last_timestamp = generate(:string)\n    reason = generate(:string)\n    message = generate(:string)\n\n    events = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        items: [\n          {\n            involved_object: { kind: 'Pod' },\n            first_timestamp: first_timestamp,\n            last_timestamp: last_timestamp,\n            reason: reason,\n            message: message,\n          },\n        ],\n      },\n    )\n\n    stubbed_controller_get_deployment_events = stub_controller_get_deployment_events(@deployment, events)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_requested(stubbed_controller_get_deployment_events)\n\n    assert_response :success\n\n    collected_result = {\n      events: [\n        {\n          first_timestamp: first_timestamp,\n          last_timestamp: last_timestamp,\n          reason: reason,\n          message: message,\n        },\n      ],\n    }.to_json\n\n    assert_equal(collected_result, response.body)\n  end\n\n  test '#index with empty logs info' do\n    events = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        items: [],\n      },\n    )\n\n    stubbed_controller_get_deployment_events = stub_controller_get_deployment_events(@deployment, events)\n\n    params = {\n      project_slug: @project.slug,\n      deployment_id: @deployment.id,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_requested(stubbed_controller_get_deployment_events)\n\n    assert_response :success\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/create_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_personal_account)\n    @account = @admin.personal_account\n    @project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])\n    @deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)\n    @metadata = {\n      'labels' => {\n        'github' => {\n          'repository' => 'feature/#24_My_awesome_feature',\n          'pull_request' => {\n            'number' => '24',\n          },\n        },\n      },\n    }\n\n    @deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))\n\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      full_image_name: \"#{image}:#{target_branch}\",\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    @template = create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin,\n                                                        payload: template_payload)\n\n    sign_in @admin\n  end\n\n  test '#create - from the existing compose file' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n\n    file_content = File.read('test/fixtures/files/test-compose-success.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      :with_named_volume,\n      image: image,\n      tag: target_branch,\n      full_image_name: \"#{image}:#{target_branch}\",\n      healthcheck: { test: ['CMD', 'curl', '-f', 'https://localhost'] },\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n    stub_dockerhub_repository('library', 'redis')\n\n    params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: {} }\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => 1,\n      -> { UffizziCore::Repo::DockerHub.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n\n    subdomains = UffizziCore::Deployment.active.map(&:subdomain)\n    assert_nil(subdomains.detect { |s| s.include?('_') })\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from the existing compose file with metadata' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n\n    file_content = File.read('test/fixtures/files/test-compose-success.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      :with_named_volume,\n      image: image,\n      tag: target_branch,\n      full_image_name: \"#{image}:#{target_branch}\",\n      healthcheck: { test: ['CMD', 'curl', '-f', 'https://localhost'] },\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n    stub_dockerhub_repository('library', 'redis')\n    params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: @metadata }\n\n    post :create, params: params, format: :json\n\n    assert_response :success\n    deployment = compose_file.deployments.first\n    assert_equal(@metadata, deployment.metadata)\n    assert_equal(deployment.subdomain.downcase, deployment.subdomain)\n    assert_equal(deployment.creation_source, UffizziCore::Deployment.creation_source.compose_file_manual)\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from the existing compose file with metadata when an active deployment exists - should return an eror' do\n    create(:deployment, metadata: @metadata, project: @project)\n\n    params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: @metadata }\n\n    post :create, params: params, format: :json\n\n    assert_response :unprocessable_entity\n  end\n\n  test '#create - from the existing compose file with github_actions creation source (for self-hosted version)' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n\n    file_content = File.read('test/fixtures/files/test-compose-success.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      image: image,\n      tag: target_branch,\n      full_image_name: \"#{image}:#{target_branch}\",\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n    stub_dockerhub_repository('library', 'redis')\n    creation_source = UffizziCore::Deployment.creation_source.github_actions\n\n    params = { project_slug: @project.slug, compose_file: {}, dependencies: [], creation_source: creation_source }\n\n    post :create, params: params, format: :json\n\n    assert_response :success\n    deployment = compose_file.deployments.first\n\n    assert_equal(creation_source, deployment.creation_source)\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from the existing compose file when credentials are removed' do\n    create_namespace_request = stub_create_namespace_request\n    file_content = File.read('test/fixtures/files/uffizzi-compose-vote-app-docker.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_content)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      full_image_name: \"#{image}:#{target_branch}\",\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n    stub_dockerhub_private_repository('library', 'redis')\n\n    params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: {} }\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => 0,\n      -> { UffizziCore::Repo::DockerHub.count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :unprocessable_entity\n    assert_not_requested(create_namespace_request)\n  end\n\n  test '#create - from the existing compose file - when the file is invalid' do\n    create(:credential, :github_container_registry, account: @admin.personal_account)\n    compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      full_image_name: \"#{image}:#{target_branch}\",\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n    params = { project_slug: @project.slug, compose_file: {}, dependencies: [], metadata: {} }\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => 0,\n      -> { UffizziCore::Repo::DockerHub.count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :unprocessable_entity\n  end\n\n  test '#create - when compose file does not exist and no params given' do\n    params = {\n      project_slug: @project.slug,\n      compose_file: {},\n      dependencies: [],\n      metadata: {},\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 0,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :not_found\n  end\n\n  test '#create - when compose file does not exist and use docker registry without auth' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    stub_docker_registry_manifests('https://ttl.sh', 'abc', '1h')\n\n    compose_file_content = File.read('test/fixtures/files/uffizzi-compose-docker-registry-anonymous.yml')\n    encoded_compose_file_content = Base64.encode64(compose_file_content)\n\n    compose_file = {\n      source: '/gem/tmp/docker-compose.uffizzi.yaml',\n      path: '/gem/tmp/docker-compose.uffizzi.yaml',\n      content: encoded_compose_file_content,\n    }\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file,\n      dependencies: [],\n      metadata: {},\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - with content when compose file does not exist' do\n    deployment_data = json_fixture('files/controller/deployments.json')\n    stubbed_namespace_request = stub_controller_get_namespace_request_any(deployment_data)\n    stubbed_controller_create_name_request = stub_create_namespace_request\n    stub_controller_apply_credential\n\n    compose_file_name = 'test-compose-full.yml'\n    file_content = File.read(\"test/fixtures/files/#{compose_file_name}\")\n    encoded_content = Base64.encode64(file_content)\n    stub_dockerhub_repository_any\n\n    # rubocop:disable Layout/LineLength\n    params = {\n      project_slug: @project.slug,\n      compose_file: {\n        source: \"/gem/tmp/#{compose_file_name}\",\n        path: \"/gem/tmp/#{compose_file_name}\",\n        content: encoded_content,\n      },\n      dependencies: [\n        {\n          content: \"ZGF0YQ1==\\n\",\n          is_file: false,\n          path: '/gem/tmp/some_app_dir',\n          source: './some_app_dir',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ2==\\n\",\n          is_file: true,\n          path: '/gem/tmp/files/some_app_file',\n          source: './files/some_app_file',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ3==\\n\",\n          is_file: false,\n          path: '/gem/tmp/some_db_dir',\n          source: './some_db_dir',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ4==\\n\",\n          is_file: true,\n          path: '/gem/tmp/some_db_file',\n          source: './some_db_file',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ==\\n\",\n          is_file: false,\n          path: '/gem/tmp',\n          source: './',\n          use_kind: 'volume',\n        },\n        {\n          content: \"S0VZPXZhbHVl\\n\",\n          path: 'env_files/env_file.env',\n          source: 'env_files/env_file.env',\n          use_kind: 'config_map',\n        },\n        {\n          content: \"UE9TVEdSRVNfVVNFUj1wb3N0Z3JlcyBQT1NUR1JFU19QQVNTV09SRD1wb3N0\\nZ3Jlcw==\\n\",\n          path: 'local.env',\n          source: 'local.env',\n          use_kind: 'config_map',\n        },\n        {\n          content: \"c2VydmVyIHsgbGlzdGVuICAgICAgIDg4ODg7IHNlcnZlcl9uYW1lICBsb2Nh\\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\\n\",\n          path: 'config_files/config_file.conf',\n          source: 'config_files/config_file.conf',\n          use_kind: 'config_map',\n        },\n        {\n          content: \"c2VydmVyIHsgbGlzdGVuICAgICAgIDgwODA7IHNlcnZlcl9uYW1lICBsb2Nh\\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\\n\",\n          path: 'app.conf',\n          source: 'app.conf',\n          use_kind: 'config_map',\n        },\n      ],\n      metadata: {},\n    }\n    # rubocop:enable Layout/LineLength\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Deployment.count } => 1,\n      -> { UffizziCore::Container.count } => 3,\n      -> { UffizziCore::HostVolumeFile.count } => 5,\n      -> { UffizziCore::ConfigFile.count } => 1,\n      -> { UffizziCore::Repo.count } => 3,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    assert_requested(stubbed_controller_create_name_request)\n    assert_requested(stubbed_namespace_request)\n\n    default_container_attributes = {\n      image: nil,\n      tag: nil,\n      service_name: nil,\n      variables: [],\n      public: false,\n      port: nil,\n      state: 'active',\n      continuously_deploy: 'enabled',\n      kind: 'user',\n      target_port: nil,\n      controller_name: nil,\n      receive_incoming_requests: false,\n      memory_request: 125 / UffizziCore::Container::REQUEST_MEMORY_RATIO,\n      memory_limit: 125,\n      secret_variables: [],\n      entrypoint: nil,\n      command: nil,\n      healthcheck: {},\n      volumes: [],\n      additional_subdomains: [],\n      source: nil,\n    }\n\n    app_container_attributes = {\n      image: 'uffizzicloud/app',\n      tag: 'latest',\n      service_name: 'app',\n      volumes: [\n        {\n          type: 'host',\n          source: './some_app_dir',\n          target: '/var/app/some_dir',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './files/some_app_file',\n          target: '/var/app/some_app_files',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './',\n          target: '/var/entire_app',\n          read_only: false,\n        },\n        {\n          type: 'named',\n          source: 'app_share',\n          target: '/some_app_share',\n          read_only: true,\n        },\n        {\n          type: 'anonymous',\n          source: '/some_anonymous_dir',\n          target: nil,\n          read_only: false,\n        },\n      ],\n      variables: [\n        {\n          name: 'POSTGRES_USER',\n          value: 'postgres POSTGRES_PASSWORD=postgres',\n        },\n        {\n          name: 'KEY',\n          value: 'value',\n        },\n      ],\n    }\n\n    db_container_attributes = {\n      image: 'library/postgres',\n      tag: 'latest',\n      service_name: 'db',\n      volumes: [\n        {\n          type: 'host',\n          source: './some_app_dir',\n          target: '/var/db/some_dir_2',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './some_db_dir',\n          target: '/var/db/some_dir_3',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './some_db_file',\n          target: '/var/db/some_db_files',\n          read_only: false,\n        },\n        {\n          type: 'named',\n          source: 'db_share',\n          target: '/some_db_share',\n          read_only: true,\n        },\n      ],\n    }\n\n    nginx_container_attributes = {\n      image: 'library/nginx',\n      tag: '1.32',\n      service_name: 'nginx',\n      port: 80,\n      target_port: 80,\n      public: true,\n      receive_incoming_requests: true,\n    }\n\n    expected_app_container_attributes = default_container_attributes.merge(app_container_attributes)\n    expected_db_container_attributes = default_container_attributes.merge(db_container_attributes)\n    expected_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes)\n\n    exclude_params = [:state, :source, :kind, :target_port, :controller_name]\n    expected_template_app_container_attributes = default_container_attributes.merge(app_container_attributes).without(exclude_params)\n    expected_template_db_container_attributes = default_container_attributes.merge(db_container_attributes).without(exclude_params)\n    expected_template_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes).without(exclude_params)\n\n    container_keys = default_container_attributes.keys\n    deployment = UffizziCore::Deployment.last\n    actual_containers_attributes = deployment.containers.map { |c| c.attributes.deep_symbolize_keys.slice(*container_keys) }\n    actual_template_containers_attributes = deployment.compose_file.template.payload\n      .deep_symbolize_keys[:containers_attributes]\n      .map { |c| c.slice(*container_keys) }\n\n    actual_app_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'app' }\n    actual_db_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'db' }\n    actual_nginx_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'nginx' }\n\n    actual_template_app_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'app' }\n    actual_template_db_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'db' }\n    actual_template_nginx_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'nginx' }\n\n    assert_equal expected_app_container_attributes, actual_app_container_attributes\n    assert_equal expected_template_app_container_attributes, actual_template_app_container_attributes\n\n    assert_equal expected_db_container_attributes, actual_db_container_attributes\n    assert_equal expected_template_db_container_attributes, actual_template_db_container_attributes\n\n    assert_equal expected_nginx_container_attributes, actual_nginx_container_attributes\n    assert_equal expected_template_nginx_container_attributes, actual_template_nginx_container_attributes\n\n    actual_host_volume_file_paths = UffizziCore::HostVolumeFile.pluck(:path)\n    expected_host_volume_file_paths = params[:dependencies].select { |d| d[:use_kind] == 'volume' }.pluck(:path)\n\n    assert_equal expected_host_volume_file_paths.sort, actual_host_volume_file_paths.sort\n\n    actual_host_volume_file_sources = UffizziCore::HostVolumeFile.pluck(:source)\n    expected_host_volume_file_sources = params[:dependencies]\n      .select { |d| d[:use_kind] == 'volume' }\n      .pluck(:source)\n      .map { |s| \"#{compose_file_name}/#{s}\" }\n\n    assert_equal expected_host_volume_file_sources.sort, actual_host_volume_file_sources.sort\n\n    actual_host_volume_file_count_which_is_file = UffizziCore::HostVolumeFile.where(is_file: true).count\n\n    assert_equal(2, actual_host_volume_file_count_which_is_file)\n  end\n\n  test '#create - file with local host volume when same host volume file exists' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n\n    stub_dockerhub_login\n    stub_dockerhub_repository('library', 'nginx')\n    create(:credential, :docker_hub, account: @admin.personal_account)\n    compose_file_content = File.read('test/fixtures/files/uffizzi-compose-with-host-volumes.yml')\n    encoded_compose_file_content = Base64.encode64(compose_file_content)\n    host_volume_content = Base64.encode64(File.binread('test/fixtures/files/file.tar.gz'))\n\n    compose_file_params = {\n      source: '/gem/tmp/dc.uffizzi-nginx.yaml',\n      path: '/gem/tmp/dc.uffizzi-nginx.yaml',\n      content: encoded_compose_file_content,\n    }\n\n    dependency = {\n      path: '/gem/tmp/share_dir',\n      source: './share_dir',\n      content: host_volume_content,\n      use_kind: UffizziCore::ComposeFile::DependenciesService::DEPENDENCY_VOLUME_USE_KIND,\n      is_file: false,\n    }\n\n    compose_file = create(:compose_file, project: @project, added_by: @admin, content: encoded_compose_file_content)\n    create(:host_volume_file, path: dependency[:path],\n                              source: 'dc.uffizzi-nginx.yaml/./share_dir',\n                              payload: Base64.decode64(dependency[:content]),\n                              is_file: false,\n                              project: @project,\n                              compose_file: compose_file)\n\n    params = { project_slug: @project.slug, compose_file: compose_file_params, dependencies: [dependency] }\n\n    differences = {\n      -> { UffizziCore::Container.count } => 1,\n      -> { UffizziCore::ContainerHostVolumeFile.count } => 1,\n      -> { UffizziCore::HostVolumeFile.count } => 0,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from amazon image with credentials' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    registry_url = 'https://323707565364.dkr.ecr.us-east-1.amazonaws.com'\n    stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')\n\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :amazon, :active, account: @account, registry_url: registry_url)\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-amazon.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      metadata: {},\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - compose file with jfrog docker registry with auth' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    stub_docker_registry_manifests('https://elnealo.jfrog.io', 'uffizzi-test-docker/webhook-test-app', 'latest')\n\n    compose_file_content = File.read('test/fixtures/files/test-compose-success-jfrog.yml')\n    encoded_compose_file_content = Base64.encode64(compose_file_content)\n\n    compose_file = {\n      source: '/gem/tmp/docker-compose.uffizzi.yaml',\n      path: '/gem/tmp/docker-compose.uffizzi.yaml',\n      content: encoded_compose_file_content,\n    }\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file,\n      dependencies: [],\n      metadata: {},\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from azure image with credentials' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    registry_url = 'account.azurecr.io/nginx:latest'\n    stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')\n\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :azure, :active, account: @account, registry_url: registry_url)\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-azure.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      metadata: {},\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from google(gcr) image with credentials' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    registry_url = 'gcr.io/project1/test-compose:latest'\n    stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')\n\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :google, :active, account: @account, registry_url: registry_url)\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-google.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      metadata: {},\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from ghcr image with credentials' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    registry_url = 'ghcr.io/project1/test-compose:latest'\n    stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')\n\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :github_container_registry, :active, account: @account, registry_url: registry_url)\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-ghcr.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      metadata: {},\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#create - from dockerhub image with credentials' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n    registry_url = 'project1/test-compose:latest'\n    stub_docker_registry_manifests(registry_url, 'test-compose', 'latest')\n    stubbed_dockerhub_login = stub_dockerhub_login\n    stub_dockerhub_repository('project1', 'test-compose')\n\n    sign_in @admin\n\n    project = create(:project, :with_members, account: @account, members: [@admin])\n    create(:credential, :docker_hub, :active, account: @account, registry_url: registry_url)\n\n    base_attributes = attributes_for(:compose_file).slice(:source, :path)\n    file_content = File.read('test/fixtures/files/uffizzi-compose-dockerhub.yml')\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = base_attributes.merge(content: encoded_content, repository_id: nil)\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { UffizziCore::Container.count } => 1,\n    }\n\n    params = {\n      project_slug: project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      metadata: {},\n    }\n\n    assert_difference differences do\n      post :create, params: params, format: :json\n    end\n\n    assert_response :success\n    assert_requested stubbed_dockerhub_login, times: 2\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/deploy_containers_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_personal_account)\n    @account = @admin.personal_account\n    @project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])\n    @deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)\n\n    @deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))\n    @credential = create(:credential, :github_container_registry, account: @account)\n\n    sign_in @admin\n  end\n\n  test '#deploy_containers' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.fake!\n\n    stub_dockerhub_login\n    repo1 = create(:repo, :docker_hub, project: @project)\n    repo2 = create(:repo, :docker_hub, project: @project)\n\n    create(:container, :with_public_port, deployment: @deployment, repo: repo1)\n    create(:container, :with_public_port, deployment: @deployment, repo: repo2)\n\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    post :deploy_containers, params: params, format: :json\n\n    assert_response :success\n    assert { UffizziCore::Deployment::DeployContainersJob.jobs.size == 1 }\n\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n  end\n\n  test '#deploy_containers if deployment has not created yet' do\n    UffizziCore::ControllerService.expects(:namespace_exists?).returns(false)\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    assert_raises UffizziCore::DeploymentNotFoundError do\n      post :deploy_containers, params: params, format: :json\n    end\n  end\n\n  test '#deploy_containers create a new docker hub activity item' do\n    UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)\n\n    webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')\n    digest_data = json_fixture('files/dockerhub/digest.json')\n    deployment_containers_data = json_fixture('files/controller/deployment_containers.json')\n    deployment_data = json_fixture('files/controller/deployments.json')\n\n    stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)\n    stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)\n    stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    namespace, name = webhooks_data[:repository][:repo_name].split('/')\n\n    repo = create(:repo,\n                  :docker_hub,\n                  project: @project,\n                  namespace: namespace,\n                  name: name)\n\n    container = create(\n      :container,\n      :with_public_port,\n      :continuously_deploy_enabled,\n      deployment: @deployment,\n      repo: repo,\n      image: webhooks_data[:repository][:repo_name],\n      tag: webhooks_data[:push_data][:tag],\n      controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],\n    )\n\n    create(:credential, :docker_hub, account: @account)\n\n    stubbed_digest_auth = stub_dockerhub_auth_for_digest(container.image)\n    stubbed_digest = stub_dockerhub_get_digest(container.image, container.tag, digest_data)\n\n    post :deploy_containers, params: params, format: :json\n\n    assert_requested stubbed_digest\n    assert_requested stubbed_digest_auth\n    assert_requested stubbed_dockerhub_login\n    assert_requested stubbed_deploy_containers_request\n    assert_requested stubbed_containers_request\n    assert_requested stubbed_namespace_request\n    assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }\n    assert { UffizziCore::ActivityItem::Docker.count == 1 }\n  end\n\n  test '#deploy_containers skip activity item creation if existing has not finished yet' do\n    UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)\n\n    webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')\n    digest_data = json_fixture('files/dockerhub/digest.json')\n    deployment_containers_data = json_fixture('files/controller/deployment_containers.json')\n    deployment_data = json_fixture('files/controller/deployments.json')\n\n    namespace, name = webhooks_data[:repository][:repo_name].split('/')\n\n    repo = create(:repo,\n                  :docker_hub,\n                  project: @project,\n                  namespace: namespace,\n                  name: name)\n\n    container = create(\n      :container,\n      :with_public_port,\n      :continuously_deploy_enabled,\n      deployment: @deployment,\n      repo: repo,\n      image: webhooks_data[:repository][:repo_name],\n      tag: webhooks_data[:push_data][:tag],\n      controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],\n    )\n\n    create(:activity_item,\n           :docker,\n           :with_building_event,\n           namespace: repo.namespace,\n           name: repo.name,\n           tag: container.tag,\n           container: container,\n           deployment: @deployment)\n\n    stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)\n    stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)\n    stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    create(:credential, :docker_hub, account: @account)\n\n    stubbed_digest_auth = stub_dockerhub_auth_for_digest(container.image)\n    stubbed_digest = stub_dockerhub_get_digest(container.image, container.tag, digest_data)\n\n    post :deploy_containers, params: params, format: :json\n\n    assert_requested stubbed_digest\n    assert_requested stubbed_digest_auth\n    assert_requested stubbed_dockerhub_login\n    assert_requested stubbed_deploy_containers_request\n    assert_requested stubbed_containers_request\n    assert_requested stubbed_namespace_request\n    assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }\n    assert { UffizziCore::ActivityItem::Docker.count == 1 }\n  end\n\n  test '#deploy_containers create a new activity item creation if existing has finished' do\n    UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)\n    webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')\n    digest_data = json_fixture('files/dockerhub/digest.json')\n    deployment_containers_data = json_fixture('files/controller/deployment_containers.json')\n    deployment_data = json_fixture('files/controller/deployments.json')\n\n    namespace, name = webhooks_data[:repository][:repo_name].split('/')\n\n    repo = create(:repo,\n                  :docker_hub,\n                  project: @project,\n                  namespace: namespace,\n                  name: name)\n\n    container = create(\n      :container,\n      :with_public_port,\n      :continuously_deploy_enabled,\n      deployment: @deployment,\n      repo: repo,\n      image: webhooks_data[:repository][:repo_name],\n      tag: webhooks_data[:push_data][:tag],\n      controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],\n    )\n\n    create(:activity_item,\n           :docker,\n           :with_deployed_event,\n           namespace: repo.namespace,\n           name: repo.name,\n           tag: container.tag,\n           container: container,\n           deployment: @deployment)\n\n    stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)\n    stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)\n    stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    create(:credential, :docker_hub, account: @account)\n\n    stubbed_digest_auth = stub_dockerhub_auth_for_digest(container.image)\n    stubbed_digest = stub_dockerhub_get_digest(container.image, container.tag, digest_data)\n\n    post :deploy_containers, params: params, format: :json\n\n    assert_requested stubbed_digest\n    assert_requested stubbed_digest_auth\n    assert_requested stubbed_dockerhub_login\n    assert_requested stubbed_deploy_containers_request\n    assert_requested stubbed_containers_request\n    assert_requested stubbed_namespace_request\n    assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }\n    assert { UffizziCore::ActivityItem::Docker.count == 2 }\n  end\n\n  test '#deploy_containers create a new activity item creation without credential' do\n    UffizziCore::ControllerService.expects(:namespace_exists?).returns(true)\n    webhooks_data = json_fixture('files/dockerhub/webhooks/push/event_data.json')\n    deployment_containers_data = json_fixture('files/controller/deployment_containers.json')\n    deployment_data = json_fixture('files/controller/deployments.json')\n\n    namespace, name = webhooks_data[:repository][:repo_name].split('/')\n\n    repo = create(:repo,\n                  :docker_hub,\n                  project: @project,\n                  namespace: namespace,\n                  name: name)\n\n    container = create(\n      :container,\n      :continuously_deploy_enabled,\n      :with_public_port,\n      :with_named_volume,\n      deployment: @deployment,\n      repo: repo,\n      image: webhooks_data[:repository][:repo_name],\n      tag: webhooks_data[:push_data][:tag],\n      controller_name: deployment_containers_data.first[:spec][:containers].first[:controllerName],\n    )\n\n    create(:activity_item,\n           :docker,\n           :with_deployed_event,\n           namespace: repo.namespace,\n           name: repo.name,\n           tag: container.tag,\n           container: container,\n           deployment: @deployment)\n\n    stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)\n    stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)\n    stubbed_deploy_containers_request = stub_deploy_containers_request(@deployment)\n\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    post :deploy_containers, params: params, format: :json\n\n    assert { UffizziCore::ActivityItem.last.digest.nil? }\n    assert_requested stubbed_deploy_containers_request\n    assert_requested stubbed_containers_request\n    assert_requested stubbed_namespace_request\n    assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }\n    assert { UffizziCore::ActivityItem::Docker.count == 2 }\n  end\n\n  test '#deploy_containers create a new activity items' do\n    metadata = {\n      'labels' => {\n        'github' => {\n          'repository' => 'feature/#24_my_awesome_feature',\n          'event' => {\n            'number' => '24',\n          },\n        },\n      },\n    }\n    @deployment.update!(metadata: metadata)\n\n    UffizziCore::ControllerService.expects(:namespace_exists?).at_least(1).returns(true)\n\n    digest_data = json_fixture('files/dockerhub/digest.json')\n    deployment_containers_data = json_fixture('files/controller/deployment_containers.json')\n    deployment_data = json_fixture('files/controller/deployments.json')\n\n    stubbed_namespace_request = stub_controller_get_namespace_request(@deployment, deployment_data)\n    stubbed_containers_request = stub_controller_containers_request(@deployment, deployment_containers_data)\n    stubbed_dockerhub_login = stub_dockerhub_login\n\n    compose_file_name = 'test-compose-full.yml'\n    file_content = File.read(\"test/fixtures/files/#{compose_file_name}\")\n    encoded_content = Base64.encode64(file_content)\n    compose_file = create(:compose_file, content: encoded_content, repository_id: nil, branch: nil, project: @project)\n    @deployment.update!(compose_file: compose_file)\n\n    controller_name = deployment_containers_data.first[:spec][:containers].first[:controllerName]\n    app_name = 'app'\n    app_namespace = 'uffizzicloud'\n    nginx_name = 'nginx'\n    nginx_namespace = 'library'\n\n    host_volume_file_attrs_app_dir = {\n      type: 'host',\n      source: './some_app_dir',\n      target: '/var/app/some_dir',\n      read_only: false,\n    }\n\n    host_volume_file_attrs_app_dir2 = {\n      type: 'host',\n      source: './',\n      target: '/var/entire_app',\n      read_only: false,\n    }\n\n    host_volume_file_attrs_app_file = {\n      type: 'host',\n      source: './files/some_app_file',\n      target: '/var/app/some_app_files',\n      read_only: false,\n    }\n\n    container_app_attrs = {\n      controller_name: controller_name,\n      service_name: app_name,\n      image: \"#{app_namespace}/#{app_name}\",\n      tag: 'latest',\n      full_image_name: \"#{app_namespace}/#{app_name}:latest\",\n\n      volumes: [\n        host_volume_file_attrs_app_dir,\n        host_volume_file_attrs_app_file,\n        host_volume_file_attrs_app_dir2,\n        {\n          type: 'named',\n          source: 'app_share',\n          target: '/some_app_share',\n          read_only: true,\n        },\n        {\n          type: 'anonymous',\n          source: '/some_anonymous_dir',\n          target: nil,\n          read_only: false,\n        },\n      ],\n    }\n\n    container_nginx_attrs = {\n      controller_name: controller_name,\n      service_name: nginx_name,\n      image: \"#{nginx_namespace}/#{nginx_name}\",\n      tag: '1.32',\n      full_image_name: \"#{nginx_namespace}/#{nginx_name}:1.32\",\n      public: true,\n      port: 80,\n      target_port: 80,\n    }\n\n    host_volume_file_app_dir_params = {\n      path: host_volume_file_attrs_app_dir[:source],\n      source: \"#{compose_file_name}/#{host_volume_file_attrs_app_dir[:source]}\",\n      payload: 'some_app_dir_data',\n      is_file: false,\n      project: @project,\n      compose_file: compose_file,\n    }\n\n    host_volume_file_app_dir2_params = {\n      path: host_volume_file_attrs_app_dir2[:source],\n      source: \"#{compose_file_name}/#{host_volume_file_attrs_app_dir2[:source]}\",\n      payload: 'some_app_dir_data',\n      is_file: false,\n      project: @project,\n      compose_file: compose_file,\n    }\n\n    host_volume_file_app_file_params = {\n      path: host_volume_file_attrs_app_file[:source],\n      source: \"#{compose_file_name}/#{host_volume_file_attrs_app_file[:source]}\",\n      payload: 'some_app_file_data',\n      is_file: true,\n      project: @project,\n      compose_file: compose_file,\n    }\n\n    docker_hub_credential = create(:credential, :docker_hub, account: @account)\n    app_repo = create(:repo, :docker_hub, project: @project, namespace: app_namespace, name: app_name)\n    nginx_repo = create(:repo, :docker_hub, project: @project, namespace: nginx_namespace, name: nginx_name)\n    host_volume_file_app_dir = create(:host_volume_file, **host_volume_file_app_dir_params)\n    host_volume_file_app_dir2 = create(:host_volume_file, **host_volume_file_app_dir2_params)\n    host_volume_file_app_file = create(:host_volume_file, **host_volume_file_app_file_params)\n    app_container = create(:container, :continuously_deploy_enabled,\n                           **{ deployment: @deployment, repo: app_repo }.merge(container_app_attrs))\n    nginx_container = create(:container, :with_public_port, :continuously_deploy_enabled,\n                             **{ deployment: @deployment, repo: nginx_repo }.merge(container_nginx_attrs))\n\n    container_host_volume_files_app_dir = create(:container_host_volume_file, container: app_container,\n                                                                              host_volume_file: host_volume_file_app_dir,\n                                                                              source_path: host_volume_file_attrs_app_dir[:source])\n    container_host_volume_files_app_dir2 = create(:container_host_volume_file, container: app_container,\n                                                                               host_volume_file: host_volume_file_app_dir2,\n                                                                               source_path: host_volume_file_attrs_app_dir2[:source])\n    container_host_volume_files_app_file = create(:container_host_volume_file, container: app_container,\n                                                                               host_volume_file: host_volume_file_app_file,\n                                                                               source_path: host_volume_file_attrs_app_file[:source])\n\n    app_config_file = create(:config_file, :compose_file_source, project: @project, payload: 'data', filename: 'config_file.conf')\n    app_container_config_file = create(:container_config_file, container: app_container, config_file: app_config_file)\n\n    stubbed_digest_auth_app = stub_dockerhub_auth_for_digest(app_container.image)\n    stubbed_digest_auth_nginx = stub_dockerhub_auth_for_digest(nginx_container.image)\n    stubbed_digest_app = stub_dockerhub_get_digest(app_container.image, app_container.tag, digest_data)\n    stubbed_digest_nginx = stub_dockerhub_get_digest(nginx_container.image, nginx_container.tag, digest_data)\n\n    expected_default_container_params = {\n      secret_variables: [],\n      memory_limit: Settings.compose.default_memory,\n      memory_request: Settings.compose.default_memory,\n      entrypoint: nil,\n      command: nil,\n      port: nil,\n      target_port: nil,\n      public: false,\n      receive_incoming_requests: false,\n      healthcheck: {},\n      volumes: nil,\n      container_config_files: [],\n      additional_subdomains: [],\n      container_host_volume_files: [],\n    }\n\n    expected_app_container_attrs = {\n      id: app_container.id,\n      kind: app_container.kind,\n      variables: [\n        {\n          name: 'UFFIZZI_URL',\n          value: \"https://#{@deployment.preview_url}\",\n        },\n        {\n          name: 'UFFIZZI_DOMAIN',\n          value: @deployment.preview_url,\n        },\n        {\n          name: 'UFFIZZI_PREDICTABLE_URL',\n          value: '',\n        },\n      ],\n      container_host_volume_files: [\n        {\n          host_volume_file_id: host_volume_file_app_dir.id,\n          source_path: container_host_volume_files_app_dir.source_path,\n        },\n        {\n          host_volume_file_id: host_volume_file_app_dir2.id,\n          source_path: container_host_volume_files_app_dir2.source_path,\n        },\n        {\n          host_volume_file_id: host_volume_file_app_file.id,\n          source_path: container_host_volume_files_app_file.source_path,\n        },\n      ],\n      container_config_files: [\n        {\n          mount_path: app_container_config_file.mount_path,\n          config_file: {\n            id: app_config_file.id,\n            filename: app_config_file.filename,\n            kind: app_config_file.kind,\n            payload: app_config_file.payload,\n          },\n        },\n      ],\n    }\n\n    expected_nginx_container_attrs = {\n      id: nginx_container.id,\n      kind: nginx_container.kind,\n      variables: [\n        {\n          name: 'UFFIZZI_URL',\n          value: \"https://#{@deployment.preview_url}\",\n        },\n        {\n          name: 'UFFIZZI_DOMAIN',\n          value: @deployment.preview_url,\n        },\n        {\n          name: 'PORT',\n          value: '80',\n        },\n        {\n          name: 'UFFIZZI_PREDICTABLE_URL',\n          value: '',\n        },\n      ],\n    }\n\n    expected_app_container = expected_default_container_params\n      .merge(expected_app_container_attrs)\n      .merge(container_app_attrs.except(:image, :tag))\n\n    expected_nginx_container = expected_default_container_params\n      .merge(expected_nginx_container_attrs)\n      .merge(container_nginx_attrs.except(:image, :tag))\n\n    expected_request_container_to_controller = {\n      containers: [\n        expected_app_container,\n        expected_nginx_container,\n      ],\n      credentials: [{ id: docker_hub_credential.id }, { id: @credential.id }],\n      deployment_url: @deployment.preview_url,\n      compose_file: { source_kind: 'local' },\n      host_volume_files: [\n        {\n          id: host_volume_file_app_dir.id,\n          source: host_volume_file_app_dir_params[:source],\n          path: host_volume_file_app_dir_params[:path],\n          payload: Base64.encode64(host_volume_file_app_dir_params[:payload]),\n          is_file: host_volume_file_app_dir_params[:is_file],\n        },\n        {\n          id: host_volume_file_app_dir2.id,\n          source: host_volume_file_app_dir2_params[:source],\n          path: host_volume_file_app_dir2_params[:path],\n          payload: Base64.encode64(host_volume_file_app_dir2_params[:payload]),\n          is_file: host_volume_file_app_dir2_params[:is_file],\n        },\n        {\n          id: host_volume_file_app_file.id,\n          source: host_volume_file_app_file_params[:source],\n          path: host_volume_file_app_file_params[:path],\n          payload: Base64.encode64(host_volume_file_app_file_params[:payload]),\n          is_file: host_volume_file_app_file_params[:is_file],\n        },\n      ],\n    }\n\n    expected_request_config_file_to_controller = {\n      config_file: {\n        id: app_config_file.id,\n        filename: app_config_file.filename,\n        kind: app_config_file.kind,\n        payload: app_config_file.payload,\n      },\n    }\n\n    stub_apply_config_file_request_with_expected(@deployment, app_config_file, expected_request_config_file_to_controller)\n    stubbed_deploy_containers_request = stub_deploy_containers_request_with_expected(@deployment, expected_request_container_to_controller)\n\n    params = { project_slug: @project.slug, id: @deployment.id }\n\n    post :deploy_containers, params: params, format: :json\n\n    assert_requested stubbed_deploy_containers_request\n    assert_requested stubbed_digest_app\n    assert_requested stubbed_digest_nginx\n    assert_requested stubbed_digest_auth_app\n    assert_requested stubbed_digest_auth_nginx\n    assert_requested stubbed_dockerhub_login, times: 2\n    assert_requested stubbed_containers_request, times: 2\n    assert_requested stubbed_namespace_request, times: 2\n    assert { UffizziCore::Deployment::DeployContainersJob.jobs.empty? }\n    assert { UffizziCore::ActivityItem::Docker.count == 2 }\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/destroy_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_personal_account)\n    @account = @admin.personal_account\n    @project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])\n    @deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)\n    @metadata = {\n      'labels' => {\n        'github' => {\n          'repository' => 'feature/#24_my_awesome_feature',\n          'pull_request' => {\n            'number' => '24',\n          },\n        },\n      },\n    }\n\n    @deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))\n    @credential = create(:credential, :github_container_registry, account: @account)\n\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    @template = create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin,\n                                                        payload: template_payload)\n\n    sign_in @admin\n  end\n\n  test '#destroy' do\n    Sidekiq::Worker.clear_all\n    Sidekiq::Testing.inline!\n    stubbed_request = stub_delete_namespace_request(@deployment)\n    container = create(:container, :with_public_port, deployment: @deployment)\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => -1,\n    }\n\n    params = {\n      project_slug: @project.slug,\n      id: @deployment.id,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_requested(stubbed_request)\n    assert { container.reload.disabled? }\n\n    assert_response :success\n    Sidekiq::Worker.clear_all\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/deployments_controller/update_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::DeploymentsControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_personal_account)\n    @account = @admin.personal_account\n    @project = create(:project, :with_members, account: @admin.personal_account, members: [@admin])\n    @deployment = create(:deployment, project: @project, state: UffizziCore::Deployment::STATE_ACTIVE)\n    @metadata = {\n      'labels' => {\n        'github' => {\n          'repository' => 'feature/#24_my_awesome_feature',\n          'pull_request' => {\n            'number' => '24',\n          },\n        },\n      },\n    }\n\n    @deployment.update!(subdomain: UffizziCore::Deployment::DomainService.build_subdomain(@deployment))\n    @credential = create(:credential, :github_container_registry, account: @account)\n\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    @template = create(:template, :compose_file_source, compose_file: @compose_file, project: @project, added_by: @admin,\n                                                        payload: template_payload)\n\n    sign_in @admin\n  end\n\n  test '#update - update deployment created from main compose file' do\n    file_content = File.read('test/fixtures/files/test-compose-success-without-dependencies.yml')\n    compose_file = create(:compose_file, project: @project, added_by: @admin)\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: @template.payload)\n    @deployment.update!(compose_file: compose_file)\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = attributes_for(:compose_file, :temporary, project: @project, added_by: @admin, content: encoded_content)\n    stub_dockerhub_repository_any\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      id: @deployment[:id],\n      metadata: {},\n    }\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 1,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 1,\n      -> { @deployment.containers.count } => 3,\n    }\n\n    assert_difference differences do\n      put :update, params: params, format: :json\n    end\n\n    assert_response :success\n\n    container_keys = [:image, :tag, :service_name, :port, :public]\n    actual_containers_attributes = UffizziCore::Container.all.map { |c| c.attributes.deep_symbolize_keys.slice(*container_keys) }\n    actual_template_containers_attributes = UffizziCore::Template.last\n      .payload\n      .deep_symbolize_keys[:containers_attributes]\n      .map { |c| c.slice(*container_keys) }\n\n    actual_app_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'app' }\n    actual_db_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'db' }\n    actual_nginx_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'nginx' }\n\n    actual_template_app_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'app' }\n    actual_template_db_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'db' }\n    actual_template_nginx_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'nginx' }\n\n    expected_app_container_attributes = {\n      image: 'uffizzicloud/app',\n      tag: 'latest',\n      service_name: 'app',\n      port: nil,\n      public: false,\n    }\n    expected_db_container_attributes = {\n      image: 'library/postgres',\n      tag: 'latest',\n      service_name: 'db',\n      port: nil,\n      public: false,\n    }\n    expected_nginx_container_attributes = {\n      image: 'library/nginx',\n      tag: '1.32',\n      service_name: 'nginx',\n      port: 80,\n      public: true,\n    }\n\n    assert_equal expected_app_container_attributes, actual_app_container_attributes\n    assert_equal expected_app_container_attributes, actual_template_app_container_attributes\n\n    assert_equal expected_db_container_attributes, actual_db_container_attributes\n    assert_equal expected_db_container_attributes, actual_template_db_container_attributes\n\n    assert_equal expected_nginx_container_attributes, actual_nginx_container_attributes\n    assert_equal expected_nginx_container_attributes, actual_template_nginx_container_attributes\n  end\n\n  test '#update - update deployment with metadata' do\n    file_content = File.read('test/fixtures/files/test-compose-success-without-dependencies.yml')\n    compose_file = create(:compose_file, project: @project, added_by: @admin)\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: @template.payload)\n    @deployment.update!(compose_file: compose_file)\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = attributes_for(:compose_file, :temporary, project: @project, added_by: @admin, content: encoded_content)\n    2.times { stub_dockerhub_repository_any }\n\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [],\n      id: @deployment[:id],\n      metadata: @metadata,\n    }\n\n    put :update, params: params, format: :json\n\n    assert_response :success\n    assert_equal(@metadata, @deployment.reload.metadata)\n  end\n\n  test '#update - update deployment created from temporary compose file' do\n    file_content = File.read('test/fixtures/files/test-compose-full.yml')\n    compose_file = create(:compose_file, :temporary, project: @project, added_by: @admin)\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: @template.payload)\n    @deployment.update!(compose_file: compose_file)\n    encoded_content = Base64.encode64(file_content)\n    compose_file_attributes = attributes_for(:compose_file, :temporary, project: @project, added_by: @admin, content: encoded_content)\n    stub_dockerhub_repository_any\n\n    # rubocop:disable Layout/LineLength\n    params = {\n      project_slug: @project.slug,\n      compose_file: compose_file_attributes,\n      dependencies: [\n        {\n          content: \"ZGF0YQ1==\\n\",\n          is_file: false,\n          path: '/gem/tmp/some_app_dir',\n          source: './some_app_dir',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ2==\\n\",\n          is_file: true,\n          path: '/gem/tmp/files/some_app_file',\n          source: './files/some_app_file',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ3==\\n\",\n          is_file: false,\n          path: '/gem/tmp/some_db_dir',\n          source: './some_db_dir',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ4==\\n\",\n          is_file: true,\n          path: '/gem/tmp/some_db_file',\n          source: './some_db_file',\n          use_kind: 'volume',\n        },\n        {\n          content: \"ZGF0YQ==\\n\",\n          is_file: false,\n          path: '/gem/tmp',\n          source: './',\n          use_kind: 'volume',\n        },\n        {\n          content: \"S0VZPXZhbHVl\\n\",\n          path: 'env_files/env_file.env',\n          source: 'env_files/env_file.env',\n          use_kind: 'config_map',\n        },\n        {\n          content: \"UE9TVEdSRVNfVVNFUj1wb3N0Z3JlcyBQT1NUR1JFU19QQVNTV09SRD1wb3N0\\nZ3Jlcw==\\n\",\n          path: 'local.env',\n          source: 'local.env',\n          use_kind: 'config_map',\n        },\n        {\n          content: \"c2VydmVyIHsgbGlzdGVuICAgICAgIDg4ODg7IHNlcnZlcl9uYW1lICBsb2Nh\\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\\n\",\n          path: 'config_files/config_file.conf',\n          source: 'config_files/config_file.conf',\n          use_kind: 'config_map',\n        },\n        {\n          content: \"c2VydmVyIHsgbGlzdGVuICAgICAgIDgwODA7IHNlcnZlcl9uYW1lICBsb2Nh\\nbGhvc3Q7IGxvY2F0aW9uIC8geyBwcm94eV9wYXNzICAgICAgaHR0cDovLzEy\\nNy4wLjAuMTo4MDg4LzsgfSBsb2NhdGlvbiAvdm90ZS8geyBwcm94eV9wYXNz\\nICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4LzsgfSB9\\n\",\n          path: 'app.conf',\n          source: 'app.conf',\n          use_kind: 'config_map',\n        },\n      ],\n      id: @deployment[:id],\n      metadata: {},\n    }\n    # rubocop:enable Layout/LineLength\n\n    differences = {\n      -> { UffizziCore::ComposeFile.temporary.count } => 0,\n      -> { UffizziCore::Template.with_creation_source(UffizziCore::Template.creation_source.compose_file).count } => 0,\n      -> { @deployment.containers.count } => 3,\n      -> { UffizziCore::HostVolumeFile.count } => 5,\n      -> { UffizziCore::ConfigFile.count } => 1,\n    }\n\n    assert_difference differences do\n      put :update, params: params, format: :json\n    end\n\n    assert_response :success\n\n    default_container_attributes = {\n      image: nil,\n      tag: nil,\n      service_name: nil,\n      variables: [],\n      public: false,\n      port: nil,\n      state: 'active',\n      continuously_deploy: 'enabled',\n      kind: 'user',\n      target_port: nil,\n      controller_name: nil,\n      receive_incoming_requests: false,\n      memory_request: 125 / UffizziCore::Container::REQUEST_MEMORY_RATIO,\n      memory_limit: 125,\n      secret_variables: [],\n      entrypoint: nil,\n      command: nil,\n      healthcheck: {},\n      volumes: [],\n      additional_subdomains: [],\n      source: nil,\n    }\n\n    app_container_attributes = {\n      image: 'uffizzicloud/app',\n      tag: 'latest',\n      service_name: 'app',\n      volumes: [\n        {\n          type: 'host',\n          source: './some_app_dir',\n          target: '/var/app/some_dir',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './files/some_app_file',\n          target: '/var/app/some_app_files',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './',\n          target: '/var/entire_app',\n          read_only: false,\n        },\n        {\n          type: 'named',\n          source: 'app_share',\n          target: '/some_app_share',\n          read_only: true,\n        },\n        {\n          type: 'anonymous',\n          source: '/some_anonymous_dir',\n          target: nil,\n          read_only: false,\n        },\n      ],\n      variables: [\n        {\n          name: 'POSTGRES_USER',\n          value: 'postgres POSTGRES_PASSWORD=postgres',\n        },\n        {\n          name: 'KEY',\n          value: 'value',\n        },\n      ],\n    }\n\n    db_container_attributes = {\n      image: 'library/postgres',\n      tag: 'latest',\n      service_name: 'db',\n      volumes: [\n        {\n          type: 'host',\n          source: './some_app_dir',\n          target: '/var/db/some_dir_2',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './some_db_dir',\n          target: '/var/db/some_dir_3',\n          read_only: false,\n        },\n        {\n          type: 'host',\n          source: './some_db_file',\n          target: '/var/db/some_db_files',\n          read_only: false,\n        },\n        {\n          type: 'named',\n          source: 'db_share',\n          target: '/some_db_share',\n          read_only: true,\n        },\n      ],\n    }\n\n    nginx_container_attributes = {\n      image: 'library/nginx',\n      tag: '1.32',\n      service_name: 'nginx',\n      port: 80,\n      target_port: 80,\n      public: true,\n      receive_incoming_requests: true,\n    }\n\n    expected_app_container_attributes = default_container_attributes.merge(app_container_attributes)\n    expected_db_container_attributes = default_container_attributes.merge(db_container_attributes)\n    expected_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes)\n\n    exclude_params = [:state, :source, :kind, :target_port, :controller_name]\n    expected_template_app_container_attributes = default_container_attributes.merge(app_container_attributes).without(exclude_params)\n    expected_template_db_container_attributes = default_container_attributes.merge(db_container_attributes).without(exclude_params)\n    expected_template_nginx_container_attributes = default_container_attributes.merge(nginx_container_attributes).without(exclude_params)\n\n    container_keys = default_container_attributes.keys\n    deployment = UffizziCore::Deployment.last\n    actual_containers_attributes = deployment.containers.map { |c| c.attributes.deep_symbolize_keys.slice(*container_keys) }\n    actual_template_containers_attributes = deployment.compose_file.template.payload\n      .deep_symbolize_keys[:containers_attributes]\n      .map { |c| c.slice(*container_keys) }\n\n    actual_app_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'app' }\n    actual_db_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'db' }\n    actual_nginx_container_attributes = actual_containers_attributes.detect { |c| c[:service_name] == 'nginx' }\n\n    actual_template_app_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'app' }\n    actual_template_db_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'db' }\n    actual_template_nginx_container_attributes = actual_template_containers_attributes.detect { |c| c[:service_name] == 'nginx' }\n\n    assert_equal expected_app_container_attributes, actual_app_container_attributes\n    assert_equal expected_template_app_container_attributes, actual_template_app_container_attributes\n\n    assert_equal expected_db_container_attributes, actual_db_container_attributes\n    assert_equal expected_template_db_container_attributes, actual_template_db_container_attributes\n\n    assert_equal expected_nginx_container_attributes, actual_nginx_container_attributes\n    assert_equal expected_template_nginx_container_attributes, actual_template_nginx_container_attributes\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects/secrets_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::Projects::SecretsControllerTest < ActionController::TestCase\n  setup do\n    @admin = create(:user, :with_personal_account)\n    @account = @admin.personal_account\n    @developer = create(:user, :developer_in_organization, organization: @account)\n    @viewer = create(:user, :viewer_in_organization, organization: @account)\n    secrets = [build(:secret, name: generate(:string), value: generate(:string))]\n    @project = create(:project, :with_members, account: @account, members: [@admin, @developer, @viewer], secrets: secrets)\n\n    sign_in @admin\n  end\n\n  test '#index admin gets list of secrets' do\n    params = {\n      project_slug: @project.slug,\n    }\n\n    get :index, params: params, format: :json\n\n    assert_response :success\n  end\n\n  test '#bulk_create admin creates secrets' do\n    new_secrets = [\n      { name: generate(:string), value: generate(:string) },\n      { name: generate(:string), value: generate(:string) },\n    ]\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    differences = {\n      -> { UffizziCore::Project.find(@project.id).secrets.count } => new_secrets.count,\n    }\n\n    assert_difference differences do\n      post :bulk_create, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#bulk_create check if a secret has already been added' do\n    new_secrets = [\n      { name: @project.secrets.first['name'], value: generate(:string) },\n    ]\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    post :bulk_create, params: params, format: :json\n    assert_response :unprocessable_entity\n\n    response_body = JSON.parse(response.body)\n    assert { response_body['errors']['secrets'].present? }\n  end\n\n  test '#bulk_create check if a secret name too long' do\n    length = UffizziCore::Api::Cli::V1::Secret::BulkAssignForm::MAX_SECRET_KEY_LENGTH + 1\n    long_name = SecureRandom.alphanumeric(length)\n    new_secrets = [\n      { name: long_name, value: generate(:string) },\n    ]\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    post :bulk_create, params: params, format: :json\n    assert_response :unprocessable_entity\n\n    response_body = JSON.parse(response.body)\n    assert { response_body['errors']['secrets'].present? }\n  end\n\n  test '#bulk_create check update secret in a compose' do\n    new_secrets = [\n      { name: generate(:string), value: generate(:string) },\n    ]\n\n    compose_file = create(:compose_file, project: @project, added_by: @admin)\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      receive_incoming_requests: true,\n      secret_variables: [{ name: new_secrets.first[:name], value: '' }],\n    )\n    template_payload = { containers_attributes: [container_attributes] }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    post :bulk_create, params: params, format: :json\n    assert_response :success\n\n    compose_file.reload\n    @project.reload\n\n    new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }\n    container = compose_file.template.payload['containers_attributes'].first\n    compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }\n\n    assert_equal(new_project_secret['value'], compose_secret['value'])\n  end\n\n  test '#bulk_create check update secret in a compose if it has invalid secrets' do\n    new_secrets = [\n      { name: generate(:string), value: generate(:string) },\n    ]\n\n    compose_additional_secrets = [\n      { name: generate(:string), value: generate(:string) },\n    ]\n\n    compose_payload = { errors: { secret_variables: [generate(:string)] } }\n    compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin, payload: compose_payload)\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      receive_incoming_requests: true,\n      secret_variables: [{ name: new_secrets.first[:name], value: '' }] + compose_additional_secrets,\n    )\n    template_payload = { containers_attributes: [container_attributes] }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    post :bulk_create, params: params, format: :json\n    assert_response :success\n\n    compose_file.reload\n    @project.reload\n\n    new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }\n    container = compose_file.template.payload['containers_attributes'].first\n    compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }\n\n    assert_equal(new_project_secret['value'], compose_secret['value'])\n    assert(compose_file.invalid_file?)\n    refute_empty(compose_file.payload['errors'])\n  end\n\n  test '#bulk_create check update secret in a compose if it has errors with secrets' do\n    @project.secrets.destroy_all\n\n    new_secrets = [\n      { name: generate(:string), value: generate(:string) },\n    ]\n\n    compose_payload = { errors: { secret_variables: [generate(:string)] } }\n    compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin, payload: compose_payload)\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      receive_incoming_requests: true,\n      secret_variables: [{ name: new_secrets.first[:name], value: '' }],\n    )\n    template_payload = { containers_attributes: [container_attributes] }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    post :bulk_create, params: params, format: :json\n    assert_response :success\n\n    compose_file.reload\n    @project.reload\n\n    new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }\n    container = compose_file.template.payload['containers_attributes'].first\n    compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }\n\n    assert { compose_file.valid_file? }\n    assert_equal(new_project_secret['value'], compose_secret['value'])\n    assert_empty(compose_file.payload['errors'])\n  end\n\n  test '#bulk_create check update secret in a compose if it has errors not related to secrets' do\n    @project.secrets.destroy_all\n\n    new_secrets = [\n      { name: generate(:string), value: generate(:string) },\n    ]\n\n    compose_payload = { errors: { secret_variables: [generate(:string)], error_key: [generate(:string)] } }\n    compose_file = create(:compose_file, :invalid_file, project: @project, added_by: @admin, payload: compose_payload)\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      receive_incoming_requests: true,\n      secret_variables: [{ name: new_secrets.first[:name], value: '' }],\n    )\n    template_payload = { containers_attributes: [container_attributes] }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n\n    params = {\n      project_slug: @project.slug,\n      secrets: new_secrets,\n    }\n\n    post :bulk_create, params: params, format: :json\n    assert_response :success\n\n    compose_file.reload\n    @project.reload\n\n    new_project_secret = @project.secrets.detect { |project_secret| project_secret['name'] == new_secrets.first[:name] }\n    container = compose_file.template.payload['containers_attributes'].first\n    compose_secret = container['secret_variables'].detect { |container_secret| container_secret['name'] == new_secrets.first[:name] }\n\n    assert { compose_file.invalid_file? }\n    assert_equal(new_project_secret['value'], compose_secret['value'])\n    refute_empty(compose_file.payload['errors'])\n  end\n\n  test '#destroy admin deletes a secret' do\n    deletable_secret = UffizziCore::Project.find(@project.id).secrets.last\n\n    params = {\n      project_slug: @project.slug,\n      id: deletable_secret['name'],\n    }\n\n    differences = {\n      -> { UffizziCore::Project.last.secrets.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n  end\n\n  test '#destroy if a project has a compose with deleted secret' do\n    deletable_secret = UffizziCore::Project.find(@project.id).secrets.last\n    compose_file = create(:compose_file, project: @project, added_by: @admin)\n\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      receive_incoming_requests: true,\n      secret_variables: [deletable_secret],\n    )\n    template_payload = { containers_attributes: [container_attributes] }\n    create(:template, :compose_file_source, compose_file: compose_file, project: @project, added_by: @admin, payload: template_payload)\n\n    params = {\n      project_slug: @project.slug,\n      id: deletable_secret['name'],\n    }\n\n    differences = {\n      -> { UffizziCore::Project.last.secrets.count } => -1,\n      -> { UffizziCore::ComposeFile.where(state: UffizziCore::ComposeFile::STATE_INVALID_FILE).count } => 1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :success\n\n    compose_file.reload\n\n    assert { compose_file.invalid_file? }\n    refute_empty(compose_file.payload['errors'])\n  end\n\n  test '#destroy if a secret doesn\\'t exist' do\n    params = {\n      project_slug: @project.slug,\n      id: generate(:string),\n    }\n\n    differences = {\n      -> { UffizziCore::Project.last.secrets.count } => 0,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: params, format: :json\n    end\n\n    assert_response :not_found\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/projects_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::ProjectsControllerTest < ActionController::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    sign_in @user\n\n    @project = create(:project, :with_members, account: @user.personal_account, members: [@user])\n  end\n\n  test '#index' do\n    get :index, format: :json\n\n    assert_response :success\n  end\n\n  test '#show' do\n    create(:compose_file, project: @project, added_by: @user)\n    create(:secret, resource: @project)\n    create(:deployment, project: @project)\n\n    get :show, params: { slug: @project.slug }, format: :json\n\n    assert_response :success\n\n    assert_equal(@project.name, JSON.parse(response.body)['project']['name'])\n  end\n\n  test '#destroy' do\n    differences = {\n      -> { UffizziCore::Project.active.count } => -1,\n    }\n\n    assert_difference differences do\n      delete :destroy, params: { slug: @project.slug }, format: :json\n    end\n\n    assert_response :success\n  end\nend\n"
  },
  {
    "path": "core/test/controllers/uffizzi_core/api/cli/v1/sessions_controller_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::Api::Cli::V1::SessionsControllerTest < ActionController::TestCase\n  test '#create - successful' do\n    password = generate(:password)\n    user = create(:user, :with_personal_account, :active, password: password)\n    params = { email: user.email, password: password }\n\n    post :create, params: { user: params }, format: :json\n\n    assert_response :success\n    assert { signed_in? }\n  end\n\n  test '#create - failed with wrong password' do\n    password = generate(:password)\n    user = create(:user, :active, password: password)\n    wrong_password = generate(:password)\n    params = { email: user.email, password: wrong_password }\n\n    post :create, params: { user: params }, format: :json\n\n    assert_response :unprocessable_entity\n    assert_not_empty JSON.parse(response.body)['errors']\n    assert { !signed_in? }\n  end\nend\n"
  },
  {
    "path": "core/test/dummy/Rakefile",
    "content": "# frozen_string_literal: true\n\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative 'config/application'\n\nRails.application.load_tasks\n"
  },
  {
    "path": "core/test/dummy/app/assets/config/manifest.js",
    "content": "//= link_tree ../images\n//= link_directory ../stylesheets .css\n//= link uffizzi_core_manifest.js\n"
  },
  {
    "path": "core/test/dummy/app/assets/images/.keep",
    "content": ""
  },
  {
    "path": "core/test/dummy/app/assets/stylesheets/application.css",
    "content": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,\n * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the bottom of the\n * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS\n * files in this directory. Styles in this file should be added after the last require_* statement.\n * It is generally better to create a new file per style scope.\n *\n *= require_tree .\n *= require_self\n */\n"
  },
  {
    "path": "core/test/dummy/app/channels/application_cable/channel.rb",
    "content": "# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Channel < ActionCable::Channel::Base\n  end\nend\n"
  },
  {
    "path": "core/test/dummy/app/channels/application_cable/connection.rb",
    "content": "# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Connection < ActionCable::Connection::Base\n  end\nend\n"
  },
  {
    "path": "core/test/dummy/app/controllers/application_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationController < ActionController::Base\nend\n"
  },
  {
    "path": "core/test/dummy/app/controllers/concerns/.keep",
    "content": ""
  },
  {
    "path": "core/test/dummy/app/helpers/application_helper.rb",
    "content": "# frozen_string_literal: true\n\nmodule ApplicationHelper\nend\n"
  },
  {
    "path": "core/test/dummy/app/javascript/packs/application.js",
    "content": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//\n// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,\n// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.\n//\n// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the\n// compiled file. JavaScript code in this file should be added after the last require_* statement.\n//\n// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details\n// about supported directives.\n//\n//= require rails-ujs\n//= require activestorage\n//= require_tree .\n"
  },
  {
    "path": "core/test/dummy/app/jobs/application_job.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecord::Deadlocked\n\n  # Most jobs are safe to ignore if the underlying records are no longer available\n  # discard_on ActiveJob::DeserializationError\nend\n"
  },
  {
    "path": "core/test/dummy/app/mailers/application_mailer.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationMailer < ActionMailer::Base\n  default from: 'from@example.com'\n  layout 'mailer'\nend\n"
  },
  {
    "path": "core/test/dummy/app/models/application_record.rb",
    "content": "# frozen_string_literal: true\n\nclass ApplicationRecord < ActiveRecord::Base\n  self.abstract_class = true\nend\n"
  },
  {
    "path": "core/test/dummy/app/models/concerns/.keep",
    "content": ""
  },
  {
    "path": "core/test/dummy/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Dummy</title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <%= csrf_meta_tags %>\n    <%= csp_meta_tag %>\n\n    <%= stylesheet_link_tag 'application', media: 'all' %>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "core/test/dummy/app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <style>\n      /* Email styles need to be inline */\n    </style>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "core/test/dummy/app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "core/test/dummy/bin/rails",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nload File.expand_path('spring', __dir__)\nAPP_PATH = File.expand_path('../config/application', __dir__)\nrequire_relative '../config/boot'\nrequire 'rails/commands'\n"
  },
  {
    "path": "core/test/dummy/bin/rake",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nload File.expand_path('spring', __dir__)\nrequire_relative '../config/boot'\nrequire 'rake'\nRake.application.run\n"
  },
  {
    "path": "core/test/dummy/bin/setup",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'fileutils'\n\n# path to your application root.\nAPP_ROOT = File.expand_path('..', __dir__)\n\ndef system!(*args)\n  system(*args) || abort(\"\\n== Command #{args} failed ==\")\nend\n\nFileUtils.chdir(APP_ROOT) do\n  # This script is a way to set up or update your development environment automatically.\n  # This script is idempotent, so that you can run it at any time and get an expectable outcome.\n  # Add necessary setup steps to this file.\n\n  puts '== Installing dependencies =='\n  system! 'gem install bundler --conservative'\n  system('bundle check') || system!('bundle install')\n\n  # puts \"\\n== Copying sample files ==\"\n  # unless File.exist?('config/database.yml')\n  #   FileUtils.cp 'config/database.yml.sample', 'config/database.yml'\n  # end\n\n  puts \"\\n== Preparing database ==\"\n  system! 'bin/rails db:prepare'\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! 'bin/rails log:clear tmp:clear'\n\n  puts \"\\n== Restarting application server ==\"\n  system! 'bin/rails restart'\nend\n"
  },
  {
    "path": "core/test/dummy/bin/spring",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nif !defined?(Spring) && [nil, 'development', 'test'].include?(ENV['RAILS_ENV'])\n  # Load Spring without loading other gems in the Gemfile, for speed.\n  require 'bundler'\n  Bundler.locked_gems.specs.detect { |spec| spec.name == 'spring' }&.tap do |spring|\n    Gem.use_paths(Gem.dir, Bundler.bundle_path.to_s, *Gem.path)\n    gem 'spring', spring.version\n    require 'spring/binstub'\n  end\nend\n"
  },
  {
    "path": "core/test/dummy/config/application.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'boot'\n\nrequire 'byebug'\nrequire 'rack/cors'\nrequire 'rails/all'\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\nrequire 'uffizzi_core'\n\nmodule Dummy\n  class Application < Rails::Application\n    config.load_defaults(Rails::VERSION::STRING.to_f)\n\n    config.hosts = Settings.allowed_hosts\n\n    config.middleware.insert_before(0, Rack::Cors) do\n      allow do\n        origins do |source|\n          uri = URI.parse(source)\n          Settings.allowed_hosts.any? { |host| uri.host == host || uri.host.ends_with?(host) }\n        end\n        resource '*', headers: :any, methods: [:get, :post, :options, :put, :patch, :delete], credentials: true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/dummy/config/boot.rb",
    "content": "# frozen_string_literal: true\n\n# Set up gems listed in the Gemfile.\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)\n\nrequire 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])\n$LOAD_PATH.unshift(File.expand_path('../../../lib', __dir__))\n"
  },
  {
    "path": "core/test/dummy/config/cable.yml",
    "content": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n  channel_prefix: dummy_production\n"
  },
  {
    "path": "core/test/dummy/config/database.yml",
    "content": "default: &default\n  adapter: postgresql\n  encoding: unicode\n  host: <%= ENV.fetch(\"DATABASE_HOST\") {\"127.0.0.1\"} %>\n  port: <%= ENV.fetch(\"DATABASE_PORT\") {5432} %>\n  pool: <%= ENV.fetch(\"DATABASE_POOL\") {5} %>\n  username: <%= ENV.fetch(\"DATABASE_USER\") {\"postgres\"} %>\n  password: <%= ENV.fetch(\"DATABASE_PASSWORD\") {\"\"} %>\n\ndevelopment:\n  <<: *default\n  database: uffizzi_core_development\n\ntest:\n  <<: *default\n  database: uffizzi_core_test\n"
  },
  {
    "path": "core/test/dummy/config/environment.rb",
    "content": "# frozen_string_literal: true\n\n# Load the Rails application.\nrequire_relative 'application'\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "core/test/dummy/config/environments/development.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'active_support/core_ext/integer/time'\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # In the development environment your application's code is reloaded any time\n  # it changes. This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.cache_classes = false\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable/disable caching. By default caching is disabled.\n  # Run rails dev:cache to toggle caching.\n  if Rails.root.join('tmp', 'caching-dev.txt').exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n\n    config.cache_store = :memory_store\n    config.public_file_server.headers = {\n      'Cache-Control' => \"public, max-age=#{2.days.to_i}\",\n    }\n  else\n    config.action_controller.perform_caching = false\n\n    config.cache_store = :null_store\n  end\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.raise_delivery_errors = false\n\n  config.action_mailer.perform_caching = false\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise exceptions for disallowed deprecations.\n  config.active_support.disallowed_deprecation = :raise\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Debug mode disables concatenation and preprocessing of assets.\n  # This option may cause significant delays in view rendering with a large\n  # number of complex assets.\n  config.assets.debug = true\n\n  # Suppress logger output for asset requests.\n  config.assets.quiet = true\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Use an evented file watcher to asynchronously detect changes in source code,\n  # routes, locales, etc. This feature depends on the listen gem.\n  # config.file_watcher = ActiveSupport::EventedFileUpdateChecker\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\nend\n"
  },
  {
    "path": "core/test/dummy/config/environments/production.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'active_support/core_ext/integer/time'\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.cache_classes = true\n\n  # Eager load code on boot. This eager loads most of Rails and\n  # your application in memory, allowing both threaded web servers\n  # and those relying on copy on write to perform better.\n  # Rake tasks automatically ignore this option for performance.\n  config.eager_load = true\n\n  # Full error reports are disabled and caching is turned on.\n  config.consider_all_requests_local       = false\n  config.action_controller.perform_caching = true\n\n  # Ensures that a master key has been made available in either ENV[\"RAILS_MASTER_KEY\"]\n  # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).\n  # config.require_master_key = true\n\n  # Disable serving static files from the `/public` folder by default since\n  # Apache or NGINX already handles this.\n  config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?\n\n  # Compress CSS using a preprocessor.\n  # config.assets.css_compressor = :sass\n\n  # Do not fallback to assets pipeline if a precompiled asset is missed.\n  config.assets.compile = false\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = 'http://assets.example.com'\n\n  # Specifies the header that your server uses for sending files.\n  # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache\n  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Mount Action Cable outside main process or domain.\n  # config.action_cable.mount_path = nil\n  # config.action_cable.url = 'wss://example.com/cable'\n  # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\\/\\/example.*/ ]\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  # config.force_ssl = true\n\n  # Include generic and useful information about system operation, but avoid logging too much\n  # information to avoid inadvertent exposure of personally identifiable information (PII).\n  config.log_level = :info\n\n  # Prepend all log lines with the following tags.\n  config.log_tags = [:request_id]\n\n  # Use a different cache store in production.\n  # config.cache_store = :mem_cache_store\n\n  # Use a real queuing backend for Active Job (and separate queues per environment).\n  # config.active_job.queue_adapter     = :resque\n  # config.active_job.queue_name_prefix = \"dummy_production\"\n\n  config.action_mailer.perform_caching = false\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Send deprecation notices to registered listeners.\n  config.active_support.deprecation = :notify\n\n  # Log disallowed deprecations.\n  config.active_support.disallowed_deprecation = :log\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Use default logging formatter so that PID and timestamp are not suppressed.\n  config.log_formatter = ::Logger::Formatter.new\n\n  # Use a different logger for distributed setups.\n  # require \"syslog/logger\"\n  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')\n\n  if ENV['RAILS_LOG_TO_STDOUT'].present?\n    logger           = ActiveSupport::Logger.new($stdout)\n    logger.formatter = config.log_formatter\n    config.logger    = ActiveSupport::TaggedLogging.new(logger)\n  end\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Inserts middleware to perform automatic connection switching.\n  # The `database_selector` hash is used to pass options to the DatabaseSelector\n  # middleware. The `delay` is used to determine how long to wait after a write\n  # to send a subsequent read to the primary.\n  #\n  # The `database_resolver` class is used by the middleware to determine which\n  # database is appropriate to use based on the time delay.\n  #\n  # The `database_resolver_context` class is used by the middleware to set\n  # timestamps for the last write to the primary. The resolver uses the context\n  # class timestamps to determine how long to wait before reading from the\n  # replica.\n  #\n  # By default Rails will store a last write timestamp in the session. The\n  # DatabaseSelector middleware is designed as such you can define your own\n  # strategy for connection switching and pass that into the middleware through\n  # these configuration options.\n  # config.active_record.database_selector = { delay: 2.seconds }\n  # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver\n  # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session\nend\n"
  },
  {
    "path": "core/test/dummy/config/environments/test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'active_support/core_ext/integer/time'\n\n# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  config.cache_classes = false\n  config.action_view.cache_template_loading = true\n\n  # Do not eager load code on boot. This avoids loading your whole application\n  # just for the purpose of running a single test. If you are using a tool that\n  # preloads Rails for running tests, you may have to set it to true.\n  config.eager_load = false\n\n  # Configure public file server for tests with Cache-Control for performance.\n  config.public_file_server.enabled = true\n  config.public_file_server.headers = {\n    'Cache-Control' => \"public, max-age=#{1.hour.to_i}\",\n  }\n\n  # Show full error reports and disable caching.\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n  config.cache_store = :null_store\n\n  # Raise exceptions instead of rendering exception templates.\n  config.action_dispatch.show_exceptions = false\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  config.action_mailer.perform_caching = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raise exceptions for disallowed deprecations.\n  config.active_support.disallowed_deprecation = :raise\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\nend\n"
  },
  {
    "path": "core/test/dummy/config/initializers/application_controller_renderer.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# ActiveSupport::Reloader.to_prepare do\n#   ApplicationController.renderer.defaults.merge!(\n#     http_host: 'example.org',\n#     https: false\n#   )\n# end\n"
  },
  {
    "path": "core/test/dummy/config/initializers/assets.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\nRails.application.config.assets.version = '1.0'\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n\n# Precompile additional assets.\n# application.js, application.css, and all non-JS/CSS in the app/assets\n# folder are already added.\n# Rails.application.config.assets.precompile += %w( admin.js admin.css )\n"
  },
  {
    "path": "core/test/dummy/config/initializers/backtrace_silencers.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code\n# by setting BACKTRACE=1 before calling your invocation, like \"BACKTRACE=1 ./bin/rails runner 'MyClass.perform'\".\nRails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE']\n"
  },
  {
    "path": "core/test/dummy/config/initializers/config.rb",
    "content": "# frozen_string_literal: true\n\nConfig.setup do |config|\n  # Name of the constant exposing loaded settings\n  config.const_name = 'Settings'\n\n  # Ability to remove elements of the array set in earlier loaded settings file. For example value: '--'.\n  #\n  # config.knockout_prefix = nil\n\n  # Overwrite an existing value when merging a `nil` value.\n  # When set to `false`, the existing value is retained after merge.\n  #\n  # config.merge_nil_values = true\n\n  # Overwrite arrays found in previously loaded settings file. When set to `false`, arrays will be merged.\n  #\n  # config.overwrite_arrays = true\n\n  # Load environment variables from the `ENV` object and override any settings defined in files.\n  #\n  # config.use_env = false\n\n  # Define ENV variable prefix deciding which variables to load into config.\n  #\n  # Reading variables from ENV is case-sensitive. If you define lowercase value below, ensure your ENV variables are\n  # prefixed in the same way.\n  #\n  # When not set it defaults to `config.const_name`.\n  #\n  config.env_prefix = 'SETTINGS'\n\n  # What string to use as level separator for settings loaded from ENV variables. Default value of '.' works well\n  # with Heroku, but you might want to change it for example for '__' to easy override settings from command line, where\n  # using dots in variable names might not be allowed (eg. Bash).\n  #\n  # config.env_separator = '.'\n\n  # Ability to process variables names:\n  #   * nil  - no change\n  #   * :downcase - convert to lower case\n  #\n  # config.env_converter = :downcase\n\n  # Parse numeric values as integers instead of strings.\n  #\n  # config.env_parse_values = true\n\n  # Validate presence and type of specific config values. Check https://github.com/dry-rb/dry-validation for details.\n  #\n  # config.schema do\n  #   required(:name).filled\n  #   required(:age).maybe(:int?)\n  #   required(:email).filled(format?: EMAIL_REGEX)\n  # end\n\n  # Evaluate ERB in YAML config files at load time.\n  #\n  # config.evaluate_erb_in_yaml = true\nend\n"
  },
  {
    "path": "core/test/dummy/config/initializers/content_security_policy.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy\n# For further information see the following documentation\n# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n\n# Rails.application.config.content_security_policy do |policy|\n#   policy.default_src :self, :https\n#   policy.font_src    :self, :https, :data\n#   policy.img_src     :self, :https, :data\n#   policy.object_src  :none\n#   policy.script_src  :self, :https\n#   policy.style_src   :self, :https\n\n#   # Specify URI for violation reports\n#   # policy.report_uri \"/csp-violation-report-endpoint\"\n# end\n\n# If you are using UJS then enable automatic nonce generation\n# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }\n\n# Set the nonce only to specific directives\n# Rails.application.config.content_security_policy_nonce_directives = %w(script-src)\n\n# Report CSP violations to a specified URI\n# For further information see the following documentation:\n# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only\n# Rails.application.config.content_security_policy_report_only = true\n"
  },
  {
    "path": "core/test/dummy/config/initializers/cookies_serializer.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Specify a serializer for the signed and encrypted cookie jars.\n# Valid options are :json, :marshal, and :hybrid.\nRails.application.config.action_dispatch.cookies_serializer = :json\n"
  },
  {
    "path": "core/test/dummy/config/initializers/filter_parameter_logging.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Configure sensitive parameters which will be filtered from the log file.\nRails.application.config.filter_parameters += [\n  :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn\n]\n"
  },
  {
    "path": "core/test/dummy/config/initializers/inflections.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, '\\1en'\n#   inflect.singular /^(ox)en/i, '\\1'\n#   inflect.irregular 'person', 'people'\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym 'RESTful'\n# end\n"
  },
  {
    "path": "core/test/dummy/config/initializers/mime_types.rb",
    "content": "# frozen_string_literal: true\n# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\n"
  },
  {
    "path": "core/test/dummy/config/initializers/octokit.rb",
    "content": "# frozen_string_literal: true\n\nmodule Octokit\n  class Client\n    module Contents\n      def contents?(repo, options = {})\n        options = options.dup\n        repo_path = options.delete(:path)\n        url = \"#{Repository.path(repo)}/contents/#{repo_path}\"\n        head(url, options).nil?\n      rescue Octokit::NotFound\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/dummy/config/initializers/permissions_policy.rb",
    "content": "# frozen_string_literal: true\n# Define an application-wide HTTP permissions policy. For further\n# information see https://developers.google.com/web/updates/2018/06/feature-policy\n#\n# Rails.application.config.permissions_policy do |f|\n#   f.camera      :none\n#   f.gyroscope   :none\n#   f.microphone  :none\n#   f.usb         :none\n#   f.fullscreen  :self\n#   f.payment     :self, \"https://secure.example.com\"\n# end\n"
  },
  {
    "path": "core/test/dummy/config/initializers/wrap_parameters.rb",
    "content": "# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsWrapper which\n# is enabled by default.\n\n# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.\nActiveSupport.on_load(:action_controller) do\n  wrap_parameters format: [:json]\nend\n\n# To enable root element in JSON for ActiveRecord objects.\n# ActiveSupport.on_load(:active_record) do\n#   self.include_root_in_json = true\n# end\n"
  },
  {
    "path": "core/test/dummy/config/locales/en.yml",
    "content": "# Files in the config/locales directory are used for internationalization\n# and are automatically loaded by Rails. If you want to use locales other\n# than English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t 'hello'\n#\n# In views, this is aliased to just `t`:\n#\n#     <%= t('hello') %>\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# The following keys must be escaped otherwise they will not be retrieved by\n# the default I18n backend:\n#\n# true, false, on, off, yes, no\n#\n# Instead, surround them with single quotes.\n#\n# en:\n#   'true': 'foo'\n#\n# To learn more, please read the Rails Internationalization guide\n# available at https://guides.rubyonrails.org/i18n.html.\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "core/test/dummy/config/puma.rb",
    "content": "# frozen_string_literal: true\n\n# Puma can serve each request in a thread from an internal thread pool.\n# The `threads` method setting takes two numbers: a minimum and maximum.\n# Any libraries that use thread pools should be configured to match\n# the maximum value specified for Puma. Default is set to 5 threads for minimum\n# and maximum; this matches the default thread size of Active Record.\n#\nmax_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)\nmin_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }\nthreads min_threads_count, max_threads_count\n\n# Specifies the `worker_timeout` threshold that Puma will use to wait before\n# terminating a worker in development environments.\n#\nworker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\n#\nport ENV.fetch('PORT', 3000)\n\n# Specifies the `environment` that Puma will run in.\n#\nenvironment ENV.fetch('RAILS_ENV', 'development')\n\n# Specifies the `pidfile` that Puma will use.\npidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid')\n\n# Specifies the number of `workers` to boot in clustered mode.\n# Workers are forked web server processes. If using threads and workers together\n# the concurrency of the application would be max `threads` * `workers`.\n# Workers do not work on JRuby or Windows (both of which do not support\n# processes).\n#\n# workers ENV.fetch(\"WEB_CONCURRENCY\") { 2 }\n\n# Use the `preload_app!` method when specifying a `workers` number.\n# This directive tells Puma to first boot the application and load code\n# before forking the application. This takes advantage of Copy On Write\n# process behavior so workers use less memory.\n#\n# preload_app!\n\n# Allow puma to be restarted by `rails restart` command.\nplugin :tmp_restart\n"
  },
  {
    "path": "core/test/dummy/config/routes.rb",
    "content": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n  mount UffizziCore::Engine => '/'\nend\n"
  },
  {
    "path": "core/test/dummy/config/settings.yml",
    "content": "app:\n  ttl_reset_password_token: 900\n  host: http://lvh.me\n  login: ''\n  password: ''\n  managed_dns_zone: example.com\ngithub:\n  app_id: 123\n  app_slug: test\n  client_id: test\n  client_secret: test\n  private_key: \"-----BEGIN RSA PRIVATE KEY-----\n  MIIBOgIBAAJBAKUZo1+jM7j760xsPlgt/5WzbRHL62kohW9hy8JpAauglOdgjRbY\n  URXle+6+VNzGBU0kUXYjzUNJLBgC+JeubvUCAwEAAQJAEzQrO6mZD5BF60q/6bPY\n  AcqwChzlEgNDmhQPBlr+db71EuLtmui7moCOTNLnZGIryd0uCGjEhXCwS653ivHW\n  NQIhANcWtxH2xHviNafPAK+5mIABOCL+MXXCI5EnjUdARMc7AiEAxIDWD+bOzavo\n  OGwiYrOQYcxxtZXGUUBKuQzpgvM5H48CIDcwbOj/GItxD7NvOg3c4XR226Ce+LHu\n  jpHARE/z/bHhAiA+hxiWmsU3oPoV6iLO8YCB/kI4m94tQJ4GYFt1tdt5dQIhALQg\n  cNfHZndOE7mwn6pm/JI8bXCseL7V2t+cXyCexlru\n  -----END RSA PRIVATE KEY-----\n  \"\n  webhook_secret: test\n  registry_url: gcr.io/test\ndocker_hub:\n  registry_url: 'https://index.docker.io/v1/'\n  public_namespace: 'library'\ncontroller:\n  url: http://controller:8080\n  login: ''\n  password: ''\n  connection:\n    retires_count: 1\n    next_retry_timeout_seconds: 1\n    timeout: 7\n    open_timeout: 5\n  limits:\n    cpu: '200m'\n  namespace_prefix: 'app-'\n  resource_create_retry_time: 15.seconds\n  resource_create_retry_count: 60\nvcluster_controller:\n  url: http://controller:8080\n  login: ''\n  password: ''\n  managed_dns_zone: ''\n  connection:\n    retires_count: 1\n    next_retry_timeout_seconds: 1\n    timeout: 7\n    open_timeout: 5\nallowed_hosts: []\ndomain: http://lvh.me\nfeatures:\n  email_delivery_enabled: true\n  stripe_enabled: false\nplatform_cluster:\n  project_id: test\ngoogle:\n  registry_url: 'https://gcr.io/'\ngithub_container_registry:\n  registry_url: 'https://ghcr.io/'\ncompose:\n  default_memory: 125\n  memory_postfixes: <%= ['b', 'k', 'm', 'g'] %>\n  memory_values: <%= [125, 250, 500, 1000, 2000, 4000] %>\n  port_min_value: 1\n  port_max_value: 65535\n  delete_after_postfixes: <%= ['h'] %>\n  delete_after_min_value: 1\n  delete_after_max_value: 720\n  default_tag: latest\n  dockerfile_default_path: Dockerfile\n  default_branch: 'master'\ncontinuous_preview:\n  default_delete_preview_after: 72\ndeployment:\n  max_memory_limit: 8000\n  subdomain:\n    length_limit: 63\nvcluster:\n  max_creation_retry_count: 5\n  max_scale_up_retry_count: 5\n"
  },
  {
    "path": "core/test/dummy/config/spring.rb",
    "content": "# frozen_string_literal: true\n\nSpring.watch(\n  '.ruby-version',\n  '.rbenv-vars',\n  'tmp/restart.txt',\n  'tmp/caching-dev.txt',\n)\n"
  },
  {
    "path": "core/test/dummy/config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n"
  },
  {
    "path": "core/test/dummy/config.ru",
    "content": "# frozen_string_literal: true\n\n# This file is used by Rack-based servers to start the application.\n\nrequire_relative 'config/environment'\n\nrun Rails.application\nRails.application.load_server\n"
  },
  {
    "path": "core/test/dummy/db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema.define(version: 2024_03_14_170113) do\n\n  # These are extensions that must be enabled in order to support this database\n  enable_extension \"plpgsql\"\n\n  create_table \"uffizzi_core_accounts\", force: :cascade do |t|\n    t.text \"name\"\n    t.text \"kind\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"customer_token\"\n    t.string \"state\"\n    t.string \"subscription_token\"\n    t.datetime \"payment_issue_at\"\n    t.string \"domain\"\n    t.boolean \"sso_enabled\", default: false\n    t.bigint \"owner_id\"\n    t.integer \"container_memory_limit\"\n    t.string \"workos_organization_id\"\n    t.string \"sso_state\"\n    t.index [\"customer_token\"], name: \"index_accounts_on_customer_token\", unique: true\n    t.index [\"domain\"], name: \"index_accounts_on_domain\", unique: true\n    t.index [\"subscription_token\"], name: \"index_accounts_on_subscription_token\", unique: true\n  end\n\n  create_table \"uffizzi_core_activity_items\", force: :cascade do |t|\n    t.bigint \"deployment_id\", null: false\n    t.string \"namespace\"\n    t.string \"name\"\n    t.string \"tag\"\n    t.string \"branch\"\n    t.string \"type\"\n    t.bigint \"container_id\", null: false\n    t.string \"commit\"\n    t.string \"commit_message\"\n    t.bigint \"build_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.jsonb \"data\", default: {}, null: false\n    t.string \"digest\"\n    t.index [\"container_id\"], name: \"index_activity_items_on_container_id\"\n    t.index [\"deployment_id\"], name: \"index_activity_items_on_deployment_id\"\n  end\n\n  create_table \"uffizzi_core_builds\", force: :cascade do |t|\n    t.bigint \"repo_id\", null: false\n    t.string \"build_id\"\n    t.string \"repository\"\n    t.string \"branch\"\n    t.string \"commit\"\n    t.string \"committer\"\n    t.string \"message\"\n    t.string \"log_url\"\n    t.integer \"status\"\n    t.datetime \"started_at\"\n    t.datetime \"ended_at\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.boolean \"deployed\"\n    t.index [\"build_id\"], name: \"index_builds_on_build_id\", unique: true\n    t.index [\"repo_id\"], name: \"index_builds_on_repo_id\"\n  end\n\n  create_table \"uffizzi_core_clusters\", force: :cascade do |t|\n    t.bigint \"project_id\", null: false\n    t.bigint \"deployed_by_id\"\n    t.string \"state\"\n    t.string \"name\"\n    t.text \"manifest\"\n    t.text \"kubeconfig\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"host\"\n    t.string \"creation_source\"\n    t.integer \"kubernetes_distribution_id\"\n    t.index [\"project_id\"], name: \"index_cluster_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_comments\", force: :cascade do |t|\n    t.string \"commentable_type\"\n    t.bigint \"commentable_id\"\n    t.text \"content\"\n    t.bigint \"user_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"ancestry\"\n    t.integer \"ancestry_depth\", default: 0\n    t.index [\"ancestry\"], name: \"index_comments_on_ancestry\"\n    t.index [\"commentable_type\", \"commentable_id\"], name: \"index_comments_on_commentable\"\n    t.index [\"user_id\"], name: \"index_comments_on_user_id\"\n  end\n\n  create_table \"uffizzi_core_compose_files\", force: :cascade do |t|\n    t.string \"source\"\n    t.bigint \"repository_id\"\n    t.string \"branch\"\n    t.string \"path\"\n    t.string \"auto_deploy\"\n    t.bigint \"added_by_id\"\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"state\"\n    t.jsonb \"payload\", default: {}, null: false\n    t.text \"content\"\n    t.string \"kind\", default: \"main\"\n    t.index [\"project_id\"], name: \"index_compose_files_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_config_files\", force: :cascade do |t|\n    t.string \"filename\"\n    t.string \"kind\"\n    t.bigint \"added_by_id\"\n    t.text \"payload\"\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"compose_file_id\"\n    t.string \"creation_source\"\n    t.string \"source\"\n    t.index [\"compose_file_id\"], name: \"index_config_files_on_compose_file_id\"\n    t.index [\"project_id\"], name: \"index_config_files_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_container_config_files\", force: :cascade do |t|\n    t.string \"mount_path\"\n    t.bigint \"container_id\", null: false\n    t.bigint \"config_file_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"config_file_id\"], name: \"index_container_config_files_on_config_file_id\"\n    t.index [\"container_id\"], name: \"index_container_config_files_on_container_id\"\n  end\n\n  create_table \"uffizzi_core_container_host_volume_files\", force: :cascade do |t|\n    t.string \"source_path\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"container_id\", null: false\n    t.bigint \"host_volume_file_id\", null: false\n    t.index [\"container_id\"], name: \"uf_core_cont_h_v_on_cont\"\n    t.index [\"host_volume_file_id\"], name: \"uf_core_cont_h_v_on_h_v_file\"\n  end\n\n  create_table \"uffizzi_core_containers\", force: :cascade do |t|\n    t.string \"image\"\n    t.string \"tag\"\n    t.jsonb \"variables\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"deployment_id\"\n    t.boolean \"public\", default: false, null: false\n    t.integer \"port\"\n    t.bigint \"repo_id\"\n    t.string \"state\"\n    t.string \"continuously_deploy\", null: false\n    t.string \"kind\", default: \"user\"\n    t.integer \"target_port\"\n    t.string \"controller_name\"\n    t.boolean \"receive_incoming_requests\"\n    t.integer \"memory_request\"\n    t.integer \"memory_limit\"\n    t.jsonb \"secret_variables\"\n    t.string \"entrypoint\"\n    t.string \"command\"\n    t.string \"service_name\"\n    t.jsonb \"healthcheck\"\n    t.jsonb \"volumes\"\n    t.string \"additional_subdomains\", default: [], array: true\n    t.string \"full_image_name\"\n    t.index [\"deployment_id\"], name: \"index_containers_on_deployment_id\"\n    t.index [\"repo_id\"], name: \"index_containers_on_repo_id\"\n  end\n\n  create_table \"uffizzi_core_coupons\", force: :cascade do |t|\n    t.string \"token\"\n    t.string \"name\", null: false\n    t.string \"currency\", null: false\n    t.bigint \"amount_off\", null: false\n    t.string \"duration\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n  end\n\n  create_table \"uffizzi_core_credentials\", force: :cascade do |t|\n    t.string \"type\"\n    t.string \"username\"\n    t.string \"password\"\n    t.bigint \"project_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"provider_ref\"\n    t.bigint \"account_id\"\n    t.string \"state\"\n    t.string \"registry_url\"\n    t.index [\"account_id\"], name: \"index_credentials_on_account_id\"\n    t.index [\"project_id\"], name: \"index_credentials_on_project_id\"\n    t.index [\"provider_ref\"], name: \"index_credentials_on_provider_ref\"\n  end\n\n  create_table \"uffizzi_core_deployment_events\", force: :cascade do |t|\n    t.string \"deployment_state\"\n    t.string \"message\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"deployment_id\", null: false\n    t.index [\"deployment_id\"], name: \"uf_core_dep_events_on_dep\"\n  end\n\n  create_table \"uffizzi_core_deployments\", force: :cascade do |t|\n    t.bigint \"project_id\", null: false\n    t.text \"kind\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"creator_name\"\n    t.string \"subdomain\"\n    t.string \"state\"\n    t.float \"memory_limit\"\n    t.bigint \"deployed_by_id\"\n    t.bigint \"continuous_preview_id_deprecated\"\n    t.jsonb \"continuous_preview_payload\"\n    t.string \"creation_source\"\n    t.bigint \"compose_file_id\"\n    t.bigint \"template_id\"\n    t.datetime \"disabled_at\"\n    t.jsonb \"metadata\", default: {}\n    t.datetime \"last_deploy_at\"\n    t.index [\"compose_file_id\"], name: \"index_deployments_on_compose_file_id\"\n    t.index [\"project_id\"], name: \"index_deployments_on_project_id\"\n    t.index [\"template_id\"], name: \"index_deployments_on_template_id\"\n  end\n\n  create_table \"uffizzi_core_events\", force: :cascade do |t|\n    t.bigint \"activity_item_id\", null: false\n    t.string \"state\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"activity_item_id\"], name: \"index_events_on_activity_item_id\"\n  end\n\n  create_table \"uffizzi_core_host_volume_files\", force: :cascade do |t|\n    t.string \"source\"\n    t.string \"path\"\n    t.boolean \"is_file\"\n    t.binary \"payload\"\n    t.bigint \"added_by_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"project_id\", null: false\n    t.bigint \"compose_file_id\", null: false\n    t.index [\"compose_file_id\"], name: \"index_host_volume_file_on_compose_file_id\"\n    t.index [\"project_id\"], name: \"index_host_volume_file_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_invitations\", force: :cascade do |t|\n    t.text \"email\", null: false\n    t.text \"token\", null: false\n    t.string \"status\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"invited_by_id\", null: false\n    t.bigint \"entityable_id\", null: false\n    t.string \"entityable_type\", null: false\n    t.string \"role\", null: false\n    t.bigint \"invitee_id\"\n    t.index [\"token\"], name: \"index_invitations_on_token\", unique: true\n  end\n\n  create_table \"uffizzi_core_kubernetes_distributions\", force: :cascade do |t|\n    t.string \"version\"\n    t.string \"distro\"\n    t.string \"image\"\n    t.boolean \"default\", default: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n  end\n\n  create_table \"uffizzi_core_memberships\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.bigint \"account_id\", null: false\n    t.text \"role\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"state\"\n    t.index [\"account_id\"], name: \"index_memberships_on_account_id\"\n    t.index [\"user_id\", \"account_id\"], name: \"index_memberships_on_user_id_and_account_id\"\n    t.index [\"user_id\"], name: \"index_memberships_on_user_id\"\n  end\n\n  create_table \"uffizzi_core_payments\", force: :cascade do |t|\n    t.bigint \"account_id\", null: false\n    t.string \"charge_id\"\n    t.string \"status\"\n    t.float \"amount\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"account_id\"], name: \"index_payments_on_account_id\"\n  end\n\n  create_table \"uffizzi_core_prices\", force: :cascade do |t|\n    t.string \"token\"\n    t.string \"slug\", null: false\n    t.string \"name\", null: false\n    t.float \"units_price\", null: false\n    t.bigint \"units_amount\", null: false\n    t.bigint \"product_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"product_id\"], name: \"index_prices_on_product_id\"\n  end\n\n  create_table \"uffizzi_core_products\", force: :cascade do |t|\n    t.string \"token\"\n    t.string \"slug\", null: false\n    t.string \"name\", null: false\n    t.string \"type\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"kind\"\n  end\n\n  create_table \"uffizzi_core_projects\", force: :cascade do |t|\n    t.text \"name\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"account_id\", null: false\n    t.string \"state\"\n    t.string \"slug\"\n    t.string \"description\"\n    t.index [\"account_id\", \"name\"], name: \"proj_uniq_name\", unique: true, where: \"((state)::text = 'active'::text)\"\n    t.index [\"account_id\"], name: \"index_projects_on_account_id\"\n  end\n\n  create_table \"uffizzi_core_ratings\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"state\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n  end\n\n  create_table \"uffizzi_core_repos\", force: :cascade do |t|\n    t.string \"namespace\"\n    t.string \"name\"\n    t.string \"tag\"\n    t.string \"type\"\n    t.string \"branch\"\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"description\"\n    t.boolean \"is_private\"\n    t.string \"slug\"\n    t.bigint \"repository_id\"\n    t.string \"kind\"\n    t.string \"dockerfile_path\"\n    t.jsonb \"args\"\n    t.string \"dockerfile_context_path\"\n    t.boolean \"deploy_preview_when_pull_request_is_opened\"\n    t.boolean \"delete_preview_when_pull_request_is_closed\"\n    t.boolean \"deploy_preview_when_image_tag_is_created\"\n    t.boolean \"delete_preview_when_image_tag_is_updated\"\n    t.boolean \"share_to_github\"\n    t.integer \"delete_preview_after\"\n    t.string \"tag_pattern_deprecated\"\n    t.index [\"project_id\"], name: \"index_repos_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_roles\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"resource_type\"\n    t.bigint \"resource_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"name\", \"resource_type\", \"resource_id\"], name: \"index_roles_on_name_and_resource_type_and_resource_id\"\n    t.index [\"resource_type\", \"resource_id\"], name: \"index_roles_on_resource_type_and_resource_id\"\n  end\n\n  create_table \"uffizzi_core_secrets\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"value\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"resource_type\"\n    t.bigint \"resource_id\"\n    t.index [\"resource_type\", \"resource_id\"], name: \"index_uffizzi_core_secrets_on_resource\"\n  end\n\n  create_table \"uffizzi_core_templates\", force: :cascade do |t|\n    t.string \"name\"\n    t.bigint \"added_by_id\"\n    t.jsonb \"payload\", null: false\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"compose_file_id\"\n    t.string \"creation_source\"\n    t.index [\"project_id\"], name: \"index_templates_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_user_projects\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.bigint \"project_id\", null: false\n    t.text \"role\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"invited_by_id\"\n    t.index [\"project_id\"], name: \"index_user_projects_on_project_id\"\n    t.index [\"user_id\", \"project_id\"], name: \"index_user_projects_on_user_id_and_project_id\"\n    t.index [\"user_id\"], name: \"index_user_projects_on_user_id\"\n  end\n\n  create_table \"uffizzi_core_users\", force: :cascade do |t|\n    t.string \"first_name\"\n    t.string \"last_name\"\n    t.string \"email\", default: \"\", null: false\n    t.string \"password_digest\", default: \"\", null: false\n    t.string \"confirmation_token\"\n    t.string \"state\"\n    t.string \"phone\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"github\"\n    t.string \"website\"\n    t.string \"twitter\"\n    t.string \"linkedin\"\n    t.string \"devto\"\n    t.string \"facebook\"\n    t.string \"blog\"\n    t.text \"bio\"\n    t.string \"status\"\n    t.string \"availability\"\n    t.string \"primary_skills\"\n    t.string \"learning\"\n    t.string \"coding_for\"\n    t.string \"education\"\n    t.string \"title\"\n    t.string \"work\"\n    t.string \"primary_location\"\n    t.string \"creation_source\"\n    t.index \"lower((email)::text)\", name: \"index_email_on_lower_email\", unique: true\n  end\n\n  create_table \"uffizzi_core_users_roles\", id: false, force: :cascade do |t|\n    t.bigint \"user_id\"\n    t.bigint \"role_id\"\n    t.index [\"role_id\"], name: \"index_users_roles_on_role_id\"\n    t.index [\"user_id\", \"role_id\"], name: \"index_users_roles_on_user_id_and_role_id\"\n    t.index [\"user_id\"], name: \"index_users_roles_on_user_id\"\n  end\n\n  add_foreign_key \"uffizzi_core_clusters\", \"uffizzi_core_projects\", column: \"project_id\"\n  add_foreign_key \"uffizzi_core_container_host_volume_files\", \"uffizzi_core_containers\", column: \"container_id\"\n  add_foreign_key \"uffizzi_core_container_host_volume_files\", \"uffizzi_core_host_volume_files\", column: \"host_volume_file_id\"\n  add_foreign_key \"uffizzi_core_deployment_events\", \"uffizzi_core_deployments\", column: \"deployment_id\"\n  add_foreign_key \"uffizzi_core_host_volume_files\", \"uffizzi_core_compose_files\", column: \"compose_file_id\"\n  add_foreign_key \"uffizzi_core_host_volume_files\", \"uffizzi_core_projects\", column: \"project_id\"\nend\n"
  },
  {
    "path": "core/test/dummy/lib/assets/.keep",
    "content": ""
  },
  {
    "path": "core/test/dummy/log/.keep",
    "content": ""
  },
  {
    "path": "core/test/dummy/public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The page you were looking for doesn't exist.</h1>\n      <p>You may have mistyped the address or the page may have moved.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "core/test/dummy/public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The change you wanted was rejected.</h1>\n      <p>Maybe you tried to change something you didn't have access to.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "core/test/dummy/public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>We're sorry, but something went wrong.</h1>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "core/test/factories/account.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :account, class: UffizziCore::Account do\n    name\n    kind { UffizziCore::Account.kind.personal }\n    customer_token { nil }\n    subscription_token { nil }\n    state { nil }\n    payment_issue_at { nil }\n    owner { nil }\n    created_at\n    domain\n\n    trait :with_admin do\n      transient do\n        admin { nil }\n      end\n\n      before(:create) do |account, evaluator|\n        account.owner = evaluator.admin\n      end\n\n      after(:create) do |account, evaluator|\n        if evaluator.admin\n          user = evaluator.admin\n          user.memberships.create(user: user, account: account, role: UffizziCore::Membership.role.admin)\n\n          account.projects.each do |project|\n            user.user_projects.create!(user: user, project: project, role: UffizziCore::UserProject.role.admin)\n          end\n        end\n      end\n    end\n\n    trait :personal_account do\n      kind { UffizziCore::Account.kind.personal }\n    end\n\n    trait :with_stripe_enitities do\n      after(:create) do |account, _evaluator|\n        if account\n          UffizziCore::StripeService.create_customer(account)\n          UffizziCore::StripeService.create_subscription(account)\n        end\n      end\n    end\n\n    trait :disabled do\n      state { :disabled }\n    end\n\n    trait :payment_issue do\n      state { :payment_issue }\n    end\n\n    trait :draft do\n      state { UffizziCore::Account::STATE_DRAFT }\n    end\n\n    trait :sso_connection_active do\n      sso_state { UffizziCore::Account::STATE_CONNECTION_ACTIVE }\n    end\n\n    trait :sso_connection_disabled do\n      sso_state { UffizziCore::Account::STATE_CONNECTION_DISABLED }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/activity_item.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :activity_item, class: UffizziCore::ActivityItem do\n    deployment { nil }\n    namespace\n    name\n    tag { '' }\n    branch { '' }\n    container { nil }\n    commit\n    commit_message\n    build_id { nil }\n\n    trait :with_building_event do\n      after(:create) do |activity_item, _evaluator|\n        activity_item.events.create(state: UffizziCore::Event.state.building)\n      end\n    end\n\n    trait :with_deploying_event do\n      after(:create) do |activity_item, _evaluator|\n        activity_item.events.create(state: UffizziCore::Event.state.deploying)\n      end\n    end\n\n    trait :with_deployed_event do\n      after(:create) do |activity_item, _evaluator|\n        activity_item.events.create(state: UffizziCore::Event.state.deployed)\n      end\n    end\n\n    trait :with_failed_event do\n      after(:create) do |activity_item, _evaluator|\n        activity_item.events.create(state: UffizziCore::Event.state.failed)\n      end\n    end\n\n    trait :docker do\n      type { UffizziCore::ActivityItem::Docker.name }\n    end\n\n    trait :github do\n      type { UffizziCore::ActivityItem::Github.name }\n    end\n\n    trait :memory_limit do\n      type { UffizziCore::ActivityItem::MemoryLimit.name }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/cluster.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :cluster, class: UffizziCore::Cluster do\n    name { generate(:cluster_name) }\n    project { nil }\n    deployed_by { nil }\n\n    trait :deployed do\n      state { :deployed }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/comments.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :comment, class: UffizziCore::Comment do\n    commentable { nil }\n    content\n    parent { nil }\n    user { nil }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/compose_files.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :compose_file, class: UffizziCore::ComposeFile do\n    source { generate(:name) }\n    repository_id { generate(:number) }\n    branch\n    path\n    auto_deploy { UffizziCore::ComposeFile::STATE_DISABLED }\n    kind { UffizziCore::ComposeFile.kind.main }\n\n    trait :auto_deploy do\n      auto_deploy { UffizziCore::ComposeFile::STATE_ENABLED }\n    end\n\n    trait :invalid_file do\n      state { :invalid_file }\n    end\n\n    trait :temporary do\n      kind { UffizziCore::ComposeFile.kind.temporary }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/config_files.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :config_file, class: UffizziCore::ConfigFile do\n    filename { generate(:name) }\n    added_by { nil }\n    kind { UffizziCore::ConfigFile.kind.config_map }\n    payload { generate(:string) }\n    project { nil }\n    creation_source { UffizziCore::ConfigFile.creation_source.manual }\n  end\n\n  trait :compose_file_source do\n    creation_source { UffizziCore::ConfigFile.creation_source.compose_file }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/container_config_files.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :container_config_file, class: UffizziCore::ContainerConfigFile do\n    mount_path { generate(:path) }\n    container { nil }\n    config_file { nil }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/container_host_volume_files.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :container_host_volume_file, class: UffizziCore::ContainerHostVolumeFile do\n    source_path { generate(:relative_path) }\n    container { nil }\n    host_volume_file { nil }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/containers.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :container, class: UffizziCore::Container do\n    image\n    full_image_name\n    tag\n    service_name\n    variables { [] }\n    secret_variables { [] }\n    deployment\n    repo { nil }\n    receive_incoming_requests { false }\n    entrypoint { nil }\n    command { nil }\n    continuously_deploy { UffizziCore::Container::STATE_CD_DISABLED }\n    healthcheck { nil }\n    volumes { nil }\n    memory_limit { Settings.compose.default_memory }\n    memory_request { Settings.compose.default_memory }\n\n    trait :with_public_port do\n      public { true }\n\n      port\n    end\n\n    trait :continuously_deploy_enabled do\n      continuously_deploy { UffizziCore::Container::STATE_CD_ENABLED }\n    end\n\n    trait :continuously_deploy_disabled do\n      continuously_deploy { UffizziCore::Container::STATE_CD_DISABLED }\n    end\n\n    initialize_with { new }\n\n    trait :active do\n      state { UffizziCore::Container::STATE_ACTIVE }\n    end\n\n    trait :with_named_volume do\n      volumes do\n        [\n          {\n            source: generate(:string),\n            target: generate(:path),\n            type: UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::NAMED_VOLUME_TYPE,\n          },\n        ]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/credentials.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :credential, class: UffizziCore::Credential do\n    username { generate(:login) }\n    password\n    account { nil }\n    registry_url { generate(:url) }\n\n    trait :docker_hub do\n      type { UffizziCore::Credential::DockerHub.name }\n    end\n\n    trait :azure do\n      type { UffizziCore::Credential::Azure.name }\n    end\n\n    trait :google do\n      type { UffizziCore::Credential::Google.name }\n      registry_url { 'https://gcr.io/' }\n    end\n\n    trait :github_container_registry do\n      type { UffizziCore::Credential::GithubContainerRegistry.name }\n      registry_url { 'https://ghcr.io' }\n    end\n\n    trait :docker_registry do\n      type { UffizziCore::Credential::DockerRegistry.name }\n      registry_url { 'https://example.com/registry' }\n    end\n\n    trait :amazon do\n      type { UffizziCore::Credential::Amazon.name }\n      registry_url { 'https://123456789876.dkr.ecr.us-east-1.amazonaws.com' }\n    end\n\n    trait :unauthorized do\n      state { :unauthorized }\n    end\n\n    trait :active do\n      state { :active }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/deployment.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :deployment, class: UffizziCore::Deployment do\n    kind { nil }\n    subdomain { generate :subdomain_name }\n    created_at { DateTime.current }\n    memory_limit { 1 }\n    continuous_preview_payload { nil }\n    creation_source { nil }\n    metadata { {} }\n\n    trait :active do\n      state { :active }\n    end\n\n    trait :disabled do\n      state { :disabled }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/host_volume_files.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :host_volume_file, class: UffizziCore::HostVolumeFile do\n    path { generate(:path) }\n    source { generate(:relative_path) }\n    payload { generate(:string) }\n    added_by { nil }\n    project { nil }\n    compose_file { nil }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/kubernetes_distribution.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :kubernetes_distribution, class: UffizziCore::KubernetesDistribution do\n    version { generate(:version) }\n    image { generate(:string) }\n    distro { generate(:string) }\n\n    trait :default do\n      default { true }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/membership.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :membership, class: UffizziCore::Membership do\n    user { nil }\n    account { nil }\n    role { UffizziCore::Membership.role.developer }\n    trait :admin do\n      role { UffizziCore::Membership.role.admin }\n    end\n    trait :developer do\n      role { UffizziCore::Membership.role.developer }\n    end\n    trait :viewer do\n      role { UffizziCore::Membership.role.viewer }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/payments.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :payment, class: UffizziCore::Payment do\n    account { nil }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/project.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :project, class: UffizziCore::Project do\n    name { generate :name }\n    slug { generate :slug }\n    description { generate :description }\n    account\n\n    trait :with_members do\n      transient do\n        members { [] }\n      end\n\n      after(:create) do |project, evaluator|\n        evaluator.members.each do |member|\n          account = project.account\n          role = account.memberships.find_by(user_id: member.id).role\n\n          project.user_projects.create(\n            user: member,\n            invited_by: account.owner,\n            role: role,\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/ratings.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :rating, class: UffizziCore::Rating do\n    name\n  end\nend\n"
  },
  {
    "path": "core/test/factories/repos.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :repo, class: UffizziCore::Repo do\n    namespace\n    name { generate(:string) }\n    tag\n    slug\n    branch\n    description\n    repository_id { generate(:number) }\n    deploy_preview_when_pull_request_is_opened { false }\n    delete_preview_when_pull_request_is_closed { false }\n    deploy_preview_when_image_tag_is_created { false }\n    delete_preview_when_image_tag_is_updated { false }\n    delete_preview_after { nil }\n    share_to_github { false }\n\n    trait :docker_hub do\n      type { UffizziCore::Repo::DockerHub.name }\n    end\n\n    trait :azure do\n      type { UffizziCore::Repo::Azure.name }\n    end\n\n    trait :google do\n      type { UffizziCore::Repo::Google.name }\n    end\n\n    trait :amazon do\n      type { UffizziCore::Repo::Amazon.name }\n    end\n\n    trait :kind_barestatic do\n      kind { UffizziCore::Repo.kind.barestatic }\n    end\n\n    trait :kind_buildpacks18 do\n      kind { UffizziCore::Repo.kind.buildpacks18 }\n    end\n\n    trait :kind_dockerfile do\n      kind { UffizziCore::Repo.kind.dockerfile }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/secrets.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :secret, class: UffizziCore::Secret do\n    resource { nil }\n    name { generate(:string) }\n    value { generate(:string) }\n  end\nend\n"
  },
  {
    "path": "core/test/factories/sequences.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  sequence :time do\n    Faker::Time.between(from: DateTime.now - 1, to: DateTime.now)\n  end\n\n  sequence :string,\n           aliases: [\n             :tag, :namespace, :branch, :phone, :website, :twitter, :github,\n             :linkedin, :devto, :facebook, :blog, :bio, :status, :availability,\n             :primary_skills, :learning, :coding_for, :education, :title, :work,\n             :primary_location, :content, :login\n           ] do |n|\n    \"string_#{n}\"\n  end\n\n  sequence :image do |n|\n    \"namespace/name-#{n}\"\n  end\n\n  sequence :full_image_name do |n|\n    \"namespace/name-#{n}:latest\"\n  end\n\n  sequence :kubernetes_name do |n|\n    \"kubernetes-name-#{n}\"\n  end\n\n  sequence :commit do |n|\n    \"commit-hash-#{n}\"\n  end\n\n  sequence :commit_message do |n|\n    \"commit-message-#{n}\"\n  end\n\n  sequence :slug do |n|\n    \"slug_#{n}\"\n  end\n\n  sequence :name, aliases: [:first_name, :last_name, :username, :description, :service_name] do |n|\n    \"Name #{n}\"\n  end\n\n  sequence :token, aliases: [:confirmation_token, :customer_token, :subscription_token] do |_n|\n    UffizziCore::TokenService.generate\n  end\n\n  sequence :url, aliases: [:image_url] do |n|\n    \"http://url#{n}.com\"\n  end\n\n  sequence :password do |n|\n    \"Password1String-#{n}\"\n  end\n\n  sequence :email do |n|\n    \"user#{n}@example.com\"\n  end\n\n  sequence :instance_name do |n|\n    \"project-name-#{Time.now.to_i}-#{n}\"\n  end\n\n  sequence :path do |n|\n    \"/volumes/directory-#{n}\"\n  end\n\n  sequence :relative_path do |n|\n    \"./directory-#{n}\"\n  end\n\n  sequence :number, aliases: [\n    :integer,\n    :mileage,\n    :ram,\n    :port,\n    :selected_storage_size,\n    :storage_initial,\n    :storage_capacity,\n    :max_connections,\n    :base_price_per_month,\n    :extra_gigabyte_price_per_hour,\n    :memory_limit,\n    :claps,\n  ] do |n|\n    n\n  end\n\n  sequence :boolean, aliases: [\n    :has_public_ip,\n    :has_storage_autoscaling,\n    :has_high_availability,\n    :backups_mandatory,\n    :has_shared_memory,\n  ] do\n    Faker::Boolean.boolean\n  end\n\n  sequence :vcpu do |n|\n    ['shared', n].sample\n  end\n\n  sequence :domain_name, aliases: [:domain] do\n    Faker::Internet.domain_name(subdomain: true)\n  end\n\n  sequence :subdomain_name do |_n|\n    Faker::Lorem\n      .words(number: 2, supplemental: true)\n      .join('-')\n      .delete(',. ')\n      .downcase\n  end\n\n  sequence :created_at, aliases: ['updated_at'] do\n    DateTime.now\n  end\n\n  sequence :cluster_name do |n|\n    \"cluster-name-#{n}\"\n  end\n\n  sequence :version do |n|\n    \"#{n}.#{n}#{n}\"\n  end\nend\n"
  },
  {
    "path": "core/test/factories/templates.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :template, class: UffizziCore::Template do\n    name\n    project { nil }\n    added_by_id { nil }\n    creation_source { UffizziCore::Template.creation_source.manual }\n\n    trait :compose_file_source do\n      creation_source { UffizziCore::Template.creation_source.compose_file }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/user.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :user, class: UffizziCore::User do\n    first_name\n    last_name\n    email\n    password\n    phone\n    confirmation_token\n    created_at\n    github\n    website\n    twitter\n    linkedin\n    devto\n    facebook\n    blog\n    bio\n    status\n    availability\n    primary_skills\n    learning\n    coding_for\n    education\n    title\n    work\n    primary_location\n\n    trait :with_personal_account do\n      after(:create) do |user, _evaluator|\n        create(:account, :with_admin, kind: UffizziCore::Account.kind.personal, admin: user, created_at: user.created_at)\n      end\n    end\n\n    trait :with_organizational_account do\n      after(:create) do |user, _evaluator|\n        create(:account, :with_admin, kind: UffizziCore::Account.kind.organizational, admin: user, created_at: user.created_at)\n      end\n    end\n\n    trait :active do\n      state { :active }\n    end\n\n    trait :disabled do\n      state { :disabled }\n    end\n\n    trait :global_admin do\n      after(:create) do |user, _evaluator|\n        user.add_role(:admin)\n      end\n    end\n\n    trait :user_google do\n      creation_source { :google }\n    end\n\n    trait :user_sso do\n      creation_source { :sso }\n    end\n\n    trait :admin_in_organization do\n      transient do\n        organization { nil }\n      end\n\n      after(:create) do |user, evaluator|\n        create(:membership, :admin, user: user, account: evaluator.organization)\n      end\n    end\n\n    trait :developer_in_organization do\n      transient do\n        organization { nil }\n      end\n\n      after(:create) do |user, evaluator|\n        create(:membership, :developer, user: user, account: evaluator.organization)\n      end\n    end\n\n    trait :viewer_in_organization do\n      transient do\n        organization { nil }\n      end\n\n      after(:create) do |user, evaluator|\n        create(:membership, :viewer, user: user, account: evaluator.organization)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/factories/user_project.rb",
    "content": "# frozen_string_literal: true\n\nFactoryBot.define do\n  factory :user_project, class: UffizziCore::UserProject do\n    user { nil }\n    project { nil }\n    role { nil }\n\n    trait :admin do\n      role { UffizziCore::UserProject.role.admin }\n    end\n    trait :developer do\n      role { UffizziCore::UserProject.role.developer }\n    end\n    trait :viewer do\n      role { UffizziCore::UserProject.role.viewer }\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/fixtures/files/cluster/manifest.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: web-app\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - containerPort: 80\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-service\nspec:\n  selector:\n    app.kubernetes.io/name: web-app\n  ports:\n    - protocol: TCP\n      port: 8080\n      targetPort: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_dependencies/configs/vote_conf.json",
    "content": "{\n  \"content\": \"c2VydmVyIHsKICBsaXN0ZW4gICAgICAgODA4MDsKICBzZXJ2ZXJfbmFtZSAg\\nZXhhbXBsZS5jb207CiAgbG9jYXRpb24gLyB7CiAgICBwcm94eV9wYXNzICAg\\nICAgaHR0cDovLzEyNy4wLjAuMTo4MC87CiAgfQogIGxvY2F0aW9uIC92b3Rl\\nLyB7CiAgICBwcm94eV9wYXNzICAgICAgaHR0cDovLzEyNy4wLjAuMTo4ODg4\\nLzsKICB9Cn0K\\n\"\n}\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/azure_services/nginx.yml",
    "content": "services:\n  nginx:\n    image: account.azurecr.io/nginx:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/boolean_option.yml",
    "content": "services:\n  on:\n    image: nginx:latest\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_empty.yml",
    "content": ""
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_memory.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    deploy:\n      resources:\n        limits:\n          memory: 1000m\n\n  redis:\n    image: redis:latest\n    deploy:\n      resources:\n        limits:\n          memory: 250000000b\n\n  ubuntu:\n    image: ubuntu:latest\n    deploy:\n      resources:\n        limits:\n          memory: 4g\n\n  postgres:\n    image: postgres:latest\n    deploy:\n      resources:\n        limits:\n          memory: 125000k\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_vote_app_github.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  postgres:\n    image: postgres:9.6\n    environment:\n      POSTGRES_USER: USER\n      POSTGRES_PASSWORD: PASSWORD\n\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote_conf\n        target: /etc/nginx/conf.d\n\n  worker:\n    build:\n      context: https://github.com/ACCOUNT/example-voting-worker\n      dockerfile: Dockerfile\n    deploy:\n      resources:\n        limits:\n          memory: 250M\n\n  vote:\n    build:\n      context: https://github.com/ACCOUNT/example-voting-vote\n      dockerfile: Dockerfile\n\n  result:\n    build:\n      context: https://github.com/ACCOUNT/example-voting-result\n      dockerfile: Dockerfile\n\nconfigs:\n  vote_conf:\n    file: ./vote.conf\n\ncontinuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 8080\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_with_continuous_preview.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    x-uffizzi-continuous-preview:\n      delete_preview_after: 12h\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_with_only_line.yml",
    "content": "services\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_with_syntax_error.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world\n    image: nginx\n    x-uffizzi-continuous-preview:\n      delete_preview_after: 12h\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_with_volumes.yml",
    "content": "services:\n  web:\n    image: web_service:latest\n    volumes:\n      - share_data:/var/web/logs\n      - share_data_2:/var/web/logs:ro\n\n  nginx:\n    image: nginx:latest\n    volumes:\n      - source: share_data\n        target: /some_share_data\n        read_only: true\n\n      - source: share_data_2\n        target: /some_share_data_2\n\n      - source: share_data_2\n        target: /some_share_data_22\n\nvolumes:\n  share_data:\n  share_data_2:\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_without_image.yml",
    "content": "services:\n  nginx:\n    environment:\n      TEST: 123\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/compose_without_services.yml",
    "content": "x-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/config_file_dependencies.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote.conf\n        target: /etc/nginx/conf.d\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 8080\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/account_custom_image.yml",
    "content": "services:\n  nginx:\n    image: account/custom_image:v1.0\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_auto_deploy_off.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    deploy:\n      x-uffizzi-auto-deploy-updates: false\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_config_files.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote.conf\n        target: /etc/nginx\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_config_invalid_ingress_service.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi-ingress:\n  service: test\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_long_syntax.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote_conf\n        target: /etc/nginx\n\nconfigs:\n  vote_conf:\n    file: ./configs/vote.conf\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_long_syntax_without_target.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote_conf\n\nconfigs:\n  vote_conf:\n    file: ./etc/nginx/vote.conf\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_short_syntax.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - vote_conf\n\nconfigs:\n  vote_conf:\n    file: ./vote.conf\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_short_syntax_invalid_path.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - vote_conf\n\nconfigs:\n  vote_conf:\n    file: ''\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_configs_short_syntax_unknown_config.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    configs:\n      - unknown_config\n\nconfigs:\n  vote_conf:\n    file: ./etc/nginx/vote.conf\n  defaulf_conf:\n    file: ./etc/nginx/default.conf\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_delete_after_integer.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n\nx-uffizzi-continuous-preview:\n  delete_preview_after: 99\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_hours.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n\nx-uffizzi-continuous-preview:\n  delete_preview_after: h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_max.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n\nx-uffizzi-continuous-preview:\n  delete_preview_after: 99999999h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_min.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n\nx-uffizzi-continuous-preview:\n  delete_preview_after: 0h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_postfix.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n\nx-uffizzi-continuous-preview:\n  delete_preview_after: 24m\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    env_file: .env\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file_duplicates.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    env_file:\n      - ./.env\n      - .env\n      - .env/\n      - .env\n      - infra/secrets.env\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file_empty.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    env_file:\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_env_file_empty_in_array.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    env_file:\n      - .env\n      -\n      - .local.env\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_envs.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    environment:\n      MULTILINE_KEY: |\n        -----BEGIN RSA PRIVATE KEY-----\n        MIIEowIBAAKCAQEAvt9uoL0Ke7He/EmdxhfBn3hwg0WLASk6zoxLlTcvNpCMUEED\n        QkUUQX9S1PCUDuW8Urr87ZRCnzurnj7EGjFSeKS7qt4kesIfaAsyBC8Sf8D7IZ7d\n        QuOS/f1R5Qt1ofSSmgmUvg6rROJs7CqtI0YLYysr2Dn2fuTZf4Jc\n        -----END RSA PRIVATE KEY-----\n      SECRET: 'secret'\n      RAILS_WORKERS_COUNT: 0\n      APP_URL: 'http://lvh.me:7000'\n      APP_PASSWORD: ''\n      RUBYOPT: -W:no-deprecated -W:no-experimental\n      CDN_HOST: https://cdn.cloud?k=1\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_ingress_port_non_integer.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: '2345'\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_deploy_auto.yml",
    "content": "services:\n  hello-world:\n    image: nginx\n    deploy:\n      x-uffizzi-auto-deploy-updates: TEST\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    deploy:\n      resources:\n        limits:\n          memory: 3g\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory_min_value.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    deploy:\n      resources:\n        limits:\n          memory: 5M\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory_postfix.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    deploy:\n      resources:\n        limits:\n          memory: 5TEST\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_memory_type.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    deploy:\n      resources:\n        limits:\n          memory: 100\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_invalid_port.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 99999\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_uffizzi_ingress.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi:\n  ingress:\n    service: nginx\n    port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_with_continuous_preview_without_x-uffizzi.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    continuous_preview:\n      delete_preview_after: 10h\n      share_to_github: false\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n\ncontinuous_preview:\n  delete_preview_after: 10h\n  share_to_github: false"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_with_ingress_without_x-uffizzi.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\ningress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_ingress.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_ingress_port.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi-ingress:\n  service: nginx\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_ingress_service.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n\nx-uffizzi-ingress:\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/nginx_without_tag.yml",
    "content": "services:\n  nginx:\n    image: nginx\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets.yml",
    "content": "services:\n  postgres:\n    image: postgres\n    secrets:\n      - postgres_user\n      - postgres_password\n\nsecrets:\n  postgres_user:\n    external: true\n    name: POSTGRES_USER\n  postgres_password:\n    external: true\n    name: POSTGRES_PASSWORD\n\nx-uffizzi-ingress:\n  service: postgres\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_duplicates.yml",
    "content": "services:\n  postgres:\n    image: postgres\n    secrets:\n      - postgres_user\n      - postgres_password\n\nsecrets:\n  postgres_user:\n    external: true\n    name: POSTGRES_USER\n  postgres_password:\n    external: true\n    name: POSTGRES_USER\n\nx-uffizzi-ingress:\n  service: postgres\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_unknown.yml",
    "content": "services:\n  postgres:\n    image: postgres:latest\n    secrets:\n      - postgres\n\nsecrets:\n  postgres_user:\n    external: true\n    name: POSTGRES_USER\n  postgres_password:\n    external: true\n    name: POSTGRES_PASSWORD\n\nx-uffizzi-ingress:\n  service: postgres\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_without_external.yml",
    "content": "services:\n  postgres:\n    image: postgres\n    secrets:\n      - postgres_user\n      - postgres_password\n\nsecrets:\n  postgres_user:\n    external: true\n    name: POSTGRES_USER\n  postgres_password:\n    external: false\n    name: POSTGRES_PASSWORD\n\nx-uffizzi-ingress:\n  service: postgres\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/postgres_secrets_without_name.yml",
    "content": "services:\n  postgres:\n    image: postgres\n    secrets:\n      - postgres_user\n      - postgres_password\n\nsecrets:\n  postgres_user:\n    external: true\n    name: POSTGRES_USER\n  postgres_password:\n    external: true\n\nx-uffizzi-ingress:\n  service: postgres\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/volumes_anonymous.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    volumes:\n      - /var/web/logs_1\n      - /var/web/logs_2\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/dockerhub_services/volumes_named.yml",
    "content": "services:\n  nginx:\n    image: nginx:latest\n    volumes:\n      - share_data:/var/web/logs_1\n\nvolumes:\n  share_data:\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/github_services/hello_world.yml",
    "content": "services:\n  hello-world:\n    build:\n      context: https://github.com/ACCOUNT/hello-world\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/google_services/cloudsql.yml",
    "content": "services:\n  nginx:\n    image: gcr.io/project-name/nginx:latest\n\n  sql-proxy:\n    image: gcr.io/cloudsql-docker/gce-proxy:1.19.1\n    command:\n      - '/cloud_sql_proxy'\n      - '-dir=/cloudsql'\n      - '-instances=instance=tcp:5432'\n      - '-credential_file=/sql-proxy.json'\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/google_services/cloudsql_entrypoint.yml",
    "content": "services:\n  nginx:\n    image: gcr.io/project-name/nginx:latest\n\n  sql-proxy:\n    image: gcr.io/cloudsql-docker/gce-proxy:1.19.1\n    entrypoint: /cloud_sql_proxy -dir=/cloudsql -instances=instance=tcp:5432 -credential_file=/sql-proxy.json\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/google_services/nginx.yml",
    "content": "services:\n  nginx:\n    image: gcr.io/project-name/nginx:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/array_command_success.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost\"]\n      interval: 1m30s\n      timeout: 10s\n      retries: 3\n      start_period: 40s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/disabled_healthcheck.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      test: [\"NONE\"]\n      interval: 1m30s\n      timeout: 10s\n      retries: 3\n      start_period: 40s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/healthcheck_with_disable.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      disable: true\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/invalid_command.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      test: 1\n      interval: 1m30s\n      timeout: 10s\n      retries: 3\n      start_period: 40s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/invalid_interval.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost\"]\n      interval: 1p30w\n      timeout: 10s\n      retries: 3\n      start_period: 40s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/invalid_options.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      interval: 1m30s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/invalid_retries.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost\"]\n      interval: 1m30s\n      timeout: 10s\n      retries: '3'\n      start_period: 40s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/healthcheck/string_command_success.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  hello-world:\n    image: nginx\n    healthcheck:\n      test: curl -f http://localhost\n      interval: 1m30s\n      timeout: 10s\n      retries: 3\n      start_period: 40s\n\nx-uffizzi:\n  continuous_preview:\n    delete_preview_after: 10h\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/compose_files/invalid_service.yml",
    "content": "services:\n  hello-^&*world:\n    image: nginx\n\nx-uffizzi-ingress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/controller/cluster_asleep.json",
    "content": "{\n  \"name\": \"uffizzi-test-cluster-vcluster\",\n  \"namespace\": \"uffizzi-test-cluster\",\n  \"uid\": \"90cffab2-812b-418f-a84b-5dcdded15bfd\",\n  \"status\": {\n    \"ready\": true,\n    \"sleep\": true,\n    \"kubeConfig\": \"test-kubeconfig\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/controller/cluster_awake.json",
    "content": "{\n  \"name\": \"uffizzi-test-cluster-vcluster\",\n  \"namespace\": \"uffizzi-test-cluster\",\n  \"uid\": \"90cffab2-812b-418f-a84b-5dcdded15bfd\",\n  \"status\": {\n    \"ready\": true,\n    \"sleep\": false,\n    \"kubeConfig\": \"test-kubeconfig\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/controller/cluster_not_ready.json",
    "content": "{\n  \"name\": \"uffizzi-test-cluster-vcluster\",\n  \"namespace\": \"uffizzi-test-cluster\",\n  \"uid\": \"90cffab2-812b-418f-a84b-5dcdded15bfd\",\n  \"status\": {\n    \"ready\": false,\n    \"kubeConfig\": \"\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/controller/cluster_ready.json",
    "content": "{\n  \"name\": \"uffizzi-test-cluster-vcluster\",\n  \"namespace\": \"uffizzi-test-cluster\",\n  \"uid\": \"90cffab2-812b-418f-a84b-5dcdded15bfd\",\n  \"status\": {\n    \"ready\": true,\n    \"kubeConfig\": \"test-kubeconfig\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/controller/deployment_containers.json",
    "content": "[\n  {\n    \"metadata\": {\n      \"name\": \"app-test-env-1-6f4c8896d-xrcfq\",\n      \"generateName\": \"app-test-env-1-6f4c8896d-\",\n      \"namespace\": \"test-env-1\",\n      \"uid\": \"7ed22d85-495e-457f-9916-880b54f8d1bc\",\n      \"resourceVersion\": \"72170704\",\n      \"creationTimestamp\": \"2021-08-20T13:05:33Z\",\n      \"labels\": {\n        \"app\": \"app-test-env-1\",\n        \"app.kubernetes.io/managed-by\": \"uffizzi\",\n        \"pod-template-hash\": \"6f4c8896d\"\n      },\n      \"annotations\": {\n        \"kubectl.kubernetes.io/restartedAt\": \"2021-08-20T13:05:33Z\"\n      },\n      \"ownerReferences\": [\n        {\n          \"apiVersion\": \"apps/v1\",\n          \"kind\": \"ReplicaSet\",\n          \"name\": \"app-test-env-1-6f4c8896d\",\n          \"uid\": \"81df5b4b-acbd-4384-9e97-9dc663275728\",\n          \"controller\": true,\n          \"blockOwnerDeletion\": true\n        }\n      ]\n    },\n    \"spec\": {\n      \"containers\": [\n        {\n          \"name\": \"f03d008a48\",\n          \"image\": \"uffizzitest/webhooks-test-app:latest\",\n          \"ports\": [\n            {\n              \"name\": \"default-port\",\n              \"containerPort\": 80,\n              \"protocol\": \"TCP\"\n            }\n          ],\n          \"env\": [\n            {\n              \"name\": \"PORT\",\n              \"value\": \"80\"\n            }\n          ],\n          \"resources\": {\n            \"limits\": {\n              \"cpu\": \"25m\",\n              \"memory\": \"250Mi\"\n            },\n            \"requests\": {\n              \"cpu\": \"25m\",\n              \"memory\": \"250Mi\"\n            }\n          },\n          \"startupProbe\": {\n            \"tcpSocket\": {\n              \"port\": 80\n            },\n            \"initialDelaySeconds\": 10,\n            \"timeoutSeconds\": 1,\n            \"periodSeconds\": 15,\n            \"successThreshold\": 1,\n            \"failureThreshold\": 80\n          },\n          \"terminationMessagePath\": \"/dev/termination-log\",\n          \"terminationMessagePolicy\": \"File\",\n          \"imagePullPolicy\": \"Always\",\n          \"controllerName\" : \"f03d008a48\"\n        }\n      ],\n      \"restartPolicy\": \"Always\",\n      \"terminationGracePeriodSeconds\": 30,\n      \"dnsPolicy\": \"ClusterFirst\",\n      \"serviceAccountName\": \"default\",\n      \"serviceAccount\": \"default\",\n      \"automountServiceAccountToken\": false,\n      \"nodeName\": \"gke-example-default-pool-8139be1d-wmaj\",\n      \"securityContext\": {},\n      \"imagePullSecrets\": [\n        {\n          \"name\": \"credential-dockerhub-3\"\n        }\n      ],\n      \"schedulerName\": \"default-scheduler\",\n      \"tolerations\": [\n        {\n          \"key\": \"sandbox.gke.io/runtime\",\n          \"operator\": \"Exists\"\n        },\n        {\n          \"key\": \"node.kubernetes.io/not-ready\",\n          \"operator\": \"Exists\",\n          \"effect\": \"NoExecute\",\n          \"tolerationSeconds\": 300\n        },\n        {\n          \"key\": \"node.kubernetes.io/unreachable\",\n          \"operator\": \"Exists\",\n          \"effect\": \"NoExecute\",\n          \"tolerationSeconds\": 300\n        }\n      ],\n      \"priority\": 0,\n      \"enableServiceLinks\": true,\n      \"preemptionPolicy\": \"PreemptLowerPriority\"\n    },\n    \"status\": {\n      \"phase\": \"Running\",\n      \"conditions\": [\n        {\n          \"type\": \"Initialized\",\n          \"status\": \"True\",\n          \"lastProbeTime\": null,\n          \"lastTransitionTime\": \"2021-08-20T13:05:33Z\"\n        },\n        {\n          \"type\": \"Ready\",\n          \"status\": \"False\",\n          \"lastProbeTime\": null,\n          \"lastTransitionTime\": \"2021-08-20T13:05:33Z\",\n          \"reason\": \"ContainersNotReady\",\n          \"message\": \"containers with unready status: [f03d008a48]\"\n        },\n        {\n          \"type\": \"ContainersReady\",\n          \"status\": \"False\",\n          \"lastProbeTime\": null,\n          \"lastTransitionTime\": \"2021-08-20T13:05:33Z\",\n          \"reason\": \"ContainersNotReady\",\n          \"message\": \"containers with unready status: [f03d008a48]\"\n        },\n        {\n          \"type\": \"PodScheduled\",\n          \"status\": \"True\",\n          \"lastProbeTime\": null,\n          \"lastTransitionTime\": \"2021-08-20T13:05:33Z\"\n        }\n      ],\n      \"hostIP\": \"10.128.0.18\",\n      \"podIP\": \"10.108.4.232\",\n      \"podIPs\": [\n        {\n          \"ip\": \"10.108.4.232\"\n        }\n      ],\n      \"startTime\": \"2021-08-20T13:05:33Z\",\n      \"containerStatuses\": [\n        {\n          \"name\": \"f03d008a48\",\n          \"state\": {\n            \"running\": {\n              \"startedAt\": \"2021-08-20T13:05:57Z\"\n            }\n          },\n          \"lastState\": {},\n          \"ready\": false,\n          \"restartCount\": 0,\n          \"image\": \"uffizzitest/webhooks-test-app:latest\",\n          \"imageID\": \"docker-pullable://uffizzitest/webhooks-test-app@sha256:a1efdad4c909517ae9eecd1a362d6f30046822f8e47793d0211aa0d5a3cc2b16\",\n          \"containerID\": \"docker://4b04f42cb9c9c24837b9ae4e458c57a31abebc2f03529447aae5350834185c80\",\n          \"started\": true\n        }\n      ],\n      \"qosClass\": \"Guaranteed\"\n    }\n  }\n]\n"
  },
  {
    "path": "core/test/fixtures/files/controller/deployment_containers_with_error.json",
    "content": "[\n    {\n        \"metadata\": {\n            \"name\": \"app-deployment-1-54c7f5f884-xcn5h\",\n            \"generate_name\": \"app-deployment-1-54c7f5f884-\",\n            \"namespace\": \"deployment-1\",\n            \"uid\": \"4992a195-4156-4aa0-a76a-43345621e7ff\",\n            \"resource_version\": \"249176761\",\n            \"creation_timestamp\": \"2022-12-05T18:11:04Z\",\n            \"labels\": {\n                \"app\": \"app-deployment-1\",\n                \"app.kubernetes.io/managed_by\": \"uffizzi\",\n                \"pod_template_hash\": \"54c7f5f884\"\n            },\n            \"annotations\": {\n                \"kubectl.kubernetes.io/restarted_at\": \"2022-12-05T18:11:03Z\"\n            },\n            \"owner_references\": [\n                {\n                    \"api_version\": \"apps/v1\",\n                    \"kind\": \"ReplicaSet\",\n                    \"name\": \"app-deployment-1-54c7f5f884\",\n                    \"uid\": \"e7615d70-e805-423c-91f4-a6679b194365\",\n                    \"controller\": true,\n                    \"block_owner_deletion\": true\n                }\n            ]\n        },\n        \"spec\": {\n            \"containers\": [\n                {\n                    \"name\": \"f03d008a48\",\n                    \"image\": \"library/nginx:latest\",\n                    \"args\": [\n                        \"exit\"\n                    ],\n                    \"ports\": [\n                        {\n                            \"name\": \"default-port\",\n                            \"container_port\": 80,\n                            \"protocol\": \"TCP\"\n                        }\n                    ],\n                    \"env\": [\n                        {\n                            \"name\": \"PORT\",\n                            \"value\": \"80\"\n                        },\n                        {\n                            \"name\": \"UFFIZZI_URL\",\n                            \"value\": \"https://deployment-1-test-preview-jymy.localhost:7000\"\n                        },\n                        {\n                            \"name\": \"UFFIZZI_DOMAIN\",\n                            \"value\": \"deployment-1-test-preview-jymy.localhost:7000\"\n                        }\n                    ],\n                    \"resources\": {\n                        \"limits\": {\n                            \"cpu\": \"1\",\n                            \"memory\": \"125Mi\"\n                        },\n                        \"requests\": {\n                            \"cpu\": \"0\",\n                            \"memory\": \"125Mi\"\n                        }\n                    },\n                    \"startup_probe\": {\n                        \"tcp_socket\": {\n                            \"port\": 80\n                        },\n                        \"initial_delay_seconds\": 10,\n                        \"timeout_seconds\": 1,\n                        \"period_seconds\": 15,\n                        \"success_threshold\": 1,\n                        \"failure_threshold\": 80\n                    },\n                    \"termination_message_path\": \"/dev/termination-log\",\n                    \"termination_message_policy\": \"File\",\n                    \"image_pull_policy\": \"Always\"\n                }\n            ],\n            \"restart_policy\": \"Always\",\n            \"termination_grace_period_seconds\": 30,\n            \"dns_policy\": \"ClusterFirst\",\n            \"node_selector\": {\n                \"sandbox.gke.io/runtime\": \"gvisor\"\n            },\n            \"service_account_name\": \"default\",\n            \"service_account\": \"default\",\n            \"automount_service_account_token\": false,\n            \"node_name\": \"gke-uffizzi-client-sandbox-588e8350-tk79\",\n            \"security_context\": {},\n            \"scheduler_name\": \"default-scheduler\",\n            \"tolerations\": [\n                {\n                    \"key\": \"sandbox.gke.io/runtime\",\n                    \"operator\": \"Exists\"\n                },\n                {\n                    \"key\": \"node.kubernetes.io/not-ready\",\n                    \"operator\": \"Exists\",\n                    \"effect\": \"NoExecute\",\n                    \"toleration_seconds\": 300\n                },\n                {\n                    \"key\": \"node.kubernetes.io/unreachable\",\n                    \"operator\": \"Exists\",\n                    \"effect\": \"NoExecute\",\n                    \"toleration_seconds\": 300\n                }\n            ],\n            \"priority\": 0,\n            \"enable_service_links\": true,\n            \"preemption_policy\": \"PreemptLowerPriority\"\n        },\n        \"status\": {\n            \"phase\": \"Running\",\n            \"conditions\": [\n                {\n                    \"type\": \"Initialized\",\n                    \"status\": \"True\",\n                    \"last_probe_time\": null,\n                    \"last_transition_time\": \"2022-12-05T18:11:04Z\"\n                },\n                {\n                    \"type\": \"Ready\",\n                    \"status\": \"False\",\n                    \"last_probe_time\": null,\n                    \"last_transition_time\": \"2022-12-05T18:11:04Z\",\n                    \"reason\": \"ContainersNotReady\",\n                    \"message\": \"containers with unready status: [f03d008a48]\"\n                },\n                {\n                    \"type\": \"ContainersReady\",\n                    \"status\": \"False\",\n                    \"last_probe_time\": null,\n                    \"last_transition_time\": \"2022-12-05T18:11:04Z\",\n                    \"reason\": \"ContainersNotReady\",\n                    \"message\": \"containers with unready status: [f03d008a48]\"\n                },\n                {\n                    \"type\": \"PodScheduled\",\n                    \"status\": \"True\",\n                    \"last_probe_time\": null,\n                    \"last_transition_time\": \"2022-12-05T18:11:04Z\"\n                }\n            ],\n            \"host_ip\": \"10.128.0.49\",\n            \"pod_ip\": \"10.20.6.38\",\n            \"pod_i_ps\": [\n                {\n                    \"ip\": \"10.20.6.38\"\n                }\n            ],\n            \"start_time\": \"2022-12-05T18:11:04Z\",\n            \"container_statuses\": [\n                {\n                    \"name\": \"f03d008a48\",\n                    \"state\": {\n                        \"waiting\": {\n                            \"reason\": \"CrashLoopBackOff\",\n                            \"message\": \"back-off 40s restarting failed container=f03d008a48 pod=app-deployment-1-54c7f5f884-xcn5h_deployment-1(4992a195-4156-4aa0-a76a-43345621e7ff)\"\n                        }\n                    },\n                    \"last_state\": {\n                        \"terminated\": {\n                            \"exit_code\": 127,\n                            \"reason\": \"Error\",\n                            \"started_at\": \"2022-12-05T18:11:46Z\",\n                            \"finished_at\": \"2022-12-05T18:11:46Z\",\n                            \"container_id\": \"containerd://f467da4f32e086c93a92a54917c054ef15e8ab84413b843495c12a277e94b2dd\"\n                        }\n                    },\n                    \"ready\": false,\n                    \"restart_count\": 3,\n                    \"image\": \"docker.io/library/nginx:latest\",\n                    \"image_id\": \"docker.io/library/nginx@sha256:e209ac2f37c70c1e0e9873a5f7231e91dcd83fdf1178d8ed36c2ec09974210ba\",\n                    \"container_id\": \"containerd://f467da4f32e086c93a92a54917c054ef15e8ab84413b843495c12a277e94b2dd\",\n                    \"started\": false\n                }\n            ],\n            \"qos_class\": \"Burstable\"\n        }\n    }\n]\n"
  },
  {
    "path": "core/test/fixtures/files/controller/deployments.json",
    "content": "{\n  \"metadata\": {\n    \"name\": \"test-env-1\",\n    \"uid\": \"e7edcd16-0708-4d84-946b-c56db6127916\",\n    \"resourceVersion\": \"72171313\",\n    \"creationTimestamp\": \"2021-08-20T13:05:25Z\",\n    \"labels\": {\n      \"app.kubernetes.io/managed-by\": \"uffizzi\",\n      \"kind\": \"standard\",\n      \"name\": \"test-env-1\"\n    },\n    \"annotations\": {\n      \"errors\": \"\",\n      \"ingressName\": \"ingress-1629464776\",\n      \"ingress_ip\": \"104.154.78.201\",\n      \"network_connectivity\": \"\",\n      \"serviceName\": \"service-1629464734\"\n    },\n    \"managedFields\": [\n      {\n        \"manager\": \"controller\",\n        \"operation\": \"Update\",\n        \"apiVersion\": \"v1\",\n        \"time\": \"2021-08-20T13:06:59Z\",\n        \"fieldsType\": \"FieldsV1\",\n        \"fieldsV1\": {\n          \"f:metadata\": {\n            \"f:annotations\": {\n              \".\": {},\n              \"f:errors\": {},\n              \"f:ingressName\": {},\n              \"f:ingress_ip\": {},\n              \"f:network_connectivity\": {},\n              \"f:serviceName\": {}\n            },\n            \"f:labels\": {\n              \".\": {},\n              \"f:app.kubernetes.io/managed-by\": {},\n              \"f:kind\": {},\n              \"f:name\": {}\n            }\n          },\n          \"f:status\": {\n            \"f:phase\": {}\n          }\n        }\n      }\n    ]\n  },\n  \"spec\": {\n    \"finalizers\": [\n      \"kubernetes\"\n    ]\n  },\n  \"status\": {\n    \"phase\": \"Active\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/controller/ingresses.json",
    "content": "{\n  \"metadata\": {\n    \"resource_version\": \"63142264\"\n  },\n  \"items\": [\n    {\n      \"metadata\": {\n        \"name\": \"result-x-default-x-uc-pr-82\",\n        \"namespace\": \"c169515535810170\",\n        \"uid\": \"a46d2a3c-f111-4bc8-8301-b8d51c668059\",\n        \"resource_version\": \"62737159\",\n        \"generation\": 1,\n        \"creation_timestamp\": \"2023-09-19T20:30:07Z\",\n        \"labels\": {\n          \"vcluster.loft.sh/managed_by\": \"uc-pr-82\",\n          \"vcluster.loft.sh/namespace\": \"default\"\n        },\n        \"annotations\": {\n          \"app.uffizzi.com/ingress_sync\": \"true\",\n          \"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\",\n          \"vcluster.loft.sh/managed_annotations\": \"app.uffizzi.com/ingress-sync\\nkubectl.kubernetes.io/last-applied-configuration\",\n          \"vcluster.loft.sh/object_name\": \"result\",\n          \"vcluster.loft.sh/object_namespace\": \"default\"\n        },\n        \"owner_references\": [\n          {\n            \"api_version\": \"v1\",\n            \"kind\": \"Service\",\n            \"name\": \"uc-pr-82\",\n            \"uid\": \"8c502e06-1e48-40bd-a36c-96ecfd7ac414\"\n          }\n        ],\n        \"managed_fields\": [\n          {\n            \"manager\": \"plugin\",\n            \"operation\": \"Update\",\n            \"api_version\": \"networking.k8s.io/v1\",\n            \"time\": \"2023-09-19T20:30:07Z\",\n            \"fields_type\": \"FieldsV1\",\n            \"fields_v1\": {\n              \"f:metadata\": {\n                \"f:annotations\": {\n                  \".\": {},\n                  \"f:app.uffizzi.com/ingress_sync\": {},\n                  \"f:kubectl.kubernetes.io/last_applied_configuration\": {},\n                  \"f:vcluster.loft.sh/managed_annotations\": {},\n                  \"f:vcluster.loft.sh/object_name\": {},\n                  \"f:vcluster.loft.sh/object_namespace\": {}\n                },\n                \"f:labels\": {\n                  \".\": {},\n                  \"f:vcluster.loft.sh/managed_by\": {},\n                  \"f:vcluster.loft.sh/namespace\": {}\n                },\n                \"f:owner_references\": {\n                  \".\": {},\n                  \"k:{\\\"uid\\\":\\\"8c502e06_1e48_40bd_a36c_96ecfd7ac414\\\"}\": {}\n                }\n              },\n              \"f:spec\": {\n                \"f:ingress_class_name\": {},\n                \"f:rules\": {},\n                \"f:tls\": {}\n              }\n            }\n          },\n          {\n            \"manager\": \"nginx-ingress-controller\",\n            \"operation\": \"Update\",\n            \"api_version\": \"networking.k8s.io/v1\",\n            \"time\": \"2023-09-19T22:14:31Z\",\n            \"fields_type\": \"FieldsV1\",\n            \"fields_v1\": {\n              \"f:status\": {\n                \"f:load_balancer\": {\n                  \"f:ingress\": {}\n                }\n              }\n            },\n            \"subresource\": \"status\"\n          }\n        ]\n      },\n      \"spec\": {\n        \"ingress_class_name\": \"uffizzi\",\n        \"tls\": [\n          {\n            \"hosts\": [\n              \"result-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com\"\n            ]\n          }\n        ],\n        \"rules\": [\n          {\n            \"host\": \"result-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com\",\n            \"http\": {\n              \"paths\": [\n                {\n                  \"path\": \"/\",\n                  \"path_type\": \"Prefix\",\n                  \"backend\": {\n                    \"service\": {\n                      \"name\": \"result-x-default-x-uc-pr-82\",\n                      \"port\": {\n                        \"number\": 5001\n                      }\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        ]\n      },\n      \"status\": {\n        \"load_balancer\": {\n          \"ingress\": [\n            {\n              \"ip\": \"34.134.99.100\"\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"metadata\": {\n        \"name\": \"uc-pr-82\",\n        \"namespace\": \"c169515535810170\",\n        \"uid\": \"ac012e17-b76c-4630-bc76-2039d1ff6270\",\n        \"resource_version\": \"62737153\",\n        \"generation\": 1,\n        \"creation_timestamp\": \"2023-09-19T20:29:21Z\",\n        \"labels\": {\n          \"app.kubernetes.io/managed_by\": \"Helm\",\n          \"helm.toolkit.fluxcd.io/name\": \"uc-pr-82\",\n          \"helm.toolkit.fluxcd.io/namespace\": \"c169515535810170\"\n        },\n        \"annotations\": {\n          \"app.uffizzi.com/ingress_sync\": \"true\",\n          \"meta.helm.sh/release_name\": \"uc-pr-82\",\n          \"meta.helm.sh/release_namespace\": \"c169515535810170\",\n          \"nginx.ingress.kubernetes.io/backend_protocol\": \"HTTPS\",\n          \"nginx.ingress.kubernetes.io/ssl_passthrough\": \"true\",\n          \"nginx.ingress.kubernetes.io/ssl_redirect\": \"true\"\n        },\n        \"managed_fields\": [\n          {\n            \"manager\": \"helm-controller\",\n            \"operation\": \"Update\",\n            \"api_version\": \"networking.k8s.io/v1\",\n            \"time\": \"2023-09-19T20:29:21Z\",\n            \"fields_type\": \"FieldsV1\",\n            \"fields_v1\": {\n              \"f:metadata\": {\n                \"f:annotations\": {\n                  \".\": {},\n                  \"f:app.uffizzi.com/ingress_sync\": {},\n                  \"f:meta.helm.sh/release_name\": {},\n                  \"f:meta.helm.sh/release_namespace\": {},\n                  \"f:nginx.ingress.kubernetes.io/backend_protocol\": {},\n                  \"f:nginx.ingress.kubernetes.io/ssl_passthrough\": {},\n                  \"f:nginx.ingress.kubernetes.io/ssl_redirect\": {}\n                },\n                \"f:labels\": {\n                  \".\": {},\n                  \"f:app.kubernetes.io/managed_by\": {},\n                  \"f:helm.toolkit.fluxcd.io/name\": {},\n                  \"f:helm.toolkit.fluxcd.io/namespace\": {}\n                }\n              },\n              \"f:spec\": {\n                \"f:rules\": {}\n              }\n            }\n          },\n          {\n            \"manager\": \"nginx-ingress-controller\",\n            \"operation\": \"Update\",\n            \"api_version\": \"networking.k8s.io/v1\",\n            \"time\": \"2023-09-19T22:14:30Z\",\n            \"fields_type\": \"FieldsV1\",\n            \"fields_v1\": {\n              \"f:status\": {\n                \"f:load_balancer\": {\n                  \"f:ingress\": {}\n                }\n              }\n            },\n            \"subresource\": \"status\"\n          }\n        ]\n      },\n      \"spec\": {\n        \"ingress_class_name\": \"uffizzi\",\n        \"rules\": [\n          {\n            \"host\": \"pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com\",\n            \"http\": {\n              \"paths\": [\n                {\n                  \"path\": \"/\",\n                  \"path_type\": \"ImplementationSpecific\",\n                  \"backend\": {\n                    \"service\": {\n                      \"name\": \"uc-pr-82\",\n                      \"port\": {\n                        \"name\": \"https\"\n                      }\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        ]\n      },\n      \"status\": {\n        \"load_balancer\": {\n          \"ingress\": [\n            {\n              \"ip\": \"34.134.99.100\"\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"metadata\": {\n        \"name\": \"vote-x-default-x-uc-pr-82\",\n        \"namespace\": \"c169515535810170\",\n        \"uid\": \"114b3dc2-6644-4d88-89f2-bbaa0b141787\",\n        \"resource_version\": \"62737156\",\n        \"generation\": 1,\n        \"creation_timestamp\": \"2023-09-19T20:30:07Z\",\n        \"labels\": {\n          \"vcluster.loft.sh/managed_by\": \"uc-pr-82\",\n          \"vcluster.loft.sh/namespace\": \"default\"\n        },\n        \"annotations\": {\n          \"app.uffizzi.com/ingress_sync\": \"true\",\n          \"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\",\n          \"vcluster.loft.sh/managed_annotations\": \"app.uffizzi.com/ingress-sync\\nkubectl.kubernetes.io/last-applied-configuration\",\n          \"vcluster.loft.sh/object_name\": \"vote\",\n          \"vcluster.loft.sh/object_namespace\": \"default\"\n        },\n        \"owner_references\": [\n          {\n            \"api_version\": \"v1\",\n            \"kind\": \"Service\",\n            \"name\": \"uc-pr-82\",\n            \"uid\": \"8c502e06-1e48-40bd-a36c-96ecfd7ac414\"\n          }\n        ],\n        \"managed_fields\": [\n          {\n            \"manager\": \"plugin\",\n            \"operation\": \"Update\",\n            \"api_version\": \"networking.k8s.io/v1\",\n            \"time\": \"2023-09-19T20:30:07Z\",\n            \"fields_type\": \"FieldsV1\",\n            \"fields_v1\": {\n              \"f:metadata\": {\n                \"f:annotations\": {\n                  \".\": {},\n                  \"f:app.uffizzi.com/ingress_sync\": {},\n                  \"f:kubectl.kubernetes.io/last_applied_configuration\": {},\n                  \"f:vcluster.loft.sh/managed_annotations\": {},\n                  \"f:vcluster.loft.sh/object_name\": {},\n                  \"f:vcluster.loft.sh/object_namespace\": {}\n                },\n                \"f:labels\": {\n                  \".\": {},\n                  \"f:vcluster.loft.sh/managed_by\": {},\n                  \"f:vcluster.loft.sh/namespace\": {}\n                },\n                \"f:owner_references\": {\n                  \".\": {},\n                  \"k:{\\\"uid\\\":\\\"8c502e06_1e48_40bd_a36c_96ecfd7ac414\\\"}\": {}\n                }\n              },\n              \"f:spec\": {\n                \"f:ingress_class_name\": {},\n                \"f:rules\": {},\n                \"f:tls\": {}\n              }\n            }\n          },\n          {\n            \"manager\": \"nginx-ingress-controller\",\n            \"operation\": \"Update\",\n            \"api_version\": \"networking.k8s.io/v1\",\n            \"time\": \"2023-09-19T22:14:30Z\",\n            \"fields_type\": \"FieldsV1\",\n            \"fields_v1\": {\n              \"f:status\": {\n                \"f:load_balancer\": {\n                  \"f:ingress\": {}\n                }\n              }\n            },\n            \"subresource\": \"status\"\n          }\n        ]\n      },\n      \"spec\": {\n        \"ingress_class_name\": \"uffizzi\",\n        \"tls\": [\n          {\n            \"hosts\": [\n              \"vote-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com\"\n            ]\n          }\n        ],\n        \"rules\": [\n          {\n            \"host\": \"vote-default-pr-82-c169515535810170.uclusters.app.qa-gke.uffizzi.com\",\n            \"http\": {\n              \"paths\": [\n                {\n                  \"path\": \"/\",\n                  \"path_type\": \"Prefix\",\n                  \"backend\": {\n                    \"service\": {\n                      \"name\": \"vote-x-default-x-uc-pr-82\",\n                      \"port\": {\n                        \"number\": 5000\n                      }\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        ]\n      },\n      \"status\": {\n        \"load_balancer\": {\n          \"ingress\": [\n            {\n              \"ip\": \"34.134.99.100\"\n            }\n          ]\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "core/test/fixtures/files/controller/logs.json",
    "content": "{ \"logs\":\n  [\n    \"2022-11-14T11:46:55.474292870Z /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration\"\n  ]\n}\n"
  },
  {
    "path": "core/test/fixtures/files/dockerhub/digest.json",
    "content": "{  \n  \"status\": 200,\n  \"headers\": {\n    \"content-length\": 1574,\n    \"content-type\": \"application/vnd.docker.distribution.manifest.v2+json\",\n    \"docker-content-digest\": \"sha256:cd0c68c5479f2db4b9e2c5fbfdb7a8acb77625322dd5b474578515422d3ddb59\",\n    \"docker-distribution-api-version\": \"registry/2.0\",\n    \"etag\": \"sha256:cd0c68c5479f2db4b9e2c5fbfdb7a8acb77625322dd5b474578515422d3ddb59\",\n    \"date\": \"Sun, 22 Aug 2021 10:11:10 GMT\",\n    \"strict-transport-security\": \"max-age=31536000\",\n    \"ratelimit-limit\": \"100;w=21600\",\n    \"ratelimit-remaining\": \"100;w=21600\"\n  },\n  \"body\": {\n     \"schemaVersion\": 2,\n     \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n     \"config\": {\n        \"mediaType\": \"application/vnd.docker.container.image.v1+json\",\n        \"size\": 7699,\n        \"digest\": \"sha256:ddcca4b8a6f0367b5de2764dfe76b0a4bfa6d75237932185923705da47004347\"\n     },\n     \"layers\": [\n        {\n           \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n           \"size\": 27145985,\n           \"digest\": \"sha256:e1acddbe380c63f0de4b77d3f287b7c81cd9d89563a230692378126b46ea6546\"\n        },\n        {\n           \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n           \"size\": 1732,\n           \"digest\": \"sha256:a31098369fccd4adaac914a87e5def0f96dc1dbc385921d38db1f1f4cef8242a\"\n        },\n        {\n           \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n           \"size\": 1417929,\n           \"digest\": \"sha256:4a49b0eba86d37bb4fa4d1a9f8826a1222ea4eb7717192674d79341a998febf8\"\n        },\n        {\n           \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n           \"size\": 10116853,\n           \"digest\": \"sha256:fddf1399eface27528e89bb5296dc40fbabe7c6b2186c0fca6dce96ae9c5d9a1\"\n        },\n        {\n           \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n           \"size\": 134,\n           \"digest\": \"sha256:5c6658b59b7200ac5a9ec19f428dc5739a95ea2f6965715443692bff6da9a79d\"\n        },\n        {\n           \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n           \"size\": 408,\n           \"digest\": \"sha256:0b88638a5b778b573dd7c4e3fda17cab7d4b229defd7943a07ac982c65e98cbb\"\n        }\n     ]\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/dockerhub/login_fail.json",
    "content": "{\n  \"status\": 401,\n  \"body\": \"{\\\"detail\\\":\\\"Incorrect authentication credentials\\\"}\\n\\n\",\n  \"response_headers\": {\n    \"date\": \"Thu, 17 Mar 2022 09:49:37 GMT\",\n    \"transfer-encoding\": \"chunked\",\n    \"server\": \"nginx\",\n    \"x-frame-options\": \"deny\",\n    \"x-content-type-options\": \"nosniff\",\n    \"x-xss-protection\": \"1; mode=block\",\n    \"strict-transport-security\": \"max-age=31536000\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/dockerhub/repository.json",
    "content": "{\n  \"user\":\"library\",\n  \"name\":\"nginx\",\n  \"namespace\":\"library\",\n  \"repository_type\":\"image\",\n  \"status\":1,\n  \"description\":\"Official build of Nginx.\",\n  \"is_private\":false,\n  \"is_automated\":false,\n  \"can_edit\":false,\n  \"star_count\":17333,\n  \"pull_count\":6917945936,\n  \"last_updated\":\"2022-08-23T22:01:16.714365Z\",\n  \"date_registered\":\"2014-06-05T19:14:45Z\",\n  \"collaborator_count\":0,\n  \"affiliation\":null,\n  \"hub_user\":\"stackbrew\",\n  \"has_starred\":false,\n  \"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![logo](https://raw.githubusercontent.com/docker-library/docs/01c12653951b2fe592c1f93a13b4e289ada0e3a1/nginx/logo.png)\\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.\",\n  \"permissions\":{\n     \"read\":true,\n     \"write\":false,\n     \"admin\":false\n  },\n  \"media_types\":[\n     \"application/vnd.docker.distribution.manifest.list.v2+json\"\n  ]\n}\n"
  },
  {
    "path": "core/test/fixtures/files/dockerhub/webhooks/push/event_data.json",
    "content": "{\n  \"push_data\": {\n    \"pushed_at\": 1613134095,\n    \"images\": [],\n    \"tag\": \"latest\",\n    \"pusher\": \"uffizzitest\"\n  },\n  \"callback_url\": \"https://registry.hub.docker.com/u/uffizzitest/webhooks-test-app/hook/2i4i3icb013ec45fffigg200jaf2hjf50/\",\n  \"repository\": {\n    \"status\": \"Active\",\n    \"description\": \"\",\n    \"is_trusted\": false,\n    \"full_description\": \"\",\n    \"repo_url\": \"https://hub.docker.com/r/uffizzitest/webhooks-test-app\",\n    \"owner\": \"uffizzitest\",\n    \"is_official\": false,\n    \"is_private\": false,\n    \"name\": \"webhooks-test-app\",\n    \"namespace\": \"uffizzitest\",\n    \"star_count\": 0,\n    \"comment_count\": 0,\n    \"date_created\": 1613133268,\n    \"repo_name\": \"uffizzitest/webhooks-test-app\"\n  },\n  \"webhook\": {\n    \"push_data\": {\n      \"pushed_at\": 1613134095,\n      \"images\": [\n\n      ],\n      \"tag\": \"latest\",\n      \"pusher\": \"uffizzitest\"\n    },\n    \"callback_url\": \"https://registry.hub.docker.com/u/uffizzitest/webhooks-test-app/hook/2i4i3icb013ec45fffigg200jaf2hjf50/\",\n    \"repository\": {\n      \"status\": \"Active\",\n      \"description\": \"\",\n      \"is_trusted\": false,\n      \"full_description\": \"\",\n      \"repo_url\": \"https://hub.docker.com/r/uffizzitest/webhooks-test-app\",\n      \"owner\": \"uffizzitest\",\n      \"is_official\": false,\n      \"is_private\": false,\n      \"name\": \"webhooks-test-app\",\n      \"namespace\": \"uffizzitest\",\n      \"star_count\": 0,\n      \"comment_count\": 0,\n      \"date_created\": 1613133268,\n      \"repo_name\": \"uffizzitest/webhooks-test-app\"\n    }\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/github/compose_files/hello_world_compose_github_container_registry.json",
    "content": "{\n  \"name\": \"uffizzi-compose-github-container-registry.yml.yml\",\n  \"path\": \"uffizzi-compose-github-container-registry.yml.yml\",\n  \"sha\": \"401853dba1c7299da89c6593d9b8347cf6442b1a\",\n  \"size\": 141,\n  \"url\": \"https://api.github.com/repos/ACCOUNT/hello-world/contents/uffizzi-compose-github-container-registry.yml.yml?ref=compose\",\n  \"html_url\": \"https://github.com/ACCOUNT/hello-world/blob/compose/uffizzi-compose-github-container-registry.yml.yml\",\n  \"git_url\": \"https://api.github.com/repos/ACCOUNT/hello-world/git/blobs/401853dba1c7299da89c6593d9b8347cf6442b1a\",\n  \"download_url\": \"https://raw.githubusercontent.com/ACCOUNT/hello-world/compose/uffizzi-compose-github-container-registry.yml.yml\",\n  \"type\": \"file\",\n  \"content\": \"c2VydmljZXM6CiAgaGVsbG8td29ybGQtYToKICAgIGltYWdlOiBnaGNyLmlv\\nL3VmZml6emkvdGVzdC1jb21wb3NlOmxhdGVzdAoKeC11ZmZpenppLWluZ3Jl\\nc3M6CiAgc2VydmljZTogaGVsbG8td29ybGQtYQogIHBvcnQ6IDgwCgp4LXVm\\nZml6emktY29udGludW91c19wcmV2aWV3OgogIGRlcGxveV9wcmV2aWV3X3do\\nZW5fcHVsbF9yZXF1ZXN0X2lzX29wZW5lZDogdHJ1ZQogIGRlbGV0ZV9wcmV2\\naWV3X3doZW5fcHVsbF9yZXF1ZXN0X2lzX2Nsb3NlZDogdHJ1ZQogIGRlbGV0\\nZV9wcmV2aWV3X2FmdGVyOiAxMGgKICBzaGFyZV90b19naXRodWI6IHRydWUK\\nICBkZXBsb3lfcHJldmlld193aGVuX2ltYWdlX3RhZ19pc19jcmVhdGVkOiB0\\ncnVlCiAgZGVsZXRlX3ByZXZpZXdfd2hlbl9pbWFnZV90YWdfaXNfdXBkYXRl\\nZDogdHJ1ZQ==\\n\",\n  \"encoding\": \"base64\",\n  \"_links\": {\n    \"self\": \"https://api.github.com/repos/ACCOUNT/hello-world/contents/uffizzi-compose-github-container-registry.yml.yml?ref=compose\",\n    \"git\": \"https://api.github.com/repos/ACCOUNT/hello-world/git/blobs/401853dba1c7299da89c6593d9b8347cf6442b1a\",\n    \"html\": \"https://github.com/ACCOUNT/hello-world/blob/compose/uffizzi-compose-github-container-registry.yml.yml\"\n  }\n}\n"
  },
  {
    "path": "core/test/fixtures/files/test-compose-full.yml",
    "content": "services:\n  app:\n    image: uffizzicloud/app\n    env_file:\n      - local.env\n      - ./env_files/env_file.env\n    configs:\n      - source: app_conf\n        target: /etc/nginxz\n    volumes:\n      - ./some_app_dir:/var/app/some_dir\n      - ./files/some_app_file:/var/app/some_app_files\n      - ./:/var/entire_app\n      - app_share:/some_app_share:ro\n      - /some_anonymous_dir\n\n  db:\n    image: postgres:latest\n    volumes:\n      - source: ./some_app_dir\n        target: /var/db/some_dir_2\n      - source: ./some_app_dir\n        target: /var/db/some_dir_2\n      - source: ./some_db_dir\n        target: /var/db/some_dir_3\n      - source: ./some_db_file\n        target: /var/db/some_db_files\n      - source: db_share\n        target: /some_db_share\n        read_only: true\n\n  nginx:\n    image: nginx:1.32\n\nconfigs:\n  app_conf:\n    file: ./app.conf\n  default_conf:\n    file: config_files/config_file.conf\n\nvolumes:\n  app_share:\n  db_share:\n\nx-uffizzi:\n  ingress:\n    service: nginx\n    port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/test-compose-success-jfrog.yml",
    "content": "services:\n  webhooks-test-app:\n    image: elnealo.jfrog.io/uffizzi-test-docker/webhook-test-app\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n\nx-uffizzi-ingress:\n  service: webhooks-test-app\n  port: 80\n\nx-uffizzi-continuous-preview:\n  delete_preview_after: 1h\n  delete_preview_when_image_tag_is_updated: true\n  deploy_preview_when_image_tag_is_created: true\n"
  },
  {
    "path": "core/test/fixtures/files/test-compose-success-without-dependencies.yml",
    "content": "services:\n  app:\n    image: uffizzicloud/app\n\n  db:\n    image: postgres:latest\n\n  nginx:\n    image: nginx:1.32\n\nx-uffizzi:\n  ingress:\n    service: nginx\n    port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/test-compose-success.yml",
    "content": "services:\n  hello-world:\n    image: redis\n    env_file:\n      - local.env\n      - ./env_files/env_file.env\n    configs:\n      - source: vote_conf\n        target: /etc/nginxz\n    volumes:\n      - share_data:/var/cache\n      - share_data_2:/var/cache_2:ro\n      - /var/some_dir\n\nconfigs:\n  vote_conf:\n    file: ./vote.conf\n  defaulf_conf:\n    file: config_files/config_file.conf\n\nvolumes:\n  share_data:\n  share_data_2:\n\nx-uffizzi:\n  ingress:\n    service: hello-world\n    port: 80\n    additional_subdomains:\n      - \"example\"\n      - \"example-hostname\"\n"
  },
  {
    "path": "core/test/fixtures/files/test-compose-without-images.yml",
    "content": "services:\n  hello-world:\n    env_file:\n      - local.env\n      - ./env_files/env_file.env\n    configs:\n      - source: vote_conf\n        target: /etc/nginxz\n\nconfigs:\n  vote_conf:\n    file: ./vote.conf\n  defaulf_conf:\n    file: config_files/config_file.conf\n\ningress:\n  service: hello-world\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-amazon.yml",
    "content": "services:\n  hello-world-a:\n    image: 323707565364.dkr.ecr.us-east-1.amazonaws.com/test-compose:latest\n\nx-uffizzi-ingress:\n  service: hello-world-a\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-azure.yml",
    "content": "services:\n  hello-world-a:\n    image: account.azurecr.io/test-compose:latest\n\nx-uffizzi-ingress:\n  service: hello-world-a\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-docker-registry-anonymous.yml",
    "content": "services:\n  hello-world-a:\n    image: ttl.sh/abc:1h\n\nx-uffizzi-ingress:\n  service: hello-world-a\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-dockerhub.yml",
    "content": "services:\n  hello-world-a:\n    image: project1/test-compose:latest\n\nx-uffizzi-ingress:\n  service: hello-world-a\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-ghcr.yml",
    "content": "services:\n  hello-world-a:\n    image: ghcr.io/project1/test-compose:latest\n\nx-uffizzi-ingress:\n  service: hello-world-a\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-google.yml",
    "content": "services:\n  hello-world-a:\n    image: gcr.io/project1/test-compose:latest\n\nx-uffizzi-ingress:\n  service: hello-world-a\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-invalid-service-name.yml",
    "content": "services:\n  hello_world:\n    image: project1/test-compose:latest\n\nx-uffizzi-ingress:\n  service: hello_world\n  port: 80\n\nx-uffizzi-continuous_preview:\n  deploy_preview_when_pull_request_is_opened: true\n  delete_preview_when_pull_request_is_closed: true\n  delete_preview_after: 10h\n  share_to_github: true\n  deploy_preview_when_image_tag_is_created: true\n  delete_preview_when_image_tag_is_updated: true\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-vote-app-docker-with-memory-request.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  postgres:\n    image: postgres:9.6\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n\n  nginx:\n    image: nginx:latest\n    deploy:\n      resources:\n        limits:\n          memory: 4000M\n    configs:\n      - source: vote_conf\n        target: /etc/nginx/conf.d\n    entrypoint: /usr/sbin/nginx-debug\n    command: \n      - \"-g\"\n      - \"daemon off;\"\n\n  worker:\n    image: uffizzicloud/example-worker:latest\n    deploy:\n      resources:\n        limits:\n          memory: 4000M\n\n  vote:\n    image: uffizzicloud/example-vote:latest\n    deploy:\n      resources:\n        limits:\n          memory: 4000M\n\n  result:\n    image: uffizzicloud/example-result:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 8080\n\nconfigs:\n  vote_conf:\n    file: configs/vote.conf\n  defaulf_conf:\n    file: config_files/config_file.conf\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-vote-app-docker.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  postgres:\n    image: postgres:9.6\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote_conf\n        target: /etc/nginx/conf.d\n    entrypoint: /usr/sbin/nginx-debug\n    command: \n      - \"-g\"\n      - \"daemon off;\"\n\n  worker:\n    image: uffizzicloud/example-worker:latest\n    deploy:\n      resources:\n        limits:\n          memory: 250M\n\n  vote:\n    image: uffizzicloud/example-vote:latest\n\n  result:\n    image: uffizzicloud/example-result:latest\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 8080\n\nconfigs:\n  vote_conf:\n    file: configs/vote.conf\n  defaulf_conf:\n    file: config_files/config_file.conf\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-vote-app-with-command-as-string.yml",
    "content": "services:\n  postgres:\n    image: postgres:12\n    command: postgres -c jit=off\n    restart: always\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_PASSWORD: postgrespassword\n  nginx:\n    image: nginx:1.32\n    command: \n      - \"-g\"\n      - \"daemon off;\"\n\nx-uffizzi:\n  ingress:\n    service: nginx\n    port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-with-host-volumes.yml",
    "content": "services:\n  web:\n    image: nginx\n    volumes:\n      - ./share_dir:/var/share_dir\n\nx-uffizzi-ingress:\n  service: web\n  port: 80\n"
  },
  {
    "path": "core/test/fixtures/files/uffizzi-compose-with_alias.yml",
    "content": "x-nginx-configs: &nginx-configs\n  - source: vote_conf\n    target: /etc/nginx/conf.d\n\nx-srv-nginx: &srv-nginx\n  nginx:\n    image: nginx:latest\n    configs: *nginx-configs\n    entrypoint: /usr/sbin/nginx-debug\n    command:\n      - \"-g\"\n      - \"daemon off;\"\n\nservices:\n  <<: *srv-nginx\n\nx-uffizzi-ingress:\n  service: nginx\n  port: 8080\n\nconfigs:\n  vote_conf:\n    file: configs/vote.conf\n  defaulf_conf:\n    file: config_files/config_file.conf\n"
  },
  {
    "path": "core/test/services/activity_item_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::ActivityItemServiceTest < ActiveSupport::TestCase\n  setup do\n    Sidekiq::Testing.fake!\n    Sidekiq::Worker.clear_all\n\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, account: @user.personal_account)\n    @deployment = create(:deployment, project: @project)\n  end\n\n  teardown do\n    Sidekiq::Testing.inline!\n  end\n\n  test '#manage_deploy_activity_item - pending resource availability' do\n    repo = create(:repo, :docker_hub, project: @project)\n    container = create(:container, :with_public_port, deployment: @deployment, repo: repo, image: 'library/nginx')\n    namespace, name = container.image.split('/')\n\n    activity_item = create(:activity_item,\n                           :docker,\n                           :with_deploying_event,\n                           namespace: namespace,\n                           name: name,\n                           tag: container.tag,\n                           container: container,\n                           deployment: @deployment)\n\n    namespace = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        metadata: {\n          annotations: {\n            network_connectivity: {\n              containers: {\n                container.id => {\n                  service: {\n                    status: :pending,\n                  },\n                },\n              },\n            }.to_json,\n          },\n        },\n      },\n    )\n\n    stubbed_controller_get_namespace_request = stub_controller_get_namespace_request(@deployment, namespace)\n\n    pod_name = generate(:name)\n    deployed_at = Time.current\n    current_time = (deployed_at - 25.minute).iso8601\n    node_name = generate(:name)\n    restart_count = 3\n\n    pods = UffizziCore::Converters.deep_lower_camelize_keys(\n      [\n        {\n          metadata: {\n            name: pod_name,\n            creation_timestamp: current_time,\n          },\n          spec: {\n            node_name: node_name,\n          },\n          status: {\n            container_statuses: [\n              {\n                name: UffizziCore::ContainerService.pod_name(container),\n                restart_count: restart_count,\n                image: container.image_name,\n                state: {\n                  running: {\n                    started_at: current_time,\n                  },\n                },\n              },\n            ],\n          },\n        },\n      ],\n    )\n\n    stubbed_controller_containers_request = stub_controller_containers_request(@deployment, pods)\n\n    differences = {\n      -> { UffizziCore::Event.count } => 0,\n      -> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 1,\n    }\n\n    assert_difference differences do\n      UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)\n\n      assert_requested stubbed_controller_get_namespace_request\n      assert_requested stubbed_controller_containers_request\n    end\n  end\n\n  test '#manage_deploy_activity_item - resource is unavailable' do\n    repo = create(:repo, :docker_hub, project: @project)\n    container = create(:container, :with_public_port, deployment: @deployment, repo: repo, image: 'library/nginx')\n    namespace, name = container.image.split('/')\n    activity_item = create(:activity_item,\n                           :with_building_event,\n                           :docker,\n                           namespace: namespace,\n                           name: name,\n                           tag: container.tag,\n                           container: container,\n                           deployment: @deployment)\n\n    domain_name = generate(:kubernetes_name)\n\n    namespace = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        metadata: {\n          annotations: {\n            network_connectivity: {\n              containers: {\n                container.id => {\n                  service: {\n                    ip: '127.0.0.1',\n                    status: :failed,\n                    port: container.port,\n                    domain_name: domain_name,\n                  },\n                },\n              },\n            }.to_json,\n          },\n        },\n      },\n    )\n\n    stubbed_controller_get_namespace_request = stub_controller_get_namespace_request(@deployment, namespace)\n\n    pod_name = generate(:name)\n    deployed_at = Time.current\n    current_time = (deployed_at - 25.minute).iso8601\n    node_name = generate(:name)\n    restart_count = 3\n\n    pods = UffizziCore::Converters.deep_lower_camelize_keys(\n      [\n        {\n          metadata: {\n            name: \"#{Settings.controller.namespace_prefix}#{pod_name}\",\n            creation_timestamp: current_time,\n          },\n          spec: {\n            node_name: node_name,\n          },\n          status: {\n            container_statuses: [\n              {\n                name: UffizziCore::ContainerService.pod_name(container),\n                restart_count: restart_count,\n                image: container.image_name,\n                state: {\n                  terminated: {},\n                },\n              },\n            ],\n          },\n        },\n      ],\n    )\n\n    stubbed_controller_containers_request = stub_controller_containers_request(@deployment, pods)\n\n    differences = {\n      -> { UffizziCore::Event.count } => 1,\n      -> { UffizziCore::Event.with_state(UffizziCore::Event.state.failed).count } => 1,\n      -> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 0,\n    }\n\n    assert_difference differences do\n      UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)\n\n      assert_requested stubbed_controller_get_namespace_request\n      assert_requested stubbed_controller_containers_request\n    end\n  end\n\n  test '#manage_deploy_activity_item - successfully deployed resources' do\n    repo = create(:repo, :docker_hub, project: @project)\n    container = create(:container, :with_public_port, deployment: @deployment, repo: repo, image: 'library/nginx')\n    namespace, name = container.image.split('/')\n    activity_item = create(:activity_item,\n                           :with_deploying_event,\n                           :docker,\n                           namespace: namespace,\n                           name: name,\n                           tag: container.tag,\n                           container: container,\n                           deployment: @deployment)\n\n    domain_name = generate(:kubernetes_name)\n\n    namespace = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        metadata: {\n          annotations: {\n            network_connectivity: {\n              containers: {\n                container.id => {\n                  service: {\n                    ip: '127.0.0.1',\n                    status: :success,\n                    port: container.port,\n                    domain_name: domain_name,\n                  },\n                },\n              },\n            }.to_json,\n          },\n        },\n      },\n    )\n\n    stubbed_controller_get_namespace_request = stub_controller_get_namespace_request(@deployment, namespace)\n\n    pod_name = generate(:name)\n    deployed_at = Time.current\n    current_time = (deployed_at - 25.minute).iso8601\n    node_name = generate(:name)\n    restart_count = 0\n\n    pods = UffizziCore::Converters.deep_lower_camelize_keys(\n      [\n        {\n          metadata: {\n            name: \"#{Settings.controller.namespace_prefix}#{pod_name}\",\n            creation_timestamp: current_time,\n          },\n          spec: {\n            node_name: node_name,\n          },\n          status: {\n            container_statuses: [\n              {\n                name: UffizziCore::ContainerService.pod_name(container),\n                restart_count: restart_count,\n                image: container.image_name,\n                state: {\n                  running: {\n                    started_at: current_time,\n                  },\n                },\n              },\n            ],\n          },\n        },\n      ],\n    )\n\n    stubbed_controller_containers_request = stub_controller_containers_request(@deployment, pods)\n\n    differences = {\n      -> { UffizziCore::Event.count } => 1,\n      -> { UffizziCore::Event.with_state(UffizziCore::Event.state.deployed).count } => 1,\n      -> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 0,\n    }\n\n    assert_difference differences do\n      UffizziCore::ActivityItemService.manage_deploy_activity_item(activity_item)\n      assert_requested stubbed_controller_get_namespace_request\n      assert_requested stubbed_controller_containers_request\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/services/compose_file_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::ComposeFileServiceTest < ActiveSupport::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @account = @user.personal_account\n    @project = create(:project, account: @account)\n  end\n\n  test '#parse - check parse services with continuous preview' do\n    content = file_fixture('files/compose_files/compose_with_continuous_preview.yml').read\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    assert_equal(2, parsed_data[:containers].count)\n    refute_empty(parsed_data[:continuous_preview])\n    refute_empty(parsed_data[:containers][1][:'x-uffizzi-continuous-preview'])\n  end\n\n  test '#parse - check invalid service name' do\n    content = file_fixture('files/compose_files/invalid_service.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Invalid config option', e.message)\n  end\n\n  test '#parse - check parse docker image' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    assert_equal(1, parsed_data[:containers].count)\n    refute_empty(parsed_data[:containers].first[:image])\n  end\n\n  test '#parse - check parse docker image without tag' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_without_tag.yml').read\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    container = parsed_data[:containers].first\n    image = container[:image]\n    tag = image[:tag]\n\n    assert_equal(Settings.compose.default_tag, tag)\n  end\n\n  test '#parse - check parse env variables from an environment key' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_envs.yml').read\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    content_data = YAML.safe_load(content)\n    variables = parsed_data[:containers].first[:environment]\n\n    assert_equal(variables.count, content_data['services']['hello-world']['environment'].keys.count)\n  end\n\n  test '#parse - check invalid ingress service' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_config_invalid_ingress_service.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Invalid ingress service', e.message)\n  end\n\n  test '#parse - check if an ingress service not found' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_without_ingress_service.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Ingress service not found', e.message)\n  end\n\n  test '#parse - check if an ingress port not specified' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_without_ingress_port.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Ingress port not specified', e.message)\n  end\n\n  test '#parse - check if an ingress port is not Integer' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_ingress_port_non_integer.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('should be an Integer type', e.message)\n  end\n\n  test '#parse - check compose without ingress' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_without_ingress.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Service ingress has not been defined', e.message)\n  end\n\n  test '#parse - check compose with ingress without x-uffizzi' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_with_ingress_without_x-uffizzi.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Service ingress has not been defined', e.message)\n  end\n\n  test '#parse - check compose with continuous_preview on top level without x-uffizzi' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_with_continuous_preview_without_x-uffizzi.yml').read\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    assert_empty(parsed_data[:continuous_preview])\n  end\n\n  test '#parse - check compose with continuous_preview on service level without x-uffizzi' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_with_continuous_preview_without_x-uffizzi.yml').read\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    refute(parsed_data[:containers][0][:'x-uffizzi-continuous-preview'])\n  end\n\n  test '#parse - check compose without services' do\n    content = file_fixture('files/compose_files/compose_without_services.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('At least one must be provided', e.message)\n  end\n\n  test '#parse - check if a port is  out of acceptable range' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_port.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match(\"Port should be specified between #{Settings.compose.port_min_value} - #{Settings.compose.port_max_value}\", e.message)\n  end\n\n  test '#parse - check if a memory has invalid postfix' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_memory_postfix.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('The postfix should be one of', e.message)\n  end\n\n  test '#parse - check if a memory has invalid type' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_memory_type.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('it should be a string', e.message)\n  end\n\n  test '#parse - check if image and build data no specified' do\n    content = file_fixture('files/compose_files/compose_without_image.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('At least one must be provided', e.message)\n  end\n\n  test '#parse - check if delete_preview_after has invalid postfix' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_postfix.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('value should be `h`', e.message)\n  end\n\n  test '#parse - check if delete_preview_after has invalid value' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_hours.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('should be an Integer type', e.message)\n  end\n\n  test '#parse - check if delete_preview_after less than min value' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_min.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Minimum delete_preview_after', e.message)\n  end\n\n  test '#parse - check if delete_preview_after more than max value' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_invalid_delete_after_max.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Maximum delete_preview_after', e.message)\n  end\n\n  test '#parse - check if delete_preview_after has only integer value' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_cp_delete_after_integer.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('should be a String type', e.message)\n  end\n\n  test '#parse - check if deploy auto is invalid' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_deploy_auto.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Invalid auto value', e.message)\n  end\n\n  test '#parse - check if an env_file is empty' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_env_file_empty.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match(\"Unsupported type of 'env_file'\", e.message)\n  end\n\n  test '#parse - check if a list of env files has an empty value' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_env_file_empty_in_array.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('env_file contains an empty value', e.message)\n  end\n\n  test '#parse - check if a list of env files has duplicated values' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_env_file_duplicates.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('env_file contains non-unique items', e.message)\n  end\n\n  test '#parse - check a boolean option' do\n    content = file_fixture('files/compose_files/boolean_option.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('The service name true must be a quoted string', e.message)\n  end\n\n  test '#parse - check if a config is unknown in short syntax' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_short_syntax_unknown_config.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('undefined config', e.message)\n  end\n\n  test '#parse - check if a config path is not specified' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_short_syntax_invalid_path.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('has an empty file', e.message)\n  end\n\n  test '#parse - check parse configs with a long syntax' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_long_syntax.yml').read\n\n    parsed_content = UffizziCore::ComposeFileService.parse(content)\n\n    content_data = YAML.safe_load(content)\n    config = parsed_content[:containers].first[:configs].first\n\n    assert_equal(content_data['services']['nginx']['configs'].first['target'], config[:target])\n  end\n\n  test '#parse - check parse configs with a long syntax if a target is not specified' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_configs_long_syntax_without_target.yml').read\n\n    parsed_content = UffizziCore::ComposeFileService.parse(content)\n\n    content_data = YAML.safe_load(content)\n    config = parsed_content[:containers].first[:configs].first\n\n    assert_equal(content_data['configs']['vote_conf']['file'], config[:target])\n  end\n\n  test '#parse - check if a secret is unknown' do\n    content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_unknown.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('undefined secret', e.message)\n  end\n\n  test '#parse - check if a secret is not external' do\n    content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_without_external.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match(\"'postgres_password' should be external\", e.message)\n  end\n\n  test '#parse - check if a secret doesn\\'t have a name' do\n    content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_without_name.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match(\"'postgres_password' secret can not be blank\", e.message)\n  end\n\n  test '#parse - raise an error if a build option is specified' do\n    content = file_fixture('files/compose_files/github_services/hello_world.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match(\"The 'build' directive is not supported\", e.message)\n  end\n\n  test '#parse - parses compose file with healthcheck and converts time to seconds when the test command is array' do\n    content = file_fixture('files/compose_files/healthcheck/array_command_success.yml').read\n\n    result = UffizziCore::ComposeFileService.parse(content)\n    container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first\n\n    assert_equal(90, container_with_healthcheck[:healthcheck][:interval])\n    refute(container_with_healthcheck[:healthcheck][:disable])\n  end\n\n  test '#parse - parses compose file with healthcheck and converts time to seconds when the test command is string' do\n    content = file_fixture('files/compose_files/healthcheck/string_command_success.yml').read\n\n    result = UffizziCore::ComposeFileService.parse(content)\n    container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first\n\n    assert_equal(90, container_with_healthcheck[:healthcheck][:interval])\n    refute(container_with_healthcheck[:healthcheck][:disable])\n  end\n\n  test \"#parse - parses compose file with healthcheck and sets disabled to false if the command is 'NONE'\" do\n    content = file_fixture('files/compose_files/healthcheck/disabled_healthcheck.yml').read\n\n    result = UffizziCore::ComposeFileService.parse(content)\n    container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first\n\n    assert(container_with_healthcheck[:healthcheck][:disable])\n  end\n\n  test '#parse - parses compose file with healthcheck disabled is true and test not set' do\n    content = file_fixture('files/compose_files/healthcheck/healthcheck_with_disable.yml').read\n\n    result = UffizziCore::ComposeFileService.parse(content)\n    container_with_healthcheck = result[:containers].select { |container| container[:container_name] == 'hello-world' }.first\n\n    assert(container_with_healthcheck[:healthcheck][:disable])\n  end\n\n  test '#parse - raises error if the healthcheck command has invalid type' do\n    content = file_fixture('files/compose_files/healthcheck/invalid_command.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match(\"Unsupported type of 'test' option\", e.message)\n  end\n\n  test '#parse - raises error if the retries field has invalid type' do\n    content = file_fixture('files/compose_files/healthcheck/invalid_retries.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_match('Invalid retries value', e.message)\n  end\n\n  test '#parse - raises error if healthcheck has invalid interval' do\n    content = file_fixture('files/compose_files/healthcheck/invalid_interval.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    error_message = \"The time interval should be in the following format '{hours}h{minutes}m{seconds}s'. \" \\\n                    'At least one value must be present.'\n    assert_match(error_message, e.message)\n  end\n\n  test '#parse - raises error if test or disble option are not present' do\n    content = file_fixture('files/compose_files/healthcheck/invalid_options.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    error_message = 'One of these options is required: test, disable'\n    assert_match(error_message, e.message)\n  end\n\n  test '#build_template_attributes - check if x-uffizzi ingress is specified' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_uffizzi_ingress.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'nginx')\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    content_data = YAML.safe_load(content)\n    nginx_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/nginx/) }\n\n    assert(nginx_container[:public])\n    assert_equal(nginx_container[:port], content_data['x-uffizzi']['ingress']['port'])\n  end\n\n  test '#build_template_attributes - check if deploy auto is disabled' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_auto_deploy_off.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'nginx')\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    continuously_deploy = attributes[:payload][:containers_attributes].first[:continuously_deploy]\n    assert_equal(:disabled, continuously_deploy)\n  end\n\n  test '#build_template_attributes - check container default memory' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'nginx')\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    memory_limit = attributes[:payload][:containers_attributes].first[:memory_limit]\n    assert_equal(Settings.compose.default_memory, memory_limit)\n  end\n\n  test '#build_template_attributes - public image and no credential exists' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    stubbed_dockerhub_repository = stub_dockerhub_repository('library', 'nginx')\n    UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n    assert_requested(stubbed_dockerhub_repository)\n  end\n\n  test '#build_template_attributes - private image and no credential exists' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    stubbed_dockerhub_repository = stub_dockerhub_private_repository('library', 'nginx')\n    assert_raise(UffizziCore::ComposeFile::BuildError) do\n      UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n    end\n    assert_requested(stubbed_dockerhub_repository)\n  end\n\n  test '#build_template_attributes - check if a memory is not in list of available values' do\n    content = file_fixture('files/compose_files/dockerhub_services/nginx_invalid_memory.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'nginx')\n\n    e = assert_raise(UffizziCore::ComposeFile::BuildError) do\n      UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n    end\n\n    assert_match('The memory should be one of the', e.message)\n  end\n\n  test '#build_template_attributes - check container memory' do\n    content = file_fixture('files/compose_files/compose_memory.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'nginx')\n    stub_dockerhub_repository('library', 'redis')\n    stub_dockerhub_repository('library', 'postgres')\n    stub_dockerhub_repository('library', 'ubuntu')\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    postgres_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/postgres/) }\n    redis_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/redis/) }\n    nginx_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/nginx/) }\n    ubuntu_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/ubuntu/) }\n\n    assert_equal(125, postgres_container[:memory_limit])\n    assert_equal(250, redis_container[:memory_limit])\n    assert_equal(1000, nginx_container[:memory_limit])\n    assert_equal(4000, ubuntu_container[:memory_limit])\n  end\n\n  test '#build_template_attributes - check azure image build' do\n    create(:credential, :azure, account: @account)\n    content = file_fixture('files/compose_files/azure_services/nginx.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    container = attributes[:payload][:containers_attributes].first\n    content_data = YAML.safe_load(content)\n\n    image_url = content_data['services']['nginx']['image'].split(':').first\n    tag = content_data['services']['nginx']['image'].split(':').last\n    image_name = image_url.split('/').last\n\n    assert_equal(image_name, container[:image])\n    assert_equal(\"#{image_url}:latest\", container[:full_image_name])\n    assert_equal(tag, container[:tag])\n    assert_equal(image_name, container[:repo_attributes][:name])\n    refute(container[:repo_attributes][:namespace])\n    assert_equal(UffizziCore::Repo::Azure.name, container[:repo_attributes][:type])\n  end\n\n  test '#build_template_attributes - check invalid azure credential' do\n    content = file_fixture('files/compose_files/azure_services/nginx.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    e = assert_raise(UffizziCore::ComposeFile::BuildError) do\n      UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n    end\n\n    assert_match(I18n.t('compose.unprocessable_image', value: 'azure'), e.message)\n  end\n\n  test '#build_template_attributes - check google image build' do\n    create(:credential, :google, account: @account)\n    content = file_fixture('files/compose_files/google_services/nginx.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    container = attributes[:payload][:containers_attributes].first\n    content_data = YAML.safe_load(content)\n\n    image_url = content_data['services']['nginx']['image'].split(':').first\n    tag = content_data['services']['nginx']['image'].split(':').last\n    project_name = image_url.split('/').second\n    image_name = image_url.split('/').last\n\n    assert_equal(\"#{project_name}/#{image_name}\", container[:image])\n    assert_equal(tag, container[:tag])\n    assert_equal(\"#{image_url}:latest\", container[:full_image_name])\n    assert_equal(image_name, container[:repo_attributes][:name])\n    assert_equal(project_name, container[:repo_attributes][:namespace])\n    assert_equal(UffizziCore::Repo::Google.name, container[:repo_attributes][:type])\n  end\n\n  test '#build_template_attributes - check build of custom docker account' do\n    content = file_fixture('files/compose_files/dockerhub_services/account_custom_image.yml').read\n    stub_dockerhub_repository('account', 'custom_image')\n\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    container = attributes[:payload][:containers_attributes].first\n    content_data = YAML.safe_load(content)\n\n    image_url = content_data['services']['nginx']['image'].split(':').first\n    tag = content_data['services']['nginx']['image'].split(':').last\n    account_name = image_url.split('/').first\n    image_name = image_url.split('/').last\n\n    assert_equal(\"#{account_name}/#{image_name}\", container[:image])\n    assert_equal(tag, container[:tag])\n    assert_equal(image_name, container[:repo_attributes][:name])\n    assert_equal(account_name, container[:repo_attributes][:namespace])\n    assert_equal(UffizziCore::Repo::DockerHub.name, container[:repo_attributes][:type])\n  end\n\n  test '#build_template_attributes - check a command build' do\n    create(:credential, :google, account: @account)\n    content = file_fixture('files/compose_files/google_services/cloudsql.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    container = attributes[:payload][:containers_attributes].last\n\n    refute_nil(container[:command])\n    assert_nil(container[:entrypoint])\n  end\n\n  test '#build_template_attributes - check an entrypoint build' do\n    create(:credential, :google, account: @account)\n    content = file_fixture('files/compose_files/google_services/cloudsql_entrypoint.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    container = attributes[:payload][:containers_attributes].last\n\n    refute_nil(container[:entrypoint])\n    assert_nil(container[:command])\n  end\n\n  test '#build_template_attributes - check secrets variables build' do\n    project_secrets = [build(:secret, name: 'POSTGRES_USER', value: generate(:string)),\n                       build(:secret, name: 'POSTGRES_PASSWORD', value: generate(:string))]\n    @project.secrets.append(project_secrets)\n    stub_dockerhub_repository('library', 'postgres')\n\n    content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    content_data = YAML.safe_load(content)\n\n    postgres_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/postgres/) }\n    secret_variables = postgres_container[:secret_variables]\n\n    assert_equal(2, secret_variables.size)\n    refute_nil(secret_variables.detect { |secret| secret[:name] == content_data['secrets']['postgres_user']['name'] })\n    refute_nil(secret_variables.detect { |secret| secret[:name] == content_data['secrets']['postgres_password']['name'] })\n  end\n\n  test '#build_template_attributes - check secrets variables build if a project secret doesn\\'t exist' do\n    content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'postgres')\n\n    e = assert_raise(UffizziCore::ComposeFile::SecretsError) do\n      UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n    end\n\n    assert_match(\"Project secret 'POSTGRES_USER' not found\", e.message)\n  end\n\n  test '#build_template_attributes - check secrets variables build if a compose has duplicates' do\n    project_secrets = [build(:secret, name: 'POSTGRES_USER', value: generate(:string)),\n                       build(:secret, name: 'POSTGRES_PASSWORD', value: generate(:string))]\n    @project.secrets.append(project_secrets)\n    stub_dockerhub_repository('library', 'postgres')\n\n    content = file_fixture('files/compose_files/dockerhub_services/postgres_secrets_duplicates.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    content_data = YAML.safe_load(content)\n\n    postgres_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/postgres/) }\n    secret_variables = postgres_container[:secret_variables]\n\n    assert_equal(1, secret_variables.size)\n    refute_nil(secret_variables.detect { |secret| secret[:name] == content_data['secrets']['postgres_user']['name'] })\n    assert_nil(secret_variables.detect { |secret| secret[:name] == 'POSTGRES_PASSWORD' })\n  end\n\n  test '#build_template_attributes - check global and container continuous preview attributes' do\n    content = file_fixture('files/compose_files/compose_with_continuous_preview.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'redis')\n    stub_dockerhub_repository('library', 'nginx')\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    nginx_repo_attributes = attributes[:payload][:containers_attributes].detect { |item| item[:image] == 'library/nginx' }[:repo_attributes]\n    redis_repo_attributes = attributes[:payload][:containers_attributes].detect { |item| item[:image] == 'library/redis' }[:repo_attributes]\n\n    assert { nginx_repo_attributes[:delete_preview_after] == 12 }\n    assert { redis_repo_attributes[:delete_preview_after] == 10 }\n  end\n\n  test '#build_template_attributes - check named volumes' do\n    content = file_fixture('files/compose_files/dockerhub_services/volumes_named.yml').read\n    parsed_data = UffizziCore::ComposeFileService.parse(content)\n    stub_dockerhub_repository('library', 'nginx')\n    attributes = UffizziCore::ComposeFileService.build_template_attributes(parsed_data, 'compose.yml', @account.credentials, @project)\n\n    content_data = YAML.safe_load(content)\n    nginx_container = attributes[:payload][:containers_attributes].detect { |container| container[:image].match(/nginx/) }\n    first_volume = nginx_container[:volumes].first\n\n    assert(nginx_container[:volumes])\n    assert_equal(UffizziCore::ComposeFile::Parsers::Services::VolumesParserService::NAMED_VOLUME_TYPE, first_volume[:type])\n    assert_equal(content_data.dig('services', 'nginx', 'volumes').first.split(':').first, first_volume[:source])\n    assert_equal(content_data.dig('services', 'nginx', 'volumes').first.split(':').second, first_volume[:target])\n  end\n\n  test '#parse - handle Psych::SyntaxError' do\n    content = file_fixture('files/compose_files/compose_with_syntax_error.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_equal(\"Syntax error: could not find expected ':' while scanning a simple key at line 5 column 3\", e.message)\n  end\n\n  test '#parse - handle empty compose file' do\n    content = file_fixture('files/compose_files/compose_empty.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_equal('Unsupported compose file', e.message)\n  end\n\n  test '#parse - handle when file has just a string' do\n    content = file_fixture('files/compose_files/compose_with_only_line.yml').read\n\n    e = assert_raise(UffizziCore::ComposeFile::ParseError) do\n      UffizziCore::ComposeFileService.parse(content)\n    end\n\n    assert_equal('Unsupported compose file', e.message)\n  end\nend\n"
  },
  {
    "path": "core/test/services/deployment_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::DeploymentServiceTest < ActiveSupport::TestCase\n  setup do\n    Sidekiq::Testing.fake!\n    Sidekiq::Worker.clear_all\n\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, account: @user.personal_account)\n    @deployment = create(:deployment, project: @project)\n  end\n\n  teardown do\n    Sidekiq::Testing.inline!\n  end\n\n  test '#deploy_containers - start to deploy dockerhub container' do\n    repo = create(:repo, :docker_hub, project: @project)\n    create(:container, :with_public_port, :active, deployment: @deployment, repo: repo)\n\n    stubbed_deploy_containers_request = stub_request(:post, \"#{Settings.controller.url}/deployments/#{@deployment.id}/containers\")\n\n    differences = {\n      -> { UffizziCore::Event.count } => 1,\n      -> { UffizziCore::Event.with_state(UffizziCore::Event.state.deploying).count } => 1,\n      -> { UffizziCore::Deployment::DeployContainersJob.jobs.size } => 0,\n      -> { UffizziCore::Deployment::ManageDeployActivityItemJob.jobs.size } => 1,\n    }\n\n    assert_difference differences do\n      UffizziCore::DeploymentService.deploy_containers(@deployment)\n    end\n    assert_requested stubbed_deploy_containers_request\n  end\n\n  test '#disable when deployment has no compose file' do\n    container = create(:container, :with_public_port, deployment: @deployment)\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => -1,\n      -> { UffizziCore::ComposeFile.count } => 0,\n      -> { UffizziCore::Template.count } => 0,\n    }\n\n    assert_difference differences do\n      UffizziCore::DeploymentService.disable!(@deployment)\n    end\n\n    container.reload\n    assert { container.disabled? }\n  end\n\n  test '#disable when the main compose file exists' do\n    user = create(:user, :with_personal_account)\n    compose_file = create(:compose_file, project: @project, added_by: user)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    template = create(:template, :compose_file_source, compose_file: compose_file, project: @project, payload: template_payload,\n                                                       added_by: user)\n    deployment = create(:deployment, project: @project, compose_file: compose_file, template: template)\n    container = create(:container, :with_public_port, deployment: deployment)\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => -1,\n      -> { UffizziCore::ComposeFile.count } => 0,\n      -> { UffizziCore::Template.count } => 0,\n    }\n\n    assert_difference differences do\n      UffizziCore::DeploymentService.disable!(deployment)\n    end\n\n    container.reload\n    assert { container.disabled? }\n  end\n\n  test '#disable when a temporary compose file exists' do\n    user = create(:user, :with_personal_account)\n    compose_file = create(:compose_file, :temporary, project: @project, added_by: user)\n    image = generate(:image)\n    image_namespace, image_name = image.split('/')\n    target_branch = generate(:branch)\n    repo_attributes = attributes_for(\n      :repo,\n      :docker_hub,\n      namespace: image_namespace,\n      name: image_name,\n      branch: target_branch,\n    )\n    container_attributes = attributes_for(\n      :container,\n      :with_public_port,\n      image: image,\n      tag: target_branch,\n      receive_incoming_requests: true,\n      repo_attributes: repo_attributes,\n    )\n    template_payload = {\n      containers_attributes: [container_attributes],\n    }\n    template = create(:template, :compose_file_source, compose_file: compose_file, project: @project, payload: template_payload,\n                                                       added_by: user)\n    deployment = create(:deployment, project: @project, compose_file: compose_file, template: template)\n    container = create(:container, :with_public_port, deployment: deployment)\n\n    differences = {\n      -> { UffizziCore::Deployment.active.count } => -1,\n      -> { UffizziCore::ComposeFile.count } => -1,\n      -> { UffizziCore::Template.count } => -1,\n    }\n\n    assert_difference differences do\n      UffizziCore::DeploymentService.disable!(deployment)\n    end\n\n    container.reload\n    assert { container.disabled? }\n  end\n\n  test '#failed - container failed' do\n    container = create(:container, deployment: @deployment)\n    create(:activity_item, :with_failed_event, container: container, deployment: @deployment)\n\n    deployment_failed = UffizziCore::DeploymentService.failed?(@deployment)\n\n    assert(deployment_failed)\n  end\n\n  test '#failed - container deployed' do\n    container = create(:container, deployment: @deployment)\n    create(:activity_item, :with_deployed_event, container: container, deployment: @deployment)\n\n    deployment_failed = UffizziCore::DeploymentService.failed?(@deployment)\n\n    refute(deployment_failed)\n  end\n\n  test '#failed - no containers' do\n    deployment_failed = UffizziCore::DeploymentService.failed?(@deployment)\n\n    refute(deployment_failed)\n  end\nend\n"
  },
  {
    "path": "core/test/services/google_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::GoogleServiceTest < ActiveSupport::TestCase\n  test '#digest' do\n    image = generate(:image)\n    tag = generate(:tag)\n    user = create(:user, :with_personal_account)\n    credential = create(:credential, :google, :active, account: user.personal_account)\n\n    headers_response = { 'docker-content-digest': generate(:string) }\n    token_response = { token: generate(:string) }\n\n    stubbed_google_registry_token = stub_google_registry_token(token_response)\n    stubbed_google_registry_manifests = stub_google_registry_manifests(image, tag, headers_response, {})\n\n    digest = UffizziCore::ContainerRegistry::GoogleService.digest(credential, image, tag)\n\n    assert_requested stubbed_google_registry_token\n    assert_requested stubbed_google_registry_manifests\n    assert digest.present?\n  end\nend\n"
  },
  {
    "path": "core/test/services/image_parser_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::ImageParserServiceTest < ActiveSupport::TestCase\n  test '#parse' do\n    image_parser_service = UffizziCore::ComposeFile::Parsers::Services::ImageParserService\n    assert_equal(\n      { registry_url: 'docker.io', namespace: 'library', name: 'redis', tag: 'latest', full_image_name: 'docker.io/library/redis:latest' },\n      image_parser_service.parse('redis'),\n    )\n    assert_equal(\n      { registry_url: 'docker.io', namespace: 'library', name: 'redis', tag: '5', full_image_name: 'docker.io/library/redis:5' },\n      image_parser_service.parse('redis:5'),\n    )\n    assert_equal(\n      { registry_url: 'docker.io', namespace: 'namespace', name: 'redis', tag: 'latest',\n        full_image_name: 'docker.io/namespace/redis:latest' },\n      image_parser_service.parse('namespace/redis'),\n    )\n    assert_equal(\n      { registry_url: 'docker.io', namespace: 'namespace', name: 'redis', tag: 'latest',\n        full_image_name: 'docker.io/namespace/redis:latest' },\n      image_parser_service.parse('docker.io/namespace/redis'),\n    )\n    assert_equal(\n      { registry_url: 'my-private.registry:5000', namespace: 'namespace', name: 'redis', tag: '5.3',\n        full_image_name: 'my-private.registry:5000/namespace/redis:5.3' },\n      image_parser_service.parse('my-private.registry:5000/namespace/redis:5.3'),\n    )\n    assert_equal(\n      { registry_url: 'localhost:80', namespace: nil, name: 'redis', tag: '5.3', full_image_name: 'localhost:80/redis:5.3' },\n      image_parser_service.parse('localhost:80/redis:5.3'),\n    )\n    assert_equal(\n      { registry_url: 'docker.io', namespace: 'library', name: 'lower_case_name', tag: 'lower_case_tag',\n        full_image_name: 'docker.io/library/lower_case_name:lower_case_tag' },\n      image_parser_service.parse('lower_case_name:lower_case_tag'),\n    )\n    assert_equal(\n      { registry_url: 'docker.io', namespace: 'namespace', name: 'lower_case_name', tag: 'UPPERCASE_TAG',\n        full_image_name: 'docker.io/namespace/lower_case_name:UPPERCASE_TAG' },\n      image_parser_service.parse('namespace/lower_case_name:UPPERCASE_TAG'),\n    )\n    assert_equal(\n      { registry_url: 'asia.gcr.io', namespace: 'uffizzi', name: 'nginx', tag: 'latest',\n        full_image_name: 'asia.gcr.io/uffizzi/nginx:latest' },\n      image_parser_service.parse('asia.gcr.io/uffizzi/nginx:latest'),\n    )\n  end\n\n  test '#parse with exception' do\n    image_parser_service = UffizziCore::ComposeFile::Parsers::Services::ImageParserService\n    assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('very:wrong:image:path') }\n    assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('UPPERCASE_NAMESPACE/UPPERCASE_NAME:UPPERCASE_TAG') }\n    assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('UPPERCASE_NAME:UPPERCASE_TAG') }\n    assert_raises(UffizziCore::ComposeFile::ParseError) { image_parser_service.parse('UPPERCASE_NAME') }\n  end\nend\n"
  },
  {
    "path": "core/test/services/manage_activity_items_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::ManageActivityItemsServiceTest < ActiveSupport::TestCase\n  setup do\n    @user = create(:user, :with_personal_account)\n    @project = create(:project, account: @user.personal_account)\n  end\n\n  test '#container_status_items - deployment has no containers' do\n    deployment = create(:deployment, project: @project)\n    stubbed_response_containers = []\n\n    namespace = UffizziCore::Converters.deep_lower_camelize_keys(\n      {\n        metadata: {\n          annotations: {\n            network_connectivity: {}.to_json,\n          },\n        },\n      },\n    )\n\n    stub_controller_containers = stub_controller_containers_request(deployment, stubbed_response_containers)\n    stub_get_controller_deployment = stub_controller_get_namespace_request(deployment, namespace)\n\n    service = UffizziCore::ManageActivityItemsService.new(deployment)\n    container_status_items = service.container_status_items\n\n    assert { container_status_items.empty? }\n    assert_requested(stub_get_controller_deployment)\n    assert_requested(stub_controller_containers)\n  end\nend\n"
  },
  {
    "path": "core/test/services/user_generator_service_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCore::UserGeneratorServiceTest < ActiveSupport::TestCase\n  test '#generate' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = generate(:string)\n\n    differences = {\n      -> { UffizziCore::User.count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.count } => 1,\n    }\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if an email gets by a user' do\n    email = nil\n    password = generate(:password)\n    project_name = generate(:string)\n\n    new_email = generate(:email)\n\n    differences = {\n      -> { UffizziCore::User.where(email: new_email).count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.count } => 1,\n    }\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:gets).returns(new_email)\n    IO.stubs(:console).returns(console_mock)\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if an email is empty' do\n    email = nil\n    password = generate(:password)\n    project_name = generate(:string)\n\n    differences = {\n      -> { UffizziCore::User.where(email: UffizziCore::UserGeneratorService::DEFAULT_USER_EMAIL).count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.count } => 1,\n    }\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:gets).returns('')\n    IO.stubs(:console).returns(console_mock)\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if a password gets by a user' do\n    email = generate(:email)\n    password = nil\n    project_name = generate(:string)\n\n    new_password = generate(:password)\n\n    differences = {\n      -> { UffizziCore::User.count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.count } => 1,\n    }\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:getpass).returns(new_password)\n    IO.stubs(:console).returns(console_mock)\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if a password is empty' do\n    email = generate(:email)\n    password = nil\n    project_name = generate(:string)\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:getpass).returns('')\n    IO.stubs(:console).returns(console_mock)\n\n    assert_raises StandardError do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if a project name gets by a user' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = nil\n\n    new_project_name = generate(:string)\n\n    differences = {\n      -> { UffizziCore::User.count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.where(name: new_project_name).count } => 1,\n    }\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:gets).returns(new_project_name)\n    IO.stubs(:console).returns(console_mock)\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if a project name is empty' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = nil\n\n    differences = {\n      -> { UffizziCore::User.count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.where(name: UffizziCore::UserGeneratorService::DEFAULT_PROJECT_NAME).count } => 1,\n    }\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:gets).returns('')\n    IO.stubs(:console).returns(console_mock)\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if a user already exists' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = generate(:string)\n\n    create(:user, :with_personal_account, email: email)\n\n    assert_raises ActiveRecord::RecordInvalid do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if a user already exists and email gets by user' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = generate(:string)\n\n    create(:user, :with_personal_account, email: email)\n\n    console_mock = mock('console_mock')\n    console_mock.stubs(:write)\n    console_mock.stubs(:gets).returns(email)\n    IO.stubs(:console).returns(console_mock)\n\n    assert_raises ActiveRecord::RecordInvalid do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if have data but no console' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = generate(:string)\n\n    IO.stubs(:console).returns(nil)\n\n    differences = {\n      -> { UffizziCore::User.count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.count } => 1,\n    }\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if no email and no console' do\n    email = nil\n    password = generate(:password)\n    project_name = generate(:string)\n\n    IO.stubs(:console).returns(nil)\n\n    assert_raises ActiveRecord::RecordInvalid do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if no password and no console' do\n    email = generate(:email)\n    password = nil\n    project_name = generate(:string)\n\n    IO.stubs(:console).returns(nil)\n\n    assert_raises ActiveRecord::RecordInvalid do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\n\n  test '#generate if no project_name and no console' do\n    email = generate(:email)\n    password = generate(:password)\n    project_name = nil\n\n    IO.stubs(:console).returns(nil)\n\n    differences = {\n      -> { UffizziCore::User.count } => 1,\n      -> { UffizziCore::Account.count } => 1,\n      -> { UffizziCore::Membership.count } => 1,\n      -> { UffizziCore::Project.count } => 1,\n    }\n\n    assert_difference differences do\n      UffizziCore::UserGeneratorService.generate(email, password, project_name)\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/support/azure_registry_stub_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::AzureRegistryStubSupport\n  def stub_azure_registry_oauth2_token(registry_url, response, code = 200)\n    service = URI.parse(registry_url).hostname\n    uri = \"#{registry_url}/oauth2/token?service=#{service}\"\n\n    stub_request(:get, uri).to_return(status: code, body: response.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_azure_registry_manifests(registry_url, image, tag, headers, body)\n    uri = \"#{registry_url}/v2/#{image}/manifests/#{tag}\"\n\n    stub_request(:get, uri).to_return(status: 200, headers: headers, body: body.to_json)\n  end\nend\n"
  },
  {
    "path": "core/test/support/controller_stub_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::ControllerStubSupport\n  def stub_controller_apply_credential\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/deployments/[0-9]*/credentials}\n\n    stub_request(:post, uri)\n  end\n\n  def stub_put_controller_deployment_request(deployment)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}\"\n\n    stub_request(:put, uri)\n  end\n\n  def stub_controller_get_namespace_request(deployable, data = nil)\n    uri = \"#{Settings.controller.url}/namespaces/#{deployable.namespace}\"\n\n    stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_controller_get_namespace_request_any(data = nil)\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/deployment-[0-9]*}\n\n    stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_delete_namespace_request(deployable)\n    uri = \"#{Settings.controller.url}/namespaces/#{deployable.namespace}\"\n\n    stub_request(:delete, uri)\n  end\n\n  def stub_controller_containers_request(deployment, data = nil)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}/containers\"\n\n    stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_controller_nodes_request\n    uri = \"#{Settings.controller.url}/nodes\"\n\n    stub_request(:get, uri)\n  end\n\n  def stub_deploy_containers_request(deployment)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}/containers\"\n\n    stub_request(:post, uri)\n  end\n\n  def stub_apply_config_file_request(deployment, config_file)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}/config_files/#{config_file.id}\"\n\n    stub_request(:post, uri)\n  end\n\n  def stub_apply_config_file_request_with_expected(deployment, config_file, expected_request)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}/config_files/#{config_file.id}\"\n\n    stub_request(:post, uri).with do |req|\n      actual_body = JSON.parse(req.body).deep_symbolize_keys.deep_sort\n      expected_body = expected_request.deep_symbolize_keys.deep_sort\n\n      is_equal = actual_body == expected_body\n\n      ap(HashDiff.diff(actual_body, expected_body)) unless is_equal\n\n      is_equal\n    end\n  end\n\n  def stub_controller_get_deployment_events(deployment, body)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}/containers/events\"\n\n    stub_request(:get, uri).to_return(status: 200, body: body.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_container_log_request(deployment_id, pod_name, limit, previous, data)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment_id}/containers/#{pod_name}/logs?limit=#{limit}&previous=#{previous}\"\n\n    stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_deploy_containers_request_with_expected(deployment, expected_request)\n    uri = \"#{Settings.controller.url}/deployments/#{deployment.id}/containers\"\n\n    stub_request(:post, uri).with do |req|\n      actual_body = JSON.parse(req.body).deep_symbolize_keys.deep_sort\n      expected_body = expected_request.deep_symbolize_keys.deep_sort\n\n      is_equal = actual_body == expected_body\n\n      ap(HashDiff.diff(actual_body, expected_body)) unless is_equal\n\n      is_equal\n    end\n  end\n\n  def stub_create_namespace_request\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces$}\n\n    stub_request(:post, uri).to_return(status: 200, body: { namespace: 'namespace' }.to_json,\n                                       headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_get_cluster_request(data = {}, _status = 200)\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\\-]+)/cluster/([A-Za-z0-9\\-]+)}\n\n    stub_request(:get, uri).to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_create_cluster_request(data = {}, status = 200)\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\\-]+)/cluster}\n\n    stub_request(:post, uri).to_return(status: status, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_scale_cluster_request(status = 200)\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\\-]+)/cluster/([A-Za-z0-9\\-]+)}\n    stub_request(:patch, uri).to_return(status: status, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_create_cluster_request_with_expected(returned_data = {}, expected_request = {}, status = 200)\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\\-]+)/cluster}\n\n    stub_request(:post, uri).with do |req|\n      actual_body = JSON.parse(req.body).deep_symbolize_keys.deep_sort\n      expected_body = expected_request.deep_symbolize_keys.deep_sort\n\n      is_equal = equal_hashes?(expected_body, actual_body)\n      ap(HashDiff.diff(actual_body, expected_body)) unless is_equal\n\n      is_equal\n    end.to_return(status: status, body: returned_data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_get_ingresses(data = {}, status = 200)\n    uri = %r{#{Regexp.quote(Settings.controller.url.to_s)}/namespaces/([A-Za-z0-9\\-]+)/ingresses}\n\n    stub_request(:get, uri).to_return(status: status, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  private\n\n  def equal_hashes?(expected, actual)\n    actual = actual.deep_symbolize_keys\n    expected.deep_symbolize_keys.all? do |k, v|\n      if v.is_a?(Regexp)\n        v.match?(actual[k])\n      else\n        v == actual[k]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "core/test/support/docker_hub_stub_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::DockerHubStubSupport\n  API_URL = 'https://hub.docker.com/v2'\n  AUTH_URL = 'https://auth.docker.io'\n\n  def stub_dockerhub_login\n    response = { token: 'mytoken' }\n    uri = %r{#{API_URL}/users/login/}\n\n    stub_request(:post, uri).to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_dockerhub_login_fail(data)\n    uri = %r{#{API_URL}/users/login/}\n\n    stub_request(:post, uri).to_return(status: 401, body: data.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_dockerhub_auth_for_digest(repository)\n    response = { token: 'mytoken' }\n    url = %r{#{AUTH_URL}.+scope=repository:#{repository}:pull&service=registry[.]docker[.]io.+}\n    stub_request(:get, url).to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_dockerhub_get_digest(image, tag, data)\n    url = \"https://index.docker.io/v2/#{image}/manifests/#{tag}\"\n    stub_request(:get, url).to_return(status: 200, body: data[:body].to_json, headers: data[:headers])\n  end\n\n  def stub_dockerhub_api_tokens(data)\n    uri = \"#{API_URL}/api_tokens\"\n\n    stub_request(:post, uri).to_return(status: 201, body: data.to_json)\n  end\n\n  def stub_dockerhub_repository(namespace, repo_name)\n    url = \"#{API_URL}/repositories/#{namespace}/#{repo_name}\"\n\n    stub_request(:get, url).to_return(status: 200)\n  end\n\n  def stub_dockerhub_private_repository(namespace, repo_name)\n    url = \"#{API_URL}/repositories/#{namespace}/#{repo_name}\"\n\n    stub_request(:get, url).to_return(status: 404)\n  end\n\n  def stub_dockerhub_repository_any\n    uri = %r{#{API_URL}/repositories/.*/.*}\n\n    stub_request(:get, uri).to_return(status: 200)\n  end\nend\n"
  },
  {
    "path": "core/test/support/docker_registry_stub_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::DockerRegistryStubSupport\n  def stub_docker_registry_manifests(registry_url, image, tag, headers: {}, body: {}, status: 200)\n    uri = \"#{registry_url}/v2/#{image}/manifests/#{tag}\"\n\n    stub_request(:get, uri).to_return(status: status, headers: headers, body: body.to_json)\n  end\nend\n"
  },
  {
    "path": "core/test/support/fixture_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::FixtureSupport\n  def file_fixture(file_path)\n    full_path = \"#{ActiveSupport::TestCase.fixture_path}/#{file_path}\"\n    File.new(full_path)\n  end\n\n  def json_fixture(file_path, symbolize_names: true)\n    data = file_fixture(file_path).read\n    JSON.parse(data, symbolize_names: symbolize_names)\n  end\nend\n"
  },
  {
    "path": "core/test/support/github_container_registry_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::GithubContainerRegistryStubSupport\n  def stub_github_container_registry_access_token(registry_url, response)\n    service = URI.parse(registry_url).hostname\n    uri = \"#{registry_url}/token?service=#{service}\"\n\n    stub_request(:get, uri).to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\nend\n"
  },
  {
    "path": "core/test/support/google_registry_stub_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::GoogleRegistryStubSupport\n  REGISTRY_URL = 'https://gcr.io'\n\n  def stub_google_registry_token(response, code = 200)\n    service = URI.parse(REGISTRY_URL).hostname\n    uri = \"#{REGISTRY_URL}/v2/token?service=#{service}\"\n\n    stub_request(:get, uri).to_return(status: code, body: response.to_json, headers: { 'Content-Type' => 'application/json' })\n  end\n\n  def stub_google_registry_manifests(image, tag, headers, body)\n    uri = \"#{REGISTRY_URL}/v2/#{image}/manifests/#{tag}\"\n\n    stub_request(:get, uri).to_return(status: 200, headers: headers, body: body.to_json)\n  end\nend\n"
  },
  {
    "path": "core/test/support/stub_support.rb",
    "content": "# frozen_string_literal: true\n\nmodule UffizziCore::StubSupport\n  def stub_controller\n    stub_request(:any, /#{Regexp.quote(Settings.controller.url.to_s)}\\/*./)\n  end\nend\n"
  },
  {
    "path": "core/test/test_helper.rb",
    "content": "# frozen_string_literal: true\n\nENV['RAILS_ENV'] = 'test'\n\nrequire_relative '../test/dummy/config/environment'\nActiveRecord::Migrator.migrations_paths = [File.expand_path('../test/dummy/db/migrate', __dir__)]\nActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__)\n\nDir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |f| require f }\n\nrequire 'awesome_print'\nrequire 'byebug'\nrequire 'factory_bot'\nrequire 'faker'\nrequire 'minitest/hooks/default'\nrequire 'minitest-power_assert'\nrequire 'minitest/mock'\nrequire 'mocha/minitest'\nrequire 'rails/test_help'\nrequire 'sidekiq/testing'\nrequire 'webmock/minitest'\nrequire 'octokit'\nrequire 'deepsort'\nrequire 'hash_diff'\n\nFactoryBot.reload\nWebMock.disable_net_connect!\nSidekiq::Testing.inline!\n\n# Load fixtures from the engine\nif ActiveSupport::TestCase.respond_to?(:fixture_path=)\n  ActiveSupport::TestCase.fixture_path = File.expand_path('fixtures', __dir__)\n  ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path\n  ActiveSupport::TestCase.file_fixture_path = \"#{ActiveSupport::TestCase.fixture_path}/files\"\n  ActiveSupport::TestCase.fixtures(:all)\nend\n\nFactoryBot.define do\n  initialize_with { new(attributes) }\nend\n\nclass ActiveSupport::TestCase\n  include FactoryBot::Syntax::Methods\n  include Minitest::Hooks\n  include ActiveModel::Validations\n  include UffizziCore::AuthManagement\n  include UffizziCore::ControllerStubSupport\n  include UffizziCore::DockerHubStubSupport\n  include UffizziCore::FixtureSupport\n  include UffizziCore::GoogleRegistryStubSupport\n  include UffizziCore::StubSupport\n  include UffizziCore::AzureRegistryStubSupport\n  include UffizziCore::GithubContainerRegistryStubSupport\n  include UffizziCore::DockerRegistryStubSupport\n\n  setup do\n    @routes = UffizziCore::Engine.routes\n  end\nend\n"
  },
  {
    "path": "core/test/uffizzi_core_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UffizziCoreTest < ActiveSupport::TestCase\n  test 'it has a version number' do\n    assert UffizziCore::VERSION\n  end\nend\n"
  },
  {
    "path": "core/uffizzi_core.gemspec",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/uffizzi_core/version'\n# rubocop:disable Metrics/BlockLength\nGem::Specification.new do |spec|\n  spec.name        = 'uffizzi_core'\n  spec.version     = UffizziCore::VERSION\n  spec.authors     = ['Josh Thurman', 'Grayson Adkins']\n  spec.email       = ['info@uffizzi.com']\n\n  spec.summary = 'uffizzi_core'\n  spec.description = 'uffizzi_core'\n  spec.homepage = 'https://uffizzi.com'\n  spec.license = 'Apache-2.0'\n  spec.required_ruby_version = '>= 2.5.0'\n\n  spec.metadata['homepage_uri'] = spec.homepage\n  spec.metadata['source_code_uri'] = 'https://github.com/UffizziCloud/uffizzi_app/tree/main/core'\n  spec.metadata['changelog_uri'] = 'https://github.com/UffizziCloud/uffizzi_app/blob/main/core/CHANGELOG.md'\n\n  spec.files = Dir['{app,config,db,lib,swagger}/**/*', 'LICENSE', 'Rakefile', 'README.md']\n\n  spec.add_dependency 'aasm'\n  spec.add_dependency 'actionpack', '~> 6.1.0'\n  spec.add_dependency 'active_model_serializers'\n  spec.add_dependency 'activerecord',  '~> 6.1.0'\n  spec.add_dependency 'activesupport', '~> 6.1.0'\n  spec.add_dependency 'ancestry'\n  spec.add_dependency 'aws-sdk-ecr', '~> 1.47'\n  spec.add_dependency 'aws-sdk-eventbridge', '~> 1.30'\n  spec.add_dependency 'aws-sdk-iam', '~> 1.61'\n  spec.add_dependency 'docker_distribution'\n  spec.add_dependency 'dotenv'\n  spec.add_dependency 'enumerize'\n  spec.add_dependency 'faraday'\n  spec.add_dependency 'faraday_curl'\n  spec.add_dependency 'faraday-follow_redirects'\n  spec.add_dependency 'faraday_middleware'\n  spec.add_dependency 'hashie'\n  spec.add_dependency 'jwt'\n  spec.add_dependency 'kaminari'\n  spec.add_dependency 'octokit'\n  spec.add_dependency 'pg', '>= 0.18', '< 2.0'\n  spec.add_dependency 'pundit'\n  spec.add_dependency 'rails', '~> 6.1.0'\n  spec.add_dependency 'ransack'\n  spec.add_dependency 'responders'\n  spec.add_dependency 'rolify'\n  spec.add_dependency 'rswag-api'\n  spec.add_dependency 'rswag-ui'\n  spec.add_dependency 'sidekiq'\n  spec.add_dependency 'sidekiq-unique-jobs'\n  spec.add_dependency 'swagger_yard'\n  spec.add_dependency 'virtus'\n\n  spec.add_development_dependency 'awesome_print'\n  spec.add_development_dependency 'bcrypt', '~> 3.1.7'\n  spec.add_development_dependency 'bump'\n  spec.add_development_dependency 'bundler', '~> 2.2'\n  spec.add_development_dependency 'byebug'\n  spec.add_development_dependency 'config'\n  spec.add_development_dependency 'deepsort', '~> 0.4.5'\n  spec.add_development_dependency 'factory_bot'\n  spec.add_development_dependency 'faker'\n  spec.add_development_dependency 'hash_diff', '~> 1.1'\n  spec.add_development_dependency 'minitest'\n  spec.add_development_dependency 'minitest-hooks'\n  spec.add_development_dependency 'minitest-power_assert'\n  spec.add_development_dependency 'mocha'\n  spec.add_development_dependency 'pry-byebug'\n  spec.add_development_dependency 'pry-inline'\n  spec.add_development_dependency 'puma'\n  spec.add_development_dependency 'rack-cors'\n  spec.add_development_dependency 'rake'\n  spec.add_development_dependency 'webmock'\nend\n# rubocop:enable Metrics/BlockLength\n"
  },
  {
    "path": "db/migrate/20220219114713_create_uffizzi_core_tables.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220218121438)\n\nclass CreateUffizziCoreTables < ActiveRecord::Migration[6.1]\n  def change\n    create_table 'uffizzi_core_accounts', force: :cascade do |t|\n      t.text 'name'\n      t.text 'kind', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'customer_token'\n      t.string 'state'\n      t.string 'subscription_token'\n      t.datetime 'payment_issue_at'\n      t.string 'domain'\n      t.boolean 'sso_enabled', default: false\n      t.bigint 'owner_id'\n      t.integer 'container_memory_limit'\n      t.string 'workos_organization_id'\n      t.string 'sso_state'\n      t.index ['customer_token'], name: 'index_accounts_on_customer_token', unique: true\n      t.index ['domain'], name: 'index_accounts_on_domain', unique: true\n      t.index ['subscription_token'], name: 'index_accounts_on_subscription_token', unique: true\n    end\n\n    create_table 'uffizzi_core_activity_items', force: :cascade do |t|\n      t.bigint 'deployment_id', null: false\n      t.string 'namespace'\n      t.string 'name'\n      t.string 'tag'\n      t.string 'branch'\n      t.string 'type'\n      t.bigint 'container_id', null: false\n      t.string 'commit'\n      t.string 'commit_message'\n      t.bigint 'build_id'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.jsonb 'data', default: {}, null: false\n      t.string 'digest'\n      t.index ['container_id'], name: 'index_activity_items_on_container_id'\n      t.index ['deployment_id'], name: 'index_activity_items_on_deployment_id'\n    end\n\n    create_table 'uffizzi_core_builds', force: :cascade do |t|\n      t.bigint 'repo_id', null: false\n      t.string 'build_id'\n      t.string 'repository'\n      t.string 'branch'\n      t.string 'commit'\n      t.string 'committer'\n      t.string 'message'\n      t.string 'log_url'\n      t.integer 'status'\n      t.datetime 'started_at'\n      t.datetime 'ended_at'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.boolean 'deployed'\n      t.index ['build_id'], name: 'index_builds_on_build_id', unique: true\n      t.index ['repo_id'], name: 'index_builds_on_repo_id'\n    end\n\n    create_table 'uffizzi_core_comments', force: :cascade do |t|\n      t.string 'commentable_type'\n      t.bigint 'commentable_id'\n      t.text 'content'\n      t.bigint 'user_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'ancestry'\n      t.integer 'ancestry_depth', default: 0\n      t.index ['ancestry'], name: 'index_comments_on_ancestry'\n      t.index ['commentable_type', 'commentable_id'], name: 'index_comments_on_commentable'\n      t.index ['user_id'], name: 'index_comments_on_user_id'\n    end\n\n    create_table 'uffizzi_core_compose_files', force: :cascade do |t|\n      t.string 'source'\n      t.bigint 'repository_id'\n      t.string 'branch'\n      t.string 'path'\n      t.string 'auto_deploy'\n      t.bigint 'added_by_id'\n      t.bigint 'project_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'state'\n      t.jsonb 'payload', default: {}, null: false\n      t.text 'content'\n      t.string 'kind', default: 'main'\n      t.index ['project_id'], name: 'index_compose_files_on_project_id'\n    end\n\n    create_table 'uffizzi_core_config_files', force: :cascade do |t|\n      t.string 'filename'\n      t.string 'kind'\n      t.bigint 'added_by_id'\n      t.text 'payload'\n      t.bigint 'project_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.bigint 'compose_file_id'\n      t.string 'creation_source'\n      t.string 'source'\n      t.index ['compose_file_id'], name: 'index_config_files_on_compose_file_id'\n      t.index ['project_id'], name: 'index_config_files_on_project_id'\n    end\n\n    create_table 'uffizzi_core_container_config_files', force: :cascade do |t|\n      t.string 'mount_path'\n      t.bigint 'container_id', null: false\n      t.bigint 'config_file_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.index ['config_file_id'], name: 'index_container_config_files_on_config_file_id'\n      t.index ['container_id'], name: 'index_container_config_files_on_container_id'\n    end\n\n    create_table 'uffizzi_core_containers', force: :cascade do |t|\n      t.string 'image'\n      t.string 'tag'\n      t.jsonb 'variables'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.bigint 'deployment_id'\n      t.boolean 'public', default: false, null: false\n      t.integer 'port'\n      t.bigint 'repo_id'\n      t.string 'state'\n      t.string 'continuously_deploy', null: false\n      t.string 'kind', default: 'user'\n      t.integer 'target_port'\n      t.string 'controller_name'\n      t.boolean 'receive_incoming_requests'\n      t.integer 'memory_request'\n      t.integer 'memory_limit'\n      t.jsonb 'secret_variables'\n      t.string 'entrypoint'\n      t.string 'command'\n      t.index ['deployment_id'], name: 'index_containers_on_deployment_id'\n      t.index ['repo_id'], name: 'index_containers_on_repo_id'\n    end\n\n    create_table 'uffizzi_core_coupons', force: :cascade do |t|\n      t.string 'token'\n      t.string 'name', null: false\n      t.string 'currency', null: false\n      t.bigint 'amount_off', null: false\n      t.string 'duration', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n    end\n\n    create_table 'uffizzi_core_credentials', force: :cascade do |t|\n      t.string 'type'\n      t.string 'username'\n      t.string 'password'\n      t.bigint 'project_id'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'provider_ref'\n      t.bigint 'account_id'\n      t.string 'state'\n      t.string 'registry_url'\n      t.index ['account_id'], name: 'index_credentials_on_account_id'\n      t.index ['project_id'], name: 'index_credentials_on_project_id'\n      t.index ['provider_ref'], name: 'index_credentials_on_provider_ref'\n    end\n\n    create_table 'uffizzi_core_deployments', force: :cascade do |t|\n      t.bigint 'project_id', null: false\n      t.text 'kind', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'creator_name'\n      t.string 'subdomain'\n      t.string 'state'\n      t.float 'memory_limit'\n      t.bigint 'deployed_by_id'\n      t.bigint 'continuous_preview_id_deprecated'\n      t.jsonb 'continuous_preview_payload'\n      t.string 'creation_source'\n      t.bigint 'compose_file_id'\n      t.bigint 'template_id'\n      t.index ['compose_file_id'], name: 'index_deployments_on_compose_file_id'\n      t.index ['project_id'], name: 'index_deployments_on_project_id'\n      t.index ['template_id'], name: 'index_deployments_on_template_id'\n    end\n\n    create_table 'uffizzi_core_events', force: :cascade do |t|\n      t.bigint 'activity_item_id', null: false\n      t.string 'state'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.index ['activity_item_id'], name: 'index_events_on_activity_item_id'\n    end\n\n    create_table 'uffizzi_core_invitations', force: :cascade do |t|\n      t.text 'email', null: false\n      t.text 'token', null: false\n      t.string 'status', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.bigint 'invited_by_id', null: false\n      t.bigint 'entityable_id', null: false\n      t.string 'entityable_type', null: false\n      t.string 'role', null: false\n      t.bigint 'invitee_id'\n      t.index ['token'], name: 'index_invitations_on_token', unique: true\n    end\n\n    create_table 'uffizzi_core_memberships', force: :cascade do |t|\n      t.bigint 'user_id', null: false\n      t.bigint 'account_id', null: false\n      t.text 'role', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.index ['account_id'], name: 'index_memberships_on_account_id'\n      t.index ['user_id', 'account_id'], name: 'index_memberships_on_user_id_and_account_id'\n      t.index ['user_id'], name: 'index_memberships_on_user_id'\n    end\n\n    create_table 'uffizzi_core_payments', force: :cascade do |t|\n      t.bigint 'account_id', null: false\n      t.string 'charge_id'\n      t.string 'status'\n      t.float 'amount'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.index ['account_id'], name: 'index_payments_on_account_id'\n    end\n\n    create_table 'uffizzi_core_prices', force: :cascade do |t|\n      t.string 'token'\n      t.string 'slug', null: false\n      t.string 'name', null: false\n      t.float 'units_price', null: false\n      t.bigint 'units_amount', null: false\n      t.bigint 'product_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.index ['product_id'], name: 'index_prices_on_product_id'\n    end\n\n    create_table 'uffizzi_core_products', force: :cascade do |t|\n      t.string 'token'\n      t.string 'slug', null: false\n      t.string 'name', null: false\n      t.string 'type', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'kind'\n    end\n\n    create_table 'uffizzi_core_projects', force: :cascade do |t|\n      t.text 'name', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.bigint 'account_id', null: false\n      t.string 'state'\n      t.string 'slug'\n      t.string 'description'\n      t.jsonb 'secrets'\n      t.index ['account_id', 'name'], name: 'index_projects_on_account_id_and_name', unique: true\n      t.index ['account_id'], name: 'index_projects_on_account_id'\n    end\n\n    create_table 'uffizzi_core_ratings', force: :cascade do |t|\n      t.string 'name'\n      t.string 'state'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n    end\n\n    create_table 'uffizzi_core_repos', force: :cascade do |t|\n      t.string 'namespace'\n      t.string 'name'\n      t.string 'tag'\n      t.string 'type'\n      t.string 'branch'\n      t.bigint 'project_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'description'\n      t.boolean 'is_private'\n      t.string 'slug'\n      t.bigint 'repository_id'\n      t.string 'kind'\n      t.string 'dockerfile_path'\n      t.jsonb 'args'\n      t.string 'dockerfile_context_path'\n      t.boolean 'deploy_preview_when_pull_request_is_opened'\n      t.boolean 'delete_preview_when_pull_request_is_closed'\n      t.boolean 'deploy_preview_when_image_tag_is_created'\n      t.boolean 'delete_preview_when_image_tag_is_updated'\n      t.boolean 'share_to_github'\n      t.integer 'delete_preview_after'\n      t.string 'tag_pattern_deprecated'\n      t.index ['project_id'], name: 'index_repos_on_project_id'\n    end\n\n    create_table 'uffizzi_core_roles', force: :cascade do |t|\n      t.string 'name'\n      t.string 'resource_type'\n      t.bigint 'resource_id'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.index ['name', 'resource_type', 'resource_id'], name: 'index_roles_on_name_and_resource_type_and_resource_id'\n      t.index ['resource_type', 'resource_id'], name: 'index_roles_on_resource_type_and_resource_id'\n    end\n\n    create_table 'uffizzi_core_templates', force: :cascade do |t|\n      t.string 'name'\n      t.bigint 'added_by_id'\n      t.jsonb 'payload', null: false\n      t.bigint 'project_id', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.bigint 'compose_file_id'\n      t.string 'creation_source'\n      t.index ['project_id'], name: 'index_templates_on_project_id'\n    end\n\n    create_table 'uffizzi_core_user_projects', force: :cascade do |t|\n      t.bigint 'user_id', null: false\n      t.bigint 'project_id', null: false\n      t.text 'role', null: false\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.bigint 'invited_by_id'\n      t.index ['project_id'], name: 'index_user_projects_on_project_id'\n      t.index ['user_id', 'project_id'], name: 'index_user_projects_on_user_id_and_project_id'\n      t.index ['user_id'], name: 'index_user_projects_on_user_id'\n    end\n\n    create_table 'uffizzi_core_users', force: :cascade do |t|\n      t.string 'first_name'\n      t.string 'last_name'\n      t.string 'email', default: '', null: false\n      t.string 'password_digest', default: '', null: false\n      t.string 'confirmation_token'\n      t.string 'state'\n      t.string 'phone'\n      t.datetime 'created_at', precision: 6, null: false\n      t.datetime 'updated_at', precision: 6, null: false\n      t.string 'github'\n      t.string 'website'\n      t.string 'twitter'\n      t.string 'linkedin'\n      t.string 'devto'\n      t.string 'facebook'\n      t.string 'blog'\n      t.text 'bio'\n      t.string 'status'\n      t.string 'availability'\n      t.string 'primary_skills'\n      t.string 'learning'\n      t.string 'coding_for'\n      t.string 'education'\n      t.string 'title'\n      t.string 'work'\n      t.string 'primary_location'\n      t.string 'creation_source'\n      t.index 'lower((email)::text)', name: 'index_email_on_lower_email', unique: true\n    end\n\n    create_table 'uffizzi_core_users_roles', id: false, force: :cascade do |t|\n      t.bigint 'user_id'\n      t.bigint 'role_id'\n      t.index ['role_id'], name: 'index_users_roles_on_role_id'\n      t.index ['user_id', 'role_id'], name: 'index_users_roles_on_user_id_and_role_id'\n      t.index ['user_id'], name: 'index_users_roles_on_user_id'\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220317112742_remove_secrets_from_projects.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220309110201)\n\nclass RemoveSecretsFromProjects < ActiveRecord::Migration[6.1]\n  def change\n    remove_column('uffizzi_core_projects', 'secrets')\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220317112743_create_project_secrets.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220310110150)\n\nclass CreateProjectSecrets < ActiveRecord::Migration[6.1]\n  def change\n    create_table('uffizzi_core_project_secrets', force: :cascade) do |t|\n      t.bigint('project_id', null: false)\n      t.string('name')\n      t.string('value')\n      t.datetime('created_at', precision: 6, null: false)\n      t.datetime('updated_at', precision: 6, null: false)\n      t.index(['project_id'], name: 'index_project_secrets_on_project_id')\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220325113623_add_name_to_uffizzi_containers.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220325113342)\nclass AddNameToUffizziContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column :uffizzi_core_containers, :name, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220412133606_rename_project_secrets_to_secrets.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220329123323)\n\nclass RenameProjectSecretsToSecrets < ActiveRecord::Migration[6.1]\n  def change\n    rename_table(:uffizzi_core_project_secrets, :uffizzi_core_secrets)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220412133607_add_resource_to_secrets.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220329124542)\n\nclass AddResourceToSecrets < ActiveRecord::Migration[6.1]\n  def change\n    add_belongs_to(:uffizzi_core_secrets, :resource, polymorphic: true)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220412133608_remove_project_ref_from_secrets.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220329143241)\n\nclass RemoveProjectRefFromSecrets < ActiveRecord::Migration[6.1]\n  def change\n    remove_reference(:uffizzi_core_secrets, :project)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220420103952_add_health_check_to_containers.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220419074956)\n\nclass AddHealthCheckToContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column :uffizzi_core_containers, :healthcheck, :jsonb\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220527135654_rename_name_to_uffizzi_containers.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220525113412)\n\nclass RenameNameToUffizziContainers < ActiveRecord::Migration[6.1]\n  def change\n    rename_column(:uffizzi_core_containers, :name, :service_name)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220617184754_add_volumes_to_uffizzi_core_containers.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220422151523)\n\nclass AddVolumesToUffizziCoreContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :volumes, :jsonb)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220708093405_add_disabled_at_to_deployments.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220704135629)\n\nclass AddDisabledAtToDeployments < ActiveRecord::Migration[6.1]\n  def up\n    change_table :uffizzi_core_deployments do |t|\n      t.datetime :disabled_at\n    end\n    UffizziCore::Deployment.disabled.update_all('disabled_at = updated_at')\n  end\n\n  def down\n    change_table :uffizzi_core_deployments do |t|\n      t.remove :disabled_at\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220817140346_add_metadata_to_deployment.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20220805164628)\n\nclass AddMetadataToDeployment < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_deployments, :metadata, :jsonb, default: {})\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220901110752_create_host_volume_files.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateHostVolumeFiles < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_host_volume_files do |t|\n      t.string :source\n      t.string :path\n      t.boolean :is_file\n      t.binary :payload\n      t.bigint :added_by_id\n      t.timestamps\n\n      t.references :project, null: false,\n                             foreign_key: true,\n                             index: { name: :index_host_volume_file_on_project_id },\n                             foreign_key: { to_table: :uffizzi_core_projects }\n      t.references :compose_file, null: false,\n                                  foreign_key: true,\n                                  index: { name: :index_host_volume_file_on_compose_file_id },\n                                  foreign_key: { to_table: :uffizzi_core_compose_files }\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220901165313_create_container_host_volume_files.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateContainerHostVolumeFiles < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_container_host_volume_files do |t|\n      t.string :source_path\n      t.timestamps\n      t.references :container, null: false,\n                               foreign_key: true,\n                               index: { name: :uf_core_cont_h_v_on_cont },\n                               foreign_key: { to_table: :uffizzi_core_containers }\n      t.references :host_volume_file, null: false,\n                                      foreign_key: true,\n                                      index: { name: :uf_core_cont_h_v_on_h_v_file },\n                                      foreign_key: { to_table: :uffizzi_core_host_volume_files }\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220927113647_add_additional_subdomains_to_containers.rb",
    "content": "# frozen_string_literal: true\n\nclass AddAdditionalSubdomainsToContainers < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :additional_subdomains, :string, array: true, default: [])\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230111000000_add_state_to_memberships.rb",
    "content": "# frozen_string_literal: true\n\nclass AddStateToMemberships < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_memberships, :state, :string)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230306142805_add_last_deploy_at_to_deployments.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20230306142513)\nclass AddLastDeployAtToDeployments < ActiveRecord::Migration[6.1]\n  def up\n    add_column :uffizzi_core_deployments, :last_deploy_at, :datetime\n\n    UffizziCore::Deployment.update_all('last_deploy_at = updated_at')\n  end\n\n  def down\n    remove_column :uffizzi_core_deployments, :last_deploy_at\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230406154547_add_full_image_name_to_container.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20230406154451)\nclass AddFullImageNameToContainer < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_containers, :full_image_name, :string)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230531135739_create_deployment_events.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateDeploymentEvents < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_deployment_events do |t|\n      t.string :deployment_state\n      t.string :message\n      t.timestamps\n      t.references :deployment, null: false,\n                                index: { name: :uf_core_dep_events_on_dep },\n                                foreign_key: { to_table: :uffizzi_core_deployments }\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230613110517_create_clusters.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20230613101901)\nclass CreateClusters < ActiveRecord::Migration[6.1]\n  def change\n    create_table('uffizzi_core_clusters', force: :cascade) do |t|\n      t.references :project, null: false,\n                             foreign_key: true,\n                             index: { name: :index_cluster_on_project_id },\n                             foreign_key: { to_table: :uffizzi_core_projects }\n      t.bigint 'deployed_by_id', foreign_key: true\n      t.string 'state'\n      t.string 'name'\n      t.text 'manifest'\n      t.text 'kubeconfig'\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230711101901_add_host_to_clusters.rb",
    "content": "# frozen_string_literal: true\n\nclass AddHostToClusters < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :host, :string)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230824150022_update_name_constraint_to_projects.rb",
    "content": "# frozen_string_literal: true\n\nclass UpdateNameConstraintToProjects < ActiveRecord::Migration[6.1]\n  def change\n    remove_index(:uffizzi_core_projects, [:account_id, :name])\n    add_index(:uffizzi_core_projects, [:account_id, :name], unique: true,\n                                                            where: \"state = 'active'\",\n                                                            name: 'proj_uniq_name')\n  end\nend\n"
  },
  {
    "path": "db/migrate/20231009103942_add_source_to_uffizzi_core_clusters.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20230810140316)\n\nclass AddSourceToUffizziCoreClusters < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :creation_source, :string)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20231009163516_create_uffizzi_core_kubernetes_distributions.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20231009162719)\nclass CreateUffizziCoreKubernetesDistributions < ActiveRecord::Migration[6.1]\n  def change\n    create_table :uffizzi_core_kubernetes_distributions do |t|\n      t.string :version\n      t.string :distro\n      t.string :image\n      t.boolean :default\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20231009201239_add_kubernetes_distribution_id_to_uffizzi_core_clusters.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20231009182412)\nclass AddKubernetesDistributionIdToUffizziCoreClusters < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :kubernetes_distribution_id, :integer, foreign_key: true)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240301200916_add_node_selector_to_cluster.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20240301200235)\nclass AddNodeSelectorToCluster < ActiveRecord::Migration[6.1]\n  def change\n    add_column(:uffizzi_core_clusters, :node_selector, :string)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240314170425_delete_node_selector_from_cluster.uffizzi_core.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from uffizzi_core (originally 20240314170113)\nclass DeleteNodeSelectorFromCluster < ActiveRecord::Migration[6.1]\n  def change\n    remove_column(:uffizzi_core_clusters, :node_selector, :string)\n  end\nend\n"
  },
  {
    "path": "db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema.define(version: 2024_03_14_170425) do\n\n  # These are extensions that must be enabled in order to support this database\n  enable_extension \"plpgsql\"\n\n  create_table \"uffizzi_core_accounts\", force: :cascade do |t|\n    t.text \"name\"\n    t.text \"kind\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"customer_token\"\n    t.string \"state\"\n    t.string \"subscription_token\"\n    t.datetime \"payment_issue_at\"\n    t.string \"domain\"\n    t.boolean \"sso_enabled\", default: false\n    t.bigint \"owner_id\"\n    t.integer \"container_memory_limit\"\n    t.string \"workos_organization_id\"\n    t.string \"sso_state\"\n    t.index [\"customer_token\"], name: \"index_accounts_on_customer_token\", unique: true\n    t.index [\"domain\"], name: \"index_accounts_on_domain\", unique: true\n    t.index [\"subscription_token\"], name: \"index_accounts_on_subscription_token\", unique: true\n  end\n\n  create_table \"uffizzi_core_activity_items\", force: :cascade do |t|\n    t.bigint \"deployment_id\", null: false\n    t.string \"namespace\"\n    t.string \"name\"\n    t.string \"tag\"\n    t.string \"branch\"\n    t.string \"type\"\n    t.bigint \"container_id\", null: false\n    t.string \"commit\"\n    t.string \"commit_message\"\n    t.bigint \"build_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.jsonb \"data\", default: {}, null: false\n    t.string \"digest\"\n    t.index [\"container_id\"], name: \"index_activity_items_on_container_id\"\n    t.index [\"deployment_id\"], name: \"index_activity_items_on_deployment_id\"\n  end\n\n  create_table \"uffizzi_core_builds\", force: :cascade do |t|\n    t.bigint \"repo_id\", null: false\n    t.string \"build_id\"\n    t.string \"repository\"\n    t.string \"branch\"\n    t.string \"commit\"\n    t.string \"committer\"\n    t.string \"message\"\n    t.string \"log_url\"\n    t.integer \"status\"\n    t.datetime \"started_at\"\n    t.datetime \"ended_at\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.boolean \"deployed\"\n    t.index [\"build_id\"], name: \"index_builds_on_build_id\", unique: true\n    t.index [\"repo_id\"], name: \"index_builds_on_repo_id\"\n  end\n\n  create_table \"uffizzi_core_clusters\", force: :cascade do |t|\n    t.bigint \"project_id\", null: false\n    t.bigint \"deployed_by_id\"\n    t.string \"state\"\n    t.string \"name\"\n    t.text \"manifest\"\n    t.text \"kubeconfig\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"host\"\n    t.string \"creation_source\"\n    t.integer \"kubernetes_distribution_id\"\n    t.index [\"project_id\"], name: \"index_cluster_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_comments\", force: :cascade do |t|\n    t.string \"commentable_type\"\n    t.bigint \"commentable_id\"\n    t.text \"content\"\n    t.bigint \"user_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"ancestry\"\n    t.integer \"ancestry_depth\", default: 0\n    t.index [\"ancestry\"], name: \"index_comments_on_ancestry\"\n    t.index [\"commentable_type\", \"commentable_id\"], name: \"index_comments_on_commentable\"\n    t.index [\"user_id\"], name: \"index_comments_on_user_id\"\n  end\n\n  create_table \"uffizzi_core_compose_files\", force: :cascade do |t|\n    t.string \"source\"\n    t.bigint \"repository_id\"\n    t.string \"branch\"\n    t.string \"path\"\n    t.string \"auto_deploy\"\n    t.bigint \"added_by_id\"\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"state\"\n    t.jsonb \"payload\", default: {}, null: false\n    t.text \"content\"\n    t.string \"kind\", default: \"main\"\n    t.index [\"project_id\"], name: \"index_compose_files_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_config_files\", force: :cascade do |t|\n    t.string \"filename\"\n    t.string \"kind\"\n    t.bigint \"added_by_id\"\n    t.text \"payload\"\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"compose_file_id\"\n    t.string \"creation_source\"\n    t.string \"source\"\n    t.index [\"compose_file_id\"], name: \"index_config_files_on_compose_file_id\"\n    t.index [\"project_id\"], name: \"index_config_files_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_container_config_files\", force: :cascade do |t|\n    t.string \"mount_path\"\n    t.bigint \"container_id\", null: false\n    t.bigint \"config_file_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"config_file_id\"], name: \"index_container_config_files_on_config_file_id\"\n    t.index [\"container_id\"], name: \"index_container_config_files_on_container_id\"\n  end\n\n  create_table \"uffizzi_core_container_host_volume_files\", force: :cascade do |t|\n    t.string \"source_path\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"container_id\", null: false\n    t.bigint \"host_volume_file_id\", null: false\n    t.index [\"container_id\"], name: \"uf_core_cont_h_v_on_cont\"\n    t.index [\"host_volume_file_id\"], name: \"uf_core_cont_h_v_on_h_v_file\"\n  end\n\n  create_table \"uffizzi_core_containers\", force: :cascade do |t|\n    t.string \"image\"\n    t.string \"tag\"\n    t.jsonb \"variables\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"deployment_id\"\n    t.boolean \"public\", default: false, null: false\n    t.integer \"port\"\n    t.bigint \"repo_id\"\n    t.string \"state\"\n    t.string \"continuously_deploy\", null: false\n    t.string \"kind\", default: \"user\"\n    t.integer \"target_port\"\n    t.string \"controller_name\"\n    t.boolean \"receive_incoming_requests\"\n    t.integer \"memory_request\"\n    t.integer \"memory_limit\"\n    t.jsonb \"secret_variables\"\n    t.string \"entrypoint\"\n    t.string \"command\"\n    t.string \"service_name\"\n    t.jsonb \"healthcheck\"\n    t.jsonb \"volumes\"\n    t.string \"additional_subdomains\", default: [], array: true\n    t.string \"full_image_name\"\n    t.index [\"deployment_id\"], name: \"index_containers_on_deployment_id\"\n    t.index [\"repo_id\"], name: \"index_containers_on_repo_id\"\n  end\n\n  create_table \"uffizzi_core_coupons\", force: :cascade do |t|\n    t.string \"token\"\n    t.string \"name\", null: false\n    t.string \"currency\", null: false\n    t.bigint \"amount_off\", null: false\n    t.string \"duration\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n  end\n\n  create_table \"uffizzi_core_credentials\", force: :cascade do |t|\n    t.string \"type\"\n    t.string \"username\"\n    t.string \"password\"\n    t.bigint \"project_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"provider_ref\"\n    t.bigint \"account_id\"\n    t.string \"state\"\n    t.string \"registry_url\"\n    t.index [\"account_id\"], name: \"index_credentials_on_account_id\"\n    t.index [\"project_id\"], name: \"index_credentials_on_project_id\"\n    t.index [\"provider_ref\"], name: \"index_credentials_on_provider_ref\"\n  end\n\n  create_table \"uffizzi_core_deployment_events\", force: :cascade do |t|\n    t.string \"deployment_state\"\n    t.string \"message\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"deployment_id\", null: false\n    t.index [\"deployment_id\"], name: \"uf_core_dep_events_on_dep\"\n  end\n\n  create_table \"uffizzi_core_deployments\", force: :cascade do |t|\n    t.bigint \"project_id\", null: false\n    t.text \"kind\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"creator_name\"\n    t.string \"subdomain\"\n    t.string \"state\"\n    t.float \"memory_limit\"\n    t.bigint \"deployed_by_id\"\n    t.bigint \"continuous_preview_id_deprecated\"\n    t.jsonb \"continuous_preview_payload\"\n    t.string \"creation_source\"\n    t.bigint \"compose_file_id\"\n    t.bigint \"template_id\"\n    t.datetime \"disabled_at\"\n    t.jsonb \"metadata\", default: {}\n    t.datetime \"last_deploy_at\"\n    t.index [\"compose_file_id\"], name: \"index_deployments_on_compose_file_id\"\n    t.index [\"project_id\"], name: \"index_deployments_on_project_id\"\n    t.index [\"template_id\"], name: \"index_deployments_on_template_id\"\n  end\n\n  create_table \"uffizzi_core_events\", force: :cascade do |t|\n    t.bigint \"activity_item_id\", null: false\n    t.string \"state\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"activity_item_id\"], name: \"index_events_on_activity_item_id\"\n  end\n\n  create_table \"uffizzi_core_host_volume_files\", force: :cascade do |t|\n    t.string \"source\"\n    t.string \"path\"\n    t.boolean \"is_file\"\n    t.binary \"payload\"\n    t.bigint \"added_by_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"project_id\", null: false\n    t.bigint \"compose_file_id\", null: false\n    t.index [\"compose_file_id\"], name: \"index_host_volume_file_on_compose_file_id\"\n    t.index [\"project_id\"], name: \"index_host_volume_file_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_invitations\", force: :cascade do |t|\n    t.text \"email\", null: false\n    t.text \"token\", null: false\n    t.string \"status\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"invited_by_id\", null: false\n    t.bigint \"entityable_id\", null: false\n    t.string \"entityable_type\", null: false\n    t.string \"role\", null: false\n    t.bigint \"invitee_id\"\n    t.index [\"token\"], name: \"index_invitations_on_token\", unique: true\n  end\n\n  create_table \"uffizzi_core_kubernetes_distributions\", force: :cascade do |t|\n    t.string \"version\"\n    t.string \"distro\"\n    t.string \"image\"\n    t.boolean \"default\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n  end\n\n  create_table \"uffizzi_core_memberships\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.bigint \"account_id\", null: false\n    t.text \"role\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"state\"\n    t.index [\"account_id\"], name: \"index_memberships_on_account_id\"\n    t.index [\"user_id\", \"account_id\"], name: \"index_memberships_on_user_id_and_account_id\"\n    t.index [\"user_id\"], name: \"index_memberships_on_user_id\"\n  end\n\n  create_table \"uffizzi_core_payments\", force: :cascade do |t|\n    t.bigint \"account_id\", null: false\n    t.string \"charge_id\"\n    t.string \"status\"\n    t.float \"amount\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"account_id\"], name: \"index_payments_on_account_id\"\n  end\n\n  create_table \"uffizzi_core_prices\", force: :cascade do |t|\n    t.string \"token\"\n    t.string \"slug\", null: false\n    t.string \"name\", null: false\n    t.float \"units_price\", null: false\n    t.bigint \"units_amount\", null: false\n    t.bigint \"product_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"product_id\"], name: \"index_prices_on_product_id\"\n  end\n\n  create_table \"uffizzi_core_products\", force: :cascade do |t|\n    t.string \"token\"\n    t.string \"slug\", null: false\n    t.string \"name\", null: false\n    t.string \"type\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"kind\"\n  end\n\n  create_table \"uffizzi_core_projects\", force: :cascade do |t|\n    t.text \"name\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"account_id\", null: false\n    t.string \"state\"\n    t.string \"slug\"\n    t.string \"description\"\n    t.index [\"account_id\", \"name\"], name: \"proj_uniq_name\", unique: true, where: \"((state)::text = 'active'::text)\"\n    t.index [\"account_id\"], name: \"index_projects_on_account_id\"\n  end\n\n  create_table \"uffizzi_core_ratings\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"state\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n  end\n\n  create_table \"uffizzi_core_repos\", force: :cascade do |t|\n    t.string \"namespace\"\n    t.string \"name\"\n    t.string \"tag\"\n    t.string \"type\"\n    t.string \"branch\"\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"description\"\n    t.boolean \"is_private\"\n    t.string \"slug\"\n    t.bigint \"repository_id\"\n    t.string \"kind\"\n    t.string \"dockerfile_path\"\n    t.jsonb \"args\"\n    t.string \"dockerfile_context_path\"\n    t.boolean \"deploy_preview_when_pull_request_is_opened\"\n    t.boolean \"delete_preview_when_pull_request_is_closed\"\n    t.boolean \"deploy_preview_when_image_tag_is_created\"\n    t.boolean \"delete_preview_when_image_tag_is_updated\"\n    t.boolean \"share_to_github\"\n    t.integer \"delete_preview_after\"\n    t.string \"tag_pattern_deprecated\"\n    t.index [\"project_id\"], name: \"index_repos_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_roles\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"resource_type\"\n    t.bigint \"resource_id\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.index [\"name\", \"resource_type\", \"resource_id\"], name: \"index_roles_on_name_and_resource_type_and_resource_id\"\n    t.index [\"resource_type\", \"resource_id\"], name: \"index_roles_on_resource_type_and_resource_id\"\n  end\n\n  create_table \"uffizzi_core_secrets\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"value\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"resource_type\"\n    t.bigint \"resource_id\"\n    t.index [\"resource_type\", \"resource_id\"], name: \"index_uffizzi_core_secrets_on_resource\"\n  end\n\n  create_table \"uffizzi_core_templates\", force: :cascade do |t|\n    t.string \"name\"\n    t.bigint \"added_by_id\"\n    t.jsonb \"payload\", null: false\n    t.bigint \"project_id\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"compose_file_id\"\n    t.string \"creation_source\"\n    t.index [\"project_id\"], name: \"index_templates_on_project_id\"\n  end\n\n  create_table \"uffizzi_core_user_projects\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.bigint \"project_id\", null: false\n    t.text \"role\", null: false\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.bigint \"invited_by_id\"\n    t.index [\"project_id\"], name: \"index_user_projects_on_project_id\"\n    t.index [\"user_id\", \"project_id\"], name: \"index_user_projects_on_user_id_and_project_id\"\n    t.index [\"user_id\"], name: \"index_user_projects_on_user_id\"\n  end\n\n  create_table \"uffizzi_core_users\", force: :cascade do |t|\n    t.string \"first_name\"\n    t.string \"last_name\"\n    t.string \"email\", default: \"\", null: false\n    t.string \"password_digest\", default: \"\", null: false\n    t.string \"confirmation_token\"\n    t.string \"state\"\n    t.string \"phone\"\n    t.datetime \"created_at\", precision: 6, null: false\n    t.datetime \"updated_at\", precision: 6, null: false\n    t.string \"github\"\n    t.string \"website\"\n    t.string \"twitter\"\n    t.string \"linkedin\"\n    t.string \"devto\"\n    t.string \"facebook\"\n    t.string \"blog\"\n    t.text \"bio\"\n    t.string \"status\"\n    t.string \"availability\"\n    t.string \"primary_skills\"\n    t.string \"learning\"\n    t.string \"coding_for\"\n    t.string \"education\"\n    t.string \"title\"\n    t.string \"work\"\n    t.string \"primary_location\"\n    t.string \"creation_source\"\n    t.index \"lower((email)::text)\", name: \"index_email_on_lower_email\", unique: true\n  end\n\n  create_table \"uffizzi_core_users_roles\", id: false, force: :cascade do |t|\n    t.bigint \"user_id\"\n    t.bigint \"role_id\"\n    t.index [\"role_id\"], name: \"index_users_roles_on_role_id\"\n    t.index [\"user_id\", \"role_id\"], name: \"index_users_roles_on_user_id_and_role_id\"\n    t.index [\"user_id\"], name: \"index_users_roles_on_user_id\"\n  end\n\n  add_foreign_key \"uffizzi_core_clusters\", \"uffizzi_core_projects\", column: \"project_id\"\n  add_foreign_key \"uffizzi_core_container_host_volume_files\", \"uffizzi_core_containers\", column: \"container_id\"\n  add_foreign_key \"uffizzi_core_container_host_volume_files\", \"uffizzi_core_host_volume_files\", column: \"host_volume_file_id\"\n  add_foreign_key \"uffizzi_core_deployment_events\", \"uffizzi_core_deployments\", column: \"deployment_id\"\n  add_foreign_key \"uffizzi_core_host_volume_files\", \"uffizzi_core_compose_files\", column: \"compose_file_id\"\n  add_foreign_key \"uffizzi_core_host_volume_files\", \"uffizzi_core_projects\", column: \"project_id\"\nend\n"
  },
  {
    "path": "db/seeds.rb",
    "content": "# frozen_string_literal: true\n\nputs 'Creating User'\n\nuser = UffizziCore::User.create!(\n  email: 'admin@uffizzi.com',\n  password: 'password',\n  state: UffizziCore::User::STATE_ACTIVE,\n  creation_source: UffizziCore::User.creation_source.system,\n)\n\nputs 'Creating Accounts'\n\npersonal_account = UffizziCore::Account.create!(\n  owner: user,\n  name: 'personal',\n  state: UffizziCore::Account::STATE_ACTIVE,\n  kind: UffizziCore::Account.kind.personal,\n)\n\norganizational_account = UffizziCore::Account.create!(\n  owner: user,\n  name: 'organizational',\n  state: UffizziCore::Account::STATE_ACTIVE,\n  kind: UffizziCore::Account.kind.organizational,\n)\n\nuser.memberships.create!(account: personal_account, role: UffizziCore::Membership.role.admin)\nuser.memberships.create!(account: organizational_account, role: UffizziCore::Membership.role.admin)\n\npersonal_project = personal_account.projects.create!(name: 'default', slug: 'default', state: UffizziCore::Project::STATE_ACTIVE)\npersonal_project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)\n\norganizational_project = organizational_account.projects.create!(name: 'uffizzi', slug: 'uffizzi',\n                                                                 state: UffizziCore::Project::STATE_ACTIVE)\norganizational_project.user_projects.create!(user: user, role: UffizziCore::UserProject.role.admin)\n\nputs 'Creating Kubernetes Distributions'\n\nUffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.25', image: 'rancher/k3s:v1.25.14-k3s1')\nUffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.26', image: 'rancher/k3s:v1.26.9-k3s1')\nUffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.27', default: true, image: 'rancher/k3s:v1.27.6-k3s1')\nUffizziCore::KubernetesDistribution.create(distro: 'k3s', version: '1.28', image: 'rancher/k3s:v1.28.2-k3s1')\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3.9'\n\nx-web-environment: &web-environment\n  RAILS_SECRET_KEY_BASE: 44a599292ee918ca52c5060bb73b9a5b754628d6d67c64d0066c2ecf25381ef67b2b7a9981332316cc09e0a4bdbd08f07b7a9277d77fd4b4f2a39a488860c18c\n  DATABASE_HOST: db\n  DATABASE_USERNAME: postgres\n  DATABASE_PASSWORD: postgres\n  BUNDLE_PATH: /bundle_cache\n  GEM_HOME: /bundle_cache\n  GEM_PATH: /bundle_cache\n  RAILS_WORKERS_COUNT: 0\n  RAILS_THREADS_COUNT: 10\n  SIDEKIQ_CONCURRENCY: 1\n  RAILS_PORT: 7001\n  RAILS_ENV: development\n  ALLOWED_HOSTS: lvh.me,.lvh.me,lvh.me:1313,.lvh.me:1313,lvh.me:7001,.lvh.me:7001,localhost,.ngrok.io,localhost:3000,web\n  REDIS_URL: redis://redis\n  APP_URL: http://web:7001\n  RUBYGEMS_API_KEY: ${RUBYGEMS_API_KEY}\n\nservices:\n  web:\n    build: .\n    environment: *web-environment\n    env_file:\n      - .env\n    volumes: &web-volumes\n      - &app-volume .:/app:cached\n      - ~/.ssh:/root/.ssh\n      - &bash-history ~/.bash_history:/root/.bash_history\n      - &bundle-cache-volume bundle_cache:/bundle_cache\n    ports:\n      - 7001:7001\n    depends_on:\n      - db\n    command: bash -c \"bundle install && bundle exec rails db:create db:migrate && bundle exec puma -C config/puma.rb\"\n  core:\n    build: core\n    environment: *web-environment\n    env_file:\n      - .env\n    volumes:\n      - ./core:/gem:cached\n      - *bash-history\n      - *bundle-cache-volume\n    depends_on:\n      - db\n    command: bash -c \"bundle install && bundle exec rails db:create db:migrate\"\n\n  db:\n    image: postgres:11.4\n    ports:\n      - 5432:5432\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n\n  sidekiq:\n    build: .\n    environment: *web-environment\n    env_file:\n      - .env\n    volumes: *web-volumes\n    depends_on:\n      - redis\n      - web\n    command: bash -c \"bundle exec sidekiq -C /app/config/sidekiq.yml\"\n\n  redis:\n    image: redis\n\n  bundle_cache:\n    image: busybox\n    volumes:\n      - *bundle-cache-volume\n\nvolumes:\n  bundle_cache:\n  core:\n\nnetworks:\n  default:\n    name: 'uffizzi_default_network'\n"
  },
  {
    "path": "docs/continuous-previews.md",
    "content": "## INTRODUCTION-\n\n# Why this Open Source Project is Needed:\n\nThese 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:\n\n-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.\n\n-Virtually all teams from can benefit from an on-demand preview environment capability and industry defined best practices.\n\n-Many teams - typically more advanced teams with time, resources, and expertise - build and maintain an Internal Development Platform (IDP) to address a Preview requirement.\n\n-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.\n\n-As a community we are not actively collaborating or innovating towards a well-defined Preview Process and technical capability: we should be. \n\n-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.  \n\n-Docker compose is a configuration-as-code format that best balances DevOps requirements (GitOps, Infrastructure-as-code, Functionality) with Developer accessibility.\n\n-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/\n\n-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.\n\n# What this Project/Team aims to accomplish:\n\n-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.\n\n-Provide well-defined guidelines for Previewing - see https://continuous-previews.org\n"
  },
  {
    "path": "docs/quickstart-guide.md",
    "content": "# Quickstart guide\n\nThis guide describes configuring a [GitHub Action](https://github.com/UffizziCloud/preview-action) to create event-driven test environments for every pull requests.\n\n## Reusable Workflow\n\nWe'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.\n\n### Workflow Calling Example\n\nThis 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.\n\n```\nname: Build Images and Handle Uffizzi Previews.\n\non:\n  pull_request:\n    types: [opened,reopened,synchronize,closed]\n\njobs:\n  build-image:\n    name: Build and Push image\n    runs-on: ubuntu-latest\n    outputs:\n      # You'll need this output to later render the Compose file.\n      tags: ${{ steps.meta.outputs.tags }}\n    steps:\n      - name: Login to DockerHub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_PASSWORD }}\n      - name: Checkout git repo\n        uses: actions/checkout@v3\n      - name: Docker metadata\n        id: meta\n        uses: docker/metadata-action@v3\n        with:\n          images: example/image\n      - name: Build and Push Image to Docker Hub\n        uses: docker/build-push-action@v2\n        with:\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n  render-compose-file:\n    name: Render Docker Compose File\n    runs-on: ubuntu-latest\n    needs:\n      - build-image\n    outputs:\n      compose-file-cache-key: ${{ steps.hash.outputs.hash }}\n      compose-file-cache-path: docker-compose.rendered.yml\n    steps:\n      - name: Checkout git repo\n        uses: actions/checkout@v3\n      - name: Render Compose File\n        run: |\n          IMAGE=$(echo ${{ needs.build-image.outputs.tags }})\n          export IMAGE\n          # Render simple template from environment variables.\n          envsubst < docker-compose.template.yml > docker-compose.rendered.yml\n          cat docker-compose.rendered.yml\n      - name: Hash Rendered Compose File\n        id: hash\n        run: echo \"::set-output name=hash::$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')\"\n      - name: Cache Rendered Compose File\n        uses: actions/cache@v3\n        with:\n          path: docker-compose.rendered.yml\n          key: ${{ steps.hash.outputs.hash }}\n\n  deploy-uffizzi-preview:\n    name: Use Remote Workflow to Preview on Uffizzi\n    needs: render-compose-file\n    uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml\n    with:\n      compose-file-cache-key: ${{ needs.render-compose-file.outputs.compose-file-cache-key }}\n      compose-file-cache-path: ${{ needs.render-compose-file.outputs.compose-file-cache-path }}\n      username: user@example.com\n      server: https://uffizzi.example.com/\n      project: default\n    secrets:\n      password: ${{ secrets.UFFIZZI_PASSWORD }}\n    permissions:\n      contents: read\n      pull-requests: write\n```\n\n### Workflow Inputs\n\n#### `compose-file-cache-key`\n\n**Required** Key of hashed compose file, using [GitHub's `cache` action](https://github.com/marketplace/actions/cache)\n\n#### `compose-file-cache-path`\n\n**Required** Path of hashed compose file, using [GitHub's `cache` action](https://github.com/marketplace/actions/cache)\n\n#### `username`\n\n**Required** Uffizzi username. To use the Uffizzi managed API service, you must first [create an account here](https://app.uffizzi.com/sign_up).\n\n#### `project`\n\n**Required** Uffizzi project name\n\n#### `server`\n\nURL of your Uffizzi installation. To use the Uffizzi managed API service, set this value to `https://app.uffizzi.com`.\n### Workflow Secrets\n\n#### `password`\n\nYour Uffizzi password. Specify a GitHub Encrypted Secret and use it! See example above.\n\n# The Action Itself\n\nIf 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.\n\n## Inputs\n\n### `compose-file`\n\n**Required** Path to a compose file within your repository\n\n### `username`\n\n**Required** Uffizzi username\n\n### `project`\n\n**Required** Uffizzi project name\n\n### `server`\n\nURL of your Uffizzi installation\n\n### `password`\n\nYour Uffizzi password. Specify a GitHub Encrypted Secret and use it! See example below.\n\n## Example usage\n\n```yaml\nuses: UffizziCloud/preview-action@v2\nwith:\n  compose-file: 'docker-compose.uffizzi.yaml'\n  username: 'admin@uffizzi.com'\n  server: 'https://app.uffizzi.com'\n  project: 'default'\n  password: ${{ secrets.UFFIZZI_PASSWORD }}\n```"
  },
  {
    "path": "lib/assets/.keep",
    "content": ""
  },
  {
    "path": "lib/tasks/.keep",
    "content": ""
  },
  {
    "path": "log/.keep",
    "content": ""
  },
  {
    "path": "public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The page you were looking for doesn't exist.</h1>\n      <p>You may have mistyped the address or the page may have moved.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The change you wanted was rejected.</h1>\n      <p>Maybe you tried to change something you didn't have access to.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>We're sorry, but something went wrong.</h1>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n"
  },
  {
    "path": "storage/.keep",
    "content": ""
  },
  {
    "path": "test/application_system_test_case.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ApplicationSystemTestCase < ActionDispatch::SystemTestCase\n  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]\nend\n"
  },
  {
    "path": "test/channels/application_cable/connection_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase\n  # test \"connects with cookies\" do\n  #   cookies.signed[:user_id] = 42\n  #\n  #   connect\n  #\n  #   assert_equal connection.user_id, \"42\"\n  # end\nend\n"
  },
  {
    "path": "test/controllers/.keep",
    "content": ""
  },
  {
    "path": "test/fixtures/.keep",
    "content": ""
  },
  {
    "path": "test/fixtures/files/.keep",
    "content": ""
  },
  {
    "path": "test/helpers/.keep",
    "content": ""
  },
  {
    "path": "test/integration/.keep",
    "content": ""
  },
  {
    "path": "test/mailers/.keep",
    "content": ""
  },
  {
    "path": "test/models/.keep",
    "content": ""
  },
  {
    "path": "test/system/.keep",
    "content": ""
  },
  {
    "path": "test/test_helper.rb",
    "content": "# frozen_string_literal: true\n\nENV['RAILS_ENV'] ||= 'test'\nrequire_relative '../config/environment'\nrequire 'rails/test_help'\n\nclass ActiveSupport::TestCase\n  # Run tests in parallel with specified workers\n  parallelize(workers: :number_of_processors)\n\n  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.\n  fixtures :all\n\n  # Add more helper methods to be used by all tests here...\nend\n"
  },
  {
    "path": "tmp/.keep",
    "content": ""
  },
  {
    "path": "uffizzi-compose-example.yml",
    "content": "services:\n  redis:\n    image: redis:latest\n\n  postgres:\n    image: postgres:9.6\n    environment:\n      POSTGRES_USER: ${VOTE_APP_POSTGRES.USER}\n      POSTGRES_PASSWORD: ${VOTE_APP_POSTGRES.PASSWORD}\n\n  nginx:\n    image: nginx:latest\n    configs:\n      - source: vote.conf\n        target: /etc/nginx/conf.d\n\n  worker:\n    build:\n      context: https://github.com/UffizziCloud/example-voting-worker:main\n      dockerfile: Dockerfile\n    deploy:\n      resources:\n        limits:\n          memory: 250M\n\n  vote:\n    build:\n      context: https://github.com/UffizziCloud/example-voting-vote:main\n      dockerfile: Dockerfile\n    deploy:\n      resources:\n        limits:\n          memory: 250M\n\n  result:\n    build:\n      context: https://github.com/UffizziCloud/example-voting-result:main\n      dockerfile: Dockerfile\n\ncontinuous_preview:\n  deploy_preview_when_pull_request_is_opened: false\n  delete_preview_when_pull_request_is_closed: false\n  deploy_preview_for_image_tag_PR_#-branchname: true\n  delete_preview_when_pull_request_is_closed: false\n  delete_preview_in_x_hours: 24\n  share_to_github: true\n\ningress:\n  service: nginx\n  port: 8080\n"
  },
  {
    "path": "vendor/.keep",
    "content": ""
  }
]