File uyuni-tools-git-72.8ca4b77.obscpio of Package uyuni-tools

07070100000000000041FD00000000000000000000000B662A752800000000000000000000000000000000000000000000000C00000000uyuni-tools07070100000001000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001400000000uyuni-tools/.github07070100000002000081B4000000000000000000000001662A75280000036B000000000000000000000000000000000000002D00000000uyuni-tools/.github/PULL_REQUEST_TEMPLATE.md<!--
SPDX-FileCopyrightText: 2024 SUSE LLC

SPDX-License-Identifier: Apache-2.0
-->

## What does this PR change?

**add description**

## Test coverage
- No tests: **add explanation**
- No tests: already covered
- Unit tests were added

- [ ] **DONE**

## Links

Issue(s): #

- [ ] **DONE**

## Changelogs

Make sure the changelogs entries you are adding are compliant with https://github.com/uyuni-project/uyuni/wiki/Contributing#changelogs and https://github.com/uyuni-project/uyuni/wiki/Contributing#uyuni-projectuyuni-repository

If you don't need a changelog check, please mark this checkbox:

- [ ] No changelog needed

If you uncheck the checkbox after the PR is created, you will need to re-run `changelog_test` (see below)

# Before you merge

Check [How to branch and merge properly](https://github.com/uyuni-project/uyuni/wiki/How-to-branch-and-merge-properly)!

07070100000003000081B4000000000000000000000001662A752800000209000000000000000000000000000000000000002300000000uyuni-tools/.github/dependabot.yml# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
07070100000004000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/.github/workflows07070100000005000081B4000000000000000000000001662A752800000623000000000000000000000000000000000000002800000000uyuni-tools/.github/workflows/build.yml# SPDX-FileCopyrightText: 2023 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

name: Build

on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
  release:
    types:
      - published

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-tags: true
          fetch-depth: 0

      - name: Setup Go ${{ matrix.go-version }}
        uses: actions/setup-go@v5
        with:
          go-version: '1.20'

      - name: Install dependencies
        run: |
          go get ./...

      - name: Compute version
        run: |
          tag=$(git describe --tags --abbrev=0)
          version=$(git describe --tags --abbrev=0 | cut -f 3 -d '-')
          offset=$(git rev-list --count ${tag}..)
          echo "VERSION=$tag-$offset" >> "$GITHUB_ENV"

      - name: Build
        run: |
          mkdir -p ./bin
          go build \
            -tags netgo \
            -ldflags "-X github.com/uyuni-project/uyuni-tools/shared/utils.Version=${{ env.VERSION }}" \
            -o ./bin \
            ./...

      - name: Build with all tags
        run: |
          mkdir -p ./bin
          go build \
            -tags netgo,nok8s,ptf \
            -ldflags "-X github.com/uyuni-project/uyuni-tools/shared/utils.Version=${{ env.VERSION }}" \
            -o ./bin \
            ./...

      - name: Test with the Go CLI
        run: go test ./...

      - name: Upload binaries
        uses: actions/upload-artifact@v4
        with:
          name: binaries
          path: ./bin/*
07070100000006000081B4000000000000000000000001662A752800000904000000000000000000000000000000000000002D00000000uyuni-tools/.github/workflows/changelogs.yml# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0
name: Changelogs


on:
  push:
    branches:
      - main
  pull_request:
    types:
      - opened
      - reopened
      - synchronize

jobs:
  changelog_test:
    name: Test changelog entries
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 1
    - id: master
      name: Get modified master changelog files
      uses: Ana06/get-changed-files@v2.3.0
      with:
        filter: '*.changes'
    - name: Fail if the master changelog files are modified
      if: steps.master.outputs.all
      run: |
        echo "Master changelog files cannot be modified directly."
        echo "Please revert your changes on the following master changelog file(s):"
        for file in ${{steps.master.outputs.all}}
        do
          echo "  - $file"
        done
        echo
        echo "See https://github.com/uyuni-project/uyuni/wiki/Contributing for a guide to writing checklogs."
        exit 1
    - id: changelogs
      name: Get modified changelog files
      if: "!contains(github.event.pull_request.body, '[x] No changelog needed')"
      uses: Ana06/get-changed-files@v2.3.0
      with:
        filter: '*.changes.*'
    - name: Fail if no changelog entries are added
      if: steps.changelogs.conclusion == 'success' && steps.changelogs.outputs.added_modified == ''
      run: |
        echo "No changelog entry found. Please add the required changelog entries."
        echo "See https://github.com/uyuni-project/uyuni/wiki/Contributing for a guide to writing checklogs."
        exit 1

  # warns the user if they merged the PR, but the changelog test failed
  warn_user_if_merged:
    name: Warn user if merged
    if: always() && github.event.action == 'closed' && github.event.pull_request.merged == true && needs.changelog_test.result == 'failure'
    needs: changelog_test
    runs-on: ubuntu-latest
    steps:
    - name: Remind the author with a comment
      uses: peter-evans/create-or-update-comment@v4
      with:
        issue-number: ${{ github.event.pull_request.number }}
        body: |
          :warning: No changelog entry has been added. @${{ github.event.pull_request.user.login }}, please add necessary changelog entries with an additional PR.
07070100000007000081B4000000000000000000000001662A752800000191000000000000000000000000000000000000002D00000000uyuni-tools/.github/workflows/check_l10n.yml# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

name: l10n-check 
on:
  push:
    branches:
      - main
  pull_request:

permissions:
  contents: read

jobs:
  l10n-check:
    name: localizable
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check localizable strings
        shell: bash -x {0}
        run: ./check_localizable

07070100000008000081B4000000000000000000000001662A752800000810000000000000000000000000000000000000003000000000uyuni-tools/.github/workflows/golangci-lint.yml# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

name: golangci-lint
on:
  push:
    branches:
      - main
  pull_request:

permissions:
  contents: read
  # Optional: allow read access to pull request. Use with `only-new-issues` option.
  # pull-requests: read

jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.20'
          cache: false
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v5
        with:
          # Require: The version of golangci-lint to use.
          # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
          # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
          version: v1.54

          # Optional: working directory, useful for monorepos
          # working-directory: somedir

          # Optional: golangci-lint command line arguments.
          #
          # Note: By default, the `.golangci.yml` file should be at the root of the repository.
          # The location of the configuration file can be changed by using `--config=`
          # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 

          # Optional: show only new issues if it's a pull request. The default value is `false`.
          # only-new-issues: true

          # Optional: if set to true, then all caching functionality will be completely disabled,
          #           takes precedence over all other caching options.
          # skip-cache: true

          # Optional: if set to true, then the action won't cache or restore ~/go/pkg.
          # skip-pkg-cache: true

          # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build.
          # skip-build-cache: true

          # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'.
          # install-mode: "goinstall"
07070100000009000081B4000000000000000000000001662A7528000001AA000000000000000000000000000000000000002800000000uyuni-tools/.github/workflows/reuse.yml# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: CC0-1.0

name: REUSE Compliance Check

on:
  push:
    branches:
      - main
  pull_request:
    types:
      - opened
      - reopened
      - synchronize

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: REUSE Compliance Check
      uses: fsfe/reuse-action@v3
0707010000000A000081B4000000000000000000000001662A752800000092000000000000000000000000000000000000001700000000uyuni-tools/.gitignore# SPDX-FileCopyrightText: 2023 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

bin
.vscode/
.idea/
vendor.tar.gz
*.pyc
__pycache__
**/tags
*.mo
0707010000000B000081B4000000000000000000000001662A752800000505000000000000000000000000000000000000001A00000000uyuni-tools/.golangci.yml# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

run:
  tests: true
  skip-dirs:
    - vendor
    - examples

linters-settings:
  dupl:
    enabled: true
  errcheck:
    enabled: true
  gofmt:
    enabled: true
    simplify: true
  goimports:
    enabled: true
  gocyclo:
    enabled: true
    min-complexity: 10
  godot:
    enabled: true
  golint:
    enabled: true
  ineffassign:
    enabled: true
  maligned:
    enabled: true
  megacheck:
    enabled: true
  misspell:
    enabled: true
  revive:
    rules:
      - name: exported
        arguments:
          - disableStutteringCheck
  staticcheck:
    enabled: false
  stylecheck:
    enabled: true
    checks: ["ST1005", "ST1019"]
  structcheck:
    enabled: true
  typecheck:
    enabled: false
  unused:
    enabled: true
  varcheck:
    enabled: true
  whitespace:
    enabled: true

linters:
  disable-all: true
  enable:
    - unused
    - dupl
    - errcheck
    - errname
      #- errorlint
    - godot
    - gofmt
    - goimports
    - gosimple
      #- gocyclo
    - revive
    - ineffassign
    - govet
      #- lll
      #- megacheck
    - misspell
    - revive
      #- staticcheck
    - stylecheck
      #- unparam
    - unused
    - whitespace

issues:
  include:
    - EXC0012



0707010000000C000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001300000000uyuni-tools/.reuse0707010000000D000081B4000000000000000000000001662A75280000034B000000000000000000000000000000000000001800000000uyuni-tools/.reuse/dep5Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: uyuni-tools
Upstream-Contact: Uyuni Project <>
Source: https://github.com/uyuni-project/uyuni-tools

# Sample paragraph, commented out:
#
# Files: src/*
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...

Files: mgradm/shared/ssl/testdata/*
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: go.mod
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: go.sum
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: uyuni-tools.changes* 
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: uyuni-tools.spec
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: .tito/tito.props
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: .tito/packages/*
Copyright: 2023 SUSE LLC
License: Apache-2.0

Files: locale/*
Copyright: 2024 SUSE LLC
License: Apache-2.0
0707010000000E000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000001200000000uyuni-tools/.tito0707010000000F000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001900000000uyuni-tools/.tito/custom07070100000010000081B4000000000000000000000001662A752800000726000000000000000000000000000000000000002300000000uyuni-tools/.tito/custom/custom.py# Copyright (c) 2018 SUSE Linux Products GmbH
# SPDX-FileCopyrightText: 2023 SUSE LLC
#
# SPDX-License-Identifier: GPL-2.0-only

"""
Code for building packages in SUSE that need generated code not tracked in git.
"""
import os

from tito.builder import Builder
from tito.common import  info_out, run_command, debug

class SuseGitExtraGenerationBuilder(Builder):

    def _setup_sources(self):

        Builder._setup_sources(self)
        setup_execution_file_name = "setup.sh"
        setup_file_dir = os.path.join(self.git_root, self.relative_project_dir)
        setup_file_path = os.path.join(setup_file_dir, setup_execution_file_name)
        if os.path.exists(setup_file_path):
            info_out("Executing %s" % setup_file_path)
            output = run_command("[[ -x %s ]] && %s" % (setup_file_path, setup_file_path), True)
            filename = output.split('\n')[-1]
        if filename and os.path.exists(os.path.join(setup_file_dir, filename)):
            info_out("Copying %s to %s" % (os.path.join(setup_file_dir, filename), self.rpmbuild_sourcedir))
            run_command("cp %s %s/" % (os.path.join(setup_file_dir, filename), self.rpmbuild_sourcedir), True)
            self.sources.append(os.path.join(self.rpmbuild_sourcedir, filename))

        source_push = os.path.join(setup_file_dir, "push.sh")
        if os.path.exists(source_push):
            push_path = os.path.join(self.rpmbuild_sourcedir, "push.sh")
            run_command("cp %s %s/" % (source_push, self.rpmbuild_sourcedir), True)
            self.sources.append(push_path)

            run_command(f"sed '/^URL: .*$/aSource10000: push.sh' -i {self.spec_file}")
            cleanup = f"\nsed '/^Source10000: push.sh/d' -i $SRPM_PKG_DIR/{self.spec_file_name}"
            with open(push_path, "a") as fd:
                fd.write(cleanup)
07070100000011000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001B00000000uyuni-tools/.tito/packages07070100000012000081B4000000000000000000000001662A75280000000B000000000000000000000000000000000000002700000000uyuni-tools/.tito/packages/uyuni-tools0.1.7-0 ./
07070100000013000081B4000000000000000000000001662A7528000000CB000000000000000000000000000000000000001D00000000uyuni-tools/.tito/tito.props[buildconfig]
builder = custom.SuseGitExtraGenerationBuilder
tagger = tito.tagger.SUSETagger
changelog_with_email = 0
changelog_do_not_remove_cherrypick = 0
no_default_changelog = 1
lib_dir=.tito/custom
07070100000014000081B4000000000000000000000001662A752800000189000000000000000000000000000000000000001300000000uyuni-tools/.vimrc" SPDX-FileCopyrightText: 2023 SUSE LLC
"
" SPDX-License-Identifier: Apache-2.0

" Local vim configuration loaded by https://github.com/LucHermitte/local_vimrc
" For local_vimrc to use this file, ensure .vimrc is in the g:local_vimrc
" list. You can set it like the following in the vim or neovim config:
"
"     let g:local_vimrc = ['.vimrc']

" Set make command
set makeprg=go\ build\ ./...
07070100000015000081B4000000000000000000000001662A752800002C5D000000000000000000000000000000000000001400000000uyuni-tools/LICENSE                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
07070100000016000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001500000000uyuni-tools/LICENSES07070100000017000081B4000000000000000000000001662A752800002828000000000000000000000000000000000000002400000000uyuni-tools/LICENSES/Apache-2.0.txtApache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.

"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:

     (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and

     (b) You must cause any modified files to carry prominent notices stating that You changed the files; and

     (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and

     (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.

     You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!)  The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
07070100000018000081B4000000000000000000000001662A752800001B88000000000000000000000000000000000000002100000000uyuni-tools/LICENSES/CC0-1.0.txtCreative Commons Legal Code

CC0 1.0 Universal

    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
    HEREUNDER.

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.

For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:

  i. the right to reproduce, adapt, distribute, perform, display,
     communicate, and translate a Work;
 ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
     likeness depicted in a Work;
 iv. rights protecting against unfair competition in regards to a Work,
     subject to the limitations in paragraph 4(a), below;
  v. rights protecting the extraction, dissemination, use and reuse of data
     in a Work;
 vi. database rights (such as those arising under Directive 96/9/EC of the
     European Parliament and of the Council of 11 March 1996 on the legal
     protection of databases, and under any national implementation
     thereof, including any amended or successor version of such
     directive); and
vii. other similar, equivalent or corresponding rights throughout the
     world based on applicable law or treaty, and any national
     implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.

4. Limitations and Disclaimers.

 a. No trademark or patent rights held by Affirmer are waived, abandoned,
    surrendered, licensed or otherwise affected by this document.
 b. Affirmer offers the Work as-is and makes no representations or
    warranties of any kind concerning the Work, express, implied,
    statutory or otherwise, including without limitation warranties of
    title, merchantability, fitness for a particular purpose, non
    infringement, or the absence of latent or other defects, accuracy, or
    the present or absence of errors, whether or not discoverable, all to
    the greatest extent permissible under applicable law.
 c. Affirmer disclaims responsibility for clearing rights of other persons
    that may apply to the Work or any use thereof, including without
    limitation any person's Copyright and Related Rights in the Work.
    Further, Affirmer disclaims responsibility for obtaining any necessary
    consents, permissions or other rights required for any use of the
    Work.
 d. Affirmer understands and acknowledges that Creative Commons is not a
    party to this document and has no duty or obligation with respect to
    this CC0 or use of the Work.
07070100000019000081B4000000000000000000000001662A7528000043B9000000000000000000000000000000000000002600000000uyuni-tools/LICENSES/GPL-2.0-only.txtGNU GENERAL PUBLIC LICENSE
Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

     a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

     b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

     c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

     a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

     b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

     c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

     one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author

     This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

     This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

     You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

     Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:

     Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.

signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
0707010000001A000081B4000000000000000000000001662A752800000FDF000000000000000000000000000000000000001600000000uyuni-tools/README.md<!--
SPDX-FileCopyrightText: 2023 SUSE LLC

SPDX-License-Identifier: Apache-2.0
-->

[![REUSE status](https://api.reuse.software/badge/git.fsfe.org/reuse/api)](https://api.reuse.software/info/git.fsfe.org/reuse/api)

# Tools to help using Uyuni as containers

**These tools are work in progress**

* `mgradm` used to help administer Uyuni servers on K8s and Podman
* `mgrctl` used to help managing Uyuni servers mainly through its API
* `mgrpxy` used to help managing Uyuni proxies

# Deployment rolling release

## For Podman deployment
Requirement:
  - openSUSE Leap Micro 15.5
  - Podman installed

*Note that other distros with a recent Podman installed could work but they have not been tested.
Please report issues if any arises on those distributions.*

Add uyuni-tool repository:
```
zypper ar https://download.opensuse.org/repositories/systemsmanagement:/Uyuni:/Stable:/ContainerUtils/openSUSE_Leap_Micro_5.5/ uyuni-container-utils
```

Install `mgradm` package: `transactional-update pkg install mgradm`

Run `mgradm` command to install Uyuni server on Podman:
```
mgradm install podman
```

If you build `uyuni-tools` on your machine, add the `--image registry.opensuse.org/systemsmanagement/uyuni/stable/containers/uyuni/server` option to the install command.
This is not needed when using the package from OBS as it defaulting with this image at build time.

**NOTE**: rolling image url is: registry.opensuse.org/systemsmanagement/uyuni/master/containers/uyuni/server


Other sub-commands are also available. Explore the tool with the help command.

A tool named `mgrctl` is also available with useful commands.

## K3s deployment

For Look at a more details documentation at:

https://github.com/uyuni-project/uyuni/tree/master/containers/doc/server-kubernetes

# Development documentation

## Building

`go build -o ./bin ./...` will produce the binaries in the root folder with `0.0.0` as version.

To produce shell completion scripts for a given shell you can run:

- `./bin/mgradm completion <shell> > $COMPLETION_FILE` for mgradm
- `./bin/mgrctl error completion <shell> > $COMPLETION_FILE` for mgrctl

You'll then need to source the resulting script(s).

As an example, to enable bash completion for mgradm:

`./bin/mgradm completion bash > ./bin/completion`

`. ./bin/completion`

The supported shells are: bash, zsh and fish.

Alternatively, if you have `podman` installed you can run the `build.sh` script to build binaries compatible with any x86_64 linux.
The version will be computed from the last git tag and offset from it.

### Building in Open Build Service

In order to adjust the image, tag and chart to the project the package is built in, add the following at the end of the project configuration:

```
Macros:
%_default_tag yourtag
%_default_image theregistry.org/path/to/the/server
%_default_chart oci://theregistry.org/path/to/the/chart
:Macros
```

### Disabling features at build time

To disable features at build time pass the `-tags` parameter with the following values in a comma-separated list:

* `nok8s`: will disable Kubernetes support

## Localization

### Developer tricks

For Localization the project uses `gettext`.
There are a few rules to follow to make strings localizable:

Add the following import in the go file and then wrap all the strings that could be localized in the `L()` function.

```
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
```

**Global variables and constants are evaluated before running the main function and thus do not take the locale into account.**
Move them in a function to work around this issue.

### Generating the POT files

In order to extract the strings from the code run the `extract_strings` script.
One POT file for each tool and one for the `shared` folder will be generated in the `locale` directory.

### Translating

The translation files should be named after the target language next to the corresponding PO file.
The `.mo` files should not be committed in the source tree as they are build results.
Those are generated using the `locale/build.sh` script.
0707010000001B000081FD000000000000000000000001662A752800000393000000000000000000000000000000000000001500000000uyuni-tools/build.sh#!/usr/bin/bash

# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0
set -e
mkdir -p ./bin

build_tags=$1
if [ -n "${build_tags}" ]; then
    build_tags="-tags ${build_tags}"
fi

tag=$(git describe --tags --abbrev=0)
version=$(git describe --tags --abbrev=0 | cut -f 3 -d '-')
offset=$(git rev-list --count ${tag}..)

VERSION_NAME=github.com/uyuni-project/uyuni-tools/shared/utils.Version

CGO_ENABLED=0 go build ${build_tags} -ldflags "-X ${VERSION_NAME}=${tag}-${offset}" -o ./bin ./...

for shell in "bash" "zsh" "fish"; do
    COMPLETION_FILE="./bin/completion.${shell}"

    # generate and source shell completion scripts for mgradm and mgrctl
    ./bin/mgradm completion ${shell} > "${COMPLETION_FILE}"
    ./bin/mgrctl completion ${shell} >> "${COMPLETION_FILE}"
    ./bin/mgrpxy completion ${shell} >> "${COMPLETION_FILE}"
done

golangci-lint run
./check_localizable
echo "DONE"
0707010000001C000081FD000000000000000000000001662A7528000003A2000000000000000000000000000000000000001E00000000uyuni-tools/check_localizable#!/bin/sh
#
# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

res=0
grep -r . --include '*.go' --exclude '*_test.go' -n \
    -e 'fmt\.Errorf("[^"]\+"' \
    -e 'errors.New("[^"]\+"' \
    -e '\(Fatal\|Error\|Info\|Warn\)()\(\.Err(err)\)\?\.Msgf\?("' \
    -e '\.Flags()\.\(String\|Int\|Bool\)\(Slice\)\?\(Var\)\?P\?([^)]\+, \+"[^"]\+")' \
    -e '\(Short\|Long\): \+["`]'

if test $? -eq 0; then
    echo -e "Fix the non localizable strings\n"
    res=1
fi

grep -r . --include '*.go' --exclude '*_test.go' -n \
    -e '\(Trace\|Debug\)()\(\.Err(err)\)\?\.Msgf\?(N\?L("' \

if test $? -eq 0; then
    echo -e "Trace and debug messages shouldn't be localizable\n"
    res=1
fi

grep -r . --include '*.go' --exclude '*_test.go' -n \
    -e '\(Short\|Long\): \+L(["`][a-z]'
if test $? -eq 0; then
    echo -e "Short and Long messages shouldn't start with a lowercase letter\n"
    res=1
fi

exit $res
0707010000001D000081FD000000000000000000000001662A752800000498000000000000000000000000000000000000001C00000000uyuni-tools/extract_strings#!/bin/sh
#
# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

export LC_ALL=C
for MODULE in mgrctl mgradm mgrpxy shared; do
    # Generate the pot file
    echo "Generate locale/${MODULE}/${MODULE}.pot"
    find ${MODULE} -type f -not -name '*_test.go' -name '*.go' | xargs xgettext --no-wrap --keyword="NL:1,2" --keyword="L" --language=Javascript --from-code=UTF-8 -o locale/${MODULE}/${MODULE}.pot -
    msguniq --no-wrap -o locale/${MODULE}/${MODULE}-uniq.pot locale/${MODULE}/${MODULE}.pot
    mv locale/${MODULE}/${MODULE}-uniq.pot locale/${MODULE}/${MODULE}.pot

    # Update the po files
    for PO in locale/${MODULE}/*.po; do
        echo -n "Update ${PO}"
        if msgmerge --no-wrap --update ${PO} locale/${MODULE}/${MODULE}.pot;
        then
            if test -f ${PO}~; then
                rm ${PO}~
            fi
        else
            echo "msgmerge for ${PO} failed"
        fi
    done
done

# Commit the changes
for change in `git diff --numstat | awk '{print $1}'`; do
    if [ $change -gt 1 ]; then
        git add -u
        git commit -m "update strings for translations"
        exit 
    fi
done
git reset --hard
0707010000001E000081B4000000000000000000000001662A752800000500000000000000000000000000000000000000001300000000uyuni-tools/go.modmodule github.com/uyuni-project/uyuni-tools

go 1.20

require (
	github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
	github.com/spf13/cobra v1.1.3
)

require (
	github.com/briandowns/spinner v1.23.0 // indirect
	github.com/chai2010/gettext-go v1.0.2 // indirect
	github.com/creack/pty v1.1.17 // indirect
	github.com/fatih/color v1.7.0 // indirect
)

require (
	github.com/fsnotify/fsnotify v1.4.7 // indirect
	github.com/hashicorp/hcl v1.0.0 // indirect
	github.com/inconshreveable/mousetrap v1.0.0 // indirect
	github.com/magiconair/properties v1.8.1 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.19 // indirect
	github.com/mitchellh/mapstructure v1.1.2 // indirect
	github.com/pelletier/go-toml v1.2.0 // indirect
	github.com/rs/zerolog v1.30.0
	github.com/spf13/afero v1.1.2 // indirect
	github.com/spf13/cast v1.3.0 // indirect
	github.com/spf13/jwalterweatherman v1.0.0 // indirect
	github.com/spf13/pflag v1.0.5
	github.com/spf13/viper v1.7.0
	github.com/subosito/gotenv v1.2.0 // indirect
	golang.org/x/sys v0.12.0 // indirect
	golang.org/x/term v0.10.0
	golang.org/x/text v0.3.2 // indirect
	gopkg.in/ini.v1 v1.51.0 // indirect
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
	gopkg.in/yaml.v2 v2.4.0 // indirect
)
0707010000001F000081B4000000000000000000000001662A752800008368000000000000000000000000000000000000001300000000uyuni-tools/go.sumcloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
07070100000020000041FD000000000000000000000006662A752800000000000000000000000000000000000000000000001300000000uyuni-tools/locale07070100000021000081FD000000000000000000000001662A7528000002F6000000000000000000000000000000000000001C00000000uyuni-tools/locale/build.sh#!/bin/sh
#
# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

PREFIX=$1
locales_dir=$(dirname $0)/
if test "x${PREFIX}" == "x"; then
    PREFIX=${locales_dir}
fi

for domain in mgrctl mgradm mgrpxy; do
    for po_file in `ls ${locales_dir}/${domain}/*.po`; do
        lang=$(basename ${po_file} | sed 's/\.po$//')
        locale_dir=${PREFIX}${lang}/LC_MESSAGES
        install -vd -m 0755 ${locale_dir}

        msgcat -o ${locale_dir}/${domain}.po ${po_file} ${locales_dir}/shared/${lang}.po
        msgfmt -c -o ${locale_dir}/${domain}.mo ${locale_dir}/${domain}.po
        if test $? -ne 0;
        then
            echo "Broken ${po_file}"
            exit 1
        fi
        rm ${locale_dir}/${domain}.po
    done
done
07070100000022000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/locale/mgradm07070100000023000081B4000000000000000000000001662A75280000C591000000000000000000000000000000000000002000000000uyuni-tools/locale/mgradm/fr.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: 2024-04-17 08:03+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"Language-Team: French <https://l10n.opensuse.org/projects/uyuni/uyuni-tools/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.9.1\n"

#: mgradm/cmd/distro/cp.go:32
#, javascript-format
msgid "Unable to unmount ISO image, leaving %s intact"
msgstr "Impossible de démonter l'image ISO, %s laissée intacte"

#: mgradm/cmd/distro/cp.go:41
#, javascript-format
msgid "unable to login and register the distribution. Manual distro registration is required: %s"
msgstr "impossible de se connecter et enregistrer la distribution. Un enregistrement manuel de la distribution est nécessaire: %s"

#: mgradm/cmd/distro/cp.go:52
#, javascript-format
msgid "unable to register the distribution. Manual distro registration is required: %s"
msgstr "impossible d'enregistrer la distribution. Un enregistrement manuel de la distribution est nécessaire: %s"

#: mgradm/cmd/distro/cp.go:54
#, javascript-format
msgid "Distribution %s successfully registered"
msgstr "Distribution %s enregistrée avec succès"

#: mgradm/cmd/distro/cp.go:74
#, javascript-format
msgid "Copying distribution %s\n"
msgstr "Copie de la distribution %s\n"

#: mgradm/cmd/distro/cp.go:76
#, javascript-format
msgid "source %s does not exists"
msgstr "la source %s n'existe pas"

#: mgradm/cmd/distro/cp.go:81
#, javascript-format
msgid "distribution already exists: %s"
msgstr "distribution déjà existante: %s"

#: mgradm/cmd/distro/cp.go:102
msgid "unable to mount ISO image. Mount manually and try again"
msgstr "impossible de monter l'image ISO. Montez-la manuellement et réessayez"

#: mgradm/cmd/distro/cp.go:107 mgradm/shared/podman/podman.go:102
#: mgradm/shared/podman/podman.go:105 mgradm/shared/podman/podman.go:108
#: mgradm/shared/podman/podman.go:116
#, javascript-format
msgid "cannot copy %s: %s"
msgstr "impossible de copier %s: %s"

#: mgradm/cmd/distro/cp.go:110
msgid "Distribution has been copied"
msgstr "La distribution a été copiée"

#: mgradm/cmd/distro/detect.go:84
msgid "unknown distribution, auto-registration is not possible"
msgstr "distribution inconnue, enregistrement automatique impossible"

#: mgradm/cmd/distro/distro.go:28
msgid "Distributions management"
msgstr "Gestion de distributions"

#: mgradm/cmd/distro/distro.go:29
msgid "Tools for autoinstallation distributions management"
msgstr "Outils pour la gestion de distributions d'installation automatique"

#: mgradm/cmd/distro/distro.go:35
msgid "Copy distribution files from ISO image to the container"
msgstr "Copier les fichiers de distribution d'une image ISO dans le conteneur"

#: mgradm/cmd/distro/distro.go:36
msgid ""
"Takes a path to an ISO file or the directory of a mounted ISO image and copies it into the container.\n"
"\n"
"Distribution name specifies the destination directory under /srv/www/distributions.\n"
"\n"
"Optional channel label specify which parent channel to associate with the distribution.\n"
"Only when API informations are provided and auto registration is done."
msgstr ""
"Prend le chemin d'un fichier iso ou d'un répertoire d'image ISO mountée et le copie dans le conteneur.\n"
"\n"
"Le nom de la distribution indique le répertoire de destination dans /srv/www/distributions.\n"
"\n"
"L'étiquette optionnelle de canal indique le canal parent à associer à la dstribution. Seulement lorsque les informations d'API sont fournies et lors de l'enregistrement automatique."

#: mgradm/cmd/hub/register/register.go:31
msgid "Register"
msgstr ""

#: mgradm/cmd/hub/register/register.go:32
msgid "Register this peripheral server to Hub API"
msgstr ""

#: mgradm/cmd/hub/register/register.go:79
#, javascript-format
msgid "invalid line format: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:93
#, javascript-format
msgid "mandatory entry missing in config: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:96
#, javascript-format
msgid "Hub API server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:99
#, javascript-format
msgid "failed to connect to the Hub server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:107
#: mgradm/cmd/hub/register/register.go:110
#, javascript-format
msgid "failed to register this peripheral server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:124
#: mgradm/cmd/hub/register/register.go:128
#, javascript-format
msgid "failed to update peripheral server info: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:130
#, javascript-format
msgid "Registered peripheral server: %s, ID: %d"
msgstr ""

#: mgradm/cmd/hub/hub.go:18
msgid "Hub management"
msgstr ""

#: mgradm/cmd/hub/hub.go:19
msgid "Tools and utilities for Hub management"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:26
msgid "Inspect"
msgstr "Inspecter"

#: mgradm/cmd/inspect/inspect.go:27
msgid "Extract information from image and deployment"
msgstr "Extrait des informations sur l'image et le déploiement"

#: mgradm/cmd/inspect/inspect.go:37
msgid "Image URL. Leave it empty to analyze the current deployment"
msgstr "URL de l'image. Laisser vide pour analyser le déploiement actuel"

#: mgradm/cmd/inspect/inspect.go:38
msgid "Image Tag. Leave it empty to analyze the current deployment"
msgstr "Tag de l'image. Laisser vide pour analyser le déploiement actuel"

#: mgradm/cmd/inspect/kubernetes.go:35 mgradm/cmd/inspect/podman.go:31
#, javascript-format
msgid "failed to determine image: %s"
msgstr "impossible de déterminer l'image: %s"

#: mgradm/cmd/inspect/kubernetes.go:44 mgradm/cmd/inspect/podman.go:40
#, javascript-format
msgid "failed to find the image of the currently running server container: %s"
msgstr "impossible de déterminer l'image du conteneur actuel du serveur: %s"

#: mgradm/cmd/inspect/kubernetes.go:50 mgradm/cmd/inspect/podman.go:45
#, javascript-format
msgid "inspect command failed: %s"
msgstr "la commande inspect a échoué: %s"

#: mgradm/cmd/inspect/kubernetes.go:55 mgradm/cmd/inspect/podman.go:49
#, javascript-format
msgid "cannot print inspect result: %s"
msgstr "impossible d'afficher le résultat de l'inspection: %s"

#: mgradm/cmd/inspect/kubernetes.go:68
#: mgradm/cmd/install/kubernetes/utils.go:32
#: mgradm/cmd/migrate/kubernetes/utils.go:37
#: mgradm/cmd/upgrade/kubernetes/utils.go:34
#, javascript-format
msgid "install %s before running this command"
msgstr "installer %s avant d'exécuter cette commande"

#: mgradm/cmd/inspect/kubernetes.go:75 mgradm/cmd/inspect/podman.go:63
#: mgradm/cmd/support/config/extractor.go:32
#: mgradm/cmd/upgrade/kubernetes/utils.go:68 mgradm/shared/kubernetes/k3s.go:36
#: mgradm/shared/kubernetes/k3s.go:104 mgradm/shared/kubernetes/k3s.go:149
#: mgradm/shared/kubernetes/certificates.go:27
#: mgradm/shared/kubernetes/certificates.go:65
#: mgradm/shared/podman/podman.go:221 mgradm/shared/podman/podman.go:281
#: mgradm/shared/podman/podman.go:306 mgradm/shared/utils/exec.go:158
#: mgradm/shared/utils/exec.go:242
#, javascript-format
msgid "failed to create temporary directory: %s"
msgstr "impossible de créér le répertoire temporaire: %s"

#: mgradm/cmd/inspect/kubernetes.go:88 mgradm/shared/kubernetes/k3s.go:64
#: mgradm/shared/kubernetes/k3s.go:113 mgradm/shared/kubernetes/k3s.go:159
#, javascript-format
msgid "cannot delete %s: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:94
#: mgradm/cmd/migrate/kubernetes/utils.go:85
#: mgradm/cmd/upgrade/kubernetes/utils.go:75
#, javascript-format
msgid "cannot find node running uyuni: %s"
msgstr "impossible de trouver le noeud sur lequel uyuni est exécuté: %s"

#: mgradm/cmd/inspect/kubernetes.go:122
#, javascript-format
msgid "cannot run inspect pod: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:127 mgradm/cmd/inspect/podman.go:100
#, javascript-format
msgid "cannot inspect data: %s"
msgstr ""

#: mgradm/cmd/inspect/podman.go:68 mgradm/cmd/install/podman/utils.go:57
#: mgradm/shared/podman/podman.go:185 mgradm/shared/podman/podman.go:245
#, javascript-format
msgid "cannot inspect host values: %s"
msgstr "impossible d'inspecter les valeurs de l'hôte: %s"

#: mgradm/cmd/install/kubernetes/kubernetes.go:27
msgid "Install a new server on a kubernetes cluster"
msgstr "Installer un nouveau server sur un cluster kubernetes"

#: mgradm/cmd/install/kubernetes/kubernetes.go:28
msgid ""
"Install a new server on a kubernetes cluster\n"
"\n"
"The install command assumes the following:\n"
"  * kubectl and helm are installed locally\n"
"  * a working kubectl configuration should be set to connect to the cluster to deploy to\n"
"\n"
"The helm values file will be overridden with the values from the command parameters or configuration.\n"
"\n"
"NOTE: installing on a remote cluster is not supported yet!\n"
msgstr ""
"Installer un nouveau server sur un cluster kubernetes\n"
"\n"
"La commande install suppose ce qui suit:\n"
"  * kubectl et helm sont installés locallement\n"
"  * une configuration kubectl doit être définie pour se connecter au cluster sur lequel déployer\n"
"\n"
"Le fichier de valeurs helm sera surchargé par les valeurs provenant des paramètres de mgradm ou sa configuration.\n"
"\n"
"NOTE: pour l'instant l'installation sur un cluster distant n'est pas supportée!\n"

#: mgradm/cmd/install/kubernetes/utils.go:61
#: mgradm/cmd/migrate/kubernetes/utils.go:180
#, javascript-format
msgid "cannot deploy certificate: %s"
msgstr "impossible de déployer le certificat: %s"

#: mgradm/cmd/install/kubernetes/utils.go:67
#, javascript-format
msgid "cannot deploy uyuni: %s"
msgstr "impossible de déployer uyuni: %s"

#: mgradm/cmd/install/kubernetes/utils.go:83
#, javascript-format
msgid "error storing the SSL CA certificate in database: %s"
msgstr "erreur lors de l'enregistrement du certificat de l'autorité de certification SSL dans la base de données: %s"

#: mgradm/cmd/install/podman/podman.go:25
msgid "Install a new server on podman"
msgstr "Installer un nouveau serveur sur podman"

#: mgradm/cmd/install/podman/podman.go:26
msgid ""
"Install a new server on podman\n"
"\n"
"The install podman command assumes podman is installed locally.\n"
"\n"
"NOTE: installing on a remote podman is not supported yet!\n"
msgstr ""
"Installer un nouveau server sur podman\n"
"\n"
"La commande install podman suppose que podman est installé locallement.\n"
"\n"
"NOTE: pour l'instant l'installation sur un podman distant n'est pas supportée!\n"

#: mgradm/cmd/install/podman/utils.go:36 mgradm/cmd/upgrade/podman/utils.go:70
msgid "Waiting for the server to start..."
msgstr ""

#: mgradm/cmd/install/podman/utils.go:38
#, javascript-format
msgid "cannot enable service: %s"
msgstr "impossible d'activer le service systemd: %s"

#: mgradm/cmd/install/podman/utils.go:52 mgradm/cmd/migrate/podman/utils.go:25
msgid "install podman before running this command"
msgstr "installer podman avant d'exécuter cette commande"

#: mgradm/cmd/install/podman/utils.go:64
#, javascript-format
msgid "Setting up the server with the FQDN '%s'"
msgstr "Installation du serveur avec le FQDN '%s'"

#: mgradm/cmd/install/podman/utils.go:68
#: mgradm/cmd/migrate/kubernetes/utils.go:44
#: mgradm/cmd/upgrade/kubernetes/utils.go:41
#: mgradm/cmd/upgrade/podman/utils.go:27 mgradm/shared/kubernetes/install.go:38
#: mgradm/shared/kubernetes/k3s.go:47 mgradm/shared/kubernetes/k3s.go:52
#: mgradm/shared/podman/podman.go:234 mgradm/shared/podman/podman.go:239
#, javascript-format
msgid "failed to compute image URL: %s"
msgstr "impossible de calculer l'URL de l'image: %s"

#: mgradm/cmd/install/podman/utils.go:88
#, javascript-format
msgid "cannot wait for system start: %s"
msgstr "impossible d'attendre le démarrage du système: %s"

#: mgradm/cmd/install/podman/utils.go:108
msgid "Run setup command in the container"
msgstr "Exécution de l'installation dans le conteneur"

#: mgradm/cmd/install/podman/utils.go:116
#, javascript-format
msgid "cannot update SSL certificate: %s"
msgstr "impossible de mettre à jour le certificat SSL: %s"

#: mgradm/cmd/install/podman/utils.go:121 mgradm/cmd/migrate/podman/utils.go:69
#, javascript-format
msgid "cannot enable podman socket: %s"
msgstr "impossible d'activer le socket podman: %s"

#: mgradm/cmd/install/podman/utils.go:132
#, javascript-format
msgid "failed to compute server FQDN: %s"
msgstr "impossible de déterminer le FQDN du serveur: %s"

#: mgradm/cmd/install/shared/flags.go:71
msgid "Can only contain letters, digits . _ and -"
msgstr "Ne peut contenir que des lettres, chiffres . _ et -"

#: mgradm/cmd/install/shared/flags.go:79
msgid "Not a valid email address"
msgstr "Pas une adresse email valide"

#: mgradm/cmd/install/shared/flags.go:119
msgid "Time zone to set on the server. Defaults to the host timezone"
msgstr "Zone horaire à définir pour le serveur. Celle de l'hôte par défaut"

#: mgradm/cmd/install/shared/flags.go:120
msgid "Administrator e-mail"
msgstr "E-mail de l'administrateur"

#: mgradm/cmd/install/shared/flags.go:121
msgid "E-Mail sending the notifications"
msgstr "E-mail pour l'envoi des notifications"

#: mgradm/cmd/install/shared/flags.go:122
msgid "Path to mirrored packages mounted on the host"
msgstr "Chemin vers le montage du mirroir paquets sur l'hôte"

#: mgradm/cmd/install/shared/flags.go:123
msgid "InterServerSync v1 parent FQDN"
msgstr "FQDN du parent InterServerSync v1"

#: mgradm/cmd/install/shared/flags.go:124
msgid "Database user"
msgstr "Utilisateur de la base de données"

#: mgradm/cmd/install/shared/flags.go:125
msgid "Database password. Randomly generated by default"
msgstr "Mot de passe de la base de données. Valeur aléatoire par défaut"

#: mgradm/cmd/install/shared/flags.go:126
msgid "Database name"
msgstr "Nom de la base de données"

#: mgradm/cmd/install/shared/flags.go:127
msgid "Database host"
msgstr "Hôte de la base de données"

#: mgradm/cmd/install/shared/flags.go:128
msgid "Database port"
msgstr "Port de la base de données"

#: mgradm/cmd/install/shared/flags.go:129
msgid "Database protocol"
msgstr "Protocole de la base de données"

#: mgradm/cmd/install/shared/flags.go:130
msgid "External database admin user name"
msgstr "Nom d'utilisateur administrateur de la base de données externe"

#: mgradm/cmd/install/shared/flags.go:131
msgid "External database admin password"
msgstr "Mot de passe administrateur de la base de données externe"

#: mgradm/cmd/install/shared/flags.go:132
msgid "External database provider. Possible values 'aws'"
msgstr "Fournisseur de base de données externe. Valeurs possibles 'aws'"

#: mgradm/cmd/install/shared/flags.go:134
msgid "Enable TFTP"
msgstr "Activer TFTP"

#: mgradm/cmd/install/shared/flags.go:135
msgid "Report database name"
msgstr "Nom de la base de données de rapport"

#: mgradm/cmd/install/shared/flags.go:136
msgid "Report database host"
msgstr "Hôte de la base de données de rapport"

#: mgradm/cmd/install/shared/flags.go:137
msgid "Report database port"
msgstr "Port de la base de données de rapport"

#: mgradm/cmd/install/shared/flags.go:138
msgid "Report Database username"
msgstr "Nom d'utilisateur de la base de données de rapport"

#: mgradm/cmd/install/shared/flags.go:139
msgid "Report database password. Randomly generated by default"
msgstr "Mot de passe de la base de données de rapport. Valeur aléatoire par défaut"

#: mgradm/cmd/install/shared/flags.go:142
msgid "SSL certificate cnames separated by commas"
msgstr "Cnames de certificat SSL séparés par des virgules"

#: mgradm/cmd/install/shared/flags.go:143
msgid "SSL certificate country"
msgstr "Pays du certificat SSL"

#: mgradm/cmd/install/shared/flags.go:144
msgid "SSL certificate state"
msgstr "État du certificat SSL"

#: mgradm/cmd/install/shared/flags.go:145
msgid "SSL certificate city"
msgstr "Ville du certificat SSL"

#: mgradm/cmd/install/shared/flags.go:146
msgid "SSL certificate organization"
msgstr "Organisation du certificat SSL"

#: mgradm/cmd/install/shared/flags.go:147
msgid "SSL certificate organization unit"
msgstr "Unité d'organisation du certificat SSL"

#: mgradm/cmd/install/shared/flags.go:148
msgid "Password for the CA key to generate"
msgstr "Mot de passe de la clé de l'autorité de certification à générer"

#: mgradm/cmd/install/shared/flags.go:149
msgid "SSL certificate E-Mail"
msgstr "E-mail du certificat SSL"

#: mgradm/cmd/install/shared/flags.go:152
msgid "Intermediate CA certificate path"
msgstr "Chemin vers un certificat d'autorité de certification intermédiaire"

#: mgradm/cmd/install/shared/flags.go:153
msgid "Root CA certificate path"
msgstr "Chemin vers le certificat de l'autorité de certification racine"

#: mgradm/cmd/install/shared/flags.go:154
msgid "Server certificate path"
msgstr "Chemin vers le certificat du serveur"

#: mgradm/cmd/install/shared/flags.go:155
msgid "Server key path"
msgstr "Chemin vers la clé du serveur"

#: mgradm/cmd/install/shared/flags.go:157
msgid "SUSE Customer Center username"
msgstr "Nom d'utilisateur du SUSE Customer Center"

#: mgradm/cmd/install/shared/flags.go:158
msgid "SUSE Customer Center password"
msgstr "Mot de passe du SUSE Customer Center"

#: mgradm/cmd/install/shared/flags.go:160
msgid "Enable tomcat and taskomatic remote debugging"
msgstr "Activer le déboggage à distance de tomcat et taskomatic"

#: mgradm/cmd/install/shared/flags.go:163
msgid "Administrator user name"
msgstr "Nom d'utilisateur de l'administrateur"

#: mgradm/cmd/install/shared/flags.go:164
msgid "Administrator password"
msgstr "Mot de passe de l'administrateur"

#: mgradm/cmd/install/shared/flags.go:165
msgid "First name of the administrator"
msgstr "Prénom de l'administrateur"

#: mgradm/cmd/install/shared/flags.go:166
msgid "Last name of the administrator"
msgstr "Nom de l'administrateur"

#: mgradm/cmd/install/shared/flags.go:167
msgid "Administrator's email"
msgstr "E-mail de l'administrateur"

#: mgradm/cmd/install/shared/flags.go:168
msgid "First organization name"
msgstr "Nom de la première organisation"

#: mgradm/cmd/install/shared/shared.go:32
#, javascript-format
msgid "cannot copy /tmp/setup.sh: %s"
msgstr "impossible de copyer /tmp/setup.sh: %s"

#: mgradm/cmd/install/shared/shared.go:37
#, javascript-format
msgid "error running the setup script: %s"
msgstr "erreur lors de l'exécution du script d'installation: %s"

#: mgradm/cmd/install/shared/shared.go:53
msgid "Server set up"
msgstr "Installation du serveur"

#: mgradm/cmd/install/shared/shared.go:115
msgid "Failed to create temporary directory"
msgstr "Échec de création du répertoire temporaire"

#: mgradm/cmd/install/shared/shared.go:125
msgid "Failed to generate setup script"
msgstr "Impossible de générer le script d'installation"

#: mgradm/cmd/install/install.go:19 mgradm/cmd/install/install.go:20
msgid "Install a new server"
msgstr "Installer un nouveau serveur sur podman"

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:28
msgid "Migrate a remote server to containers running on a kubernetes cluster"
msgstr "Migrer un serveur distant vers des conteneurs sur un cluster kubernetes existant"

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:29
msgid ""
"Migrate a remote server to containers running on a kubernetes cluster\n"
"\n"
"This migration command assumes a few things:\n"
"  * the SSH configuration for the source server is complete, including user and\n"
"    all needed options to connect to the machine,\n"
"  * an SSH agent is started and the key to use to connect to the server is added to it,\n"
"  * kubectl and helm are installed locally,\n"
"  * a working kubectl configuration should be set to connect to the cluster to deploy to\n"
"\n"
"When migrating a server with a automatically generated SSL Root CA certificate, the private key\n"
"password will be required to convert it to RSA in a kubernetes secret.\n"
"This is not needed if the source server does not have a generated SSL CA certificate.\n"
"\n"
"NOTE: migrating to a remote cluster is not supported yet!\n"
msgstr ""
"Migrer un serveur distant vers des conteneurs sur un cluster kubernetes existant\n"
"\n"
"Cette commande suppose ce qui suit:\n"
"  * la configuration de SSH pour le serveur source doit être complète, incluant l'utilisateur et\n"
"    toutes les options nécessaires pour se connecter à la machine,\n"
"  * un agent SSH est démarré et la clé à utiliser pour se connecter au serveur y est chargée,\n"
"  * kubectl et helm sont installés localement,\n"
"  * une configuration kubectl doit être définie pour se connecter au cluster vers lequel déployer\n"
"\n"
"Lors de la migration d'un serveur avec une autorité de certification SSL racine générée automatiquement, le\n"
"mot de passe de la clé privée sera nécessaire pour la convertir en RSA dans un secret kubernetes.\n"
"Ceci n'est pas nécessaire si le serveur source n'a pas d'autorité de certification SSL générée.\n"
"\n"
"NOTE: pour l'instant la migration version un cluster distant n'est pas supportée!\n"

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:53
msgid "SSL CA generated private key password"
msgstr "Mot de passe de la clé privée de l'autorité de certification SSL générée"

#: mgradm/cmd/migrate/kubernetes/utils.go:56 mgradm/shared/utils/exec.go:169
#, javascript-format
msgid "failed to generate migration script: %s"
msgstr "impossible de générer le script de migration: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:78
#, javascript-format
msgid "cannot run deploy: %s"
msgstr "impossible d'exécuter le déploiement: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:89
#, javascript-format
msgid "cannot run migration: %s"
msgstr "impossible d'effectuer la migration: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:94
#, javascript-format
msgid "cannot read data from container: %s"
msgstr "impossible de lire les données du conteneur: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:100
#: mgradm/cmd/migrate/kubernetes/utils.go:133
#, javascript-format
msgid "cannot set replicas to 0: %s"
msgstr "impossible de mettre le nombre de répliques à 0: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:112
#, javascript-format
msgid "cannot setup SSL: %s"
msgstr "impossible de configurer SSL: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:124
#, javascript-format
msgid "cannot upgrade helm chart to image %s using new SSL certificate: %s"
msgstr "impossible de mettre à jour le helm chart pour l'image %s en utilisant le nouveau certificat SSL: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:128
#, javascript-format
msgid "cannot wait for deployment of %s: %s"
msgstr "impossible d'attendre le déploiement de %s: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:138
#: mgradm/cmd/migrate/kubernetes/utils.go:144
#: mgradm/cmd/migrate/podman/utils.go:44
#: mgradm/cmd/upgrade/kubernetes/utils.go:93
#: mgradm/cmd/upgrade/kubernetes/utils.go:103
#: mgradm/cmd/upgrade/podman/utils.go:50 mgradm/cmd/upgrade/podman/utils.go:60
#, javascript-format
msgid "cannot run PostgreSQL version upgrade script: %s"
msgstr "impossible d'exécuter le script de mise à jour de la version de PostgreSQL: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:148
#: mgradm/cmd/migrate/podman/utils.go:54
#: mgradm/cmd/upgrade/kubernetes/utils.go:107
#: mgradm/cmd/upgrade/podman/utils.go:64
#, javascript-format
msgid "cannot run post upgrade script: %s"
msgstr "impossible d'exécuter le script post mise à jour: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:153
#: mgradm/cmd/upgrade/kubernetes/utils.go:112
#, javascript-format
msgid "cannot upgrade to image %s: %s"
msgstr "impossible de mettre à jour avec l'image %s: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:171
#, javascript-format
msgid "failed to strip text part from CA certificate: %s"
msgstr "impossible de supprimer la partie text du certificat de l'autorité de certification: %s"

#: mgradm/cmd/migrate/podman/podman.go:25
msgid "Migrate a remote server to containers running on podman"
msgstr "Migrer un serveur distant vers des conteneurs sur podman"

#: mgradm/cmd/migrate/podman/podman.go:26
msgid ""
"Migrate a remote server to containers running on podman\n"
"\n"
"This migration command assumes a few things:\n"
"  * the SSH configuration for the source server is complete, including user and\n"
"    all needed options to connect to the machine,\n"
"  * an SSH agent is started and the key to use to connect to the server is added to it,\n"
"  * podman is installed locally\n"
"\n"
"NOTE: migrating to a remote podman is not supported yet!\n"
msgstr ""
"Migrer un serveur distant vers des conteneurs sur podman\n"
"\n"
"Cette commande suppose ce qui suit:\n"
"  * la configuration de SSH pour le serveur source doit être complète, incluant l'utilisateur et\n"
"    toutes les options nécessaires pour se connecter à la machine,\n"
"  * un agent SSH est démarré et la clé à utiliser pour se connecter au serveur y est chargée,\n"
"  * kubectl et helm sont installés localement\n"
"\n"
"NOTE: pour l'instant la migration version un cluster distant n'est pas supportée!\n"

#: mgradm/cmd/migrate/podman/utils.go:30
#, javascript-format
msgid "cannot compute image: %s"
msgstr "impossible de déterminer l'image: %s"

#: mgradm/cmd/migrate/podman/utils.go:39
#, javascript-format
msgid "cannot run migration script: %s"
msgstr "impossible d'exécuter le script de migration: %s"

#: mgradm/cmd/migrate/podman/utils.go:50
#, javascript-format
msgid "cannot run PostgreSQL finalize script: %s"
msgstr "impossible d'exécuter le script de finalisation de PostgreSQL: %s"

#: mgradm/cmd/migrate/podman/utils.go:58
#, javascript-format
msgid "cannot generate systemd service file: %s"
msgstr "impossible de générer le fichier du service systemd: %s"

#: mgradm/cmd/migrate/podman/utils.go:66
msgid "Server migrated"
msgstr "Serveur migré"

#: mgradm/cmd/migrate/shared/shared.go:20
msgid "SSH_AUTH_SOCK is not defined, start an SSH agent and try again"
msgstr "SSH_AUTH_SOCK n'est pas défini, démarrer un agent SSH et réessayer"

#: mgradm/cmd/migrate/shared/shared.go:30
msgid "Failed to find home directory to look for SSH config"
msgstr "Impossible de trouver le répertoire home pour chercher la configuration SSH"

#: mgradm/cmd/migrate/migrate.go:19 mgradm/cmd/migrate/migrate.go:20
msgid "Migrate a remote server to containers"
msgstr "Migrer un serveur distant vers des conteneurs"

#: mgradm/cmd/restart/nokubernetes.go:23 mgradm/cmd/start/nokubernetes.go:23
#: mgradm/cmd/status/nokubernetes.go:23 mgradm/cmd/stop/nokubernetes.go:23
msgid "built without kubernetes support"
msgstr "compilé sans support de kubernetes"

#: mgradm/cmd/restart/restart.go:23 mgradm/cmd/restart/restart.go:24
msgid "Restart the server"
msgstr "Redémarrer le serveur"

#: mgradm/cmd/start/start.go:23 mgradm/cmd/start/start.go:24
msgid "Start the server"
msgstr "Démarrer le serveur"

#: mgradm/cmd/status/podman.go:29
#, javascript-format
msgid "failed to get status of the server service: %s"
msgstr ""

#: mgradm/cmd/status/podman.go:37 mgradm/cmd/status/kubernetes.go:61
#, javascript-format
msgid "failed to run spacewalk-service status: %s"
msgstr ""

#: mgradm/cmd/status/status.go:24 mgradm/cmd/status/status.go:25
msgid "Get the server status"
msgstr ""

#: mgradm/cmd/status/status.go:46
msgid "no installed server detected"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:32
#, javascript-format
msgid "failed to discover the cluster type: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:37
msgid "no uyuni helm release installed on the cluster"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:42
#, javascript-format
msgid "failed to find the uyuni deployment namespace: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:48
#, javascript-format
msgid "failed to get deployment status: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:51
#, javascript-format
msgid "Some replicas are not ready: %d / %d"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:55
msgid "the pod is not running"
msgstr ""

#: mgradm/cmd/stop/stop.go:23 mgradm/cmd/stop/stop.go:24
msgid "Stop the server"
msgstr "Arrêter le serveur"

#: mgradm/cmd/support/config/config.go:23
msgid "Extract configuration and logs"
msgstr "Extraire la configuration et les logs"

#: mgradm/cmd/support/config/config.go:24
msgid ""
"Extract the host or cluster configuration and logs as well as those from \n"
"the containers for support to help debugging."
msgstr ""
"Extraire la configuration de l'hôte ou du cluster et les logs ainsi que ceux\n"
"des conteneurs pour aider le support à débugguer."

#: mgradm/cmd/support/config/config.go:32
msgid "path where to extract the data"
msgstr "chemin vers lequel extraire les données"

#: mgradm/cmd/support/config/extractor.go:40
msgid "Running supportconfig in the container"
msgstr "Exécution de supportconfig dans le conteneur"

#: mgradm/cmd/support/config/extractor.go:43
msgid "failed to run supportconfig"
msgstr "impossible d'exécuter supportconfig"

#: mgradm/cmd/support/config/extractor.go:47
msgid "failed to find container supportconfig tarball from command output"
msgstr "impossible de trouver l'archive du supportconfig du conteneur à partir de la sortie de la commande"

#: mgradm/cmd/support/config/extractor.go:54
#, javascript-format
msgid "cannot copy tarball: %s"
msgstr "impossible de copier l'archive: %s"

#: mgradm/cmd/support/config/extractor.go:60
#, javascript-format
msgid "failed to remove %s%s file in the container: %s"
msgstr "impossible de supprimer le fichier %s%s dans le conteneur: %s"

#: mgradm/cmd/support/config/extractor.go:69
#, javascript-format
msgid "failed to run supportconfig on the host: %s"
msgstr "impossible de trouver supportconfig sur l'hôte: %s"

#: mgradm/cmd/support/config/extractor.go:79
msgid "failed to find host supportconfig tarball from command output"
msgstr "impossible de trouver l'archive du supportconfig de l'hôte à partir de la sortie de la commande"

#: mgradm/cmd/support/config/extractor.go:82
msgid "supportconfig is not available on the host, skipping it"
msgstr "supportconfig n'est pas disponible sur l'hôte, passé"

#: mgradm/cmd/support/config/extractor.go:88
msgid "Preparing the tarball"
msgstr "Préparation de l'archive"

#: mgradm/cmd/support/config/extractor.go:96
#, javascript-format
msgid "failed to add %s to tarball: %s"
msgstr "impossible d'ajouter %s à l'archive: %s"

#: mgradm/cmd/support/support.go:18 mgradm/cmd/support/support.go:19
msgid "Commands for support operations"
msgstr "Commandes pour les opérations de support"

#: mgradm/cmd/uninstall/kubernetes.go:50 mgradm/cmd/uninstall/kubernetes.go:51
#, javascript-format
msgid "Would run %s"
msgstr "Exécuterait %s"

#: mgradm/cmd/uninstall/kubernetes.go:53 mgradm/cmd/uninstall/kubernetes.go:58
#, javascript-format
msgid "Running %s"
msgstr "Exécute %s"

#: mgradm/cmd/uninstall/kubernetes.go:55
msgid "Failed deleting config map"
msgstr "Impossible de supprimer la config map"

#: mgradm/cmd/uninstall/kubernetes.go:66
msgid "Failed deleting secret"
msgstr "Impossible de supprimer le secret"

#: mgradm/cmd/uninstall/podman.go:38
#, javascript-format
msgid "cannot delete volume %s: %s"
msgstr "impossible de supprimer le volue %s: %s"

#: mgradm/cmd/uninstall/podman.go:41
msgid "All volumes removed"
msgstr "Tous les volumes ont été supprimés"

#: mgradm/cmd/uninstall/uninstall.go:26
msgid "Uninstall a server"
msgstr "Désinstaller un serveur"

#: mgradm/cmd/uninstall/uninstall.go:27
msgid ""
"Uninstall a server and optionally the corresponding volumes.\n"
"By default it will only print what would be done, use --force to actually remove."
msgstr ""
"Désinstaller un serveur et optionnellement les volumes associés.\n"
"By default it will only print what would be done, use --force to actually remove."

#: mgradm/cmd/uninstall/uninstall.go:35
msgid "Actually remove the server"
msgstr "Réellement supprimer le serveur"

#: mgradm/cmd/uninstall/uninstall.go:36
msgid "Also remove the volumes"
msgstr "Supprimer également les volumes"

#: mgradm/cmd/upgrade/kubernetes/kubernetes.go:27
#: mgradm/cmd/upgrade/kubernetes/kubernetes.go:28
msgid "Upgrade a local server on kubernetes"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:46
#, javascript-format
msgid "cannot inspect kubernetes values: %s"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:56
msgid "inspect function did non return fqdn value"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:80
#, javascript-format
msgid "cannot set replica to 0: %s"
msgstr "impossible de mettre le nombre de répliques à 0: %s"

#: mgradm/cmd/upgrade/kubernetes/utils.go:90 mgradm/shared/kubernetes/k3s.go:39
#: mgradm/shared/podman/podman.go:216
#, javascript-format
msgid "Previous PostgreSQL is %s, new one is %s. Performing a DB version upgrade..."
msgstr "Le PostgreSQL précédent est %s, le nouveau est %s. Mise à jour de la version de la base de données..."

#: mgradm/cmd/upgrade/kubernetes/utils.go:96
#: mgradm/cmd/upgrade/podman/utils.go:53
#, javascript-format
msgid "Upgrading to %s without changing PostgreSQL version"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:98
#: mgradm/cmd/upgrade/podman/utils.go:55
#, javascript-format
msgid "trying to downgrade PostgreSQL from %s to %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/utils.go:32
#, javascript-format
msgid "cannot inspect podman values: %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/utils.go:42
#, javascript-format
msgid "cannot stop service %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:27
msgid "Upgrade a local server on podman"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:36
msgid "list available tag for an image"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:43
msgid "Failed to unmarshall configuration"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:46
#, javascript-format
msgid "Available Tags for image: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:42
msgid "cannot find neither /etc/uyuni-release nor /etc/susemanagere-release"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:53
#, javascript-format
msgid "cannot check server release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:59
#, javascript-format
msgid "currently SUSE Manager %s is installed, instead the image is Uyuni. Upgrade is not supported"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:63
#, javascript-format
msgid "currently Uyuni %s is installed, instead the image is SUSE Manager. Upgrade is not supported"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:70
#, javascript-format
msgid "failed to read current uyuni release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:74
#: mgradm/cmd/upgrade/shared/shared.go:88
#, javascript-format
msgid "cannot fetch release from image %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:78
#: mgradm/cmd/upgrade/shared/shared.go:92
#, javascript-format
msgid "cannot downgrade from version %s to %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:84
#, javascript-format
msgid "failed to read current susemanager release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:97
#, javascript-format
msgid "cannot fetch postgresql version from %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:101
msgid "posgresql is not installed in the current deployment"
msgstr ""

#: mgradm/cmd/upgrade/upgrade.go:18 mgradm/cmd/upgrade/upgrade.go:19
msgid "Upgrade local server"
msgstr ""

#: mgradm/cmd/cmd.go:39
msgid "Uyuni administration tool"
msgstr "Outil d'administration d'Uyuni"

#: mgradm/cmd/cmd.go:40
msgid "Tool to help administering Uyuni servers in containers"
msgstr "Outil pour aider à administrer des serveurs Uyuni dans des conteneurs"

#: mgradm/cmd/cmd.go:53
#, javascript-format
msgid "Welcome to %s"
msgstr "Bienvenue à %s"

#: mgradm/cmd/cmd.go:54
#, javascript-format
msgid "Executing command: %s"
msgstr "Exécution de la commande: %s"

#: mgradm/cmd/cmd.go:58
msgid "configuration file path"
msgstr "chemin vers le fichier de configuration"

#: mgradm/cmd/cmd.go:59
msgid "application log level"
msgstr "niveau de verbosité de l'application"

#: mgradm/cmd/gpg/add/gpg.go:46
msgid "Run the import"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:55
#, fuzzy, javascript-format
msgid "failed to create folder %s: %s"
msgstr "impossible de créér le répertoire temporaire: %s"

#: mgradm/cmd/gpg/add/gpg.go:58
#, fuzzy, javascript-format
msgid "failed to create keyring %s: %s"
msgstr "Impossible de créer l'issuer"

#: mgradm/cmd/gpg/add/gpg.go:71
#, fuzzy, javascript-format
msgid "failed to create temporary directory %s"
msgstr "impossible de créér le répertoire temporaire: %s"

#: mgradm/cmd/gpg/add/gpg.go:78
#, fuzzy, javascript-format
msgid "failed to parse %s"
msgstr "impossible de générer %s"

#: mgradm/cmd/gpg/add/gpg.go:85
#, fuzzy, javascript-format
msgid "failed to download %s"
msgstr "impossible de générer %s"

#: mgradm/cmd/gpg/add/gpg.go:90
#, fuzzy, javascript-format
msgid "failed to show key %s"
msgstr "impossible de générer %s"

#: mgradm/cmd/gpg/add/gpg.go:97
#, fuzzy, javascript-format
msgid "failed to cp %s to %s"
msgstr "impossible d'ajouter %s à l'archive: %s"

#: mgradm/cmd/gpg/add/gpg.go:107 mgradm/cmd/gpg/add/gpg.go:114
#, fuzzy, javascript-format
msgid "Running: %s"
msgstr "Exécute %s"

#: mgradm/cmd/gpg/add/gpg.go:109
#, fuzzy, javascript-format
msgid "failed to run import key: %s"
msgstr "impossible d'exécuter le conteneur %s: %s"

#: mgradm/cmd/gpg/add/gpg.go:116
#, fuzzy, javascript-format
msgid "failed to restart uyuni-update-config: %s"
msgstr "Impossible lire la date de début: %s\n"

#: mgradm/shared/kubernetes/install.go:44
#, javascript-format
msgid "cannot upgrade: %s"
msgstr "impossible de mettre à jour: %s"

#: mgradm/shared/kubernetes/install.go:50
#: mgradm/shared/kubernetes/certificates.go:139
#, javascript-format
msgid "cannot deploy: %s"
msgstr "impossible d'exécuter le déploiement: %s"

#: mgradm/shared/kubernetes/install.go:65
#, javascript-format
msgid "cannot install cert-manager and self-sign issuer: %s"
msgstr "impossible d'installer cert-manager et l'issuer auto-signé: %s"

#: mgradm/shared/kubernetes/install.go:90
msgid "Installing Uyuni"
msgstr "Installation d'Uyuni"

#: mgradm/shared/kubernetes/k3s.go:56 mgradm/shared/podman/podman.go:260
#, javascript-format
msgid "Using migration image %s"
msgstr "Utilisation de l'image de migration %s"

#: mgradm/shared/kubernetes/k3s.go:59
#, javascript-format
msgid "cannot generate PostgreSQL database version upgrade script: %s"
msgstr "impossible d'exécuter le script de mise à jour de la version de PostgreSQL: %s"

#: mgradm/shared/kubernetes/k3s.go:93 mgradm/shared/kubernetes/k3s.go:139
#: mgradm/shared/kubernetes/k3s.go:186
#, javascript-format
msgid "error running container %s: %s"
msgstr "erreur lors de l'exécution du conteneur %s: %s"

#: mgradm/shared/kubernetes/k3s.go:109 mgradm/shared/kubernetes/k3s.go:154
#, javascript-format
msgid "cannot generate PostgreSQL finalization script %s"
msgstr "impossible d'exécuter le script de finalisation de PostgreSQL: %s"

#: mgradm/shared/kubernetes/certificates.go:32
msgid "Creating SSL server certificate secret"
msgstr "Création du certificat SSL du serveur"

#: mgradm/shared/kubernetes/certificates.go:42
msgid "Failed to generate uyuni-crt secret definition"
msgstr "Impossible de générer la définition du secret uyuni-crt"

#: mgradm/shared/kubernetes/certificates.go:46
msgid "Failed to create uyuni-crt TLS secret"
msgstr "Impossible de créer le secret TLS uyuni-crt"

#: mgradm/shared/kubernetes/certificates.go:59
#, javascript-format
msgid "cannot install cert manager: %s"
msgstr "impossible d'installer cert manager: %s"

#: mgradm/shared/kubernetes/certificates.go:62
#, fuzzy
msgid "Creating SSL certificate issuer"
msgstr "Création du certificat SSL du serveur"

#: mgradm/shared/kubernetes/certificates.go:86
#, javascript-format
msgid "failed to generate issuer definition: %s"
msgstr "impossible de générer la définition de l'issuer: %s"

#: mgradm/shared/kubernetes/certificates.go:91
msgid "Failed to create issuer"
msgstr "Impossible de créer l'issuer"

#: mgradm/shared/kubernetes/certificates.go:103
msgid "Issuer didn't turn ready after 60s"
msgstr "L'issuer n'est pas devenu prêt après 60s"

#: mgradm/shared/kubernetes/certificates.go:109
msgid "Installing cert-manager"
msgstr "Installation de cert-manager"

#: mgradm/shared/kubernetes/certificates.go:132
#, javascript-format
msgid "cannot run helm upgrade: %s"
msgstr "impossible d'exécuter helm upgrade: %s"

#: mgradm/shared/kubernetes/certificates.go:149
msgid "Extracting CA certificate to a configmap"
msgstr "Extraction du certificat de l'autorité de certification dans une configmap"

#: mgradm/shared/kubernetes/certificates.go:152
#, javascript-format
msgid "CA cert: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:154
msgid "uyuni-ca configmap already existing, skipping extraction"
msgstr "La configmap uyuni-ca existe déjà, extraction passée"

#: mgradm/shared/kubernetes/certificates.go:160
msgid "Failed to get uyuni-ca certificate"
msgstr "Impossible d'obtenir le certificate uyuni-ca"

#: mgradm/shared/kubernetes/certificates.go:165
msgid "Failed to base64 decode CA certificate"
msgstr "Impossible de décoder le base64 du certificat de l'autorité de certification"

#: mgradm/shared/kubernetes/certificates.go:174
msgid "Failed to create uyuni-ca config map from certificate"
msgstr "Impossible de créer la config map uyuni-ca à partir du certificat"

#: mgradm/shared/podman/podman.go:53
#, javascript-format
msgid "cannot setup network: %s"
msgstr "impossible de configurer le réseau: %s"

#: mgradm/shared/podman/podman.go:56
#, fuzzy
msgid "Enabling system service"
msgstr "impossible de générer le service systemd: %s"

#: mgradm/shared/podman/podman.go:66
#, javascript-format
msgid "failed to generate systemd service unit file: %s"
msgstr "impossible de générer le fichier du service systemd: %s"

#: mgradm/shared/podman/podman.go:70
#, javascript-format
msgid "cannot generate systemd conf file: %s"
msgstr "impossible de générer le fichier de configuration du service systemd: %s"

#: mgradm/shared/podman/podman.go:82
msgid "failed to create temporary folder on container to copy certificates to"
msgstr "impossible de créér le répertoire temporaire dans lequel coper les certificats: %s"

#: mgradm/shared/podman/podman.go:122
msgid "failed to update SSL certificate"
msgstr "impossible de mettre à jour le certificat SSL: %s"

#: mgradm/shared/podman/podman.go:127
msgid "failed to remove copied certificate files in the container"
msgstr "impossible de copier les fichiers du certificat dans le conteneur"

#: mgradm/shared/podman/podman.go:133
msgid "failed to remove now useless ssl-build folder in the container"
msgstr "impossible de supprimer le répertoire ssl-build devenu inutile dans le conteneur"

#: mgradm/shared/podman/podman.go:138
msgid "Restarting services after updating the certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:154
#, javascript-format
msgid "failed to run %s container: %s"
msgstr "impossible d'exécuter le conteneur %s: %s"

#: mgradm/shared/podman/podman.go:164
#, javascript-format
msgid "cannot generate migration script: %s"
msgstr "impossible de générer le script de migration: %s"

#: mgradm/shared/podman/podman.go:200 mgradm/shared/utils/exec.go:146
msgid "Migrating server"
msgstr ""

#: mgradm/shared/podman/podman.go:203
#, javascript-format
msgid "cannot run uyuni migration container: %s"
msgstr "impossible d'exécuter le conteneur de migration: %s"

#: mgradm/shared/podman/podman.go:208
#, javascript-format
msgid "cannot read extracted data: %s"
msgstr "impossible de lire les données extraites: %s"

#: mgradm/shared/podman/podman.go:264
#, javascript-format
msgid "cannot generate PostgreSQL database version upgrade script %s"
msgstr "impossible de générer le script de mise à jour de la version de PostgreSQL: %s"

#: mgradm/shared/podman/podman.go:291 mgradm/shared/podman/podman.go:315
#, javascript-format
msgid "cannot generate PostgreSQL finalization script: %s"
msgstr "impossible de générer le script de finalisation de PostgreSQL: %s"

#: mgradm/shared/ssl/ssl.go:47
msgid "Failed to find a non-CA certificate"
msgstr "Impossible de trouver un certificat qui ne soit pas une autorité de certification"

#: mgradm/shared/ssl/ssl.go:86
msgid "expected to find a certificate, got none"
msgstr "certificat attendu, aucun trouvé"

#: mgradm/shared/ssl/ssl.go:92
#, javascript-format
msgid "Failed to read certificate file %s"
msgstr "Impossible de lire le fichier de certificat %s"

#: mgradm/shared/ssl/ssl.go:128
msgid "Failed to extract data from certificate"
msgstr "Impossible d'extraire les données du certificat"

#: mgradm/shared/ssl/ssl.go:149
#, javascript-format
msgid "Failed to parse start date: %s\n"
msgstr "Impossible lire la date de début: %s\n"

#: mgradm/shared/ssl/ssl.go:155
#, javascript-format
msgid "Failed to parse end date: %s\n"
msgstr "Impossible de lire la date de fin: %s\n"

#: mgradm/shared/ssl/ssl.go:199
msgid "No CA found"
msgstr "Aucune autorité de certification trouvée"

#: mgradm/shared/ssl/ssl.go:206
msgid "No CA found for server certificate"
msgstr "Aucune autorité de certification trouvée pour le certificat du serveur"

#: mgradm/shared/ssl/ssl.go:215
#, javascript-format
msgid "Missing CA with subject hash %s"
msgstr "Autorité de certification avec  hachage du sujet %s manquante"

#: mgradm/shared/ssl/ssl.go:236
msgid "server certificate is required"
msgstr "certificat du serveur nécessaire"

#: mgradm/shared/ssl/ssl.go:237
msgid "server key is required"
msgstr "la clé du serveur est nécessaire"

#: mgradm/shared/ssl/ssl.go:249
#, javascript-format
msgid "%s file is not accessible"
msgstr "le fichier %s n'est pas accessible"

#: mgradm/shared/ssl/ssl.go:257
msgid "Source server SSL CA private key password"
msgstr "Mot de passe de la clé privée de l'autorité de certification SSL source"

#: mgradm/shared/ssl/ssl.go:264
msgid "Failed to convert CA private key to RSA"
msgstr "Echec de conversion de la clé privée de l'autorité de certification en RSA"

#: mgradm/shared/utils/cmd_utils.go:49
msgid "Server certificate, key and root CA need to be all provided"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:57
msgid "Kubernetes namespace where to install uyuni"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:58
msgid "URL to the uyuni helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:59
msgid "Version of the uyuni helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:60
msgid "Path to a values YAML file to use for Uyuni helm install"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:61
msgid "Kubernetes namespace where to install cert-manager"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:62
msgid "URL to the cert-manager helm chart. To be used for offline installations"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:63
msgid "Version of the cert-manager helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:64
msgid "Path to a values YAML file to use for cert-manager helm install"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:69 mgradm/shared/utils/cmd_utils.go:77
msgid "Image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:70 mgradm/shared/utils/cmd_utils.go:78
msgid "Tag Image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:80
msgid "set whether to pull the images or not during upgrade. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:85
#, fuzzy
msgid "Migration image"
msgstr "Utilisation de l'image de migration %s"

#: mgradm/shared/utils/cmd_utils.go:86
#, fuzzy
msgid "Migration image tag"
msgstr "Utilisation de l'image de migration %s"

#: mgradm/shared/utils/cmd_utils.go:88
msgid "set whether to pull the migration images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: mgradm/shared/utils/exec.go:51
#, javascript-format
msgid "exec command failed: %s"
msgstr "la commande exec a échoué: %s"

#: mgradm/shared/utils/exec.go:85 mgradm/shared/utils/exec.go:103
#: mgradm/shared/utils/exec.go:117
#, javascript-format
msgid "failed to generate %s"
msgstr "impossible de générer %s"

#: mgradm/shared/utils/exec.go:126
msgid "failed to read data extracted from source host"
msgstr "impossible de lire les données extraites de l'hôte source"

#: mgradm/shared/utils/exec.go:130 mgradm/shared/utils/exec.go:219
#, javascript-format
msgid "cannot read config: %s"
msgstr "impossible de lire la configuration: %s"

#: mgradm/shared/utils/exec.go:133
msgid "cannot retrieve timezone"
msgstr "impossible d'obtenir la zone horaire"

#: mgradm/shared/utils/exec.go:136
msgid "cannot retrieve source PostgreSQL version"
msgstr "impossible d'obtenir la version de PostgreSQL sur le serveur d'origine: %s"

#: mgradm/shared/utils/exec.go:139
msgid "cannot retrieve image PostgreSQL version"
msgstr "impossible d'obtenir la version de PostgreSQL sur l'image: %s"

#: mgradm/shared/utils/exec.go:149
#, javascript-format
msgid "error running the migration script: %s"
msgstr "erreur lors de l'exécution du script de migration: %s"

#: mgradm/shared/utils/exec.go:196
#, javascript-format
msgid "Image is: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:212
#, javascript-format
msgid "cannot parse file %s: %s"
msgstr "impossible d'analyser le fichier %s: %s"

#: mgradm/shared/utils/exec.go:250
#, javascript-format
msgid "failed to run inspect script in host system: %s"
msgstr "impossible d'exécuter le script d'inspection sur le système hôte: %s"

#: mgradm/shared/utils/exec.go:255
#, javascript-format
msgid "cannot inspect host data: %s"
msgstr "impossible d'inspecter les valeurs de l'hôte: %s"

#: mgradm/shared/utils/exec.go:270 mgradm/shared/utils/exec.go:284
#, javascript-format
msgid "failed to generate inspect script: %s"
msgstr "impossible de générer le script d'inspection: %s"

#, javascript-format
#~ msgid "install %s before running this command: %s"
#~ msgstr "installer %s avant d'exécuter cette commande: %s"

#, javascript-format
#~ msgid "install podman before running this command: %s"
#~ msgstr "installer podman avant d'exécuter cette commande: %s"
07070100000024000081B4000000000000000000000001662A752800009172000000000000000000000000000000000000002000000000uyuni-tools/locale/mgradm/it.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: 2024-04-17 08:03+0000\n"
"Last-Translator: Michele Bussolotto <michele.bussolotto@suse.com>\n"
"Language-Team: Italian <https://l10n.opensuse.org/projects/uyuni/uyuni-tools/it/>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.9.1\n"

#: mgradm/cmd/distro/cp.go:32
#, javascript-format
msgid "Unable to unmount ISO image, leaving %s intact"
msgstr ""

#: mgradm/cmd/distro/cp.go:41
#, javascript-format
msgid "unable to login and register the distribution. Manual distro registration is required: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:52
#, javascript-format
msgid "unable to register the distribution. Manual distro registration is required: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:54
#, javascript-format
msgid "Distribution %s successfully registered"
msgstr ""

#: mgradm/cmd/distro/cp.go:74
#, javascript-format
msgid "Copying distribution %s\n"
msgstr ""

#: mgradm/cmd/distro/cp.go:76
#, javascript-format
msgid "source %s does not exists"
msgstr ""

#: mgradm/cmd/distro/cp.go:81
#, javascript-format
msgid "distribution already exists: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:102
msgid "unable to mount ISO image. Mount manually and try again"
msgstr ""

#: mgradm/cmd/distro/cp.go:107 mgradm/shared/podman/podman.go:102
#: mgradm/shared/podman/podman.go:105 mgradm/shared/podman/podman.go:108
#: mgradm/shared/podman/podman.go:116
#, javascript-format
msgid "cannot copy %s: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/cmd/distro/cp.go:110
msgid "Distribution has been copied"
msgstr ""

#: mgradm/cmd/distro/detect.go:84
msgid "unknown distribution, auto-registration is not possible"
msgstr ""

#: mgradm/cmd/distro/distro.go:28
msgid "Distributions management"
msgstr ""

#: mgradm/cmd/distro/distro.go:29
msgid "Tools for autoinstallation distributions management"
msgstr ""

#: mgradm/cmd/distro/distro.go:35
msgid "Copy distribution files from ISO image to the container"
msgstr ""

#: mgradm/cmd/distro/distro.go:36
msgid ""
"Takes a path to an ISO file or the directory of a mounted ISO image and copies it into the container.\n"
"\n"
"Distribution name specifies the destination directory under /srv/www/distributions.\n"
"\n"
"Optional channel label specify which parent channel to associate with the distribution.\n"
"Only when API informations are provided and auto registration is done."
msgstr ""

#: mgradm/cmd/hub/register/register.go:31
msgid "Register"
msgstr ""

#: mgradm/cmd/hub/register/register.go:32
msgid "Register this peripheral server to Hub API"
msgstr ""

#: mgradm/cmd/hub/register/register.go:79
#, javascript-format
msgid "invalid line format: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:93
#, javascript-format
msgid "mandatory entry missing in config: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:96
#, javascript-format
msgid "Hub API server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:99
#, javascript-format
msgid "failed to connect to the Hub server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:107
#: mgradm/cmd/hub/register/register.go:110
#, javascript-format
msgid "failed to register this peripheral server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:124
#: mgradm/cmd/hub/register/register.go:128
#, javascript-format
msgid "failed to update peripheral server info: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:130
#, javascript-format
msgid "Registered peripheral server: %s, ID: %d"
msgstr ""

#: mgradm/cmd/hub/hub.go:18
msgid "Hub management"
msgstr ""

#: mgradm/cmd/hub/hub.go:19
msgid "Tools and utilities for Hub management"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:26
msgid "Inspect"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:27
msgid "Extract information from image and deployment"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:37
msgid "Image URL. Leave it empty to analyze the current deployment"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:38
msgid "Image Tag. Leave it empty to analyze the current deployment"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:35 mgradm/cmd/inspect/podman.go:31
#, javascript-format
msgid "failed to determine image: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:44 mgradm/cmd/inspect/podman.go:40
#, javascript-format
msgid "failed to find the image of the currently running server container: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:50 mgradm/cmd/inspect/podman.go:45
#, javascript-format
msgid "inspect command failed: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:55 mgradm/cmd/inspect/podman.go:49
#, javascript-format
msgid "cannot print inspect result: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:68
#: mgradm/cmd/install/kubernetes/utils.go:32
#: mgradm/cmd/migrate/kubernetes/utils.go:37
#: mgradm/cmd/upgrade/kubernetes/utils.go:34
#, javascript-format
msgid "install %s before running this command"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:75 mgradm/cmd/inspect/podman.go:63
#: mgradm/cmd/support/config/extractor.go:32
#: mgradm/cmd/upgrade/kubernetes/utils.go:68 mgradm/shared/kubernetes/k3s.go:36
#: mgradm/shared/kubernetes/k3s.go:104 mgradm/shared/kubernetes/k3s.go:149
#: mgradm/shared/kubernetes/certificates.go:27
#: mgradm/shared/kubernetes/certificates.go:65
#: mgradm/shared/podman/podman.go:221 mgradm/shared/podman/podman.go:281
#: mgradm/shared/podman/podman.go:306 mgradm/shared/utils/exec.go:158
#: mgradm/shared/utils/exec.go:242
#, javascript-format
msgid "failed to create temporary directory: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:88 mgradm/shared/kubernetes/k3s.go:64
#: mgradm/shared/kubernetes/k3s.go:113 mgradm/shared/kubernetes/k3s.go:159
#, javascript-format
msgid "cannot delete %s: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:94
#: mgradm/cmd/migrate/kubernetes/utils.go:85
#: mgradm/cmd/upgrade/kubernetes/utils.go:75
#, javascript-format
msgid "cannot find node running uyuni: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:122
#, javascript-format
msgid "cannot run inspect pod: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:127 mgradm/cmd/inspect/podman.go:100
#, fuzzy, javascript-format
msgid "cannot inspect data: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/cmd/inspect/podman.go:68 mgradm/cmd/install/podman/utils.go:57
#: mgradm/shared/podman/podman.go:185 mgradm/shared/podman/podman.go:245
#, javascript-format
msgid "cannot inspect host values: %s"
msgstr ""

#: mgradm/cmd/install/kubernetes/kubernetes.go:27
msgid "Install a new server on a kubernetes cluster"
msgstr ""

#: mgradm/cmd/install/kubernetes/kubernetes.go:28
msgid ""
"Install a new server on a kubernetes cluster\n"
"\n"
"The install command assumes the following:\n"
"  * kubectl and helm are installed locally\n"
"  * a working kubectl configuration should be set to connect to the cluster to deploy to\n"
"\n"
"The helm values file will be overridden with the values from the command parameters or configuration.\n"
"\n"
"NOTE: installing on a remote cluster is not supported yet!\n"
msgstr ""

#: mgradm/cmd/install/kubernetes/utils.go:61
#: mgradm/cmd/migrate/kubernetes/utils.go:180
#, javascript-format
msgid "cannot deploy certificate: %s"
msgstr ""

#: mgradm/cmd/install/kubernetes/utils.go:67
#, javascript-format
msgid "cannot deploy uyuni: %s"
msgstr ""

#: mgradm/cmd/install/kubernetes/utils.go:83
#, javascript-format
msgid "error storing the SSL CA certificate in database: %s"
msgstr ""

#: mgradm/cmd/install/podman/podman.go:25
msgid "Install a new server on podman"
msgstr ""

#: mgradm/cmd/install/podman/podman.go:26
msgid ""
"Install a new server on podman\n"
"\n"
"The install podman command assumes podman is installed locally.\n"
"\n"
"NOTE: installing on a remote podman is not supported yet!\n"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:36 mgradm/cmd/upgrade/podman/utils.go:70
msgid "Waiting for the server to start..."
msgstr ""

#: mgradm/cmd/install/podman/utils.go:38
#, javascript-format
msgid "cannot enable service: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:52 mgradm/cmd/migrate/podman/utils.go:25
msgid "install podman before running this command"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:64
#, javascript-format
msgid "Setting up the server with the FQDN '%s'"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:68
#: mgradm/cmd/migrate/kubernetes/utils.go:44
#: mgradm/cmd/upgrade/kubernetes/utils.go:41
#: mgradm/cmd/upgrade/podman/utils.go:27 mgradm/shared/kubernetes/install.go:38
#: mgradm/shared/kubernetes/k3s.go:47 mgradm/shared/kubernetes/k3s.go:52
#: mgradm/shared/podman/podman.go:234 mgradm/shared/podman/podman.go:239
#, javascript-format
msgid "failed to compute image URL: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:88
#, javascript-format
msgid "cannot wait for system start: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:108
msgid "Run setup command in the container"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:116
#, javascript-format
msgid "cannot update SSL certificate: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:121 mgradm/cmd/migrate/podman/utils.go:69
#, javascript-format
msgid "cannot enable podman socket: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:132
#, javascript-format
msgid "failed to compute server FQDN: %s"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:71
msgid "Can only contain letters, digits . _ and -"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:79
msgid "Not a valid email address"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:119
msgid "Time zone to set on the server. Defaults to the host timezone"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:120
msgid "Administrator e-mail"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:121
msgid "E-Mail sending the notifications"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:122
msgid "Path to mirrored packages mounted on the host"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:123
msgid "InterServerSync v1 parent FQDN"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:124
msgid "Database user"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:125
msgid "Database password. Randomly generated by default"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:126
msgid "Database name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:127
msgid "Database host"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:128
msgid "Database port"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:129
msgid "Database protocol"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:130
msgid "External database admin user name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:131
msgid "External database admin password"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:132
msgid "External database provider. Possible values 'aws'"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:134
msgid "Enable TFTP"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:135
msgid "Report database name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:136
msgid "Report database host"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:137
msgid "Report database port"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:138
msgid "Report Database username"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:139
msgid "Report database password. Randomly generated by default"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:142
msgid "SSL certificate cnames separated by commas"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:143
msgid "SSL certificate country"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:144
msgid "SSL certificate state"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:145
msgid "SSL certificate city"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:146
msgid "SSL certificate organization"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:147
msgid "SSL certificate organization unit"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:148
msgid "Password for the CA key to generate"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:149
msgid "SSL certificate E-Mail"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:152
msgid "Intermediate CA certificate path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:153
msgid "Root CA certificate path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:154
msgid "Server certificate path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:155
msgid "Server key path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:157
msgid "SUSE Customer Center username"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:158
msgid "SUSE Customer Center password"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:160
msgid "Enable tomcat and taskomatic remote debugging"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:163
msgid "Administrator user name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:164
msgid "Administrator password"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:165
msgid "First name of the administrator"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:166
msgid "Last name of the administrator"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:167
msgid "Administrator's email"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:168
msgid "First organization name"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:32
#, javascript-format
msgid "cannot copy /tmp/setup.sh: %s"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:37
#, javascript-format
msgid "error running the setup script: %s"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:53
msgid "Server set up"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:115
msgid "Failed to create temporary directory"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:125
msgid "Failed to generate setup script"
msgstr ""

#: mgradm/cmd/install/install.go:19 mgradm/cmd/install/install.go:20
msgid "Install a new server"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:28
msgid "Migrate a remote server to containers running on a kubernetes cluster"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:29
msgid ""
"Migrate a remote server to containers running on a kubernetes cluster\n"
"\n"
"This migration command assumes a few things:\n"
"  * the SSH configuration for the source server is complete, including user and\n"
"    all needed options to connect to the machine,\n"
"  * an SSH agent is started and the key to use to connect to the server is added to it,\n"
"  * kubectl and helm are installed locally,\n"
"  * a working kubectl configuration should be set to connect to the cluster to deploy to\n"
"\n"
"When migrating a server with a automatically generated SSL Root CA certificate, the private key\n"
"password will be required to convert it to RSA in a kubernetes secret.\n"
"This is not needed if the source server does not have a generated SSL CA certificate.\n"
"\n"
"NOTE: migrating to a remote cluster is not supported yet!\n"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:53
msgid "SSL CA generated private key password"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:56 mgradm/shared/utils/exec.go:169
#, javascript-format
msgid "failed to generate migration script: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:78
#, javascript-format
msgid "cannot run deploy: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:89
#, javascript-format
msgid "cannot run migration: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:94
#, javascript-format
msgid "cannot read data from container: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:100
#: mgradm/cmd/migrate/kubernetes/utils.go:133
#, fuzzy, javascript-format
msgid "cannot set replicas to 0: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:112
#, fuzzy, javascript-format
msgid "cannot setup SSL: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/cmd/migrate/kubernetes/utils.go:124
#, javascript-format
msgid "cannot upgrade helm chart to image %s using new SSL certificate: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:128
#, javascript-format
msgid "cannot wait for deployment of %s: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:138
#: mgradm/cmd/migrate/kubernetes/utils.go:144
#: mgradm/cmd/migrate/podman/utils.go:44
#: mgradm/cmd/upgrade/kubernetes/utils.go:93
#: mgradm/cmd/upgrade/kubernetes/utils.go:103
#: mgradm/cmd/upgrade/podman/utils.go:50 mgradm/cmd/upgrade/podman/utils.go:60
#, javascript-format
msgid "cannot run PostgreSQL version upgrade script: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:148
#: mgradm/cmd/migrate/podman/utils.go:54
#: mgradm/cmd/upgrade/kubernetes/utils.go:107
#: mgradm/cmd/upgrade/podman/utils.go:64
#, javascript-format
msgid "cannot run post upgrade script: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:153
#: mgradm/cmd/upgrade/kubernetes/utils.go:112
#, javascript-format
msgid "cannot upgrade to image %s: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:171
#, javascript-format
msgid "failed to strip text part from CA certificate: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/podman.go:25
msgid "Migrate a remote server to containers running on podman"
msgstr ""

#: mgradm/cmd/migrate/podman/podman.go:26
msgid ""
"Migrate a remote server to containers running on podman\n"
"\n"
"This migration command assumes a few things:\n"
"  * the SSH configuration for the source server is complete, including user and\n"
"    all needed options to connect to the machine,\n"
"  * an SSH agent is started and the key to use to connect to the server is added to it,\n"
"  * podman is installed locally\n"
"\n"
"NOTE: migrating to a remote podman is not supported yet!\n"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:30
#, javascript-format
msgid "cannot compute image: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:39
#, javascript-format
msgid "cannot run migration script: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:50
#, javascript-format
msgid "cannot run PostgreSQL finalize script: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:58
#, javascript-format
msgid "cannot generate systemd service file: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:66
msgid "Server migrated"
msgstr ""

#: mgradm/cmd/migrate/shared/shared.go:20
msgid "SSH_AUTH_SOCK is not defined, start an SSH agent and try again"
msgstr ""

#: mgradm/cmd/migrate/shared/shared.go:30
msgid "Failed to find home directory to look for SSH config"
msgstr ""

#: mgradm/cmd/migrate/migrate.go:19 mgradm/cmd/migrate/migrate.go:20
msgid "Migrate a remote server to containers"
msgstr ""

#: mgradm/cmd/restart/nokubernetes.go:23 mgradm/cmd/start/nokubernetes.go:23
#: mgradm/cmd/status/nokubernetes.go:23 mgradm/cmd/stop/nokubernetes.go:23
msgid "built without kubernetes support"
msgstr ""

#: mgradm/cmd/restart/restart.go:23 mgradm/cmd/restart/restart.go:24
msgid "Restart the server"
msgstr ""

#: mgradm/cmd/start/start.go:23 mgradm/cmd/start/start.go:24
msgid "Start the server"
msgstr ""

#: mgradm/cmd/status/podman.go:29
#, javascript-format
msgid "failed to get status of the server service: %s"
msgstr ""

#: mgradm/cmd/status/podman.go:37 mgradm/cmd/status/kubernetes.go:61
#, javascript-format
msgid "failed to run spacewalk-service status: %s"
msgstr ""

#: mgradm/cmd/status/status.go:24 mgradm/cmd/status/status.go:25
msgid "Get the server status"
msgstr ""

#: mgradm/cmd/status/status.go:46
msgid "no installed server detected"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:32
#, javascript-format
msgid "failed to discover the cluster type: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:37
msgid "no uyuni helm release installed on the cluster"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:42
#, javascript-format
msgid "failed to find the uyuni deployment namespace: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:48
#, javascript-format
msgid "failed to get deployment status: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:51
#, javascript-format
msgid "Some replicas are not ready: %d / %d"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:55
msgid "the pod is not running"
msgstr ""

#: mgradm/cmd/stop/stop.go:23 mgradm/cmd/stop/stop.go:24
msgid "Stop the server"
msgstr ""

#: mgradm/cmd/support/config/config.go:23
msgid "Extract configuration and logs"
msgstr ""

#: mgradm/cmd/support/config/config.go:24
msgid ""
"Extract the host or cluster configuration and logs as well as those from \n"
"the containers for support to help debugging."
msgstr ""

#: mgradm/cmd/support/config/config.go:32
msgid "path where to extract the data"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:40
msgid "Running supportconfig in the container"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:43
msgid "failed to run supportconfig"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:47
msgid "failed to find container supportconfig tarball from command output"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:54
#, javascript-format
msgid "cannot copy tarball: %s"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:60
#, javascript-format
msgid "failed to remove %s%s file in the container: %s"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:69
#, javascript-format
msgid "failed to run supportconfig on the host: %s"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:79
msgid "failed to find host supportconfig tarball from command output"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:82
msgid "supportconfig is not available on the host, skipping it"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:88
msgid "Preparing the tarball"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:96
#, javascript-format
msgid "failed to add %s to tarball: %s"
msgstr ""

#: mgradm/cmd/support/support.go:18 mgradm/cmd/support/support.go:19
msgid "Commands for support operations"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:50 mgradm/cmd/uninstall/kubernetes.go:51
#, javascript-format
msgid "Would run %s"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:53 mgradm/cmd/uninstall/kubernetes.go:58
#, javascript-format
msgid "Running %s"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:55
msgid "Failed deleting config map"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:66
msgid "Failed deleting secret"
msgstr ""

#: mgradm/cmd/uninstall/podman.go:38
#, javascript-format
msgid "cannot delete volume %s: %s"
msgstr ""

#: mgradm/cmd/uninstall/podman.go:41
msgid "All volumes removed"
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:26
msgid "Uninstall a server"
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:27
msgid ""
"Uninstall a server and optionally the corresponding volumes.\n"
"By default it will only print what would be done, use --force to actually remove."
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:35
msgid "Actually remove the server"
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:36
msgid "Also remove the volumes"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/kubernetes.go:27
#: mgradm/cmd/upgrade/kubernetes/kubernetes.go:28
msgid "Upgrade a local server on kubernetes"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:46
#, javascript-format
msgid "cannot inspect kubernetes values: %s"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:56
msgid "inspect function did non return fqdn value"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:80
#, javascript-format
msgid "cannot set replica to 0: %s"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:90 mgradm/shared/kubernetes/k3s.go:39
#: mgradm/shared/podman/podman.go:216
#, javascript-format
msgid "Previous PostgreSQL is %s, new one is %s. Performing a DB version upgrade..."
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:96
#: mgradm/cmd/upgrade/podman/utils.go:53
#, javascript-format
msgid "Upgrading to %s without changing PostgreSQL version"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:98
#: mgradm/cmd/upgrade/podman/utils.go:55
#, javascript-format
msgid "trying to downgrade PostgreSQL from %s to %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/utils.go:32
#, javascript-format
msgid "cannot inspect podman values: %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/utils.go:42
#, javascript-format
msgid "cannot stop service %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:27
msgid "Upgrade a local server on podman"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:36
msgid "list available tag for an image"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:43
msgid "Failed to unmarshall configuration"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:46
#, javascript-format
msgid "Available Tags for image: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:42
msgid "cannot find neither /etc/uyuni-release nor /etc/susemanagere-release"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:53
#, javascript-format
msgid "cannot check server release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:59
#, javascript-format
msgid "currently SUSE Manager %s is installed, instead the image is Uyuni. Upgrade is not supported"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:63
#, javascript-format
msgid "currently Uyuni %s is installed, instead the image is SUSE Manager. Upgrade is not supported"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:70
#, javascript-format
msgid "failed to read current uyuni release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:74
#: mgradm/cmd/upgrade/shared/shared.go:88
#, javascript-format
msgid "cannot fetch release from image %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:78
#: mgradm/cmd/upgrade/shared/shared.go:92
#, javascript-format
msgid "cannot downgrade from version %s to %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:84
#, javascript-format
msgid "failed to read current susemanager release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:97
#, javascript-format
msgid "cannot fetch postgresql version from %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:101
msgid "posgresql is not installed in the current deployment"
msgstr ""

#: mgradm/cmd/upgrade/upgrade.go:18 mgradm/cmd/upgrade/upgrade.go:19
msgid "Upgrade local server"
msgstr ""

#: mgradm/cmd/cmd.go:39
msgid "Uyuni administration tool"
msgstr ""

#: mgradm/cmd/cmd.go:40
msgid "Tool to help administering Uyuni servers in containers"
msgstr ""

#: mgradm/cmd/cmd.go:53
#, javascript-format
msgid "Welcome to %s"
msgstr ""

#: mgradm/cmd/cmd.go:54
#, javascript-format
msgid "Executing command: %s"
msgstr ""

#: mgradm/cmd/cmd.go:58
msgid "configuration file path"
msgstr ""

#: mgradm/cmd/cmd.go:59
msgid "application log level"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:46
msgid "Run the import"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:55
#, javascript-format
msgid "failed to create folder %s: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:58
#, javascript-format
msgid "failed to create keyring %s: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:71
#, javascript-format
msgid "failed to create temporary directory %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:78
#, javascript-format
msgid "failed to parse %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:85
#, javascript-format
msgid "failed to download %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:90
#, javascript-format
msgid "failed to show key %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:97
#, javascript-format
msgid "failed to cp %s to %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:107 mgradm/cmd/gpg/add/gpg.go:114
#, javascript-format
msgid "Running: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:109
#, javascript-format
msgid "failed to run import key: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:116
#, javascript-format
msgid "failed to restart uyuni-update-config: %s"
msgstr ""

#: mgradm/shared/kubernetes/install.go:44
#, fuzzy, javascript-format
msgid "cannot upgrade: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/shared/kubernetes/install.go:50
#: mgradm/shared/kubernetes/certificates.go:139
#, fuzzy, javascript-format
msgid "cannot deploy: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/shared/kubernetes/install.go:65
#, javascript-format
msgid "cannot install cert-manager and self-sign issuer: %s"
msgstr ""

#: mgradm/shared/kubernetes/install.go:90
msgid "Installing Uyuni"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:56 mgradm/shared/podman/podman.go:260
#, javascript-format
msgid "Using migration image %s"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:59
#, javascript-format
msgid "cannot generate PostgreSQL database version upgrade script: %s"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:93 mgradm/shared/kubernetes/k3s.go:139
#: mgradm/shared/kubernetes/k3s.go:186
#, javascript-format
msgid "error running container %s: %s"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:109 mgradm/shared/kubernetes/k3s.go:154
#, javascript-format
msgid "cannot generate PostgreSQL finalization script %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:32
msgid "Creating SSL server certificate secret"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:42
msgid "Failed to generate uyuni-crt secret definition"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:46
msgid "Failed to create uyuni-crt TLS secret"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:59
#, javascript-format
msgid "cannot install cert manager: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:62
msgid "Creating SSL certificate issuer"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:86
#, javascript-format
msgid "failed to generate issuer definition: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:91
msgid "Failed to create issuer"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:103
msgid "Issuer didn't turn ready after 60s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:109
msgid "Installing cert-manager"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:132
#, javascript-format
msgid "cannot run helm upgrade: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:149
msgid "Extracting CA certificate to a configmap"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:152
#, javascript-format
msgid "CA cert: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:154
msgid "uyuni-ca configmap already existing, skipping extraction"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:160
msgid "Failed to get uyuni-ca certificate"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:165
msgid "Failed to base64 decode CA certificate"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:174
msgid "Failed to create uyuni-ca config map from certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:53
#, fuzzy, javascript-format
msgid "cannot setup network: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/shared/podman/podman.go:56
msgid "Enabling system service"
msgstr ""

#: mgradm/shared/podman/podman.go:66
#, javascript-format
msgid "failed to generate systemd service unit file: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:70
#, javascript-format
msgid "cannot generate systemd conf file: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:82
msgid "failed to create temporary folder on container to copy certificates to"
msgstr ""

#: mgradm/shared/podman/podman.go:122
msgid "failed to update SSL certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:127
msgid "failed to remove copied certificate files in the container"
msgstr ""

#: mgradm/shared/podman/podman.go:133
msgid "failed to remove now useless ssl-build folder in the container"
msgstr ""

#: mgradm/shared/podman/podman.go:138
msgid "Restarting services after updating the certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:154
#, javascript-format
msgid "failed to run %s container: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:164
#, javascript-format
msgid "cannot generate migration script: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:200 mgradm/shared/utils/exec.go:146
msgid "Migrating server"
msgstr ""

#: mgradm/shared/podman/podman.go:203
#, javascript-format
msgid "cannot run uyuni migration container: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:208
#, javascript-format
msgid "cannot read extracted data: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:264
#, javascript-format
msgid "cannot generate PostgreSQL database version upgrade script %s"
msgstr ""

#: mgradm/shared/podman/podman.go:291 mgradm/shared/podman/podman.go:315
#, javascript-format
msgid "cannot generate PostgreSQL finalization script: %s"
msgstr ""

#: mgradm/shared/ssl/ssl.go:47
msgid "Failed to find a non-CA certificate"
msgstr ""

#: mgradm/shared/ssl/ssl.go:86
msgid "expected to find a certificate, got none"
msgstr ""

#: mgradm/shared/ssl/ssl.go:92
#, javascript-format
msgid "Failed to read certificate file %s"
msgstr ""

#: mgradm/shared/ssl/ssl.go:128
msgid "Failed to extract data from certificate"
msgstr ""

#: mgradm/shared/ssl/ssl.go:149
#, javascript-format
msgid "Failed to parse start date: %s\n"
msgstr ""

#: mgradm/shared/ssl/ssl.go:155
#, javascript-format
msgid "Failed to parse end date: %s\n"
msgstr ""

#: mgradm/shared/ssl/ssl.go:199
msgid "No CA found"
msgstr ""

#: mgradm/shared/ssl/ssl.go:206
msgid "No CA found for server certificate"
msgstr ""

#: mgradm/shared/ssl/ssl.go:215
#, javascript-format
msgid "Missing CA with subject hash %s"
msgstr ""

#: mgradm/shared/ssl/ssl.go:236
msgid "server certificate is required"
msgstr ""

#: mgradm/shared/ssl/ssl.go:237
msgid "server key is required"
msgstr ""

#: mgradm/shared/ssl/ssl.go:249
#, javascript-format
msgid "%s file is not accessible"
msgstr ""

#: mgradm/shared/ssl/ssl.go:257
msgid "Source server SSL CA private key password"
msgstr ""

#: mgradm/shared/ssl/ssl.go:264
msgid "Failed to convert CA private key to RSA"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:49
msgid "Server certificate, key and root CA need to be all provided"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:57
msgid "Kubernetes namespace where to install uyuni"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:58
msgid "URL to the uyuni helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:59
msgid "Version of the uyuni helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:60
msgid "Path to a values YAML file to use for Uyuni helm install"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:61
msgid "Kubernetes namespace where to install cert-manager"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:62
msgid "URL to the cert-manager helm chart. To be used for offline installations"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:63
msgid "Version of the cert-manager helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:64
msgid "Path to a values YAML file to use for cert-manager helm install"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:69 mgradm/shared/utils/cmd_utils.go:77
msgid "Image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:70 mgradm/shared/utils/cmd_utils.go:78
msgid "Tag Image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:80
msgid "set whether to pull the images or not during upgrade. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:85
msgid "Migration image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:86
msgid "Migration image tag"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:88
msgid "set whether to pull the migration images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: mgradm/shared/utils/exec.go:51
#, javascript-format
msgid "exec command failed: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:85 mgradm/shared/utils/exec.go:103
#: mgradm/shared/utils/exec.go:117
#, javascript-format
msgid "failed to generate %s"
msgstr ""

#: mgradm/shared/utils/exec.go:126
msgid "failed to read data extracted from source host"
msgstr ""

#: mgradm/shared/utils/exec.go:130 mgradm/shared/utils/exec.go:219
#, fuzzy, javascript-format
msgid "cannot read config: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/shared/utils/exec.go:133
msgid "cannot retrieve timezone"
msgstr ""

#: mgradm/shared/utils/exec.go:136
msgid "cannot retrieve source PostgreSQL version"
msgstr ""

#: mgradm/shared/utils/exec.go:139
msgid "cannot retrieve image PostgreSQL version"
msgstr ""

#: mgradm/shared/utils/exec.go:149
#, javascript-format
msgid "error running the migration script: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:196
#, javascript-format
msgid "Image is: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:212
#, fuzzy, javascript-format
msgid "cannot parse file %s: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/shared/utils/exec.go:250
#, javascript-format
msgid "failed to run inspect script in host system: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:255
#, fuzzy, javascript-format
msgid "cannot inspect host data: %s"
msgstr "impossibile copiare %s: %s"

#: mgradm/shared/utils/exec.go:270 mgradm/shared/utils/exec.go:284
#, javascript-format
msgid "failed to generate inspect script: %s"
msgstr ""
07070100000025000081B4000000000000000000000001662A752800008FA7000000000000000000000000000000000000002500000000uyuni-tools/locale/mgradm/mgradm.pot# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: mgradm/cmd/distro/cp.go:32
#, javascript-format
msgid "Unable to unmount ISO image, leaving %s intact"
msgstr ""

#: mgradm/cmd/distro/cp.go:41
#, javascript-format
msgid "unable to login and register the distribution. Manual distro registration is required: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:52
#, javascript-format
msgid "unable to register the distribution. Manual distro registration is required: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:54
#, javascript-format
msgid "Distribution %s successfully registered"
msgstr ""

#: mgradm/cmd/distro/cp.go:74
#, javascript-format
msgid "Copying distribution %s\n"
msgstr ""

#: mgradm/cmd/distro/cp.go:76
#, javascript-format
msgid "source %s does not exists"
msgstr ""

#: mgradm/cmd/distro/cp.go:81
#, javascript-format
msgid "distribution already exists: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:102
msgid "unable to mount ISO image. Mount manually and try again"
msgstr ""

#: mgradm/cmd/distro/cp.go:107 mgradm/shared/podman/podman.go:102
#: mgradm/shared/podman/podman.go:105 mgradm/shared/podman/podman.go:108
#: mgradm/shared/podman/podman.go:116
#, javascript-format
msgid "cannot copy %s: %s"
msgstr ""

#: mgradm/cmd/distro/cp.go:110
msgid "Distribution has been copied"
msgstr ""

#: mgradm/cmd/distro/detect.go:84
msgid "unknown distribution, auto-registration is not possible"
msgstr ""

#: mgradm/cmd/distro/distro.go:28
msgid "Distributions management"
msgstr ""

#: mgradm/cmd/distro/distro.go:29
msgid "Tools for autoinstallation distributions management"
msgstr ""

#: mgradm/cmd/distro/distro.go:35
msgid "Copy distribution files from ISO image to the container"
msgstr ""

#: mgradm/cmd/distro/distro.go:36
msgid ""
"Takes a path to an ISO file or the directory of a mounted ISO image and copies it into the container.\n"
"\n"
"Distribution name specifies the destination directory under /srv/www/distributions.\n"
"\n"
"Optional channel label specify which parent channel to associate with the distribution.\n"
"Only when API informations are provided and auto registration is done."
msgstr ""

#: mgradm/cmd/hub/register/register.go:31
msgid "Register"
msgstr ""

#: mgradm/cmd/hub/register/register.go:32
msgid "Register this peripheral server to Hub API"
msgstr ""

#: mgradm/cmd/hub/register/register.go:79
#, javascript-format
msgid "invalid line format: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:93
#, javascript-format
msgid "mandatory entry missing in config: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:96
#, javascript-format
msgid "Hub API server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:99
#, javascript-format
msgid "failed to connect to the Hub server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:107
#: mgradm/cmd/hub/register/register.go:110
#, javascript-format
msgid "failed to register this peripheral server: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:124
#: mgradm/cmd/hub/register/register.go:128
#, javascript-format
msgid "failed to update peripheral server info: %s"
msgstr ""

#: mgradm/cmd/hub/register/register.go:130
#, javascript-format
msgid "Registered peripheral server: %s, ID: %d"
msgstr ""

#: mgradm/cmd/hub/hub.go:18
msgid "Hub management"
msgstr ""

#: mgradm/cmd/hub/hub.go:19
msgid "Tools and utilities for Hub management"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:26
msgid "Inspect"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:27
msgid "Extract information from image and deployment"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:37
msgid "Image URL. Leave it empty to analyze the current deployment"
msgstr ""

#: mgradm/cmd/inspect/inspect.go:38
msgid "Image Tag. Leave it empty to analyze the current deployment"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:35 mgradm/cmd/inspect/podman.go:31
#, javascript-format
msgid "failed to determine image: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:44 mgradm/cmd/inspect/podman.go:40
#, javascript-format
msgid "failed to find the image of the currently running server container: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:50 mgradm/cmd/inspect/podman.go:45
#, javascript-format
msgid "inspect command failed: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:55 mgradm/cmd/inspect/podman.go:49
#, javascript-format
msgid "cannot print inspect result: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:68
#: mgradm/cmd/install/kubernetes/utils.go:32
#: mgradm/cmd/migrate/kubernetes/utils.go:37
#: mgradm/cmd/upgrade/kubernetes/utils.go:34
#, javascript-format
msgid "install %s before running this command"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:75 mgradm/cmd/inspect/podman.go:63
#: mgradm/cmd/support/config/extractor.go:32
#: mgradm/cmd/upgrade/kubernetes/utils.go:68 mgradm/shared/kubernetes/k3s.go:36
#: mgradm/shared/kubernetes/k3s.go:104 mgradm/shared/kubernetes/k3s.go:149
#: mgradm/shared/kubernetes/certificates.go:27
#: mgradm/shared/kubernetes/certificates.go:65
#: mgradm/shared/podman/podman.go:221 mgradm/shared/podman/podman.go:281
#: mgradm/shared/podman/podman.go:306 mgradm/shared/utils/exec.go:158
#: mgradm/shared/utils/exec.go:242
#, javascript-format
msgid "failed to create temporary directory: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:88 mgradm/shared/kubernetes/k3s.go:64
#: mgradm/shared/kubernetes/k3s.go:113 mgradm/shared/kubernetes/k3s.go:159
#, javascript-format
msgid "cannot delete %s: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:94
#: mgradm/cmd/migrate/kubernetes/utils.go:85
#: mgradm/cmd/upgrade/kubernetes/utils.go:75
#, javascript-format
msgid "cannot find node running uyuni: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:122
#, javascript-format
msgid "cannot run inspect pod: %s"
msgstr ""

#: mgradm/cmd/inspect/kubernetes.go:127 mgradm/cmd/inspect/podman.go:100
#, javascript-format
msgid "cannot inspect data: %s"
msgstr ""

#: mgradm/cmd/inspect/podman.go:68 mgradm/cmd/install/podman/utils.go:57
#: mgradm/shared/podman/podman.go:185 mgradm/shared/podman/podman.go:245
#, javascript-format
msgid "cannot inspect host values: %s"
msgstr ""

#: mgradm/cmd/install/kubernetes/kubernetes.go:27
msgid "Install a new server on a kubernetes cluster"
msgstr ""

#: mgradm/cmd/install/kubernetes/kubernetes.go:28
msgid ""
"Install a new server on a kubernetes cluster\n"
"\n"
"The install command assumes the following:\n"
"  * kubectl and helm are installed locally\n"
"  * a working kubectl configuration should be set to connect to the cluster to deploy to\n"
"\n"
"The helm values file will be overridden with the values from the command parameters or configuration.\n"
"\n"
"NOTE: installing on a remote cluster is not supported yet!\n"
msgstr ""

#: mgradm/cmd/install/kubernetes/utils.go:61
#: mgradm/cmd/migrate/kubernetes/utils.go:180
#, javascript-format
msgid "cannot deploy certificate: %s"
msgstr ""

#: mgradm/cmd/install/kubernetes/utils.go:67
#, javascript-format
msgid "cannot deploy uyuni: %s"
msgstr ""

#: mgradm/cmd/install/kubernetes/utils.go:83
#, javascript-format
msgid "error storing the SSL CA certificate in database: %s"
msgstr ""

#: mgradm/cmd/install/podman/podman.go:25
msgid "Install a new server on podman"
msgstr ""

#: mgradm/cmd/install/podman/podman.go:26
msgid ""
"Install a new server on podman\n"
"\n"
"The install podman command assumes podman is installed locally.\n"
"\n"
"NOTE: installing on a remote podman is not supported yet!\n"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:36 mgradm/cmd/upgrade/podman/utils.go:70
msgid "Waiting for the server to start..."
msgstr ""

#: mgradm/cmd/install/podman/utils.go:38
#, javascript-format
msgid "cannot enable service: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:52 mgradm/cmd/migrate/podman/utils.go:25
msgid "install podman before running this command"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:64
#, javascript-format
msgid "Setting up the server with the FQDN '%s'"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:68
#: mgradm/cmd/migrate/kubernetes/utils.go:44
#: mgradm/cmd/upgrade/kubernetes/utils.go:41
#: mgradm/cmd/upgrade/podman/utils.go:27 mgradm/shared/kubernetes/install.go:38
#: mgradm/shared/kubernetes/k3s.go:47 mgradm/shared/kubernetes/k3s.go:52
#: mgradm/shared/podman/podman.go:234 mgradm/shared/podman/podman.go:239
#, javascript-format
msgid "failed to compute image URL: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:88
#, javascript-format
msgid "cannot wait for system start: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:108
msgid "Run setup command in the container"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:116
#, javascript-format
msgid "cannot update SSL certificate: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:121 mgradm/cmd/migrate/podman/utils.go:69
#, javascript-format
msgid "cannot enable podman socket: %s"
msgstr ""

#: mgradm/cmd/install/podman/utils.go:132
#, javascript-format
msgid "failed to compute server FQDN: %s"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:71
msgid "Can only contain letters, digits . _ and -"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:79
msgid "Not a valid email address"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:119
msgid "Time zone to set on the server. Defaults to the host timezone"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:120
msgid "Administrator e-mail"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:121
msgid "E-Mail sending the notifications"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:122
msgid "Path to mirrored packages mounted on the host"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:123
msgid "InterServerSync v1 parent FQDN"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:124
msgid "Database user"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:125
msgid "Database password. Randomly generated by default"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:126
msgid "Database name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:127
msgid "Database host"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:128
msgid "Database port"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:129
msgid "Database protocol"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:130
msgid "External database admin user name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:131
msgid "External database admin password"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:132
msgid "External database provider. Possible values 'aws'"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:134
msgid "Enable TFTP"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:135
msgid "Report database name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:136
msgid "Report database host"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:137
msgid "Report database port"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:138
msgid "Report Database username"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:139
msgid "Report database password. Randomly generated by default"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:142
msgid "SSL certificate cnames separated by commas"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:143
msgid "SSL certificate country"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:144
msgid "SSL certificate state"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:145
msgid "SSL certificate city"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:146
msgid "SSL certificate organization"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:147
msgid "SSL certificate organization unit"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:148
msgid "Password for the CA key to generate"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:149
msgid "SSL certificate E-Mail"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:152
msgid "Intermediate CA certificate path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:153
msgid "Root CA certificate path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:154
msgid "Server certificate path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:155
msgid "Server key path"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:157
msgid "SUSE Customer Center username"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:158
msgid "SUSE Customer Center password"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:160
msgid "Enable tomcat and taskomatic remote debugging"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:163
msgid "Administrator user name"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:164
msgid "Administrator password"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:165
msgid "First name of the administrator"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:166
msgid "Last name of the administrator"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:167
msgid "Administrator's email"
msgstr ""

#: mgradm/cmd/install/shared/flags.go:168
msgid "First organization name"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:32
#, javascript-format
msgid "cannot copy /tmp/setup.sh: %s"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:37
#, javascript-format
msgid "error running the setup script: %s"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:53
msgid "Server set up"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:115
msgid "Failed to create temporary directory"
msgstr ""

#: mgradm/cmd/install/shared/shared.go:125
msgid "Failed to generate setup script"
msgstr ""

#: mgradm/cmd/install/install.go:19 mgradm/cmd/install/install.go:20
msgid "Install a new server"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:28
msgid "Migrate a remote server to containers running on a kubernetes cluster"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:29
msgid ""
"Migrate a remote server to containers running on a kubernetes cluster\n"
"\n"
"This migration command assumes a few things:\n"
"  * the SSH configuration for the source server is complete, including user and\n"
"    all needed options to connect to the machine,\n"
"  * an SSH agent is started and the key to use to connect to the server is added to it,\n"
"  * kubectl and helm are installed locally,\n"
"  * a working kubectl configuration should be set to connect to the cluster to deploy to\n"
"\n"
"When migrating a server with a automatically generated SSL Root CA certificate, the private key\n"
"password will be required to convert it to RSA in a kubernetes secret.\n"
"This is not needed if the source server does not have a generated SSL CA certificate.\n"
"\n"
"NOTE: migrating to a remote cluster is not supported yet!\n"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/kubernetes.go:53
msgid "SSL CA generated private key password"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:56 mgradm/shared/utils/exec.go:169
#, javascript-format
msgid "failed to generate migration script: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:78
#, javascript-format
msgid "cannot run deploy: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:89
#, javascript-format
msgid "cannot run migration: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:94
#, javascript-format
msgid "cannot read data from container: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:100
#: mgradm/cmd/migrate/kubernetes/utils.go:133
#, javascript-format
msgid "cannot set replicas to 0: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:112
#, javascript-format
msgid "cannot setup SSL: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:124
#, javascript-format
msgid "cannot upgrade helm chart to image %s using new SSL certificate: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:128
#, javascript-format
msgid "cannot wait for deployment of %s: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:138
#: mgradm/cmd/migrate/kubernetes/utils.go:144
#: mgradm/cmd/migrate/podman/utils.go:44
#: mgradm/cmd/upgrade/kubernetes/utils.go:93
#: mgradm/cmd/upgrade/kubernetes/utils.go:103
#: mgradm/cmd/upgrade/podman/utils.go:50 mgradm/cmd/upgrade/podman/utils.go:60
#, javascript-format
msgid "cannot run PostgreSQL version upgrade script: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:148
#: mgradm/cmd/migrate/podman/utils.go:54
#: mgradm/cmd/upgrade/kubernetes/utils.go:107
#: mgradm/cmd/upgrade/podman/utils.go:64
#, javascript-format
msgid "cannot run post upgrade script: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:153
#: mgradm/cmd/upgrade/kubernetes/utils.go:112
#, javascript-format
msgid "cannot upgrade to image %s: %s"
msgstr ""

#: mgradm/cmd/migrate/kubernetes/utils.go:171
#, javascript-format
msgid "failed to strip text part from CA certificate: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/podman.go:25
msgid "Migrate a remote server to containers running on podman"
msgstr ""

#: mgradm/cmd/migrate/podman/podman.go:26
msgid ""
"Migrate a remote server to containers running on podman\n"
"\n"
"This migration command assumes a few things:\n"
"  * the SSH configuration for the source server is complete, including user and\n"
"    all needed options to connect to the machine,\n"
"  * an SSH agent is started and the key to use to connect to the server is added to it,\n"
"  * podman is installed locally\n"
"\n"
"NOTE: migrating to a remote podman is not supported yet!\n"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:30
#, javascript-format
msgid "cannot compute image: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:39
#, javascript-format
msgid "cannot run migration script: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:50
#, javascript-format
msgid "cannot run PostgreSQL finalize script: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:58
#, javascript-format
msgid "cannot generate systemd service file: %s"
msgstr ""

#: mgradm/cmd/migrate/podman/utils.go:66
msgid "Server migrated"
msgstr ""

#: mgradm/cmd/migrate/shared/shared.go:20
msgid "SSH_AUTH_SOCK is not defined, start an SSH agent and try again"
msgstr ""

#: mgradm/cmd/migrate/shared/shared.go:30
msgid "Failed to find home directory to look for SSH config"
msgstr ""

#: mgradm/cmd/migrate/migrate.go:19 mgradm/cmd/migrate/migrate.go:20
msgid "Migrate a remote server to containers"
msgstr ""

#: mgradm/cmd/restart/nokubernetes.go:23 mgradm/cmd/start/nokubernetes.go:23
#: mgradm/cmd/status/nokubernetes.go:23 mgradm/cmd/stop/nokubernetes.go:23
msgid "built without kubernetes support"
msgstr ""

#: mgradm/cmd/restart/restart.go:23 mgradm/cmd/restart/restart.go:24
msgid "Restart the server"
msgstr ""

#: mgradm/cmd/start/start.go:23 mgradm/cmd/start/start.go:24
msgid "Start the server"
msgstr ""

#: mgradm/cmd/status/podman.go:29
#, javascript-format
msgid "failed to get status of the server service: %s"
msgstr ""

#: mgradm/cmd/status/podman.go:37 mgradm/cmd/status/kubernetes.go:61
#, javascript-format
msgid "failed to run spacewalk-service status: %s"
msgstr ""

#: mgradm/cmd/status/status.go:24 mgradm/cmd/status/status.go:25
msgid "Get the server status"
msgstr ""

#: mgradm/cmd/status/status.go:46
msgid "no installed server detected"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:32
#, javascript-format
msgid "failed to discover the cluster type: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:37
msgid "no uyuni helm release installed on the cluster"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:42
#, javascript-format
msgid "failed to find the uyuni deployment namespace: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:48
#, javascript-format
msgid "failed to get deployment status: %s"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:51
#, javascript-format
msgid "Some replicas are not ready: %d / %d"
msgstr ""

#: mgradm/cmd/status/kubernetes.go:55
msgid "the pod is not running"
msgstr ""

#: mgradm/cmd/stop/stop.go:23 mgradm/cmd/stop/stop.go:24
msgid "Stop the server"
msgstr ""

#: mgradm/cmd/support/config/config.go:23
msgid "Extract configuration and logs"
msgstr ""

#: mgradm/cmd/support/config/config.go:24
msgid ""
"Extract the host or cluster configuration and logs as well as those from \n"
"the containers for support to help debugging."
msgstr ""

#: mgradm/cmd/support/config/config.go:32
msgid "path where to extract the data"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:40
msgid "Running supportconfig in the container"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:43
msgid "failed to run supportconfig"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:47
msgid "failed to find container supportconfig tarball from command output"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:54
#, javascript-format
msgid "cannot copy tarball: %s"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:60
#, javascript-format
msgid "failed to remove %s%s file in the container: %s"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:69
#, javascript-format
msgid "failed to run supportconfig on the host: %s"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:79
msgid "failed to find host supportconfig tarball from command output"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:82
msgid "supportconfig is not available on the host, skipping it"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:88
msgid "Preparing the tarball"
msgstr ""

#: mgradm/cmd/support/config/extractor.go:96
#, javascript-format
msgid "failed to add %s to tarball: %s"
msgstr ""

#: mgradm/cmd/support/support.go:18 mgradm/cmd/support/support.go:19
msgid "Commands for support operations"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:50 mgradm/cmd/uninstall/kubernetes.go:51
#, javascript-format
msgid "Would run %s"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:53 mgradm/cmd/uninstall/kubernetes.go:58
#, javascript-format
msgid "Running %s"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:55
msgid "Failed deleting config map"
msgstr ""

#: mgradm/cmd/uninstall/kubernetes.go:66
msgid "Failed deleting secret"
msgstr ""

#: mgradm/cmd/uninstall/podman.go:38
#, javascript-format
msgid "cannot delete volume %s: %s"
msgstr ""

#: mgradm/cmd/uninstall/podman.go:41
msgid "All volumes removed"
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:26
msgid "Uninstall a server"
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:27
msgid ""
"Uninstall a server and optionally the corresponding volumes.\n"
"By default it will only print what would be done, use --force to actually remove."
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:35
msgid "Actually remove the server"
msgstr ""

#: mgradm/cmd/uninstall/uninstall.go:36
msgid "Also remove the volumes"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/kubernetes.go:27
#: mgradm/cmd/upgrade/kubernetes/kubernetes.go:28
msgid "Upgrade a local server on kubernetes"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:46
#, javascript-format
msgid "cannot inspect kubernetes values: %s"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:56
msgid "inspect function did non return fqdn value"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:80
#, javascript-format
msgid "cannot set replica to 0: %s"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:90 mgradm/shared/kubernetes/k3s.go:39
#: mgradm/shared/podman/podman.go:216
#, javascript-format
msgid "Previous PostgreSQL is %s, new one is %s. Performing a DB version upgrade..."
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:96
#: mgradm/cmd/upgrade/podman/utils.go:53
#, javascript-format
msgid "Upgrading to %s without changing PostgreSQL version"
msgstr ""

#: mgradm/cmd/upgrade/kubernetes/utils.go:98
#: mgradm/cmd/upgrade/podman/utils.go:55
#, javascript-format
msgid "trying to downgrade PostgreSQL from %s to %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/utils.go:32
#, javascript-format
msgid "cannot inspect podman values: %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/utils.go:42
#, javascript-format
msgid "cannot stop service %s"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:27
msgid "Upgrade a local server on podman"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:36
msgid "list available tag for an image"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:43
msgid "Failed to unmarshall configuration"
msgstr ""

#: mgradm/cmd/upgrade/podman/podman.go:46
#, javascript-format
msgid "Available Tags for image: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:42
msgid "cannot find neither /etc/uyuni-release nor /etc/susemanagere-release"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:53
#, javascript-format
msgid "cannot check server release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:59
#, javascript-format
msgid "currently SUSE Manager %s is installed, instead the image is Uyuni. Upgrade is not supported"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:63
#, javascript-format
msgid "currently Uyuni %s is installed, instead the image is SUSE Manager. Upgrade is not supported"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:70
#, javascript-format
msgid "failed to read current uyuni release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:74
#: mgradm/cmd/upgrade/shared/shared.go:88
#, javascript-format
msgid "cannot fetch release from image %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:78
#: mgradm/cmd/upgrade/shared/shared.go:92
#, javascript-format
msgid "cannot downgrade from version %s to %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:84
#, javascript-format
msgid "failed to read current susemanager release: %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:97
#, javascript-format
msgid "cannot fetch postgresql version from %s"
msgstr ""

#: mgradm/cmd/upgrade/shared/shared.go:101
msgid "posgresql is not installed in the current deployment"
msgstr ""

#: mgradm/cmd/upgrade/upgrade.go:18 mgradm/cmd/upgrade/upgrade.go:19
msgid "Upgrade local server"
msgstr ""

#: mgradm/cmd/cmd.go:39
msgid "Uyuni administration tool"
msgstr ""

#: mgradm/cmd/cmd.go:40
msgid "Tool to help administering Uyuni servers in containers"
msgstr ""

#: mgradm/cmd/cmd.go:53
#, javascript-format
msgid "Welcome to %s"
msgstr ""

#: mgradm/cmd/cmd.go:54
#, javascript-format
msgid "Executing command: %s"
msgstr ""

#: mgradm/cmd/cmd.go:58
msgid "configuration file path"
msgstr ""

#: mgradm/cmd/cmd.go:59
msgid "application log level"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:46
msgid "Run the import"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:55
#, javascript-format
msgid "failed to create folder %s: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:58
#, javascript-format
msgid "failed to create keyring %s: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:71
#, javascript-format
msgid "failed to create temporary directory %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:78
#, javascript-format
msgid "failed to parse %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:85
#, javascript-format
msgid "failed to download %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:90
#, javascript-format
msgid "failed to show key %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:97
#, javascript-format
msgid "failed to cp %s to %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:107 mgradm/cmd/gpg/add/gpg.go:114
#, javascript-format
msgid "Running: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:109
#, javascript-format
msgid "failed to run import key: %s"
msgstr ""

#: mgradm/cmd/gpg/add/gpg.go:116
#, javascript-format
msgid "failed to restart uyuni-update-config: %s"
msgstr ""

#: mgradm/shared/kubernetes/install.go:44
#, javascript-format
msgid "cannot upgrade: %s"
msgstr ""

#: mgradm/shared/kubernetes/install.go:50
#: mgradm/shared/kubernetes/certificates.go:139
#, javascript-format
msgid "cannot deploy: %s"
msgstr ""

#: mgradm/shared/kubernetes/install.go:65
#, javascript-format
msgid "cannot install cert-manager and self-sign issuer: %s"
msgstr ""

#: mgradm/shared/kubernetes/install.go:90
msgid "Installing Uyuni"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:56 mgradm/shared/podman/podman.go:260
#, javascript-format
msgid "Using migration image %s"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:59
#, javascript-format
msgid "cannot generate PostgreSQL database version upgrade script: %s"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:93 mgradm/shared/kubernetes/k3s.go:139
#: mgradm/shared/kubernetes/k3s.go:186
#, javascript-format
msgid "error running container %s: %s"
msgstr ""

#: mgradm/shared/kubernetes/k3s.go:109 mgradm/shared/kubernetes/k3s.go:154
#, javascript-format
msgid "cannot generate PostgreSQL finalization script %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:32
msgid "Creating SSL server certificate secret"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:42
msgid "Failed to generate uyuni-crt secret definition"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:46
msgid "Failed to create uyuni-crt TLS secret"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:59
#, javascript-format
msgid "cannot install cert manager: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:62
msgid "Creating SSL certificate issuer"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:86
#, javascript-format
msgid "failed to generate issuer definition: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:91
msgid "Failed to create issuer"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:103
msgid "Issuer didn't turn ready after 60s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:109
msgid "Installing cert-manager"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:132
#, javascript-format
msgid "cannot run helm upgrade: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:149
msgid "Extracting CA certificate to a configmap"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:152
#, javascript-format
msgid "CA cert: %s"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:154
msgid "uyuni-ca configmap already existing, skipping extraction"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:160
msgid "Failed to get uyuni-ca certificate"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:165
msgid "Failed to base64 decode CA certificate"
msgstr ""

#: mgradm/shared/kubernetes/certificates.go:174
msgid "Failed to create uyuni-ca config map from certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:53
#, javascript-format
msgid "cannot setup network: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:56
msgid "Enabling system service"
msgstr ""

#: mgradm/shared/podman/podman.go:66
#, javascript-format
msgid "failed to generate systemd service unit file: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:70
#, javascript-format
msgid "cannot generate systemd conf file: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:82
msgid "failed to create temporary folder on container to copy certificates to"
msgstr ""

#: mgradm/shared/podman/podman.go:122
msgid "failed to update SSL certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:127
msgid "failed to remove copied certificate files in the container"
msgstr ""

#: mgradm/shared/podman/podman.go:133
msgid "failed to remove now useless ssl-build folder in the container"
msgstr ""

#: mgradm/shared/podman/podman.go:138
msgid "Restarting services after updating the certificate"
msgstr ""

#: mgradm/shared/podman/podman.go:154
#, javascript-format
msgid "failed to run %s container: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:164
#, javascript-format
msgid "cannot generate migration script: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:200 mgradm/shared/utils/exec.go:146
msgid "Migrating server"
msgstr ""

#: mgradm/shared/podman/podman.go:203
#, javascript-format
msgid "cannot run uyuni migration container: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:208
#, javascript-format
msgid "cannot read extracted data: %s"
msgstr ""

#: mgradm/shared/podman/podman.go:264
#, javascript-format
msgid "cannot generate PostgreSQL database version upgrade script %s"
msgstr ""

#: mgradm/shared/podman/podman.go:291 mgradm/shared/podman/podman.go:315
#, javascript-format
msgid "cannot generate PostgreSQL finalization script: %s"
msgstr ""

#: mgradm/shared/ssl/ssl.go:47
msgid "Failed to find a non-CA certificate"
msgstr ""

#: mgradm/shared/ssl/ssl.go:86
msgid "expected to find a certificate, got none"
msgstr ""

#: mgradm/shared/ssl/ssl.go:92
#, javascript-format
msgid "Failed to read certificate file %s"
msgstr ""

#: mgradm/shared/ssl/ssl.go:128
msgid "Failed to extract data from certificate"
msgstr ""

#: mgradm/shared/ssl/ssl.go:149
#, javascript-format
msgid "Failed to parse start date: %s\n"
msgstr ""

#: mgradm/shared/ssl/ssl.go:155
#, javascript-format
msgid "Failed to parse end date: %s\n"
msgstr ""

#: mgradm/shared/ssl/ssl.go:199
msgid "No CA found"
msgstr ""

#: mgradm/shared/ssl/ssl.go:206
msgid "No CA found for server certificate"
msgstr ""

#: mgradm/shared/ssl/ssl.go:215
#, javascript-format
msgid "Missing CA with subject hash %s"
msgstr ""

#: mgradm/shared/ssl/ssl.go:236
msgid "server certificate is required"
msgstr ""

#: mgradm/shared/ssl/ssl.go:237
msgid "server key is required"
msgstr ""

#: mgradm/shared/ssl/ssl.go:249
#, javascript-format
msgid "%s file is not accessible"
msgstr ""

#: mgradm/shared/ssl/ssl.go:257
msgid "Source server SSL CA private key password"
msgstr ""

#: mgradm/shared/ssl/ssl.go:264
msgid "Failed to convert CA private key to RSA"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:49
msgid "Server certificate, key and root CA need to be all provided"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:57
msgid "Kubernetes namespace where to install uyuni"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:58
msgid "URL to the uyuni helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:59
msgid "Version of the uyuni helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:60
msgid "Path to a values YAML file to use for Uyuni helm install"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:61
msgid "Kubernetes namespace where to install cert-manager"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:62
msgid "URL to the cert-manager helm chart. To be used for offline installations"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:63
msgid "Version of the cert-manager helm chart"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:64
msgid "Path to a values YAML file to use for cert-manager helm install"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:69 mgradm/shared/utils/cmd_utils.go:77
msgid "Image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:70 mgradm/shared/utils/cmd_utils.go:78
msgid "Tag Image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:80
msgid "set whether to pull the images or not during upgrade. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:85
msgid "Migration image"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:86
msgid "Migration image tag"
msgstr ""

#: mgradm/shared/utils/cmd_utils.go:88
msgid "set whether to pull the migration images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: mgradm/shared/utils/exec.go:51
#, javascript-format
msgid "exec command failed: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:85 mgradm/shared/utils/exec.go:103
#: mgradm/shared/utils/exec.go:117
#, javascript-format
msgid "failed to generate %s"
msgstr ""

#: mgradm/shared/utils/exec.go:126
msgid "failed to read data extracted from source host"
msgstr ""

#: mgradm/shared/utils/exec.go:130 mgradm/shared/utils/exec.go:219
#, javascript-format
msgid "cannot read config: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:133
msgid "cannot retrieve timezone"
msgstr ""

#: mgradm/shared/utils/exec.go:136
msgid "cannot retrieve source PostgreSQL version"
msgstr ""

#: mgradm/shared/utils/exec.go:139
msgid "cannot retrieve image PostgreSQL version"
msgstr ""

#: mgradm/shared/utils/exec.go:149
#, javascript-format
msgid "error running the migration script: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:196
#, javascript-format
msgid "Image is: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:212
#, javascript-format
msgid "cannot parse file %s: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:250
#, javascript-format
msgid "failed to run inspect script in host system: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:255
#, javascript-format
msgid "cannot inspect host data: %s"
msgstr ""

#: mgradm/shared/utils/exec.go:270 mgradm/shared/utils/exec.go:284
#, javascript-format
msgid "failed to generate inspect script: %s"
msgstr ""
07070100000026000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/locale/mgrctl07070100000027000081B4000000000000000000000001662A7528000017BE000000000000000000000000000000000000002000000000uyuni-tools/locale/mgrctl/fr.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: 2024-04-08 14:10+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4.2\n"

#: mgrctl/cmd/api/api.go:25
msgid "JSON over HTTP API helper tool"
msgstr "Utilitaire pour l'API JSON sur HTTP"

#: mgrctl/cmd/api/api.go:30
msgid "Call API GET request"
msgstr "Appeler une requête API GET"

#: mgrctl/cmd/api/api.go:31
msgid "Takes an API path and optional parameters and then issues GET request with them. If user and password are provided, calls login before API call"
msgstr "Effectue une requête GET avec un chemin d'API et des paramètres optionnels. Appelle login avant la requête de l'API si l'utilisateur et le mot de passe sont fournis"

#: mgrctl/cmd/api/api.go:39
msgid "Call API POST request"
msgstr "Appeler une requête API POST"

#: mgrctl/cmd/api/api.go:40
msgid "Takes an API path and parameters and then issues POST request with them. User and password are mandatory. Parameters can be either JSON encoded string or one or more key=value pairs."
msgstr "Effectue une requête POST avec un chemin d'API et des paramètres. L'utilisateur et le mot de passe sont obligatoires. Les paramètres peuvent être soit une chaîne de caractères encodée en JSON ou une ou plusieurs paire clé=valeur."

#: mgrctl/cmd/api/get.go:25 mgrctl/cmd/api/post.go:25
#, javascript-format
msgid "unable to login to the server: %s"
msgstr "impossible de se connecter au serveur: %s"

#: mgrctl/cmd/api/get.go:32 mgrctl/cmd/api/post.go:48
#, javascript-format
msgid "error in query %s: %s"
msgstr "erreur dans la requête %s: %s"

#: mgrctl/cmd/cp/cp.go:31
msgid "Copy files to and from the containers"
msgstr "Copie des fichiers vers et depuis les conteneurs"

#: mgrctl/cmd/cp/cp.go:32
msgid ""
"Takes a source and destination parameters.\n"
"\tOne of them can be prefixed with 'server:' to indicate the path is within the server pod."
msgstr ""
"Prend une source et une destination comme paramètres.\n"
"\tL'un d'eux peut être préfixé par 'server:' pour indiquer que le chemin est dans le conteneur du serveur."

#: mgrctl/cmd/cp/cp.go:41
msgid "failed to unmarshall configuration"
msgstr "impossible d'analyser la configuration"

#: mgrctl/cmd/cp/cp.go:47
msgid "User or UID to set on the destination file"
msgstr "Utilisateur ou UID à définir sur le fichier de destination"

#: mgrctl/cmd/cp/cp.go:48
msgid "Group or GID to set on the destination file"
msgstr "Groupe ou GID à définir sur le fichier de destination"

#: mgrctl/cmd/exec/exec.go:37
msgid "Execute commands inside the uyuni containers using 'sh -c'"
msgstr "Exécute des commandes à l'intérieur des conteneurs uyuni avec 'sh -c'"

#: mgrctl/cmd/exec/exec.go:43
msgid "environment variables to pass to the command, separated by commas"
msgstr "variables d'environnement à passer à la commade, séparées par des virgules"

#: mgrctl/cmd/exec/exec.go:44
msgid "Pass stdin to the container"
msgstr "Passer l'entrée standard au conteneur"

#: mgrctl/cmd/exec/exec.go:45
msgid "Stdin is a TTY"
msgstr "L'entrée standard est un TTY"

#: mgrctl/cmd/exec/exec.go:98
msgid "Command failed"
msgstr "La commande a échoué"

#: mgrctl/cmd/exec/exec.go:102
msgid "Command returned with exit code 0"
msgstr "La commande a été exécutée avec un code de retour de 0"

#: mgrctl/cmd/exec/exec.go:116
#, javascript-format
msgid "cannot write: %s"
msgstr "impossible d'écrire: %s"

#: mgrctl/cmd/exec/exec.go:131
#, javascript-format
msgid "Running: %s %s"
msgstr "Exécution: %s %s"

#: mgrctl/cmd/org/createFirst.go:28
msgid "Create the first user and organization"
msgstr "Créer les premiers utilisateurs et organisations"

#: mgrctl/cmd/org/createFirst.go:35
msgid "Administrator user name"
msgstr "Nom d'utilisateur de l'administrateur"

#: mgrctl/cmd/org/createFirst.go:36
msgid "Administrator password"
msgstr "Mot de passe de l'administrateur"

#: mgrctl/cmd/org/createFirst.go:37
msgid "The first name of the administrator"
msgstr "Le prénom de l'administrateur"

#: mgrctl/cmd/org/createFirst.go:38
msgid "The last name of the administrator"
msgstr "Le nom de l'administrateur"

#: mgrctl/cmd/org/createFirst.go:39
msgid "The administrator's email"
msgstr "L'adresse email de l'administrateur"

#: mgrctl/cmd/org/createFirst.go:40
msgid "The first organization name"
msgstr "Le nom de la première organisation"

#: mgrctl/cmd/org/createFirst.go:51
#, javascript-format
msgid "Organization %s created with id %d"
msgstr "Organisation %s créée avec l'id %d"

#: mgrctl/cmd/org/org.go:18
msgid "Organization-related commands"
msgstr "Commandes liées à l'organisation"

#: mgrctl/cmd/term/term.go:21
msgid "Run a terminal inside the server container"
msgstr "Exécuter un terminal dans le conteneur du serveur"

#: mgrctl/cmd/cmd.go:30
msgid "Uyuni control tool"
msgstr "Outil de contrôle d'Uyuni"

#: mgrctl/cmd/cmd.go:31
msgid "Tool to help managing Uyuni servers mainly through their API"
msgstr "Outil pour aider à gérer des serveurs Uyuni, principalement via leur API"

#: mgrctl/cmd/cmd.go:38
msgid "configuration file path"
msgstr "chemin vers le fichier de configuration"

#: mgrctl/cmd/cmd.go:39
msgid "application log level"
msgstr "niveau de verbosité de l'application"

#: mgrctl/cmd/cmd.go:47
#, javascript-format
msgid "Welcome to %s"
msgstr "Bienvenue à %s"

#: mgrctl/cmd/cmd.go:48
#, javascript-format
msgid "Executing command: %s"
msgstr "Exécution de la commande: %s"

#: mgrctl/cmd/cmd.go:54
msgid "Failed to create api command"
msgstr "Echec lors de la création de la commande api"

#: mgrctl/cmd/cmd.go:63
msgid "Failed to create org command"
msgstr "Echec lors de la création de la commande org"
07070100000028000081B4000000000000000000000001662A752800001022000000000000000000000000000000000000002000000000uyuni-tools/locale/mgrctl/it.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: mgrctl/cmd/api/api.go:25
msgid "JSON over HTTP API helper tool"
msgstr ""

#: mgrctl/cmd/api/api.go:30
msgid "Call API GET request"
msgstr ""

#: mgrctl/cmd/api/api.go:31
msgid "Takes an API path and optional parameters and then issues GET request with them. If user and password are provided, calls login before API call"
msgstr ""

#: mgrctl/cmd/api/api.go:39
msgid "Call API POST request"
msgstr ""

#: mgrctl/cmd/api/api.go:40
msgid "Takes an API path and parameters and then issues POST request with them. User and password are mandatory. Parameters can be either JSON encoded string or one or more key=value pairs."
msgstr ""

#: mgrctl/cmd/api/get.go:25 mgrctl/cmd/api/post.go:25
#, javascript-format
msgid "unable to login to the server: %s"
msgstr ""

#: mgrctl/cmd/api/get.go:32 mgrctl/cmd/api/post.go:48
#, javascript-format
msgid "error in query %s: %s"
msgstr ""

#: mgrctl/cmd/cp/cp.go:31
msgid "Copy files to and from the containers"
msgstr ""

#: mgrctl/cmd/cp/cp.go:32
msgid ""
"Takes a source and destination parameters.\n"
"\tOne of them can be prefixed with 'server:' to indicate the path is within the server pod."
msgstr ""

#: mgrctl/cmd/cp/cp.go:41
msgid "failed to unmarshall configuration"
msgstr ""

#: mgrctl/cmd/cp/cp.go:47
msgid "User or UID to set on the destination file"
msgstr ""

#: mgrctl/cmd/cp/cp.go:48
msgid "Group or GID to set on the destination file"
msgstr ""

#: mgrctl/cmd/exec/exec.go:37
msgid "Execute commands inside the uyuni containers using 'sh -c'"
msgstr ""

#: mgrctl/cmd/exec/exec.go:43
msgid "environment variables to pass to the command, separated by commas"
msgstr ""

#: mgrctl/cmd/exec/exec.go:44
msgid "Pass stdin to the container"
msgstr ""

#: mgrctl/cmd/exec/exec.go:45
msgid "Stdin is a TTY"
msgstr ""

#: mgrctl/cmd/exec/exec.go:98
msgid "Command failed"
msgstr ""

#: mgrctl/cmd/exec/exec.go:102
msgid "Command returned with exit code 0"
msgstr ""

#: mgrctl/cmd/exec/exec.go:116
#, javascript-format
msgid "cannot write: %s"
msgstr ""

#: mgrctl/cmd/exec/exec.go:131
#, javascript-format
msgid "Running: %s %s"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:28
msgid "Create the first user and organization"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:35
msgid "Administrator user name"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:36
msgid "Administrator password"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:37
msgid "The first name of the administrator"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:38
msgid "The last name of the administrator"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:39
msgid "The administrator's email"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:40
msgid "The first organization name"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:51
#, javascript-format
msgid "Organization %s created with id %d"
msgstr ""

#: mgrctl/cmd/org/org.go:18
msgid "Organization-related commands"
msgstr ""

#: mgrctl/cmd/term/term.go:21
msgid "Run a terminal inside the server container"
msgstr ""

#: mgrctl/cmd/cmd.go:30
msgid "Uyuni control tool"
msgstr ""

#: mgrctl/cmd/cmd.go:31
msgid "Tool to help managing Uyuni servers mainly through their API"
msgstr ""

#: mgrctl/cmd/cmd.go:38
msgid "configuration file path"
msgstr ""

#: mgrctl/cmd/cmd.go:39
msgid "application log level"
msgstr ""

#: mgrctl/cmd/cmd.go:47
#, javascript-format
msgid "Welcome to %s"
msgstr ""

#: mgrctl/cmd/cmd.go:48
#, javascript-format
msgid "Executing command: %s"
msgstr ""

#: mgrctl/cmd/cmd.go:54
msgid "Failed to create api command"
msgstr ""

#: mgrctl/cmd/cmd.go:63
msgid "Failed to create org command"
msgstr ""
07070100000029000081B4000000000000000000000001662A75280000103D000000000000000000000000000000000000002500000000uyuni-tools/locale/mgrctl/mgrctl.pot# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: mgrctl/cmd/api/api.go:25
msgid "JSON over HTTP API helper tool"
msgstr ""

#: mgrctl/cmd/api/api.go:30
msgid "Call API GET request"
msgstr ""

#: mgrctl/cmd/api/api.go:31
msgid "Takes an API path and optional parameters and then issues GET request with them. If user and password are provided, calls login before API call"
msgstr ""

#: mgrctl/cmd/api/api.go:39
msgid "Call API POST request"
msgstr ""

#: mgrctl/cmd/api/api.go:40
msgid "Takes an API path and parameters and then issues POST request with them. User and password are mandatory. Parameters can be either JSON encoded string or one or more key=value pairs."
msgstr ""

#: mgrctl/cmd/api/get.go:25 mgrctl/cmd/api/post.go:25
#, javascript-format
msgid "unable to login to the server: %s"
msgstr ""

#: mgrctl/cmd/api/get.go:32 mgrctl/cmd/api/post.go:48
#, javascript-format
msgid "error in query %s: %s"
msgstr ""

#: mgrctl/cmd/cp/cp.go:31
msgid "Copy files to and from the containers"
msgstr ""

#: mgrctl/cmd/cp/cp.go:32
msgid ""
"Takes a source and destination parameters.\n"
"\tOne of them can be prefixed with 'server:' to indicate the path is within the server pod."
msgstr ""

#: mgrctl/cmd/cp/cp.go:41
msgid "failed to unmarshall configuration"
msgstr ""

#: mgrctl/cmd/cp/cp.go:47
msgid "User or UID to set on the destination file"
msgstr ""

#: mgrctl/cmd/cp/cp.go:48
msgid "Group or GID to set on the destination file"
msgstr ""

#: mgrctl/cmd/exec/exec.go:37
msgid "Execute commands inside the uyuni containers using 'sh -c'"
msgstr ""

#: mgrctl/cmd/exec/exec.go:43
msgid "environment variables to pass to the command, separated by commas"
msgstr ""

#: mgrctl/cmd/exec/exec.go:44
msgid "Pass stdin to the container"
msgstr ""

#: mgrctl/cmd/exec/exec.go:45
msgid "Stdin is a TTY"
msgstr ""

#: mgrctl/cmd/exec/exec.go:98
msgid "Command failed"
msgstr ""

#: mgrctl/cmd/exec/exec.go:102
msgid "Command returned with exit code 0"
msgstr ""

#: mgrctl/cmd/exec/exec.go:116
#, javascript-format
msgid "cannot write: %s"
msgstr ""

#: mgrctl/cmd/exec/exec.go:131
#, javascript-format
msgid "Running: %s %s"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:28
msgid "Create the first user and organization"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:35
msgid "Administrator user name"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:36
msgid "Administrator password"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:37
msgid "The first name of the administrator"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:38
msgid "The last name of the administrator"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:39
msgid "The administrator's email"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:40
msgid "The first organization name"
msgstr ""

#: mgrctl/cmd/org/createFirst.go:51
#, javascript-format
msgid "Organization %s created with id %d"
msgstr ""

#: mgrctl/cmd/org/org.go:18
msgid "Organization-related commands"
msgstr ""

#: mgrctl/cmd/term/term.go:21
msgid "Run a terminal inside the server container"
msgstr ""

#: mgrctl/cmd/cmd.go:30
msgid "Uyuni control tool"
msgstr ""

#: mgrctl/cmd/cmd.go:31
msgid "Tool to help managing Uyuni servers mainly through their API"
msgstr ""

#: mgrctl/cmd/cmd.go:38
msgid "configuration file path"
msgstr ""

#: mgrctl/cmd/cmd.go:39
msgid "application log level"
msgstr ""

#: mgrctl/cmd/cmd.go:47
#, javascript-format
msgid "Welcome to %s"
msgstr ""

#: mgrctl/cmd/cmd.go:48
#, javascript-format
msgid "Executing command: %s"
msgstr ""

#: mgrctl/cmd/cmd.go:54
msgid "Failed to create api command"
msgstr ""

#: mgrctl/cmd/cmd.go:63
msgid "Failed to create org command"
msgstr ""
0707010000002A000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/locale/mgrpxy0707010000002B000081B4000000000000000000000001662A75280000274A000000000000000000000000000000000000002000000000uyuni-tools/locale/mgrpxy/fr.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: 2024-04-08 11:17+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4.2\n"

#: mgrpxy/cmd/install/kubernetes/kubernetes.go:25
msgid "Install a new proxy on a running kubernetes cluster"
msgstr "Installer un nouveau proxy sur un cluster kubernetes existant"

#: mgrpxy/cmd/install/kubernetes/kubernetes.go:26
msgid ""
"Install a new proxy on a running kubernetes cluster.\n"
"\n"
"It only takes the path to the configuration tarball generated by the server\n"
"as parameter.\n"
"\n"
"The install kubernetes command assumes kubectl is installed locally.\n"
"\n"
"NOTE: for now installing on a remote kubernetes cluster is not supported!\n"
msgstr ""
"Installer un nouveau proxy sur un cluster kubernetes existant.\n"
"\n"
"Ne prend que le chemin vers l'archive de configuration générée par le serveur\n"
"comme paramètre.\n"
"\n"
"La command install kubernetes suppose que kubectl est installé en local.\n"
"\n"
"NOTE: pour l'instant l'installation sur un cluster kubernetes distant n'est pas supportée!\n"

#: mgrpxy/cmd/install/kubernetes/utils.go:26
#, javascript-format
msgid "install %s before running this command"
msgstr "installer %s avant d'exécuter cette commande"

#: mgrpxy/cmd/install/kubernetes/utils.go:35
#, javascript-format
msgid "failed to create temporary directory: %s"
msgstr "impossible de créer le répertoire temporaire: %s"

#: mgrpxy/cmd/install/kubernetes/utils.go:40
msgid "failed to extract configuration"
msgstr "impossible d'extraire la configuration"

#: mgrpxy/cmd/install/kubernetes/utils.go:62
#, javascript-format
msgid "cannot deploy proxy helm chart: %s"
msgstr "impossible de déployer le helm chart du proxy: %s"

#: mgrpxy/cmd/install/podman/utils.go:35
msgid "install podman before running this command"
msgstr "installer podman avant d'exécuter cette commande"

#: mgrpxy/cmd/install/podman/utils.go:40
#, javascript-format
msgid "failed to extract proxy config from %s file: %s"
msgstr "impossible d'extraire la configuration du proxy du fichier %s: %s"

#: mgrpxy/cmd/install/podman/utils.go:76
#, javascript-format
msgid "cannot inspect host values: %s"
msgstr "impossible d'inspecter les valeurs de l'hôte: %s"

#: mgrpxy/cmd/install/podman/utils.go:95
#, javascript-format
msgid "Setting up proxy with configuration %s"
msgstr "Installation du proxy avec la configuration %s"

#: mgrpxy/cmd/install/podman/podman.go:25
msgid "Install a new proxy on podman"
msgstr "Installer un nouveau proxy sur podman"

#: mgrpxy/cmd/install/podman/podman.go:26
msgid ""
"Install a new proxy on podman\n"
"\n"
"It only takes the path to the configuration tarball generated by the server\n"
"as parameter.\n"
"\n"
"The install podman command assumes podman is installed locally.\n"
"\n"
"NOTE: for now installing on a remote podman is not supported!\n"
msgstr ""
"Installer un nouveau proxy sur podman\n"
"\n"
"Ne prend que le chemin vers l'archive de configuration générée par le serveur\n"
"comme paramètre.\n"
"\n"
"La command install podman suppose que podman est installé en local.\n"
"\n"
"NOTE: pour l'instant l'installation sur un podman distant n'est pas supportée!\n"

#: mgrpxy/cmd/install/install.go:19 mgrpxy/cmd/install/install.go:20
msgid "Install a new proxy from scratch"
msgstr "Installer un nouveau proxy"

#: mgrpxy/cmd/restart/restart.go:23 mgrpxy/cmd/restart/restart.go:24
msgid "Restart the proxy"
msgstr "Redémarrer le proxy"

#: mgrpxy/cmd/start/start.go:23 mgrpxy/cmd/start/start.go:24
msgid "Start the proxy"
msgstr "Démarrer le proxy"

#: mgrpxy/cmd/status/kubernetes.go:27
#, javascript-format
msgid "failed to discover the cluster type: %s"
msgstr "impossible de détecter le type de cluster: %s"

#: mgrpxy/cmd/status/kubernetes.go:32
msgid "no uyuni-proxy helm release installed on the cluster"
msgstr "aucune release helm uyuni-proxy installée sur le cluster"

#: mgrpxy/cmd/status/kubernetes.go:37
#, javascript-format
msgid "failed to find the uyuni-proxy deployment namespace: %s"
msgstr "impossible de trouver le namespace du déploiement de uyuni-proxy: %s"

#: mgrpxy/cmd/status/kubernetes.go:43
#, javascript-format
msgid "failed to get deployment status: %s"
msgstr "impossible de déterminer l'état du déploiement: %s"

#: mgrpxy/cmd/status/kubernetes.go:46
#, javascript-format
msgid "Some replicas are not ready: %d / %d"
msgstr "Certaines répliques ne sont pas prêtes: %d / %d"

#: mgrpxy/cmd/status/kubernetes.go:50
msgid "the pod is not running"
msgstr "le pod n'est pas exécuté"

#: mgrpxy/cmd/status/kubernetes.go:53
msgid "Proxy containers up and running"
msgstr "Les conteneurs du proxy sont démarrés"

#: mgrpxy/cmd/status/podman.go:30
#, javascript-format
msgid "Failed to get status of the %s service"
msgstr "Impossible d'obtenir l'état du service %s"

#: mgrpxy/cmd/status/podman.go:31
msgid "failed to get the status of at least one service"
msgstr "impossible d'obtenir l'état d'au moins un des services"

#: mgrpxy/cmd/status/status.go:24 mgrpxy/cmd/status/status.go:25
msgid "Get the proxy status"
msgstr "Obtenir le status du proxy"

#: mgrpxy/cmd/status/status.go:46
msgid "no installed proxy detected"
msgstr "aucun proxy installé détecté"

#: mgrpxy/cmd/stop/stop.go:23 mgrpxy/cmd/stop/stop.go:24
msgid "Stop the proxy"
msgstr "Arrêter le proxy"

#: mgrpxy/cmd/uninstall/podman.go:48
#, javascript-format
msgid "cannot delete volume %s: %s"
msgstr "impossible de suppimer le volume %s: %s"

#: mgrpxy/cmd/uninstall/podman.go:51
msgid "All volumes removed"
msgstr "Tous les volumes ont été supprimés"

#: mgrpxy/cmd/uninstall/uninstall.go:23
msgid "Uninstall a proxy"
msgstr "Désinstaller un proxy"

#: mgrpxy/cmd/uninstall/uninstall.go:24
msgid ""
"Uninstall a proxy and optionally the corresponding volumes.\n"
"By default it will only print what would be done, use --force to actually remove."
msgstr ""
"Désinstaller un proxy et optionnellement les volumes associés.\n"
"Par défaut les actions qui seraient effectuées sont seulement affichées, utiliser --force pour effectivement supprimer."

#: mgrpxy/cmd/uninstall/uninstall.go:36
#, javascript-format
msgid "failed to determine suitable backend: %s"
msgstr "impossible de déterminer le back-end à utiliser: %s"

#: mgrpxy/cmd/uninstall/uninstall.go:51
msgid "Actually remove the proxy"
msgstr "Réellement supprimer le proxy"

#: mgrpxy/cmd/uninstall/uninstall.go:52
msgid "Also remove the volumes"
msgstr "Supprimer également les volumes"

#: mgrpxy/cmd/cmd.go:31
msgid "Uyuni proxy administration tool"
msgstr "Outil d'administration de proxy Uyuni"

#: mgrpxy/cmd/cmd.go:32
msgid "Tool to help administering Uyuni proxies in containers"
msgstr "Outil pour aider à administrer des proxy Uyuni dans des conteneurs"

#: mgrpxy/cmd/cmd.go:45
#, javascript-format
msgid "Welcome to %s"
msgstr "Bienvenue à %s"

#: mgrpxy/cmd/cmd.go:46
#, javascript-format
msgid "Executing command: %s"
msgstr "Exécution de la commande: %s"

#: mgrpxy/cmd/cmd.go:50
msgid "configuration file path"
msgstr "chemin vers le fichier de configuration"

#: mgrpxy/cmd/cmd.go:51
msgid "application log level"
msgstr "niveau de verbosité de l'application"

#: mgrpxy/shared/kubernetes/cmd.go:25
msgid "Kubernetes namespace where to install the proxy"
msgstr "Namespace kubernetes dans lequel installer le proxy"

#: mgrpxy/shared/kubernetes/cmd.go:26
msgid "URL to the proxy helm chart"
msgstr "URL du helm chart du proxy"

#: mgrpxy/shared/kubernetes/cmd.go:27
msgid "Version of the proxy helm chart"
msgstr "Version du helm chart du proxy"

#: mgrpxy/shared/kubernetes/cmd.go:28
msgid "Path to a values YAML file to use for proxy helm install"
msgstr "Chemin vers un fichier YAML de valeurs à utiliser pour l'installation helm du proxy"

#: mgrpxy/shared/kubernetes/deploy.go:23
msgid "Installing Uyuni proxy"
msgstr "Installer un proxy Uyuni"

#: mgrpxy/shared/kubernetes/deploy.go:51
#, javascript-format
msgid "cannot run helm upgrade: %s"
msgstr "impossible d'exécuter helm upgrade: %s"

#: mgrpxy/shared/podman/podman.go:24
#, javascript-format
msgid "cannot setup network: %s"
msgstr "impossible de configurer le réseau: %s"

#: mgrpxy/shared/podman/podman.go:27
msgid "Generating systemd services"
msgstr "Génération de services systemd"

#: mgrpxy/shared/podman/podman.go:102
#, javascript-format
msgid "failed to generate systemd file: %s"
msgstr "impossible de générer le fichier systemd: %s"

#: mgrpxy/shared/utils/cmd.go:17
#, javascript-format
msgid "argument is not an existing file: %s"
msgstr "le paramètre n'est pas un fichier existant: %s"

#: mgrpxy/shared/utils/flags.go:48
#, javascript-format
msgid "Invalid proxy container name: %s"
msgstr "Nom de conteneur de proxy invalide: %s"

#: mgrpxy/shared/utils/flags.go:62
msgid "Failed to compute image URL"
msgstr "Impossible de calculer l'URL de l'image"

#: mgrpxy/shared/utils/flags.go:70
msgid "registry URL prefix containing the all the container images"
msgstr "prefixe de l'URL du registre contenant toutes les images de conteneurs"

#: mgrpxy/shared/utils/flags.go:71
msgid "image tag"
msgstr "tag de l'image"

#: mgrpxy/shared/utils/flags.go:83
#, javascript-format
msgid "Image for %s container, overrides the namespace if set"
msgstr "Image pour le conteneur %s, remplace le namespace is défini"

#: mgrpxy/shared/utils/flags.go:85
#, javascript-format
msgid "Tag for %s container, overrides the global value if set"
msgstr "Tag pour le conteneur %sm remplace la valeur globale si définie"

#, javascript-format
#~ msgid "cannot generate systemd file: %s"
#~ msgstr "impossible de générer le fichier systemd: %s"
0707010000002C000081B4000000000000000000000001662A752800001AC6000000000000000000000000000000000000002000000000uyuni-tools/locale/mgrpxy/it.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: mgrpxy/cmd/install/kubernetes/kubernetes.go:25
msgid "Install a new proxy on a running kubernetes cluster"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/kubernetes.go:26
msgid ""
"Install a new proxy on a running kubernetes cluster.\n"
"\n"
"It only takes the path to the configuration tarball generated by the server\n"
"as parameter.\n"
"\n"
"The install kubernetes command assumes kubectl is installed locally.\n"
"\n"
"NOTE: for now installing on a remote kubernetes cluster is not supported!\n"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:26
#, javascript-format
msgid "install %s before running this command"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:35
#, javascript-format
msgid "failed to create temporary directory: %s"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:40
msgid "failed to extract configuration"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:62
#, javascript-format
msgid "cannot deploy proxy helm chart: %s"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:35
msgid "install podman before running this command"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:40
#, javascript-format
msgid "failed to extract proxy config from %s file: %s"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:76
#, javascript-format
msgid "cannot inspect host values: %s"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:95
#, javascript-format
msgid "Setting up proxy with configuration %s"
msgstr ""

#: mgrpxy/cmd/install/podman/podman.go:25
msgid "Install a new proxy on podman"
msgstr ""

#: mgrpxy/cmd/install/podman/podman.go:26
msgid ""
"Install a new proxy on podman\n"
"\n"
"It only takes the path to the configuration tarball generated by the server\n"
"as parameter.\n"
"\n"
"The install podman command assumes podman is installed locally.\n"
"\n"
"NOTE: for now installing on a remote podman is not supported!\n"
msgstr ""

#: mgrpxy/cmd/install/install.go:19 mgrpxy/cmd/install/install.go:20
msgid "Install a new proxy from scratch"
msgstr ""

#: mgrpxy/cmd/restart/restart.go:23 mgrpxy/cmd/restart/restart.go:24
msgid "Restart the proxy"
msgstr ""

#: mgrpxy/cmd/start/start.go:23 mgrpxy/cmd/start/start.go:24
msgid "Start the proxy"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:27
#, javascript-format
msgid "failed to discover the cluster type: %s"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:32
msgid "no uyuni-proxy helm release installed on the cluster"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:37
#, javascript-format
msgid "failed to find the uyuni-proxy deployment namespace: %s"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:43
#, javascript-format
msgid "failed to get deployment status: %s"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:46
#, javascript-format
msgid "Some replicas are not ready: %d / %d"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:50
msgid "the pod is not running"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:53
msgid "Proxy containers up and running"
msgstr ""

#: mgrpxy/cmd/status/podman.go:30
#, javascript-format
msgid "Failed to get status of the %s service"
msgstr ""

#: mgrpxy/cmd/status/podman.go:31
msgid "failed to get the status of at least one service"
msgstr ""

#: mgrpxy/cmd/status/status.go:24 mgrpxy/cmd/status/status.go:25
msgid "Get the proxy status"
msgstr ""

#: mgrpxy/cmd/status/status.go:46
msgid "no installed proxy detected"
msgstr ""

#: mgrpxy/cmd/stop/stop.go:23 mgrpxy/cmd/stop/stop.go:24
msgid "Stop the proxy"
msgstr ""

#: mgrpxy/cmd/uninstall/podman.go:48
#, javascript-format
msgid "cannot delete volume %s: %s"
msgstr ""

#: mgrpxy/cmd/uninstall/podman.go:51
msgid "All volumes removed"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:23
msgid "Uninstall a proxy"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:24
msgid ""
"Uninstall a proxy and optionally the corresponding volumes.\n"
"By default it will only print what would be done, use --force to actually remove."
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:36
#, javascript-format
msgid "failed to determine suitable backend: %s"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:51
msgid "Actually remove the proxy"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:52
msgid "Also remove the volumes"
msgstr ""

#: mgrpxy/cmd/cmd.go:31
msgid "Uyuni proxy administration tool"
msgstr ""

#: mgrpxy/cmd/cmd.go:32
msgid "Tool to help administering Uyuni proxies in containers"
msgstr ""

#: mgrpxy/cmd/cmd.go:45
#, javascript-format
msgid "Welcome to %s"
msgstr ""

#: mgrpxy/cmd/cmd.go:46
#, javascript-format
msgid "Executing command: %s"
msgstr ""

#: mgrpxy/cmd/cmd.go:50
msgid "configuration file path"
msgstr ""

#: mgrpxy/cmd/cmd.go:51
msgid "application log level"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:25
msgid "Kubernetes namespace where to install the proxy"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:26
msgid "URL to the proxy helm chart"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:27
msgid "Version of the proxy helm chart"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:28
msgid "Path to a values YAML file to use for proxy helm install"
msgstr ""

#: mgrpxy/shared/kubernetes/deploy.go:23
msgid "Installing Uyuni proxy"
msgstr ""

#: mgrpxy/shared/kubernetes/deploy.go:51
#, javascript-format
msgid "cannot run helm upgrade: %s"
msgstr ""

#: mgrpxy/shared/podman/podman.go:24
#, javascript-format
msgid "cannot setup network: %s"
msgstr ""

#: mgrpxy/shared/podman/podman.go:27
msgid "Generating systemd services"
msgstr ""

#: mgrpxy/shared/podman/podman.go:102
#, javascript-format
msgid "failed to generate systemd file: %s"
msgstr ""

#: mgrpxy/shared/utils/cmd.go:17
#, javascript-format
msgid "argument is not an existing file: %s"
msgstr ""

#: mgrpxy/shared/utils/flags.go:48
#, javascript-format
msgid "Invalid proxy container name: %s"
msgstr ""

#: mgrpxy/shared/utils/flags.go:62
msgid "Failed to compute image URL"
msgstr ""

#: mgrpxy/shared/utils/flags.go:70
msgid "registry URL prefix containing the all the container images"
msgstr ""

#: mgrpxy/shared/utils/flags.go:71
msgid "image tag"
msgstr ""

#: mgrpxy/shared/utils/flags.go:83
#, javascript-format
msgid "Image for %s container, overrides the namespace if set"
msgstr ""

#: mgrpxy/shared/utils/flags.go:85
#, javascript-format
msgid "Tag for %s container, overrides the global value if set"
msgstr ""
0707010000002D000081B4000000000000000000000001662A752800001AE1000000000000000000000000000000000000002500000000uyuni-tools/locale/mgrpxy/mgrpxy.pot# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: mgrpxy/cmd/install/kubernetes/kubernetes.go:25
msgid "Install a new proxy on a running kubernetes cluster"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/kubernetes.go:26
msgid ""
"Install a new proxy on a running kubernetes cluster.\n"
"\n"
"It only takes the path to the configuration tarball generated by the server\n"
"as parameter.\n"
"\n"
"The install kubernetes command assumes kubectl is installed locally.\n"
"\n"
"NOTE: for now installing on a remote kubernetes cluster is not supported!\n"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:26
#, javascript-format
msgid "install %s before running this command"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:35
#, javascript-format
msgid "failed to create temporary directory: %s"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:40
msgid "failed to extract configuration"
msgstr ""

#: mgrpxy/cmd/install/kubernetes/utils.go:62
#, javascript-format
msgid "cannot deploy proxy helm chart: %s"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:35
msgid "install podman before running this command"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:40
#, javascript-format
msgid "failed to extract proxy config from %s file: %s"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:76
#, javascript-format
msgid "cannot inspect host values: %s"
msgstr ""

#: mgrpxy/cmd/install/podman/utils.go:95
#, javascript-format
msgid "Setting up proxy with configuration %s"
msgstr ""

#: mgrpxy/cmd/install/podman/podman.go:25
msgid "Install a new proxy on podman"
msgstr ""

#: mgrpxy/cmd/install/podman/podman.go:26
msgid ""
"Install a new proxy on podman\n"
"\n"
"It only takes the path to the configuration tarball generated by the server\n"
"as parameter.\n"
"\n"
"The install podman command assumes podman is installed locally.\n"
"\n"
"NOTE: for now installing on a remote podman is not supported!\n"
msgstr ""

#: mgrpxy/cmd/install/install.go:19 mgrpxy/cmd/install/install.go:20
msgid "Install a new proxy from scratch"
msgstr ""

#: mgrpxy/cmd/restart/restart.go:23 mgrpxy/cmd/restart/restart.go:24
msgid "Restart the proxy"
msgstr ""

#: mgrpxy/cmd/start/start.go:23 mgrpxy/cmd/start/start.go:24
msgid "Start the proxy"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:27
#, javascript-format
msgid "failed to discover the cluster type: %s"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:32
msgid "no uyuni-proxy helm release installed on the cluster"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:37
#, javascript-format
msgid "failed to find the uyuni-proxy deployment namespace: %s"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:43
#, javascript-format
msgid "failed to get deployment status: %s"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:46
#, javascript-format
msgid "Some replicas are not ready: %d / %d"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:50
msgid "the pod is not running"
msgstr ""

#: mgrpxy/cmd/status/kubernetes.go:53
msgid "Proxy containers up and running"
msgstr ""

#: mgrpxy/cmd/status/podman.go:30
#, javascript-format
msgid "Failed to get status of the %s service"
msgstr ""

#: mgrpxy/cmd/status/podman.go:31
msgid "failed to get the status of at least one service"
msgstr ""

#: mgrpxy/cmd/status/status.go:24 mgrpxy/cmd/status/status.go:25
msgid "Get the proxy status"
msgstr ""

#: mgrpxy/cmd/status/status.go:46
msgid "no installed proxy detected"
msgstr ""

#: mgrpxy/cmd/stop/stop.go:23 mgrpxy/cmd/stop/stop.go:24
msgid "Stop the proxy"
msgstr ""

#: mgrpxy/cmd/uninstall/podman.go:48
#, javascript-format
msgid "cannot delete volume %s: %s"
msgstr ""

#: mgrpxy/cmd/uninstall/podman.go:51
msgid "All volumes removed"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:23
msgid "Uninstall a proxy"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:24
msgid ""
"Uninstall a proxy and optionally the corresponding volumes.\n"
"By default it will only print what would be done, use --force to actually remove."
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:36
#, javascript-format
msgid "failed to determine suitable backend: %s"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:51
msgid "Actually remove the proxy"
msgstr ""

#: mgrpxy/cmd/uninstall/uninstall.go:52
msgid "Also remove the volumes"
msgstr ""

#: mgrpxy/cmd/cmd.go:31
msgid "Uyuni proxy administration tool"
msgstr ""

#: mgrpxy/cmd/cmd.go:32
msgid "Tool to help administering Uyuni proxies in containers"
msgstr ""

#: mgrpxy/cmd/cmd.go:45
#, javascript-format
msgid "Welcome to %s"
msgstr ""

#: mgrpxy/cmd/cmd.go:46
#, javascript-format
msgid "Executing command: %s"
msgstr ""

#: mgrpxy/cmd/cmd.go:50
msgid "configuration file path"
msgstr ""

#: mgrpxy/cmd/cmd.go:51
msgid "application log level"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:25
msgid "Kubernetes namespace where to install the proxy"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:26
msgid "URL to the proxy helm chart"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:27
msgid "Version of the proxy helm chart"
msgstr ""

#: mgrpxy/shared/kubernetes/cmd.go:28
msgid "Path to a values YAML file to use for proxy helm install"
msgstr ""

#: mgrpxy/shared/kubernetes/deploy.go:23
msgid "Installing Uyuni proxy"
msgstr ""

#: mgrpxy/shared/kubernetes/deploy.go:51
#, javascript-format
msgid "cannot run helm upgrade: %s"
msgstr ""

#: mgrpxy/shared/podman/podman.go:24
#, javascript-format
msgid "cannot setup network: %s"
msgstr ""

#: mgrpxy/shared/podman/podman.go:27
msgid "Generating systemd services"
msgstr ""

#: mgrpxy/shared/podman/podman.go:102
#, javascript-format
msgid "failed to generate systemd file: %s"
msgstr ""

#: mgrpxy/shared/utils/cmd.go:17
#, javascript-format
msgid "argument is not an existing file: %s"
msgstr ""

#: mgrpxy/shared/utils/flags.go:48
#, javascript-format
msgid "Invalid proxy container name: %s"
msgstr ""

#: mgrpxy/shared/utils/flags.go:62
msgid "Failed to compute image URL"
msgstr ""

#: mgrpxy/shared/utils/flags.go:70
msgid "registry URL prefix containing the all the container images"
msgstr ""

#: mgrpxy/shared/utils/flags.go:71
msgid "image tag"
msgstr ""

#: mgrpxy/shared/utils/flags.go:83
#, javascript-format
msgid "Image for %s container, overrides the namespace if set"
msgstr ""

#: mgrpxy/shared/utils/flags.go:85
#, javascript-format
msgid "Tag for %s container, overrides the global value if set"
msgstr ""
0707010000002E000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/locale/shared0707010000002F000081B4000000000000000000000001662A752800005B3C000000000000000000000000000000000000002000000000uyuni-tools/locale/shared/fr.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: 2024-04-08 14:09+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.4.2\n"

#: shared/api/org/createFirst.go:21
#, javascript-format
msgid "failed to connect to the server: %s"
msgstr "impossible de se connecter au serveur: %s"

#: shared/api/org/createFirst.go:35
#, javascript-format
msgid "failed to create first user and organization: %s"
msgstr "impossible de créer les premiers utilisateur et organisation: %s"

#: shared/api/api.go:71
msgid "FQDN of the server to connect to"
msgstr ""

#: shared/api/api.go:72
msgid "API user username"
msgstr ""

#: shared/api/api.go:73
msgid "Password for the API user"
msgstr ""

#: shared/api/api.go:74
msgid "Path to a cert file of the CA"
msgstr ""

#: shared/api/api.go:75
msgid "If set, server certificate will not be checked for validity"
msgstr ""

#: shared/api/api.go:124
#, javascript-format
msgid "unknown error: %d"
msgstr ""

#: shared/api/api.go:166
msgid "API server password"
msgstr ""

#: shared/api/api.go:181
msgid "Unable to create login data"
msgstr ""

#: shared/api/api.go:211
msgid "auth cookie not found in login response"
msgstr ""

#: shared/api/api.go:227
msgid "Unable to convert data to JSON"
msgstr ""

#: shared/completion/completion.go:20 shared/completion/completion.go:21
msgid "Generate shell completion script"
msgstr ""

#: shared/completion/completion.go:30 shared/completion/completion.go:34
#: shared/completion/completion.go:38
#, javascript-format
msgid "cannot generate %s completion: %s"
msgstr ""

#: shared/kubernetes/helm.go:56
#, javascript-format
msgid "failed to %s helm chart %s in namespace %s"
msgstr ""

#: shared/kubernetes/helm.go:76
#, javascript-format
msgid "Failed to find %s's namespace, skipping removal"
msgstr ""

#: shared/kubernetes/helm.go:84
msgid "Cannot guess namespace"
msgstr ""

#: shared/kubernetes/helm.go:93 shared/podman/network.go:94
#: shared/podman/systemd.go:47 shared/podman/systemd.go:70
#: shared/podman/systemd.go:71 shared/podman/utils.go:98
#, javascript-format
msgid "Would run %s"
msgstr ""

#: shared/kubernetes/helm.go:95
#, javascript-format
msgid "Uninstalling %s"
msgstr ""

#: shared/kubernetes/helm.go:97
#, javascript-format
msgid "failed to run helm %s: %s"
msgstr "impossible d'exécuter helm %s: %s"

#: shared/kubernetes/helm.go:113
#, javascript-format
msgid "failed to detect %s's namespace using helm: %s"
msgstr ""

#: shared/kubernetes/helm.go:117
#, javascript-format
msgid "helm provided an invalid JSON output: %s"
msgstr ""

#: shared/kubernetes/helm.go:123
msgid "found no or more than one deployment"
msgstr ""

#: shared/kubernetes/k3s.go:21
msgid "Installing K3s Traefik configuration"
msgstr ""

#: shared/kubernetes/k3s.go:28
msgid "Failed to write K3s Traefik configuration"
msgstr "Erreur lors de l'écriture de la configuration Traefik pour K3s"

#: shared/kubernetes/k3s.go:32
msgid "Waiting for Traefik to be reloaded"
msgstr ""

#: shared/kubernetes/kubernetes.go:54
#, javascript-format
msgid "failed to get kubelet version: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:73
msgid "No ingressroutetcp resource deployed"
msgstr ""

#: shared/kubernetes/kubernetes.go:80
#, javascript-format
msgid "failed to get pod commands to look for nginx controller: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:94
#, javascript-format
msgid "cannot stop %s: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:105
msgid "Already running"
msgstr ""

#: shared/kubernetes/rke2.go:21
msgid "Installing RKE2 Nginx configuration"
msgstr ""

#: shared/kubernetes/rke2.go:29
msgid "Failed to write Rke2 nginx configuration"
msgstr "Erreur lors de l'écriture de la configuration nginx pour RKE2"

#: shared/kubernetes/rke2.go:33
msgid "Waiting for Nginx controller to be reloaded"
msgstr ""

#: shared/kubernetes/uninstall.go:13
msgid ""
"\n"
"Note that removing the volumes could also be handled automatically depending on the StorageClass used\n"
"when installed on a kubernetes cluster.\n"
"\n"
"For instance on a default K3S install, the local-path-provider storage volumes will\n"
"be automatically removed when deleting the deployment even if --purge-volumes argument is not used."
msgstr ""

#: shared/kubernetes/utils.go:51
#, javascript-format
msgid "failed to pull image: %s"
msgstr "impossible de télécharger l'image: %s"

#: shared/kubernetes/utils.go:54
#, javascript-format
msgid "Waiting for %s deployment to be ready in %s namespace\n"
msgstr ""

#: shared/kubernetes/utils.go:63
#, javascript-format
msgid "failed to find a ready replica for deployment %s in namespace %s after 60s"
msgstr ""

#: shared/kubernetes/utils.go:68
#, javascript-format
msgid "Waiting for image of %s pod in %s namespace to be pulled"
msgstr ""

#: shared/kubernetes/utils.go:81
#, javascript-format
msgid "failed to get failed events for pod %s"
msgstr ""

#: shared/kubernetes/utils.go:86
msgid "failed to pull image"
msgstr ""

#: shared/kubernetes/utils.go:93
#, javascript-format
msgid "failed to get events for pod %s"
msgstr ""

#: shared/kubernetes/utils.go:139
#, javascript-format
msgid "failed to parse deployment status: %s"
msgstr "erreur d'analyse de l'état du déploiement: %s"

#: shared/kubernetes/utils.go:153
#, javascript-format
msgid "cannot run kubectl %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:158
#, javascript-format
msgid "cannot get pods for %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:165
#, javascript-format
msgid "replica to %d failed: %s"
msgstr ""

#: shared/kubernetes/utils.go:178
#, javascript-format
msgid "cannot check if pod %s is running in app %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:188 shared/kubernetes/utils.go:233
#: shared/kubernetes/utils.go:327
#, javascript-format
msgid "cannot execute %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:207
#, javascript-format
msgid "cannot get pod informations %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:216
#, javascript-format
msgid "cannot set replicas for %s to zero"
msgstr ""

#: shared/kubernetes/utils.go:243
#, javascript-format
msgid "pod %s replica is not %d in %s seconds: %s"
msgstr ""

#: shared/kubernetes/utils.go:266
#, javascript-format
msgid "%s is not a valid image pull policy value"
msgstr ""

#: shared/kubernetes/utils.go:286
#, javascript-format
msgid "cannot run %s using image %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:290
#, javascript-format
msgid "deleting pod %s. Status fails with error %s"
msgstr ""

#: shared/kubernetes/utils.go:303 shared/kubernetes/utils.go:312
#, javascript-format
msgid "cannot delete pod %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:334
#, javascript-format
msgid "error during execution of %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:339
#, javascript-format
msgid "pod %s status is not %s in %s seconds: %s"
msgstr ""

#: shared/kubernetes/utils.go:356
#, javascript-format
msgid "cannot find node name matching filter %s"
msgstr ""

#: shared/kubernetes/utils.go:365
#, javascript-format
msgid "cannot serialize pod definition override: %s"
msgstr ""

#: shared/podman/images.go:35
#, javascript-format
msgid "Ensure image %s is available"
msgstr ""

#: shared/podman/images.go:54
#, javascript-format
msgid "Cannot use RPM image for %s: %s"
msgstr ""

#: shared/podman/images.go:56
#, javascript-format
msgid "Using the %s image loaded from the RPM instead of its online version %s"
msgstr ""

#: shared/podman/images.go:60
#, javascript-format
msgid "Cannot find RPM image for %s"
msgstr ""

#: shared/podman/images.go:68
#, javascript-format
msgid "image %s is missing and cannot be fetched"
msgstr ""

#: shared/podman/images.go:93
#, javascript-format
msgid "cannot unmarshal image RPM metadata: %s"
msgstr ""

#: shared/podman/images.go:138
#, javascript-format
msgid "Cannot unmarshal metadata file %s: %s"
msgstr ""

#: shared/podman/images.go:160
#, javascript-format
msgid "error parsing: %s"
msgstr ""

#: shared/podman/images.go:168 shared/podman/images.go:182
#: shared/podman/images.go:197
#, javascript-format
msgid "failed to check if image %s has already been pulled"
msgstr ""

#: shared/podman/images.go:203
#, javascript-format
msgid "Pulling image %s"
msgstr ""

#: shared/podman/images.go:222
#, javascript-format
msgid "cannot find any tag for image %s: %s"
msgstr ""

#: shared/podman/network.go:23
#, javascript-format
msgid "Setting up %s network"
msgstr ""

#: shared/podman/network.go:35
#, javascript-format
msgid "%s network doesn't have IPv6, deleting existing network to enable IPv6 on it"
msgstr ""

#: shared/podman/network.go:39
#, javascript-format
msgid "failed to remove %s podman network: %s"
msgstr ""

#: shared/podman/network.go:42
#, javascript-format
msgid "Reusing existing %s network"
msgstr ""

#: shared/podman/network.go:55
#, javascript-format
msgid "failed to find podman's network backend: %s"
msgstr ""

#: shared/podman/network.go:57
#, javascript-format
msgid "Podman's network backend (%s) is not netavark, skipping IPv6 enabling on %s network"
msgstr ""

#: shared/podman/network.go:65
#, javascript-format
msgid "failed to create %s network with IPv6 enabled: %s"
msgstr ""

#: shared/podman/network.go:91
#, javascript-format
msgid "Network %s already removed"
msgstr "Réseau %s déjà supprimé"

#: shared/podman/network.go:98
#, javascript-format
msgid "Failed to remove network %s"
msgstr "Impossible de supprimer le réseau %s"

#: shared/podman/network.go:100
msgid "Network removed"
msgstr "Réseau supprimé"

#: shared/podman/systemd.go:44
#, javascript-format
msgid "Systemd has no %s.service unit"
msgstr ""

#: shared/podman/systemd.go:48
#, javascript-format
msgid "Would remove %s"
msgstr ""

#: shared/podman/systemd.go:50
#, javascript-format
msgid "Disable %s service"
msgstr ""

#: shared/podman/systemd.go:54
#, javascript-format
msgid "Failed to disable %s service"
msgstr ""

#: shared/podman/systemd.go:58
#, javascript-format
msgid "Remove %s"
msgstr ""

#: shared/podman/systemd.go:60
#, javascript-format
msgid "Failed to remove %s.service file"
msgstr ""

#: shared/podman/systemd.go:75
msgid "failed to reset-failed systemd"
msgstr ""

#: shared/podman/systemd.go:79
msgid "failed to reload systemd daemon"
msgstr ""

#: shared/podman/systemd.go:97
#, javascript-format
msgid "failed to restart systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:105
#, javascript-format
msgid "failed to start systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:113
#, javascript-format
msgid "failed to stop systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:121
#, javascript-format
msgid "failed to enable %s systemd service: %s"
msgstr ""

#: shared/podman/systemd.go:132
#, javascript-format
msgid "failed to create %s folder: %s"
msgstr "impossible de créer le répertoire %s: %s"

#: shared/podman/systemd.go:138
#, javascript-format
msgid "cannot write %s file: %s"
msgstr ""

#: shared/podman/utils.go:47
msgid "Extra arguments to pass to podman"
msgstr ""

#: shared/podman/utils.go:53
msgid "Path to custom /var/cache volume"
msgstr ""

#: shared/podman/utils.go:54
msgid "Path to custom /var/lib/pgsql volume"
msgstr ""

#: shared/podman/utils.go:55
msgid "Path to custom /var/spacewalk volume"
msgstr ""

#: shared/podman/utils.go:62
#, javascript-format
msgid "failed to enable podman.socket unit: %s"
msgstr ""

#: shared/podman/utils.go:72
#, javascript-format
msgid "Would run podman kill %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:73
#, javascript-format
msgid "Would run podman remove %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:75
#, javascript-format
msgid "Run podman kill %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:78
msgid "Failed to kill the server"
msgstr "Impossible de tuer le serveur"

#: shared/podman/utils.go:80
#, javascript-format
msgid "Run podman remove %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:83
msgid "Error removing container"
msgstr ""

#: shared/podman/utils.go:88
msgid "Container already removed"
msgstr ""

#: shared/podman/utils.go:100
#, javascript-format
msgid "Run %s"
msgstr ""

#: shared/podman/utils.go:103
#, javascript-format
msgid "Failed to remove volume %s"
msgstr ""

#: shared/podman/utils.go:134
#, javascript-format
msgid "volume folder (%s) already exists, cannot link it to %s"
msgstr ""

#: shared/podman/utils.go:138
#, javascript-format
msgid "failed to create volumes folder %s: %s"
msgstr "impossible de créer le répertoire des volumes %s: %s"

#: shared/podman/utils.go:142
#, javascript-format
msgid "failed to link volume folder %s to %s: %s"
msgstr "impossible de lier le répertoire du volume %s vers %s: %s"

#: shared/podman/utils.go:152
#, javascript-format
msgid "failed to get podman's volumes folder: %s"
msgstr ""

#: shared/utils/cmd.go:47 shared/utils/cmd.go:48
msgid "failed to unmarshall configuration"
msgstr "erreur d'analyse du fichier de configuration"

#: shared/utils/cmd.go:55
msgid "tool to use to reach the container. Possible values: 'podman', 'podman-remote', 'kubectl'. Default guesses which to use."
msgstr "outil à utiliser pour accéder au conteneur. Valeurs possibles: 'podman', 'podman-remote', 'kubectl'. Par défaut la valeur à utiliser sera détectée."

#: shared/utils/cmd.go:69
msgid "set whether to pull the images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr "défini si les images doivent être téléchargées ou pas. La valeur peut être une parmi 'Never', 'IfNotPresent' ou 'Always'"

#: shared/utils/config.go:33
#, javascript-format
msgid "Using config file %s"
msgstr "Utilisation du fichier de configuration %s"

#: shared/utils/config.go:40
msgid "Failed to find home directory"
msgstr "Impossible de trouver le repertoire home"

#: shared/utils/config.go:59
#, javascript-format
msgid "failed to parse configuration file %s: %s"
msgstr "erreur d'analyse du fichier de configuration %s: %s"

#: shared/utils/config.go:78
#, javascript-format
msgid "failed to bind %s config to parameter %s: %s"
msgstr "impossible de lier la configuration %s au paramètre %s: %s"

#: shared/utils/config.go:90
msgid ""
"Usage:{{if .Runnable}}\n"
"  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n"
"  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}\n"
"\n"
"Aliases:\n"
"  {{.NameAndAliases}}{{end}}{{if .HasExample}}\n"
"\n"
"Examples:\n"
"{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n"
"  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\n"
"\n"
"Flags:\n"
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\n"
"\n"
"Global Flags:\n"
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}\n"
"\n"
"Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n"
"  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Use \"{{.CommandPath}} [command] --help\" for more information about a command.{{end}}\n"
msgstr ""
"Utilisation:{{if .Runnable}}\n"
"  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n"
"  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}\n"
"\n"
"Alias:\n"
"  {{.NameAndAliases}}{{end}}{{if .HasExample}}\n"
"\n"
"Exemples:\n"
"{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Commandes possibles:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n"
"  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\n"
"\n"
"Paramètres:\n"
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\n"
"\n"
"Paramètres globaux:\n"
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}\n"
"\n"
"Sujets d'aide complémentaires:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n"
"  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Utiliser \"{{.CommandPath}} [command] --help\" pour plus d'informations à propos d'une commande.{{end}}\n"

#: shared/utils/config.go:118
msgid ""
"\n"
"Configuration:\n"
"\n"
"  All the non-global flags can alternatively be passed as configuration.\n"
"  \n"
"  The configuration file is a YAML file with entries matching the flag name.\n"
"  The name of a flag is the part after the '--' of the command line parameter.\n"
"  Every '_' character in the flag name means a nested property.\n"
"  \n"
"  For instance the '--tz CEST' and '--ssl-password secret' will be mapped to\n"
"  this YAML configuration:\n"
"  \n"
"    tz: CEST\n"
"    ssl:\n"
"      password: secret\n"
"  \n"
"  The configuration file will be searched in the following places and order:\n"
"  · $XDG_CONFIG_HOME/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $HOME/.config/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $PWD/{{ .ConfigFile }}\n"
"  · the value of the --config flag\n"
"\n"
"\n"
"Environment variables:\n"
"\n"
"  All the non-global flags can also be passed as environment variables.\n"
"  \n"
"  The environment variable name is the flag name with '-' replaced by with '_'\n"
"  and the {{ .EnvPrefix }} prefix.\n"
"  \n"
"  For example the '--tz CEST' flag will be mapped to '{{ .EnvPrefix }}_TZ'\n"
"  and '--ssl-password' flags to '{{ .EnvPrefix }}_SSL_PASSWORD' \n"
msgstr ""
"\n"
"Configuration:\n"
"\n"
"  Tous les paramètres qui ne sont pas globaux peuvent également être passés comme configuration.\n"
"  \n"
"  Le fichier de configuration est un fichier YAML avec des entrées correspondant au nom du paramètre.\n"
"  Le nom d'un paramètre est la partie après le '--' du paramètre de ligne de commande.\n"
"  Tous les caractères '_' du nom du paramètres indiquent une propriété imbriquée.\n"
"  \n"
"  Par exemple les paramètres '--tz CEST' et '--ssl-password secret' seront associés à cette configuration YAML:\n"
"  \n"
"    tz: CEST\n"
"    ssl:\n"
"      password: secret\n"
"  \n"
"  Le fichier de configuration sera recherché dans les répertoires suivants et dans l'ordre:\n"
"  · $XDG_CONFIG_HOME/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $HOME/.config/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $PWD/{{ .ConfigFile }}\n"
"  · la valeur du paramètre --config\n"
"\n"
"\n"
"Variables d'environment:\n"
"\n"
"  Tous les paramètres non globaux peuvent également être passés comme variables d'environnement.\n"
"  \n"
"  Le nom de la variable d'environnement est le nom du paramètre où les '-' sont remplacés par '_'\n"
"  et préfixé par {{ .EnvPrefix }}.\n"
"  \n"
"  Par exemple le paramètre '--tz CEST' sera associé à '{{ .EnvPrefix }}_TZ'\n"
"  et '--ssl-password' flags à '{{ .EnvPrefix }}_SSL_PASSWORD' \n"

#: shared/utils/config.go:163
msgid "failed to compute config help command"
msgstr "impossible de créer la commande d'aide config"

#: shared/utils/tar.go:48
#, javascript-format
msgid "Skipping extraction of %s in %s file as it resolves outside the target path"
msgstr "Passe l'extraction de %s dans le fichier %s puisqu'il se situe en dehors de chemin cible"

#: shared/utils/tar.go:90
#, javascript-format
msgid "failed to write tar.gz to %s: %s"
msgstr "impossible d'écrire l'archive tar.gz vers %s: %s"

#: shared/utils/template.go:25
#, javascript-format
msgid "%s file already present, not overwriting"
msgstr "Fichier %s déjà présent, pas remplacé"

#: shared/utils/template.go:32
#, javascript-format
msgid "failed to open %s for writing: %s"
msgstr "impossible d'ouvrir %s en écriture: %s"

#: shared/utils/utils.go:34
#, fuzzy, javascript-format
msgid "Has to be more than %d character long"
msgid_plural "Has to be more than %d characters long"
msgstr[0] "Doit contenir plus de %d caractère"
msgstr[1] "Doit contenir plus de %d caractères"

#: shared/utils/utils.go:38
#, javascript-format
msgid "Has to be less than %d character long"
msgid_plural "Has to be less than %d characters long"
msgstr[0] "Doit contenir moins de %d caractère"
msgstr[1] "Doit contenir moins de %d caractères"

#: shared/utils/utils.go:51
msgid "Failed to read password"
msgstr "Impossible de lecture du mot de passe"

#: shared/utils/utils.go:57
msgid "Cannot contain spaces or tabs"
msgstr "Ne peut contenir d'espace ou de tabulation"

#: shared/utils/utils.go:78
msgid "Failed to read input"
msgstr "Impossible de lire la saisie"

#: shared/utils/utils.go:86
msgid "A value is required"
msgstr "Une valeur est requise"

#: shared/utils/utils.go:96
#, javascript-format
msgid "invalid image name: %s"
msgstr "nom d'image invalid: %s"

#: shared/utils/utils.go:100
#, javascript-format
msgid "tag missing on %s"
msgstr "tag missing dans %s"

#: shared/utils/utils.go:119
#, javascript-format
msgid "Failed to run %s"
msgstr "Erreur lors de l'exécution de %s"

#: shared/utils/utils.go:130
#, javascript-format
msgid "Failed to get %s file informations"
msgstr "Impossible d'obtenir les informations du fichier %s"

#: shared/utils/utils.go:139
#, javascript-format
msgid "Failed to read file %s"
msgstr "Impossible de lire le fichier %s"

#: shared/utils/utils.go:154
#, javascript-format
msgid "Would remove file %s"
msgstr "Supprimerait le fichier %s"

#: shared/utils/utils.go:156
#, javascript-format
msgid "Removing file %s"
msgstr "Suppression du fichier %s"

#: shared/utils/utils.go:158
#, javascript-format
msgid "Failed to remove file %s"
msgstr "Impossible de supprimer le fichier %s"

#: shared/utils/utils.go:168
msgid "Failed to read random data"
msgstr "Impossible de lire des données aléatoires"

#: shared/utils/utils.go:179
#, javascript-format
msgid "error downloading from %s: %s"
msgstr ""

#: shared/utils/utils.go:185
#, javascript-format
msgid "bad status: %s"
msgstr ""

#: shared/connection.go:56
#, javascript-format
msgid "backend command not found in PATH: %s"
msgstr ""

#: shared/connection.go:68
msgid "kubectl not configured to connect to a cluster, ignoring"
msgstr ""

#: shared/connection.go:101
msgid "uyuni container is not accessible with one of podman, podman-remote or kubectl"
msgstr ""

#: shared/connection.go:104
#, javascript-format
msgid "unsupported backend %s"
msgstr ""

#: shared/connection.go:125
#, javascript-format
msgid "container %s is not running on podman"
msgstr ""

#: shared/connection.go:146
#, javascript-format
msgid "the container is not running, %s %s command not executed: %s"
msgstr ""

#: shared/connection.go:193
msgid "server didn't start within 60s. Check for the service status"
msgstr ""

#: shared/connection.go:223 shared/connection.go:262
#, javascript-format
msgid "unknown container kind: %s"
msgstr ""

#: shared/connection.go:306
msgid "failed to determine suitable backend"
msgstr ""

#: shared/connection.go:316
msgid "no supported backend found"
msgstr ""
07070100000030000081B4000000000000000000000001662A7528000049E8000000000000000000000000000000000000002000000000uyuni-tools/locale/shared/it.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: shared/api/org/createFirst.go:21
#, javascript-format
msgid "failed to connect to the server: %s"
msgstr ""

#: shared/api/org/createFirst.go:35
#, javascript-format
msgid "failed to create first user and organization: %s"
msgstr ""

#: shared/api/api.go:71
msgid "FQDN of the server to connect to"
msgstr ""

#: shared/api/api.go:72
msgid "API user username"
msgstr ""

#: shared/api/api.go:73
msgid "Password for the API user"
msgstr ""

#: shared/api/api.go:74
msgid "Path to a cert file of the CA"
msgstr ""

#: shared/api/api.go:75
msgid "If set, server certificate will not be checked for validity"
msgstr ""

#: shared/api/api.go:124
#, javascript-format
msgid "unknown error: %d"
msgstr ""

#: shared/api/api.go:166
msgid "API server password"
msgstr ""

#: shared/api/api.go:181
msgid "Unable to create login data"
msgstr ""

#: shared/api/api.go:211
msgid "auth cookie not found in login response"
msgstr ""

#: shared/api/api.go:227
msgid "Unable to convert data to JSON"
msgstr ""

#: shared/completion/completion.go:20 shared/completion/completion.go:21
msgid "Generate shell completion script"
msgstr ""

#: shared/completion/completion.go:30 shared/completion/completion.go:34
#: shared/completion/completion.go:38
#, javascript-format
msgid "cannot generate %s completion: %s"
msgstr ""

#: shared/kubernetes/helm.go:56
#, javascript-format
msgid "failed to %s helm chart %s in namespace %s"
msgstr ""

#: shared/kubernetes/helm.go:76
#, javascript-format
msgid "Failed to find %s's namespace, skipping removal"
msgstr ""

#: shared/kubernetes/helm.go:84
msgid "Cannot guess namespace"
msgstr ""

#: shared/kubernetes/helm.go:93 shared/podman/network.go:94
#: shared/podman/systemd.go:47 shared/podman/systemd.go:70
#: shared/podman/systemd.go:71 shared/podman/utils.go:98
#, javascript-format
msgid "Would run %s"
msgstr ""

#: shared/kubernetes/helm.go:95
#, javascript-format
msgid "Uninstalling %s"
msgstr ""

#: shared/kubernetes/helm.go:97
#, javascript-format
msgid "failed to run helm %s: %s"
msgstr ""

#: shared/kubernetes/helm.go:113
#, javascript-format
msgid "failed to detect %s's namespace using helm: %s"
msgstr ""

#: shared/kubernetes/helm.go:117
#, javascript-format
msgid "helm provided an invalid JSON output: %s"
msgstr ""

#: shared/kubernetes/helm.go:123
msgid "found no or more than one deployment"
msgstr ""

#: shared/kubernetes/k3s.go:21
msgid "Installing K3s Traefik configuration"
msgstr ""

#: shared/kubernetes/k3s.go:28
msgid "Failed to write K3s Traefik configuration"
msgstr ""

#: shared/kubernetes/k3s.go:32
msgid "Waiting for Traefik to be reloaded"
msgstr ""

#: shared/kubernetes/kubernetes.go:54
#, javascript-format
msgid "failed to get kubelet version: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:73
msgid "No ingressroutetcp resource deployed"
msgstr ""

#: shared/kubernetes/kubernetes.go:80
#, javascript-format
msgid "failed to get pod commands to look for nginx controller: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:94
#, javascript-format
msgid "cannot stop %s: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:105
msgid "Already running"
msgstr ""

#: shared/kubernetes/rke2.go:21
msgid "Installing RKE2 Nginx configuration"
msgstr ""

#: shared/kubernetes/rke2.go:29
msgid "Failed to write Rke2 nginx configuration"
msgstr ""

#: shared/kubernetes/rke2.go:33
msgid "Waiting for Nginx controller to be reloaded"
msgstr ""

#: shared/kubernetes/uninstall.go:13
msgid ""
"\n"
"Note that removing the volumes could also be handled automatically depending on the StorageClass used\n"
"when installed on a kubernetes cluster.\n"
"\n"
"For instance on a default K3S install, the local-path-provider storage volumes will\n"
"be automatically removed when deleting the deployment even if --purge-volumes argument is not used."
msgstr ""

#: shared/kubernetes/utils.go:51
#, javascript-format
msgid "failed to pull image: %s"
msgstr ""

#: shared/kubernetes/utils.go:54
#, javascript-format
msgid "Waiting for %s deployment to be ready in %s namespace\n"
msgstr ""

#: shared/kubernetes/utils.go:63
#, javascript-format
msgid "failed to find a ready replica for deployment %s in namespace %s after 60s"
msgstr ""

#: shared/kubernetes/utils.go:68
#, javascript-format
msgid "Waiting for image of %s pod in %s namespace to be pulled"
msgstr ""

#: shared/kubernetes/utils.go:81
#, javascript-format
msgid "failed to get failed events for pod %s"
msgstr ""

#: shared/kubernetes/utils.go:86
msgid "failed to pull image"
msgstr ""

#: shared/kubernetes/utils.go:93
#, javascript-format
msgid "failed to get events for pod %s"
msgstr ""

#: shared/kubernetes/utils.go:139
#, javascript-format
msgid "failed to parse deployment status: %s"
msgstr ""

#: shared/kubernetes/utils.go:153
#, javascript-format
msgid "cannot run kubectl %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:158
#, javascript-format
msgid "cannot get pods for %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:165
#, javascript-format
msgid "replica to %d failed: %s"
msgstr ""

#: shared/kubernetes/utils.go:178
#, javascript-format
msgid "cannot check if pod %s is running in app %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:188 shared/kubernetes/utils.go:233
#: shared/kubernetes/utils.go:327
#, javascript-format
msgid "cannot execute %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:207
#, javascript-format
msgid "cannot get pod informations %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:216
#, javascript-format
msgid "cannot set replicas for %s to zero"
msgstr ""

#: shared/kubernetes/utils.go:243
#, javascript-format
msgid "pod %s replica is not %d in %s seconds: %s"
msgstr ""

#: shared/kubernetes/utils.go:266
#, javascript-format
msgid "%s is not a valid image pull policy value"
msgstr ""

#: shared/kubernetes/utils.go:286
#, javascript-format
msgid "cannot run %s using image %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:290
#, javascript-format
msgid "deleting pod %s. Status fails with error %s"
msgstr ""

#: shared/kubernetes/utils.go:303 shared/kubernetes/utils.go:312
#, javascript-format
msgid "cannot delete pod %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:334
#, javascript-format
msgid "error during execution of %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:339
#, javascript-format
msgid "pod %s status is not %s in %s seconds: %s"
msgstr ""

#: shared/kubernetes/utils.go:356
#, javascript-format
msgid "cannot find node name matching filter %s"
msgstr ""

#: shared/kubernetes/utils.go:365
#, javascript-format
msgid "cannot serialize pod definition override: %s"
msgstr ""

#: shared/podman/images.go:35
#, javascript-format
msgid "Ensure image %s is available"
msgstr ""

#: shared/podman/images.go:54
#, javascript-format
msgid "Cannot use RPM image for %s: %s"
msgstr ""

#: shared/podman/images.go:56
#, javascript-format
msgid "Using the %s image loaded from the RPM instead of its online version %s"
msgstr ""

#: shared/podman/images.go:60
#, javascript-format
msgid "Cannot find RPM image for %s"
msgstr ""

#: shared/podman/images.go:68
#, javascript-format
msgid "image %s is missing and cannot be fetched"
msgstr ""

#: shared/podman/images.go:93
#, javascript-format
msgid "cannot unmarshal image RPM metadata: %s"
msgstr ""

#: shared/podman/images.go:138
#, javascript-format
msgid "Cannot unmarshal metadata file %s: %s"
msgstr ""

#: shared/podman/images.go:160
#, javascript-format
msgid "error parsing: %s"
msgstr ""

#: shared/podman/images.go:168 shared/podman/images.go:182
#: shared/podman/images.go:197
#, javascript-format
msgid "failed to check if image %s has already been pulled"
msgstr ""

#: shared/podman/images.go:203
#, javascript-format
msgid "Pulling image %s"
msgstr ""

#: shared/podman/images.go:222
#, javascript-format
msgid "cannot find any tag for image %s: %s"
msgstr ""

#: shared/podman/network.go:23
#, javascript-format
msgid "Setting up %s network"
msgstr ""

#: shared/podman/network.go:35
#, javascript-format
msgid "%s network doesn't have IPv6, deleting existing network to enable IPv6 on it"
msgstr ""

#: shared/podman/network.go:39
#, javascript-format
msgid "failed to remove %s podman network: %s"
msgstr ""

#: shared/podman/network.go:42
#, javascript-format
msgid "Reusing existing %s network"
msgstr ""

#: shared/podman/network.go:55
#, javascript-format
msgid "failed to find podman's network backend: %s"
msgstr ""

#: shared/podman/network.go:57
#, javascript-format
msgid "Podman's network backend (%s) is not netavark, skipping IPv6 enabling on %s network"
msgstr ""

#: shared/podman/network.go:65
#, javascript-format
msgid "failed to create %s network with IPv6 enabled: %s"
msgstr ""

#: shared/podman/network.go:91
#, javascript-format
msgid "Network %s already removed"
msgstr ""

#: shared/podman/network.go:98
#, javascript-format
msgid "Failed to remove network %s"
msgstr ""

#: shared/podman/network.go:100
msgid "Network removed"
msgstr ""

#: shared/podman/systemd.go:44
#, javascript-format
msgid "Systemd has no %s.service unit"
msgstr ""

#: shared/podman/systemd.go:48
#, javascript-format
msgid "Would remove %s"
msgstr ""

#: shared/podman/systemd.go:50
#, javascript-format
msgid "Disable %s service"
msgstr ""

#: shared/podman/systemd.go:54
#, javascript-format
msgid "Failed to disable %s service"
msgstr ""

#: shared/podman/systemd.go:58
#, javascript-format
msgid "Remove %s"
msgstr ""

#: shared/podman/systemd.go:60
#, javascript-format
msgid "Failed to remove %s.service file"
msgstr ""

#: shared/podman/systemd.go:75
msgid "failed to reset-failed systemd"
msgstr ""

#: shared/podman/systemd.go:79
msgid "failed to reload systemd daemon"
msgstr ""

#: shared/podman/systemd.go:97
#, javascript-format
msgid "failed to restart systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:105
#, javascript-format
msgid "failed to start systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:113
#, javascript-format
msgid "failed to stop systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:121
#, javascript-format
msgid "failed to enable %s systemd service: %s"
msgstr ""

#: shared/podman/systemd.go:132
#, javascript-format
msgid "failed to create %s folder: %s"
msgstr ""

#: shared/podman/systemd.go:138
#, javascript-format
msgid "cannot write %s file: %s"
msgstr ""

#: shared/podman/utils.go:47
msgid "Extra arguments to pass to podman"
msgstr ""

#: shared/podman/utils.go:53
msgid "Path to custom /var/cache volume"
msgstr ""

#: shared/podman/utils.go:54
msgid "Path to custom /var/lib/pgsql volume"
msgstr ""

#: shared/podman/utils.go:55
msgid "Path to custom /var/spacewalk volume"
msgstr ""

#: shared/podman/utils.go:62
#, javascript-format
msgid "failed to enable podman.socket unit: %s"
msgstr ""

#: shared/podman/utils.go:72
#, javascript-format
msgid "Would run podman kill %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:73
#, javascript-format
msgid "Would run podman remove %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:75
#, javascript-format
msgid "Run podman kill %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:78
msgid "Failed to kill the server"
msgstr ""

#: shared/podman/utils.go:80
#, javascript-format
msgid "Run podman remove %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:83
msgid "Error removing container"
msgstr ""

#: shared/podman/utils.go:88
msgid "Container already removed"
msgstr ""

#: shared/podman/utils.go:100
#, javascript-format
msgid "Run %s"
msgstr ""

#: shared/podman/utils.go:103
#, javascript-format
msgid "Failed to remove volume %s"
msgstr ""

#: shared/podman/utils.go:134
#, javascript-format
msgid "volume folder (%s) already exists, cannot link it to %s"
msgstr ""

#: shared/podman/utils.go:138
#, javascript-format
msgid "failed to create volumes folder %s: %s"
msgstr ""

#: shared/podman/utils.go:142
#, javascript-format
msgid "failed to link volume folder %s to %s: %s"
msgstr ""

#: shared/podman/utils.go:152
#, javascript-format
msgid "failed to get podman's volumes folder: %s"
msgstr ""

#: shared/utils/cmd.go:47 shared/utils/cmd.go:48
msgid "failed to unmarshall configuration"
msgstr ""

#: shared/utils/cmd.go:55
msgid "tool to use to reach the container. Possible values: 'podman', 'podman-remote', 'kubectl'. Default guesses which to use."
msgstr ""

#: shared/utils/cmd.go:69
msgid "set whether to pull the images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: shared/utils/config.go:33
#, javascript-format
msgid "Using config file %s"
msgstr ""

#: shared/utils/config.go:40
msgid "Failed to find home directory"
msgstr ""

#: shared/utils/config.go:59
#, javascript-format
msgid "failed to parse configuration file %s: %s"
msgstr ""

#: shared/utils/config.go:78
#, javascript-format
msgid "failed to bind %s config to parameter %s: %s"
msgstr ""

#: shared/utils/config.go:90
msgid ""
"Usage:{{if .Runnable}}\n"
"  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n"
"  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}\n"
"\n"
"Aliases:\n"
"  {{.NameAndAliases}}{{end}}{{if .HasExample}}\n"
"\n"
"Examples:\n"
"{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n"
"  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\n"
"\n"
"Flags:\n"
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\n"
"\n"
"Global Flags:\n"
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}\n"
"\n"
"Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n"
"  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Use \"{{.CommandPath}} [command] --help\" for more information about a command.{{end}}\n"
msgstr ""

#: shared/utils/config.go:118
msgid ""
"\n"
"Configuration:\n"
"\n"
"  All the non-global flags can alternatively be passed as configuration.\n"
"  \n"
"  The configuration file is a YAML file with entries matching the flag name.\n"
"  The name of a flag is the part after the '--' of the command line parameter.\n"
"  Every '_' character in the flag name means a nested property.\n"
"  \n"
"  For instance the '--tz CEST' and '--ssl-password secret' will be mapped to\n"
"  this YAML configuration:\n"
"  \n"
"    tz: CEST\n"
"    ssl:\n"
"      password: secret\n"
"  \n"
"  The configuration file will be searched in the following places and order:\n"
"  · $XDG_CONFIG_HOME/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $HOME/.config/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $PWD/{{ .ConfigFile }}\n"
"  · the value of the --config flag\n"
"\n"
"\n"
"Environment variables:\n"
"\n"
"  All the non-global flags can also be passed as environment variables.\n"
"  \n"
"  The environment variable name is the flag name with '-' replaced by with '_'\n"
"  and the {{ .EnvPrefix }} prefix.\n"
"  \n"
"  For example the '--tz CEST' flag will be mapped to '{{ .EnvPrefix }}_TZ'\n"
"  and '--ssl-password' flags to '{{ .EnvPrefix }}_SSL_PASSWORD' \n"
msgstr ""

#: shared/utils/config.go:163
msgid "failed to compute config help command"
msgstr ""

#: shared/utils/tar.go:48
#, javascript-format
msgid "Skipping extraction of %s in %s file as it resolves outside the target path"
msgstr ""

#: shared/utils/tar.go:90
#, javascript-format
msgid "failed to write tar.gz to %s: %s"
msgstr ""

#: shared/utils/template.go:25
#, javascript-format
msgid "%s file already present, not overwriting"
msgstr ""

#: shared/utils/template.go:32
#, javascript-format
msgid "failed to open %s for writing: %s"
msgstr ""

#: shared/utils/utils.go:34
#, javascript-format
msgid "Has to be more than %d character long"
msgid_plural "Has to be more than %d characters long"
msgstr[0] ""
msgstr[1] ""

#: shared/utils/utils.go:38
#, javascript-format
msgid "Has to be less than %d character long"
msgid_plural "Has to be less than %d characters long"
msgstr[0] ""
msgstr[1] ""

#: shared/utils/utils.go:51
msgid "Failed to read password"
msgstr ""

#: shared/utils/utils.go:57
msgid "Cannot contain spaces or tabs"
msgstr ""

#: shared/utils/utils.go:78
msgid "Failed to read input"
msgstr ""

#: shared/utils/utils.go:86
msgid "A value is required"
msgstr ""

#: shared/utils/utils.go:96
#, javascript-format
msgid "invalid image name: %s"
msgstr ""

#: shared/utils/utils.go:100
#, javascript-format
msgid "tag missing on %s"
msgstr ""

#: shared/utils/utils.go:119
#, javascript-format
msgid "Failed to run %s"
msgstr ""

#: shared/utils/utils.go:130
#, javascript-format
msgid "Failed to get %s file informations"
msgstr ""

#: shared/utils/utils.go:139
#, javascript-format
msgid "Failed to read file %s"
msgstr ""

#: shared/utils/utils.go:154
#, javascript-format
msgid "Would remove file %s"
msgstr ""

#: shared/utils/utils.go:156
#, javascript-format
msgid "Removing file %s"
msgstr ""

#: shared/utils/utils.go:158
#, javascript-format
msgid "Failed to remove file %s"
msgstr ""

#: shared/utils/utils.go:168
msgid "Failed to read random data"
msgstr ""

#: shared/utils/utils.go:179
#, javascript-format
msgid "error downloading from %s: %s"
msgstr ""

#: shared/utils/utils.go:185
#, javascript-format
msgid "bad status: %s"
msgstr ""

#: shared/connection.go:56
#, javascript-format
msgid "backend command not found in PATH: %s"
msgstr ""

#: shared/connection.go:68
msgid "kubectl not configured to connect to a cluster, ignoring"
msgstr ""

#: shared/connection.go:101
msgid "uyuni container is not accessible with one of podman, podman-remote or kubectl"
msgstr ""

#: shared/connection.go:104
#, javascript-format
msgid "unsupported backend %s"
msgstr ""

#: shared/connection.go:125
#, javascript-format
msgid "container %s is not running on podman"
msgstr ""

#: shared/connection.go:146
#, javascript-format
msgid "the container is not running, %s %s command not executed: %s"
msgstr ""

#: shared/connection.go:193
msgid "server didn't start within 60s. Check for the service status"
msgstr ""

#: shared/connection.go:223 shared/connection.go:262
#, javascript-format
msgid "unknown container kind: %s"
msgstr ""

#: shared/connection.go:306
msgid "failed to determine suitable backend"
msgstr ""

#: shared/connection.go:316
msgid "no supported backend found"
msgstr ""
07070100000031000081B4000000000000000000000001662A752800004A38000000000000000000000000000000000000002500000000uyuni-tools/locale/shared/shared.pot# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 15:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: shared/api/org/createFirst.go:21
#, javascript-format
msgid "failed to connect to the server: %s"
msgstr ""

#: shared/api/org/createFirst.go:35
#, javascript-format
msgid "failed to create first user and organization: %s"
msgstr ""

#: shared/api/api.go:71
msgid "FQDN of the server to connect to"
msgstr ""

#: shared/api/api.go:72
msgid "API user username"
msgstr ""

#: shared/api/api.go:73
msgid "Password for the API user"
msgstr ""

#: shared/api/api.go:74
msgid "Path to a cert file of the CA"
msgstr ""

#: shared/api/api.go:75
msgid "If set, server certificate will not be checked for validity"
msgstr ""

#: shared/api/api.go:124
#, javascript-format
msgid "unknown error: %d"
msgstr ""

#: shared/api/api.go:166
msgid "API server password"
msgstr ""

#: shared/api/api.go:181
msgid "Unable to create login data"
msgstr ""

#: shared/api/api.go:211
msgid "auth cookie not found in login response"
msgstr ""

#: shared/api/api.go:227
msgid "Unable to convert data to JSON"
msgstr ""

#: shared/completion/completion.go:20 shared/completion/completion.go:21
msgid "Generate shell completion script"
msgstr ""

#: shared/completion/completion.go:30 shared/completion/completion.go:34
#: shared/completion/completion.go:38
#, javascript-format
msgid "cannot generate %s completion: %s"
msgstr ""

#: shared/kubernetes/helm.go:56
#, javascript-format
msgid "failed to %s helm chart %s in namespace %s"
msgstr ""

#: shared/kubernetes/helm.go:76
#, javascript-format
msgid "Failed to find %s's namespace, skipping removal"
msgstr ""

#: shared/kubernetes/helm.go:84
msgid "Cannot guess namespace"
msgstr ""

#: shared/kubernetes/helm.go:93 shared/podman/network.go:94
#: shared/podman/systemd.go:47 shared/podman/systemd.go:70
#: shared/podman/systemd.go:71 shared/podman/utils.go:98
#, javascript-format
msgid "Would run %s"
msgstr ""

#: shared/kubernetes/helm.go:95
#, javascript-format
msgid "Uninstalling %s"
msgstr ""

#: shared/kubernetes/helm.go:97
#, javascript-format
msgid "failed to run helm %s: %s"
msgstr ""

#: shared/kubernetes/helm.go:113
#, javascript-format
msgid "failed to detect %s's namespace using helm: %s"
msgstr ""

#: shared/kubernetes/helm.go:117
#, javascript-format
msgid "helm provided an invalid JSON output: %s"
msgstr ""

#: shared/kubernetes/helm.go:123
msgid "found no or more than one deployment"
msgstr ""

#: shared/kubernetes/k3s.go:21
msgid "Installing K3s Traefik configuration"
msgstr ""

#: shared/kubernetes/k3s.go:28
msgid "Failed to write K3s Traefik configuration"
msgstr ""

#: shared/kubernetes/k3s.go:32
msgid "Waiting for Traefik to be reloaded"
msgstr ""

#: shared/kubernetes/kubernetes.go:54
#, javascript-format
msgid "failed to get kubelet version: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:73
msgid "No ingressroutetcp resource deployed"
msgstr ""

#: shared/kubernetes/kubernetes.go:80
#, javascript-format
msgid "failed to get pod commands to look for nginx controller: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:94
#, javascript-format
msgid "cannot stop %s: %s"
msgstr ""

#: shared/kubernetes/kubernetes.go:105
msgid "Already running"
msgstr ""

#: shared/kubernetes/rke2.go:21
msgid "Installing RKE2 Nginx configuration"
msgstr ""

#: shared/kubernetes/rke2.go:29
msgid "Failed to write Rke2 nginx configuration"
msgstr ""

#: shared/kubernetes/rke2.go:33
msgid "Waiting for Nginx controller to be reloaded"
msgstr ""

#: shared/kubernetes/uninstall.go:13
msgid ""
"\n"
"Note that removing the volumes could also be handled automatically depending on the StorageClass used\n"
"when installed on a kubernetes cluster.\n"
"\n"
"For instance on a default K3S install, the local-path-provider storage volumes will\n"
"be automatically removed when deleting the deployment even if --purge-volumes argument is not used."
msgstr ""

#: shared/kubernetes/utils.go:51
#, javascript-format
msgid "failed to pull image: %s"
msgstr ""

#: shared/kubernetes/utils.go:54
#, javascript-format
msgid "Waiting for %s deployment to be ready in %s namespace\n"
msgstr ""

#: shared/kubernetes/utils.go:63
#, javascript-format
msgid "failed to find a ready replica for deployment %s in namespace %s after 60s"
msgstr ""

#: shared/kubernetes/utils.go:68
#, javascript-format
msgid "Waiting for image of %s pod in %s namespace to be pulled"
msgstr ""

#: shared/kubernetes/utils.go:81
#, javascript-format
msgid "failed to get failed events for pod %s"
msgstr ""

#: shared/kubernetes/utils.go:86
msgid "failed to pull image"
msgstr ""

#: shared/kubernetes/utils.go:93
#, javascript-format
msgid "failed to get events for pod %s"
msgstr ""

#: shared/kubernetes/utils.go:139
#, javascript-format
msgid "failed to parse deployment status: %s"
msgstr ""

#: shared/kubernetes/utils.go:153
#, javascript-format
msgid "cannot run kubectl %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:158
#, javascript-format
msgid "cannot get pods for %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:165
#, javascript-format
msgid "replica to %d failed: %s"
msgstr ""

#: shared/kubernetes/utils.go:178
#, javascript-format
msgid "cannot check if pod %s is running in app %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:188 shared/kubernetes/utils.go:233
#: shared/kubernetes/utils.go:327
#, javascript-format
msgid "cannot execute %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:207
#, javascript-format
msgid "cannot get pod informations %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:216
#, javascript-format
msgid "cannot set replicas for %s to zero"
msgstr ""

#: shared/kubernetes/utils.go:243
#, javascript-format
msgid "pod %s replica is not %d in %s seconds: %s"
msgstr ""

#: shared/kubernetes/utils.go:266
#, javascript-format
msgid "%s is not a valid image pull policy value"
msgstr ""

#: shared/kubernetes/utils.go:286
#, javascript-format
msgid "cannot run %s using image %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:290
#, javascript-format
msgid "deleting pod %s. Status fails with error %s"
msgstr ""

#: shared/kubernetes/utils.go:303 shared/kubernetes/utils.go:312
#, javascript-format
msgid "cannot delete pod %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:334
#, javascript-format
msgid "error during execution of %s: %s"
msgstr ""

#: shared/kubernetes/utils.go:339
#, javascript-format
msgid "pod %s status is not %s in %s seconds: %s"
msgstr ""

#: shared/kubernetes/utils.go:356
#, javascript-format
msgid "cannot find node name matching filter %s"
msgstr ""

#: shared/kubernetes/utils.go:365
#, javascript-format
msgid "cannot serialize pod definition override: %s"
msgstr ""

#: shared/podman/images.go:35
#, javascript-format
msgid "Ensure image %s is available"
msgstr ""

#: shared/podman/images.go:54
#, javascript-format
msgid "Cannot use RPM image for %s: %s"
msgstr ""

#: shared/podman/images.go:56
#, javascript-format
msgid "Using the %s image loaded from the RPM instead of its online version %s"
msgstr ""

#: shared/podman/images.go:60
#, javascript-format
msgid "Cannot find RPM image for %s"
msgstr ""

#: shared/podman/images.go:68
#, javascript-format
msgid "image %s is missing and cannot be fetched"
msgstr ""

#: shared/podman/images.go:93
#, javascript-format
msgid "cannot unmarshal image RPM metadata: %s"
msgstr ""

#: shared/podman/images.go:138
#, javascript-format
msgid "Cannot unmarshal metadata file %s: %s"
msgstr ""

#: shared/podman/images.go:160
#, javascript-format
msgid "error parsing: %s"
msgstr ""

#: shared/podman/images.go:168 shared/podman/images.go:182
#: shared/podman/images.go:197
#, javascript-format
msgid "failed to check if image %s has already been pulled"
msgstr ""

#: shared/podman/images.go:203
#, javascript-format
msgid "Pulling image %s"
msgstr ""

#: shared/podman/images.go:222
#, javascript-format
msgid "cannot find any tag for image %s: %s"
msgstr ""

#: shared/podman/network.go:23
#, javascript-format
msgid "Setting up %s network"
msgstr ""

#: shared/podman/network.go:35
#, javascript-format
msgid "%s network doesn't have IPv6, deleting existing network to enable IPv6 on it"
msgstr ""

#: shared/podman/network.go:39
#, javascript-format
msgid "failed to remove %s podman network: %s"
msgstr ""

#: shared/podman/network.go:42
#, javascript-format
msgid "Reusing existing %s network"
msgstr ""

#: shared/podman/network.go:55
#, javascript-format
msgid "failed to find podman's network backend: %s"
msgstr ""

#: shared/podman/network.go:57
#, javascript-format
msgid "Podman's network backend (%s) is not netavark, skipping IPv6 enabling on %s network"
msgstr ""

#: shared/podman/network.go:65
#, javascript-format
msgid "failed to create %s network with IPv6 enabled: %s"
msgstr ""

#: shared/podman/network.go:91
#, javascript-format
msgid "Network %s already removed"
msgstr ""

#: shared/podman/network.go:98
#, javascript-format
msgid "Failed to remove network %s"
msgstr ""

#: shared/podman/network.go:100
msgid "Network removed"
msgstr ""

#: shared/podman/systemd.go:44
#, javascript-format
msgid "Systemd has no %s.service unit"
msgstr ""

#: shared/podman/systemd.go:48
#, javascript-format
msgid "Would remove %s"
msgstr ""

#: shared/podman/systemd.go:50
#, javascript-format
msgid "Disable %s service"
msgstr ""

#: shared/podman/systemd.go:54
#, javascript-format
msgid "Failed to disable %s service"
msgstr ""

#: shared/podman/systemd.go:58
#, javascript-format
msgid "Remove %s"
msgstr ""

#: shared/podman/systemd.go:60
#, javascript-format
msgid "Failed to remove %s.service file"
msgstr ""

#: shared/podman/systemd.go:75
msgid "failed to reset-failed systemd"
msgstr ""

#: shared/podman/systemd.go:79
msgid "failed to reload systemd daemon"
msgstr ""

#: shared/podman/systemd.go:97
#, javascript-format
msgid "failed to restart systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:105
#, javascript-format
msgid "failed to start systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:113
#, javascript-format
msgid "failed to stop systemd %s.service: %s"
msgstr ""

#: shared/podman/systemd.go:121
#, javascript-format
msgid "failed to enable %s systemd service: %s"
msgstr ""

#: shared/podman/systemd.go:132
#, javascript-format
msgid "failed to create %s folder: %s"
msgstr ""

#: shared/podman/systemd.go:138
#, javascript-format
msgid "cannot write %s file: %s"
msgstr ""

#: shared/podman/utils.go:47
msgid "Extra arguments to pass to podman"
msgstr ""

#: shared/podman/utils.go:53
msgid "Path to custom /var/cache volume"
msgstr ""

#: shared/podman/utils.go:54
msgid "Path to custom /var/lib/pgsql volume"
msgstr ""

#: shared/podman/utils.go:55
msgid "Path to custom /var/spacewalk volume"
msgstr ""

#: shared/podman/utils.go:62
#, javascript-format
msgid "failed to enable podman.socket unit: %s"
msgstr ""

#: shared/podman/utils.go:72
#, javascript-format
msgid "Would run podman kill %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:73
#, javascript-format
msgid "Would run podman remove %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:75
#, javascript-format
msgid "Run podman kill %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:78
msgid "Failed to kill the server"
msgstr ""

#: shared/podman/utils.go:80
#, javascript-format
msgid "Run podman remove %s for container id: %s"
msgstr ""

#: shared/podman/utils.go:83
msgid "Error removing container"
msgstr ""

#: shared/podman/utils.go:88
msgid "Container already removed"
msgstr ""

#: shared/podman/utils.go:100
#, javascript-format
msgid "Run %s"
msgstr ""

#: shared/podman/utils.go:103
#, javascript-format
msgid "Failed to remove volume %s"
msgstr ""

#: shared/podman/utils.go:134
#, javascript-format
msgid "volume folder (%s) already exists, cannot link it to %s"
msgstr ""

#: shared/podman/utils.go:138
#, javascript-format
msgid "failed to create volumes folder %s: %s"
msgstr ""

#: shared/podman/utils.go:142
#, javascript-format
msgid "failed to link volume folder %s to %s: %s"
msgstr ""

#: shared/podman/utils.go:152
#, javascript-format
msgid "failed to get podman's volumes folder: %s"
msgstr ""

#: shared/utils/cmd.go:47 shared/utils/cmd.go:48
msgid "failed to unmarshall configuration"
msgstr ""

#: shared/utils/cmd.go:55
msgid "tool to use to reach the container. Possible values: 'podman', 'podman-remote', 'kubectl'. Default guesses which to use."
msgstr ""

#: shared/utils/cmd.go:69
msgid "set whether to pull the images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"
msgstr ""

#: shared/utils/config.go:33
#, javascript-format
msgid "Using config file %s"
msgstr ""

#: shared/utils/config.go:40
msgid "Failed to find home directory"
msgstr ""

#: shared/utils/config.go:59
#, javascript-format
msgid "failed to parse configuration file %s: %s"
msgstr ""

#: shared/utils/config.go:78
#, javascript-format
msgid "failed to bind %s config to parameter %s: %s"
msgstr ""

#: shared/utils/config.go:90
msgid ""
"Usage:{{if .Runnable}}\n"
"  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}\n"
"  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}\n"
"\n"
"Aliases:\n"
"  {{.NameAndAliases}}{{end}}{{if .HasExample}}\n"
"\n"
"Examples:\n"
"{{.Example}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name \"help\"))}}\n"
"  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\n"
"\n"
"Flags:\n"
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\n"
"\n"
"Global Flags:\n"
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}\n"
"\n"
"Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}\n"
"  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}\n"
"\n"
"Use \"{{.CommandPath}} [command] --help\" for more information about a command.{{end}}\n"
msgstr ""

#: shared/utils/config.go:118
msgid ""
"\n"
"Configuration:\n"
"\n"
"  All the non-global flags can alternatively be passed as configuration.\n"
"  \n"
"  The configuration file is a YAML file with entries matching the flag name.\n"
"  The name of a flag is the part after the '--' of the command line parameter.\n"
"  Every '_' character in the flag name means a nested property.\n"
"  \n"
"  For instance the '--tz CEST' and '--ssl-password secret' will be mapped to\n"
"  this YAML configuration:\n"
"  \n"
"    tz: CEST\n"
"    ssl:\n"
"      password: secret\n"
"  \n"
"  The configuration file will be searched in the following places and order:\n"
"  · $XDG_CONFIG_HOME/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $HOME/.config/{{ .Name }}/{{ .ConfigFile }}\n"
"  · $PWD/{{ .ConfigFile }}\n"
"  · the value of the --config flag\n"
"\n"
"\n"
"Environment variables:\n"
"\n"
"  All the non-global flags can also be passed as environment variables.\n"
"  \n"
"  The environment variable name is the flag name with '-' replaced by with '_'\n"
"  and the {{ .EnvPrefix }} prefix.\n"
"  \n"
"  For example the '--tz CEST' flag will be mapped to '{{ .EnvPrefix }}_TZ'\n"
"  and '--ssl-password' flags to '{{ .EnvPrefix }}_SSL_PASSWORD' \n"
msgstr ""

#: shared/utils/config.go:163
msgid "failed to compute config help command"
msgstr ""

#: shared/utils/tar.go:48
#, javascript-format
msgid "Skipping extraction of %s in %s file as it resolves outside the target path"
msgstr ""

#: shared/utils/tar.go:90
#, javascript-format
msgid "failed to write tar.gz to %s: %s"
msgstr ""

#: shared/utils/template.go:25
#, javascript-format
msgid "%s file already present, not overwriting"
msgstr ""

#: shared/utils/template.go:32
#, javascript-format
msgid "failed to open %s for writing: %s"
msgstr ""

#: shared/utils/utils.go:34
#, javascript-format
msgid "Has to be more than %d character long"
msgid_plural "Has to be more than %d characters long"
msgstr[0] ""
msgstr[1] ""

#: shared/utils/utils.go:38
#, javascript-format
msgid "Has to be less than %d character long"
msgid_plural "Has to be less than %d characters long"
msgstr[0] ""
msgstr[1] ""

#: shared/utils/utils.go:51
msgid "Failed to read password"
msgstr ""

#: shared/utils/utils.go:57
msgid "Cannot contain spaces or tabs"
msgstr ""

#: shared/utils/utils.go:78
msgid "Failed to read input"
msgstr ""

#: shared/utils/utils.go:86
msgid "A value is required"
msgstr ""

#: shared/utils/utils.go:96
#, javascript-format
msgid "invalid image name: %s"
msgstr ""

#: shared/utils/utils.go:100
#, javascript-format
msgid "tag missing on %s"
msgstr ""

#: shared/utils/utils.go:119
#, javascript-format
msgid "Failed to run %s"
msgstr ""

#: shared/utils/utils.go:130
#, javascript-format
msgid "Failed to get %s file informations"
msgstr ""

#: shared/utils/utils.go:139
#, javascript-format
msgid "Failed to read file %s"
msgstr ""

#: shared/utils/utils.go:154
#, javascript-format
msgid "Would remove file %s"
msgstr ""

#: shared/utils/utils.go:156
#, javascript-format
msgid "Removing file %s"
msgstr ""

#: shared/utils/utils.go:158
#, javascript-format
msgid "Failed to remove file %s"
msgstr ""

#: shared/utils/utils.go:168
msgid "Failed to read random data"
msgstr ""

#: shared/utils/utils.go:179
#, javascript-format
msgid "error downloading from %s: %s"
msgstr ""

#: shared/utils/utils.go:185
#, javascript-format
msgid "bad status: %s"
msgstr ""

#: shared/connection.go:56
#, javascript-format
msgid "backend command not found in PATH: %s"
msgstr ""

#: shared/connection.go:68
msgid "kubectl not configured to connect to a cluster, ignoring"
msgstr ""

#: shared/connection.go:101
msgid "uyuni container is not accessible with one of podman, podman-remote or kubectl"
msgstr ""

#: shared/connection.go:104
#, javascript-format
msgid "unsupported backend %s"
msgstr ""

#: shared/connection.go:125
#, javascript-format
msgid "container %s is not running on podman"
msgstr ""

#: shared/connection.go:146
#, javascript-format
msgid "the container is not running, %s %s command not executed: %s"
msgstr ""

#: shared/connection.go:193
msgid "server didn't start within 60s. Check for the service status"
msgstr ""

#: shared/connection.go:223 shared/connection.go:262
#, javascript-format
msgid "unknown container kind: %s"
msgstr ""

#: shared/connection.go:306
msgid "failed to determine suitable backend"
msgstr ""

#: shared/connection.go:316
msgid "no supported backend found"
msgstr ""
07070100000032000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000001300000000uyuni-tools/mgradm07070100000033000041FD00000000000000000000000F662A752800000000000000000000000000000000000000000000001700000000uyuni-tools/mgradm/cmd07070100000034000081B4000000000000000000000001662A752800000CBC000000000000000000000000000000000000001E00000000uyuni-tools/mgradm/cmd/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
	"os"
	"path"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/completion"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"

	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/distro"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/gpg"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/hub"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/inspect"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/install"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/restart"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/start"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/status"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/stop"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/support"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/uninstall"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/upgrade"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

// NewCommand returns a new cobra.Command implementing the root command for kinder.
func NewUyuniadmCommand() (*cobra.Command, error) {
	globalFlags := &types.GlobalFlags{}
	name := path.Base(os.Args[0])
	rootCmd := &cobra.Command{
		Use:          name,
		Short:        L("Uyuni administration tool"),
		Long:         L("Tool to help administering Uyuni servers in containers"),
		Version:      utils.Version,
		SilenceUsage: true, // Don't show usage help on errors
	}

	rootCmd.SetUsageTemplate(utils.GetLocalizedUsageTemplate())

	rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
		utils.LogInit(true)
		utils.SetLogLevel(globalFlags.LogLevel)

		// do not log if running the completion cmd as the output is redirected to create a file to source
		if cmd.Name() != "completion" {
			log.Info().Msgf(L("Welcome to %s"), name)
			log.Info().Msgf(L("Executing command: %s"), cmd.Name())
		}
	}

	rootCmd.PersistentFlags().StringVarP(&globalFlags.ConfigPath, "config", "c", "", L("configuration file path"))
	rootCmd.PersistentFlags().StringVar(&globalFlags.LogLevel, "logLevel", "", L("application log level")+"(trace|debug|info|warn|error|fatal|panic)")

	migrateCmd := migrate.NewCommand(globalFlags)
	rootCmd.AddCommand(migrateCmd)

	installCmd := install.NewCommand(globalFlags)
	rootCmd.AddCommand(installCmd)

	rootCmd.AddCommand(uninstall.NewCommand(globalFlags))
	distroCmd, err := distro.NewCommand(globalFlags)
	if err != nil {
		return rootCmd, err
	}
	rootCmd.AddCommand(distroCmd)
	rootCmd.AddCommand(completion.NewCommand(globalFlags))
	rootCmd.AddCommand(support.NewCommand(globalFlags))
	rootCmd.AddCommand(start.NewCommand(globalFlags))
	rootCmd.AddCommand(hub.NewCommand(globalFlags))
	rootCmd.AddCommand(restart.NewCommand(globalFlags))
	rootCmd.AddCommand(stop.NewCommand(globalFlags))
	rootCmd.AddCommand(status.NewCommand(globalFlags))
	rootCmd.AddCommand(inspect.NewCommand(globalFlags))
	rootCmd.AddCommand(upgrade.NewCommand(globalFlags))
	rootCmd.AddCommand(gpg.NewCommand(globalFlags))

	rootCmd.AddCommand(utils.GetConfigHelpCommand())

	return rootCmd, err
}
07070100000035000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/mgradm/cmd/distro07070100000036000081B4000000000000000000000001662A7528000014FD000000000000000000000000000000000000002400000000uyuni-tools/mgradm/cmd/distro/cp.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package distro

import (
	"fmt"
	"os"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func umountAndRemove(mountpoint string) {
	umountCmd := []string{
		"/usr/bin/umount",
		mountpoint,
	}

	if err := utils.RunCmd("/usr/bin/sudo", umountCmd...); err != nil {
		log.Error().Err(err).Msgf(L("Unable to unmount ISO image, leaving %s intact"), mountpoint)
	}

	if err := os.Remove(mountpoint); err != nil {
		log.Error().Err(err).Msgf(L("unable to remove temporary directory, leaving %s intact"), mountpoint)
	}
}

func registerDistro(connection *api.ConnectionDetails, distro *types.Distribution) error {
	client, err := api.Init(connection)
	if err != nil {
		return fmt.Errorf(L("unable to login and register the distribution. Manual distro registration is required: %s"), err)
	}
	data := map[string]interface{}{
		"treeLabel":    distro.TreeLabel,
		"basePath":     distro.BasePath,
		"channelLabel": distro.ChannelLabel,
		"installType":  distro.InstallType,
	}

	_, err = client.Post("kickstart/tree/create", data)
	if err != nil {
		return fmt.Errorf(L("unable to register the distribution. Manual distro registration is required: %s"), err)
	}
	log.Info().Msgf(L("Distribution %s successfully registered"), distro.TreeLabel)
	return nil
}

func prepareSource(source string) (string, bool, error) {
	srcdir := source
	needremove := false
	if strings.HasSuffix(source, ".iso") {
		log.Debug().Msg("Source is an ISO image")
		tmpdir, err := os.MkdirTemp("", "mgradm-distcp")
		if err != nil {
			return "", needremove, err
		}
		srcdir = tmpdir

		mountCmd := []string{
			"/usr/bin/mount",
			"-o", "ro,loop",
			source,
			srcdir,
		}
		if out, err := utils.RunCmdOutput(zerolog.DebugLevel, "/usr/bin/sudo", mountCmd...); err != nil {
			log.Debug().Msgf("Error mounting ISO image: '%s'", out)
			return "", needremove, fmt.Errorf(L("unable to mount ISO image: %s"), out)
		}
		needremove = true
	}
	return srcdir, needremove, nil
}

func copyDistro(srcdir string, distro types.Distribution, flags *flagpole) error {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)

	const distrosPath = "/srv/www/distributions/"
	dstpath := distrosPath + distro.TreeLabel
	distro.BasePath = dstpath
	if cnx.TestExistenceInPod(dstpath) {
		return fmt.Errorf(L("distribution with same name already exists: %s"), dstpath)
	}

	if _, err := cnx.Exec("sh", "-c", "mkdir -p "+distrosPath); err != nil {
		return fmt.Errorf(L("cannot create %s path in container: %s"), distrosPath, err)
	}

	log.Info().Msgf(L("Copying distribution %s"), distro.TreeLabel)
	if err := cnx.Copy(srcdir, "server:"+dstpath, "tomcat", "susemanager"); err != nil {
		return fmt.Errorf(L("cannot copy %s: %s"), dstpath, err)
	}
	log.Info().Msgf(L("Distribution has been copied into %s"), distro.BasePath)
	return nil
}

func getServerFqdn(flags *flagpole) (string, error) {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)
	fqdn, err := cnx.Exec("sh", "-c", "cat /etc/rhn/rhn.conf 2>/dev/null | grep 'java.hostname' | cut -d' ' -f3")
	return strings.TrimSuffix(string(fqdn), "\n"), err
}

func distroCp(
	globalFlags *types.GlobalFlags,
	flags *flagpole,
	cmd *cobra.Command,
	args []string,
) error {
	source := args[0]
	distroDetails := types.DistributionDetails{}
	if len(args) >= 2 {
		distroDetails.Name = args[1]
		if len(args) > 3 {
			distroDetails.Version = args[2]
			distroDetails.Arch = types.GetArch(args[3])
		}
	}
	channelLabel := flags.ChannelLabel

	if !utils.FileExists(source) {
		return fmt.Errorf(L("source %s does not exists"), source)
	}

	srcdir, needremove, err := prepareSource(source)
	if err != nil {
		return err
	}
	if needremove {
		defer umountAndRemove(srcdir)
	}

	distribution := types.Distribution{}
	if err := detectDistro(srcdir, distroDetails, channelLabel, flags, &distribution); err != nil {
		// If we do not want to do the registration, we don't need all the details for mere copy, just name
		if flags.ConnectionDetails.User != "" || distroDetails.Name == "" {
			return err
		}
		log.Debug().Msgf("Would not be able to auto register")
		distribution.TreeLabel = distroDetails.Name
	}

	if len(args) == 1 {
		log.Info().Msgf(L("Auto-detected distribution %s"), distribution.TreeLabel)
	}

	if err := copyDistro(srcdir, distribution, flags); err != nil {
		return err
	}

	// Fill server FQDN if not provided, ignore error, will be hanled later
	if flags.ConnectionDetails.Server == "" {
		flags.ConnectionDetails.Server, _ = getServerFqdn(flags)
		log.Debug().Msgf("Using api-server FQDN '%s'", flags.ConnectionDetails.Server)
	}

	if flags.ConnectionDetails.User != "" && flags.ConnectionDetails.Password != "" {
		return registerDistro(&flags.ConnectionDetails, &distribution)
	}
	log.Info().Msgf(L("Continue by registering autoinstallation distribution"))
	return nil
}
07070100000037000081B4000000000000000000000001662A7528000011F0000000000000000000000000000000000000002800000000uyuni-tools/mgradm/cmd/distro/detect.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package distro

import (
	"fmt"
	"path/filepath"

	"github.com/rs/zerolog/log"
	"github.com/spf13/viper"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

var productMap = map[string]map[string]map[types.Arch]types.Distribution{
	"SUSE Linux Enterprise": {
		"15 SP4": {
			types.AMD64: {
				TreeLabel:    "SLES15SP4",
				InstallType:  "sles15generic",
				ChannelLabel: "sle-product-sles15-sp4-pool-x86_64",
			},
		},
		"15 SP5": {
			types.AMD64: {
				TreeLabel:    "SLES15SP5",
				InstallType:  "sles15generic",
				ChannelLabel: "sle-product-sles15-sp5-pool-x86_64",
			},
		},
		"15 SP6": {
			types.AMD64: {
				TreeLabel:    "SLES15SP6",
				InstallType:  "sles15generic",
				ChannelLabel: "sle-product-sles15-sp6-pool-x86_64",
			},
			types.AArch64: {
				TreeLabel:    "SLES15SP6",
				InstallType:  "sles15generic",
				ChannelLabel: "sle-product-sles15-sp6-pool-aarch64",
			},
		},
		"12 SP5": {
			types.AMD64: {
				TreeLabel:    "SLES12SP5",
				InstallType:  "sles12generic",
				ChannelLabel: "sles12-sp5-pool-x86_64",
			},
		},
	},

	"Red Hat Enterprise Linux": {
		"7": {
			types.AMD64: {
				TreeLabel:    "RHEL7",
				InstallType:  "rhel_7",
				ChannelLabel: "rhel7-pool-x86_64",
			},
		},
		"8": {
			types.AMD64: {
				TreeLabel:    "RHEL8",
				InstallType:  "rhel_8",
				ChannelLabel: "rhel8-pool-x86_64",
			},
		},
		"9": {
			types.AMD64: {
				TreeLabel:    "RHEL9",
				InstallType:  "rhel_9",
				ChannelLabel: "rhel9-pool-x86_64",
			},
		},
	},
}

func getDistroFromDetails(distro string, version string, arch types.Arch, flags *flagpole) (types.Distribution, error) {
	productFromConfig := flags.ProductMap
	var distribution types.Distribution
	var ok bool

	if productFromConfig[distro] != nil {
		distribution, ok = productFromConfig[distro][version][arch]
	} else if productMap[distro] != nil {
		distribution, ok = productMap[distro][version][arch]
	}

	if !ok {
		return types.Distribution{}, fmt.Errorf(L("distribution not found in product map. Please update productmap or provide channel label"))
	}
	return distribution, nil
}

func getDistroFromTreeinfo(path string, flags *flagpole) (types.Distribution, error) {
	treeinfopath := filepath.Join(path, ".treeinfo")
	log.Debug().Msgf("Reading .treeinfo %s", treeinfopath)
	treeInfoViper := viper.New()
	treeInfoViper.SetConfigType("ini")
	treeInfoViper.SetConfigName(".treeinfo")
	treeInfoViper.AddConfigPath(path)
	if err := treeInfoViper.ReadInConfig(); err != nil {
		return types.Distribution{}, fmt.Errorf(L("unable to read distribution treeinfo. Please provide distribution details and/or channel label"))
	}

	dname := treeInfoViper.GetString("release.name")
	dversion := treeInfoViper.GetString("release.version")
	darch := treeInfoViper.GetString("general.arch")
	log.Debug().Msgf("Detected distribution %s, version %s. arch %s", dname, dversion, darch)

	return getDistroFromDetails(dname, dversion, types.GetArch(darch), flags)
}

func detectDistro(path string, distroDetails types.DistributionDetails, channelLabel string, flags *flagpole, distro *types.Distribution) error {
	treeinfopath := filepath.Join(path, ".treeinfo")
	if !utils.FileExists(treeinfopath) {
		log.Debug().Msgf(".treeinfo %s does not exists", treeinfopath)
		if distroDetails.Name != "" {
			if channelLabel != "" {
				log.Debug().Msg("Using channel override")
				*distro = types.Distribution{
					InstallType:  "generic_rpm",
					TreeLabel:    distroDetails.Name,
					ChannelLabel: channelLabel,
				}
				return nil
			} else if distroDetails.Version != "" && distroDetails.Arch != types.UnknownArch {
				log.Debug().Msg("Using distro details override")
				var err error
				*distro, err = getDistroFromDetails(distroDetails.Name, distroDetails.Version, distroDetails.Arch, flags)
				return err
			}
			return fmt.Errorf(L("distribution treeinfo %s does not exists. Please provide distribution details and/or channel label"), treeinfopath)
		}
		return fmt.Errorf(L("distribution treeinfo %s does not exists. Please provide distribution details and/or channel label"), treeinfopath)
	} else {
		var err error
		*distro, err = getDistroFromTreeinfo(path, flags)
		if err != nil {
			return err
		}

		// Overrides from the command line
		if distroDetails.Name != "" {
			distro.TreeLabel = distroDetails.Name
		}
		if channelLabel != "" {
			distro.ChannelLabel = channelLabel
		}
	}

	return nil
}
07070100000038000081B4000000000000000000000001662A752800001199000000000000000000000000000000000000002800000000uyuni-tools/mgradm/cmd/distro/distro.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package distro

import (
	"strings"
	"text/template"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
	"gopkg.in/yaml.v2"
)

type flagpole struct {
	Backend           string
	ChannelLabel      string `mapstructure:"channel"`
	ProductMap        map[string]map[string]map[types.Arch]types.Distribution
	ConnectionDetails api.ConnectionDetails `mapstructure:"api"`
}

type productMapTemplateData struct {
	DefaultProductMap string
}

func getProductMapHelp() string {
	return L(`Auto installation distribution product mapping.

For distribution to be registered by the Uyuni server it is important to map distribution to the correct software channel.

Software channels can be named differently without any corellation to distribution name; it is then needed to allow custom distribution name to software channel mapping.

One way to set software channel is by flag --channel to the distribution copy command.

For frequent usage it is possible to write custom product mapping to the mgradm configuration file in the following format:

ProductMap:
  <distribution name>:
    <distribution version>:
      <distribution architecture>:
        ChannelLabel: <channel label>
        InstallType: <one of rhel_7|rhel_8|rhel_9|sles12generic|sles15generic|generic_rpm>
        TreeName: <custom distribution name>

Where
* <distribution name> is the name of the distribution, by default taken from '.treeinfo' file from the media. If '.treeinfo' is not found or available, command line option is required and used.
* <distribution version> is the version of the distribution, by default taken from '.treeinfo' file from the media. If'.treeinfo' is not found, command line option is required and used.
* <distribution architecture> is distribution architecture, by default taken from '.treeinfo' file from the media. If '.treeinfo' is not found, command line option is required and used.
* ChannelLabel is the channel label from Uyuni server and which is to be used for this distribution; can be overridden by command line flag.
* InstallType is used when installer is known (for autoyast or kickstart) or use 'generic_rpm'.
* TreeName is how the distribution will be presented in the Uyuni server UI. If not set <distribution name> is used.

Build-in product map:

{{ .DefaultProductMap }}
`)
}

// NewCommand command for distribution management.
func NewCommand(globalFlags *types.GlobalFlags) (*cobra.Command, error) {
	var flags flagpole

	distroCmd := &cobra.Command{
		Use:     "distribution",
		Short:   L("Distributions management"),
		Long:    L("Tools for autoinstallation distributions management"),
		Aliases: []string{"distro"},
	}

	cpCmd := &cobra.Command{
		Use:   "copy path-to-source [distribution-name version arch]",
		Short: L("Copy distribution files from iso to the container"),
		Long: L(`Takes a path to source iso file or directory with mounted iso and copies it into the container.

Optional parameters 'distribution-name', 'version' and 'arch' specifies custom distribution. If not set, distribution name is autodetected.

Note: API details are required for auto registration.`),
		Aliases: []string{"cp"},
		RunE: func(cmd *cobra.Command, args []string) error {
			return utils.CommandHelper(globalFlags, cmd, args, &flags, distroCp)
		},
	}
	cpCmd.Flags().String("channel", "", L("Set parent channel for the distribution."))

	cpCmdHelp := &cobra.Command{
		Use:   "productmap",
		Short: L("Help on using custom distribution product map"),
	}
	prettyPrintedProductMap := ""
	if prettyPrintedProductMapBytes, err := yaml.Marshal(map[string]interface{}{"ProductMap": productMap}); err == nil {
		prettyPrintedProductMap = string(prettyPrintedProductMapBytes)
	}

	t := template.Must(template.New("help").Parse(getProductMapHelp()))
	var helpBuilder strings.Builder
	if err := t.Execute(&helpBuilder, productMapTemplateData{
		DefaultProductMap: prettyPrintedProductMap,
	}); err != nil {
		log.Fatal().Err(err).Msg(L("failed to compute config help command"))
	}
	cpCmdHelp.SetHelpTemplate(helpBuilder.String())

	if err := api.AddAPIFlags(distroCmd, true); err != nil {
		return distroCmd, err
	}
	distroCmd.AddCommand(cpCmd)
	distroCmd.AddCommand(cpCmdHelp)

	return distroCmd, nil
}
07070100000039000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001B00000000uyuni-tools/mgradm/cmd/gpg0707010000003A000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/gpg/add0707010000003B000081B4000000000000000000000001662A752800001090000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/gpg/add/gpg.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package gpgadd

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const customKeyringPath = "/var/spacewalk/gpg/customer-build-keys.gpg"

type gpgAddFlags struct {
	Backend string
	Force   bool
}

// NewCommand import gpg keys from 3rd party repository.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	gpgAddKeyCmd := &cobra.Command{
		Use:   "add [URL]...",
		Short: L("Add GPG keys for 3rd party repositories"),
		Args:  cobra.MinimumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags gpgAddFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, gpgAddKeys)
		},
	}

	gpgAddKeyCmd.Flags().BoolP("force", "f", false, L("Import without asking confirmation"))
	utils.AddBackendFlag(gpgAddKeyCmd)
	return gpgAddKeyCmd
}

func gpgAddKeys(globalFlags *types.GlobalFlags, flags *gpgAddFlags, cmd *cobra.Command, args []string) error {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)
	if !utils.FileExists(customKeyringPath) {
		if err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, "mkdir", "-m", "700", "-p", filepath.Dir(customKeyringPath)); err != nil {
			return fmt.Errorf(L("failed to create folder %s: %s"), filepath.Dir(customKeyringPath), err)
		}
		if err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, "gpg", "--no-default-keyring", "--keyring", customKeyringPath, "--fingerprint"); err != nil {
			return fmt.Errorf(L("failed to create keyring %s: %s"), customKeyringPath, err)
		}
	}
	gpgAddCmd := []string{"gpg", "--no-default-keyring", "--import", "--import-options", "import-minimal"}

	gpgAddCmd = append(gpgAddCmd, "--keyring", customKeyringPath)

	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory %s"), err)
	}

	for _, keyURL := range args {
		// Parse the URL
		parsedURL, err := url.Parse(keyURL)
		if err != nil {
			log.Error().Err(err).Msgf(L("failed to parse %s"), keyURL)
			continue
		}

		keyname := path.Base(parsedURL.Path)
		hostKeyPath := filepath.Join(scriptDir, keyname)
		if err := utils.DownloadFile(hostKeyPath, keyURL); err != nil {
			log.Error().Err(err).Msgf(L("failed to download %s"), keyURL)
			continue
		}

		if err := utils.RunCmdStdMapping(zerolog.InfoLevel, "gpg", "--show-key", hostKeyPath); err != nil {
			log.Error().Err(err).Msgf(L("failed to show key %s"), hostKeyPath)
			continue
		}
		if !flags.Force {
			ret, err := utils.YesNo(L("Do you really want to trust this key"))
			if err != nil {
				return err
			}
			if !ret {
				return nil
			}
		}

		containerKeyPath := filepath.Join(filepath.Dir(customKeyringPath), keyname)

		if err := cnx.Copy(hostKeyPath, "server:"+containerKeyPath, "", ""); err != nil {
			log.Error().Err(err).Msgf(L("failed to cp %s to %s"), hostKeyPath, containerKeyPath)
			continue
		}
		defer func() {
			_ = adm_utils.ExecCommand(zerolog.Disabled, cnx, "rm", containerKeyPath)
		}()

		gpgAddCmd = append(gpgAddCmd, containerKeyPath)
	}

	log.Info().Msgf(L("Running: %s"), strings.Join(gpgAddCmd, " "))
	if err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, gpgAddCmd...); err != nil {
		return fmt.Errorf(L("failed to run import key: %s"), err)
	}

	//this is for running import-suma-build-keys, who import customer-build-keys.gpg
	uyuniUpdateCmd := []string{"systemctl", "restart", "uyuni-update-config"}
	log.Info().Msgf(L("Running: %s"), strings.Join(uyuniUpdateCmd, " "))
	if err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, uyuniUpdateCmd...); err != nil {
		return fmt.Errorf(L("failed to restart uyuni-update-config: %s"), err)
	}
	return err
}
0707010000003C000081B4000000000000000000000001662A752800000282000000000000000000000000000000000000002200000000uyuni-tools/mgradm/cmd/gpg/gpg.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package gpg

import (
	"github.com/spf13/cobra"
	gpgadd "github.com/uyuni-project/uyuni-tools/mgradm/cmd/gpg/add"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand import gpg keys from 3rd party repository.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	gpgKeyCmd := &cobra.Command{
		Use:   "gpg",
		Short: L("Manage GPG keys for 3rd party repositories"),
		Args:  cobra.ExactArgs(1),
	}

	gpgKeyCmd.AddCommand(gpgadd.NewCommand(globalFlags))

	return gpgKeyCmd
}
0707010000003D000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001B00000000uyuni-tools/mgradm/cmd/hub0707010000003E000081B4000000000000000000000001662A7528000002B9000000000000000000000000000000000000002200000000uyuni-tools/mgradm/cmd/hub/hub.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package hub

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/hub/register"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand command for Hub management.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	hubCmd := &cobra.Command{
		Use:     "hub",
		Short:   L("Hub management"),
		Long:    L("Tools and utilities for Hub management"),
		Aliases: []string{"hub"},
	}

	hubCmd.SetUsageTemplate(hubCmd.UsageTemplate())
	hubCmd.AddCommand(register.NewCommand(globalFlags))
	return hubCmd
}
0707010000003F000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002400000000uyuni-tools/mgradm/cmd/hub/register07070100000040000081B4000000000000000000000001662A752800000F5A000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/hub/register/register.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package register

import (
	"fmt"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type configFlags struct {
	Backend           string
	ConnectionDetails api.ConnectionDetails `mapstructure:"api"`
}

// NewCommand command for registering peripheral server to hub.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	registerCmd := &cobra.Command{
		Use:   "register",
		Short: L("Register"),
		Long:  L("Register this peripheral server to Hub API"),
		Args:  cobra.MaximumNArgs(0),

		RunE: func(cmd *cobra.Command, args []string) error {
			var flags configFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, register)
		},
	}
	registerCmd.SetUsageTemplate(registerCmd.UsageTemplate())

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(registerCmd)
	}

	if err := api.AddAPIFlags(registerCmd, false); err != nil {
		return nil
	}

	return registerCmd
}

func register(globalFlags *types.GlobalFlags, flags *configFlags, cmd *cobra.Command, args []string) error {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)
	config, err := getRhnConfig(cnx)
	if err != nil {
		return err
	}
	err = registerToHub(config, &flags.ConnectionDetails)
	return err
}

func getRhnConfig(cnx *shared.Connection) (map[string]string, error) {
	out, err := cnx.Exec("/bin/cat", "/etc/rhn/rhn.conf")
	if err != nil {
		return nil, err
	}
	config := make(map[string]string)

	lines := strings.Split(string(out), "\n")
	for _, line := range lines {
		if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") {
			continue
		}
		log.Trace().Msgf("Config: %s", line)

		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			return nil, fmt.Errorf(L("invalid line format: %s"), line)
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])
		config[key] = value
	}

	return config, nil
}

func registerToHub(config map[string]string, cnxDetails *api.ConnectionDetails) error {
	for _, key := range []string{"java.hostname", "report_db_name", "report_db_port", "report_db_user", "report_db_password"} {
		if _, ok := config[key]; !ok {
			return fmt.Errorf(L("mandatory entry missing in config: %s"), key)
		}
	}
	log.Info().Msgf(L("Hub API server: %s"), cnxDetails.Server)
	client, err := api.Init(cnxDetails)
	if err != nil {
		return fmt.Errorf(L("failed to connect to the Hub server: %s"), err)
	}
	data := map[string]interface{}{
		"fqdn": config["java.hostname"],
	}

	ret, err := api.Post[int](client, "system/registerPeripheralServer", data)
	if err != nil {
		return fmt.Errorf(L("failed to register this peripheral server: %s"), err)
	}
	if !ret.Success {
		return fmt.Errorf(L("failed to register this peripheral server: %s"), ret.Message)
	}
	id := ret.Result

	data = map[string]interface{}{
		"sid":              id,
		"reportDbName":     config["report_db_name"],
		"reportDbHost":     config["java.hostname"],
		"reportDbPort":     config["report_db_port"],
		"reportDbUser":     config["report_db_user"],
		"reportDbPassword": config["report_db_password"],
	}
	ret, err = api.Post[int](client, "system/updatePeripheralServerInfo", data)
	if err != nil {
		return fmt.Errorf(L("failed to update peripheral server info: %s"), err)
	}

	if !ret.Success {
		return fmt.Errorf(L("failed to update peripheral server info: %s"), ret.Message)
	}
	log.Info().Msgf(L("Registered peripheral server: %s, ID: %d"), config["java.hostname"], id)
	return nil
}
07070100000041000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/inspect07070100000042000081B4000000000000000000000001662A75280000061F000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/inspect/inspect.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package inspect

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"

	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type inspectFlags struct {
	Image      string
	Tag        string
	PullPolicy string
}

// NewCommand for extracting information from image and deployment.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	inspectCmd := &cobra.Command{
		Use:   "inspect",
		Short: L("Inspect"),
		Long:  L("Extract information from image and deployment"),
		Args:  cobra.MaximumNArgs(0),

		RunE: func(cmd *cobra.Command, args []string) error {
			var flags inspectFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, inspect)
		},
	}

	inspectCmd.SetUsageTemplate(inspectCmd.UsageTemplate())
	inspectCmd.Flags().String("image", "", L("Image URL. Leave it empty to analyze the current deployment"))
	inspectCmd.Flags().String("tag", "", L("Image Tag. Leave it empty to analyze the current deployment"))
	utils.AddPullPolicyFlag(inspectCmd)

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(inspectCmd)
	}

	return inspectCmd
}

func inspect(globalFlags *types.GlobalFlags, flags *inspectFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChoosePodmanOrKubernetes(cmd.Flags(), podmanInspect, kuberneteInspect)
	if err != nil {
		return err
	}
	return fn(globalFlags, flags, cmd, args)
}
07070100000043000081B4000000000000000000000001662A75280000064D000000000000000000000000000000000000002D00000000uyuni-tools/mgradm/cmd/inspect/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package inspect

import (
	"encoding/json"
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"

	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	shared_kubernetes "github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func kuberneteInspect(
	globalFlags *types.GlobalFlags,
	flags *inspectFlags,
	cmd *cobra.Command,
	args []string,
) error {
	serverImage, err := utils.ComputeImage(flags.Image, flags.Tag)
	if err != nil && len(serverImage) > 0 {
		return fmt.Errorf(L("failed to determine image: %s"), err)
	}

	if len(serverImage) <= 0 {
		log.Debug().Msg("Use deployed image")

		cnx := shared.NewConnection("kubectl", "", shared_kubernetes.ServerFilter)
		serverImage, err = adm_utils.RunningImage(cnx, "uyuni")
		if err != nil {
			return fmt.Errorf(L("failed to find the image of the currently running server container: %s"))
		}
	}

	inspectResult, err := shared_kubernetes.InspectKubernetes(serverImage, flags.PullPolicy)
	if err != nil {
		return fmt.Errorf(L("inspect command failed: %s"), err)
	}

	prettyInspectOutput, err := json.MarshalIndent(inspectResult, "", "  ")
	if err != nil {
		return fmt.Errorf(L("cannot print inspect result: %s"), err)
	}

	outputString := "\n" + string(prettyInspectOutput)
	log.Info().Msgf(outputString)

	return nil
}
07070100000044000081B4000000000000000000000001662A75280000015D000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/inspect/nobuild.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package inspect

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kuberneteInspect(
	globalFlags *types.GlobalFlags,
	flags *inspectFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return nil
}
07070100000045000081B4000000000000000000000001662A75280000063A000000000000000000000000000000000000002900000000uyuni-tools/mgradm/cmd/inspect/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package inspect

import (
	"encoding/json"
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func podmanInspect(
	globalFlags *types.GlobalFlags,
	flags *inspectFlags,
	cmd *cobra.Command,
	args []string,
) error {
	serverImage, err := utils.ComputeImage(flags.Image, flags.Tag)
	if err != nil && len(serverImage) > 0 {
		return fmt.Errorf(L("failed to determine image: %s"), err)
	}

	if len(serverImage) <= 0 {
		log.Debug().Msg("Use deployed image")

		cnx := shared.NewConnection("podman", shared_podman.ServerContainerName, "")
		serverImage, err = adm_utils.RunningImage(cnx, shared_podman.ServerContainerName)
		if err != nil {
			return fmt.Errorf(L("failed to find the image of the currently running server container: %s"))
		}
	}
	inspectResult, err := shared_podman.Inspect(serverImage, flags.PullPolicy)
	if err != nil {
		return fmt.Errorf(L("inspect command failed: %s"), err)
	}
	prettyInspectOutput, err := json.MarshalIndent(inspectResult, "", "  ")
	if err != nil {
		return fmt.Errorf(L("cannot print inspect result: %s"), err)
	}

	outputString := "\n" + string(prettyInspectOutput)
	log.Info().Msgf(outputString)

	return nil
}
07070100000046000041FD000000000000000000000005662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/install07070100000047000081B4000000000000000000000001662A752800000326000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/install/install.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package install

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand for installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install",
		Short: L("Install a new server"),
		Long:  L("Install a new server"),
	}

	installCmd.AddCommand(podman.NewCommand(globalFlags))

	if kubernetesCmd := kubernetes.NewCommand(globalFlags); kubernetesCmd != nil {
		installCmd.AddCommand(kubernetesCmd)
	}

	return installCmd
}
07070100000048000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/install/kubernetes07070100000049000081B4000000000000000000000001662A752800000601000000000000000000000000000000000000003800000000uyuni-tools/mgradm/cmd/install/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/shared"
	cmd_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type kubernetesInstallFlags struct {
	shared.InstallFlags `mapstructure:",squash"`
	Helm                cmd_utils.HelmFlags
}

// NewCommand for kubernetes installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	kubernetesCmd := &cobra.Command{
		Use:   "kubernetes [fqdn]",
		Short: L("Install a new server on a kubernetes cluster"),
		Long: L(`Install a new server on a kubernetes cluster

The install command assumes the following:
  * kubectl and helm are installed locally
  * a working kubectl configuration should be set to connect to the cluster to deploy to

The helm values file will be overridden with the values from the command parameters or configuration.

NOTE: installing on a remote cluster is not supported yet!
`),
		Args: cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags kubernetesInstallFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, installForKubernetes)
		},
	}

	shared.AddInstallFlags(kubernetesCmd)
	cmd_utils.AddHelmInstallFlag(kubernetesCmd)

	return kubernetesCmd
}
0707010000004A000081B4000000000000000000000001662A752800000124000000000000000000000000000000000000003500000000uyuni-tools/mgradm/cmd/install/kubernetes/nobuild.go// SPDX-FileCopyrightText: 2023 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	return nil
}
0707010000004B000081B4000000000000000000000001662A752800000B5B000000000000000000000000000000000000003300000000uyuni-tools/mgradm/cmd/install/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package kubernetes

import (
	"fmt"
	"os/exec"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	install_shared "github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/shared"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	shared_kubernetes "github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func installForKubernetes(globalFlags *types.GlobalFlags,
	flags *kubernetesInstallFlags,
	cmd *cobra.Command,
	args []string,
) error {
	for _, binary := range []string{"kubectl", "helm"} {
		if _, err := exec.LookPath(binary); err != nil {
			return fmt.Errorf(L("install %s before running this command"), binary)
		}
	}

	flags.CheckParameters(cmd, "kubectl")
	cnx := shared.NewConnection("kubectl", "", shared_kubernetes.ServerFilter)

	fqdn := args[0]

	helmArgs := []string{"--set", "timezone=" + flags.TZ}
	if flags.MirrorPath != "" {
		// TODO Handle claims for multi-node clusters
		helmArgs = append(helmArgs, "--set", "mirror.hostPath="+flags.MirrorPath)
	}
	if flags.Debug.Java {
		helmArgs = append(helmArgs, "--set", "exposeJavaDebug=true")
	}

	// Check the kubernetes cluster setup
	clusterInfos, err := shared_kubernetes.CheckCluster()
	if err != nil {
		return err
	}

	// Deploy the SSL CA or server certificate
	ca := ssl.SslPair{}
	sslArgs, err := kubernetes.DeployCertificate(&flags.Helm, &flags.Ssl, "", &ca, clusterInfos.GetKubeconfig(), fqdn,
		flags.Image.PullPolicy)
	if err != nil {
		return fmt.Errorf(L("cannot deploy certificate: %s"), err)
	}
	helmArgs = append(helmArgs, sslArgs...)

	// Deploy Uyuni and wait for it to be up
	if err := kubernetes.Deploy(cnx, &flags.Image, &flags.Helm, &flags.Ssl, clusterInfos, fqdn, flags.Debug.Java, helmArgs...); err != nil {
		return fmt.Errorf(L("cannot deploy uyuni: %s"), err)
	}

	// Create setup script + env variables and copy it to the container
	envs := map[string]string{
		"NO_SSL": "Y",
	}

	if err := install_shared.RunSetup(cnx, &flags.InstallFlags, args[0], envs); err != nil {
		if stopErr := shared_kubernetes.Stop(shared_kubernetes.ServerFilter); stopErr != nil {
			log.Error().Msgf(L("Failed to stop service: %v"), stopErr)
		}
		return err
	}

	// The CA needs to be added to the database for Kickstart use.
	err = adm_utils.ExecCommand(zerolog.DebugLevel, cnx,
		"/usr/bin/rhn-ssl-dbstore", "--ca-cert=/etc/pki/trust/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT")
	if err != nil {
		return fmt.Errorf(L("error storing the SSL CA certificate in database: %s"), err)
	}
	return nil
}
0707010000004C000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/install/podman0707010000004D000081B4000000000000000000000001662A7528000004C4000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/install/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type podmanInstallFlags struct {
	shared.InstallFlags `mapstructure:",squash"`
	Podman              podman.PodmanFlags
}

// NewCommand for podman installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	podmanCmd := &cobra.Command{
		Use:   "podman [fqdn]",
		Short: L("Install a new server on podman"),
		Long: L(`Install a new server on podman

The install podman command assumes podman is installed locally.

NOTE: installing on a remote podman is not supported yet!
`),
		Args: cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags podmanInstallFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, installForPodman)
		},
	}

	shared.AddInstallFlags(podmanCmd)
	podman.AddPodmanInstallFlag(podmanCmd)

	return podmanCmd
}
0707010000004E000081B4000000000000000000000001662A7528000013E7000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/install/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"errors"
	"fmt"
	"os/exec"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	install_shared "github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/shared"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func setupCocoContainer(flags *podmanInstallFlags) error {
	if flags.Coco.Replicas > 0 {
		if flags.Coco.Replicas > 1 {
			log.Warn().Msgf(L("Currently only one replica is supported, starting just one instead of %d"), flags.Coco.Replicas)
		}

		tag := flags.Coco.Image.Tag
		if tag == "" {
			tag = flags.Image.Tag
		}
		cocoImage, err := utils.ComputeImage(flags.Coco.Image.Name, tag)
		if err != nil {
			return fmt.Errorf(L("failed to compute image URL, %s"), err)
		}

		if err := podman.GenerateAttestationSystemdService(cocoImage, flags.Db); err != nil {
			return fmt.Errorf(L("cannot generate systemd service: %s"), err)
		}

		if err := shared_podman.EnableService(shared_podman.ServerAttestationService); err != nil {
			return fmt.Errorf(L("cannot enable service: %s"), err)
		}
	}
	return nil
}

func waitForSystemStart(cnx *shared.Connection, image string, flags *podmanInstallFlags) error {
	podmanArgs := flags.Podman.Args
	if flags.MirrorPath != "" {
		podmanArgs = append(podmanArgs, "-v", flags.MirrorPath+":/mirror")
	}

	if err := podman.GenerateSystemdService(flags.TZ, image, flags.Debug.Java, podmanArgs); err != nil {
		return err
	}

	log.Info().Msg(L("Waiting for the server to start..."))
	if err := shared_podman.EnableService(shared_podman.ServerService); err != nil {
		return fmt.Errorf(L("cannot enable service: %s"), err)
	}

	return cnx.WaitForServer()
}

func installForPodman(
	globalFlags *types.GlobalFlags,
	flags *podmanInstallFlags,
	cmd *cobra.Command,
	args []string,
) error {
	flags.CheckParameters(cmd, "podman")
	if _, err := exec.LookPath("podman"); err != nil {
		return errors.New(L("install podman before running this command"))
	}

	inspectedHostValues, err := utils.InspectHost()
	if err != nil {
		return fmt.Errorf(L("cannot inspect host values: %s"), err)
	}

	fqdn, err := getFqdn(args)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("Setting up the server with the FQDN '%s'"), fqdn)

	image, err := utils.ComputeImage(flags.Image.Name, flags.Image.Tag)
	if err != nil {
		return fmt.Errorf(L("failed to compute image URL: %s"), err)
	}
	pullArgs := []string{}
	_, scc_user_exist := inspectedHostValues["host_scc_username"]
	_, scc_user_password := inspectedHostValues["host_scc_password"]
	if scc_user_exist && scc_user_password {
		pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
	}

	preparedImage, err := shared_podman.PrepareImage(image, flags.Image.PullPolicy, pullArgs...)
	if err != nil {
		return err
	}

	if err := shared_podman.LinkVolumes(&flags.Podman.Mounts); err != nil {
		return err
	}

	cnx := shared.NewConnection("podman", shared_podman.ServerContainerName, "")
	if err := waitForSystemStart(cnx, preparedImage, flags); err != nil {
		return fmt.Errorf(L("cannot wait for system start: %s"), err)
	}

	caPassword := flags.Ssl.Password
	if flags.Ssl.UseExisting() {
		// We need to have a password for the generated CA, even though it will be thrown away after install
		caPassword = "dummy"
	}

	env := map[string]string{
		"CERT_O":       flags.Ssl.Org,
		"CERT_OU":      flags.Ssl.OU,
		"CERT_CITY":    flags.Ssl.City,
		"CERT_STATE":   flags.Ssl.State,
		"CERT_COUNTRY": flags.Ssl.Country,
		"CERT_EMAIL":   flags.Ssl.Email,
		"CERT_CNAMES":  strings.Join(append([]string{fqdn}, flags.Ssl.Cnames...), ","),
		"CERT_PASS":    caPassword,
	}

	log.Info().Msg(L("Run setup command in the container"))

	if err := install_shared.RunSetup(cnx, &flags.InstallFlags, fqdn, env); err != nil {
		if stopErr := shared_podman.StopService(shared_podman.ServerService); stopErr != nil {
			log.Error().Msgf(L("Failed to stop service: %v"), stopErr)
		}
		return err
	}

	if err := setupCocoContainer(flags); err != nil {
		return err
	}

	if flags.Ssl.UseExisting() {
		if err := podman.UpdateSslCertificate(cnx, &flags.Ssl.Ca, &flags.Ssl.Server); err != nil {
			return fmt.Errorf(L("cannot update SSL certificate: %s"), err)
		}
	}

	if err := shared_podman.EnablePodmanSocket(); err != nil {
		return fmt.Errorf(L("cannot enable podman socket: %s"), err)
	}
	return nil
}

func getFqdn(args []string) (string, error) {
	if len(args) == 1 {
		return args[0], nil
	} else {
		fqdn_b, err := utils.RunCmdOutput(zerolog.DebugLevel, "hostname", "-f")
		if err != nil {
			return "", fmt.Errorf(L("failed to compute server FQDN: %s"), err)
		}
		return strings.TrimSpace(string(fqdn_b)), nil
	}
}
0707010000004F000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/install/shared07070100000050000081B4000000000000000000000001662A75280000274A000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/install/shared/flags.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"fmt"
	"net/mail"
	"regexp"
	"strings"

	"github.com/spf13/cobra"
	cmd_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	apiTypes "github.com/uyuni-project/uyuni-tools/shared/api/types"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// DbFlags can store all values required to connect to a database.
type DbFlags struct {
	Host     string
	Name     string
	Port     int
	User     string
	Password string
	Protocol string
	Provider string
	Admin    struct {
		User     string
		Password string
	}
}

// SccFlags can store SCC Credentials.
type SccFlags struct {
	User     string
	Password string
}

// DebugFlags contains information about enabled/disabled debug.
type DebugFlags struct {
	Java bool
}

// CocoFlags contains settings for coco attestation container.
type CocoFlags struct {
	Replicas int
	Image    types.ImageFlags `mapstructure:",squash"`
}

// InstallFlags stores all the flags used by install command.
type InstallFlags struct {
	TZ           string
	Email        string
	EmailFrom    string
	IssParent    string
	MirrorPath   string
	Tftp         bool
	Db           DbFlags
	ReportDb     DbFlags
	Ssl          cmd_utils.SslCertFlags
	Scc          SccFlags
	Debug        DebugFlags
	Image        types.ImageFlags `mapstructure:",squash"`
	Coco         CocoFlags
	Admin        apiTypes.User
	Organization string
}

// idChecker verifies that the value is a valid identifier.
func idChecker(value string) bool {
	r := regexp.MustCompile(`^([[:alnum:]]|[._-])+$`)
	if r.MatchString(value) {
		return true
	}
	fmt.Println(L("Can only contain letters, digits . _ and -"))
	return false
}

// emailChecker verifies that the value is a valid email address.
func emailChecker(value string) bool {
	address, err := mail.ParseAddress(value)
	if err != nil || address.Name != "" || strings.ContainsAny(value, "<>") {
		fmt.Println(L("Not a valid email address"))
		return false
	}
	return true
}

// CheckParameters checks parameters for install command.
func (flags *InstallFlags) CheckParameters(cmd *cobra.Command, command string) {
	if flags.Db.Password == "" {
		flags.Db.Password = utils.GetRandomBase64(30)
	}

	if flags.ReportDb.Password == "" {
		flags.ReportDb.Password = utils.GetRandomBase64(30)
	}

	// Make sure we have all the required 3rd party flags or none
	flags.Ssl.CheckParameters()

	// Since we use cert-manager for self-signed certificates on kubernetes we don't need password for it
	if !flags.Ssl.UseExisting() && command == "podman" {
		utils.AskPasswordIfMissing(&flags.Ssl.Password, cmd.Flag("ssl-password").Usage, 0, 0)
	}

	// Use the host timezone if the user didn't define one
	if flags.TZ == "" {
		flags.TZ = utils.GetLocalTimezone()
	}

	utils.AskIfMissing(&flags.Email, cmd.Flag("email").Usage, 0, 0, emailChecker)
	utils.AskIfMissing(&flags.EmailFrom, cmd.Flag("emailfrom").Usage, 0, 0, emailChecker)

	utils.AskIfMissing(&flags.Admin.Login, cmd.Flag("admin-login").Usage, 1, 64, idChecker)
	utils.AskPasswordIfMissing(&flags.Admin.Password, cmd.Flag("admin-password").Usage, 5, 48)
	utils.AskIfMissing(&flags.Admin.Email, cmd.Flag("admin-email").Usage, 1, 128, emailChecker)
	utils.AskIfMissing(&flags.Organization, cmd.Flag("organization").Usage, 3, 128, nil)
}

// AddInstallFlags add flags to installa command.
func AddInstallFlags(cmd *cobra.Command) {
	cmd.Flags().String("tz", "", L("Time zone to set on the server. Defaults to the host timezone"))
	cmd.Flags().String("email", "admin@example.com", L("Administrator e-mail"))
	cmd.Flags().String("emailfrom", "admin@example.com", L("E-Mail sending the notifications"))
	cmd.Flags().String("mirrorPath", "", L("Path to mirrored packages mounted on the host"))
	cmd.Flags().String("issParent", "", L("InterServerSync v1 parent FQDN"))

	cmd.Flags().String("db-user", "spacewalk", L("Database user"))
	cmd.Flags().String("db-password", "", L("Database password. Randomly generated by default"))
	cmd.Flags().String("db-name", "susemanager", L("Database name"))
	cmd.Flags().String("db-host", "localhost", L("Database host"))
	cmd.Flags().Int("db-port", 5432, L("Database port"))
	cmd.Flags().String("db-protocol", "tcp", L("Database protocol"))
	cmd.Flags().String("db-admin-user", "", L("External database admin user name"))
	cmd.Flags().String("db-admin-password", "", L("External database admin password"))
	cmd.Flags().String("db-provider", "", L("External database provider. Possible values 'aws'"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "db", Title: L("Database Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "db-user", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-password", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-name", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-host", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-port", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-protocol", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-admin-user", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-admin-password", "db")
	_ = utils.AddFlagToHelpGroupID(cmd, "db-provider", "db")

	cmd.Flags().Bool("tftp", true, L("Enable TFTP"))
	cmd.Flags().String("reportdb-name", "reportdb", L("Report database name"))
	cmd.Flags().String("reportdb-host", "localhost", L("Report database host"))
	cmd.Flags().Int("reportdb-port", 5432, L("Report database port"))
	cmd.Flags().String("reportdb-user", "pythia_susemanager", L("Report Database username"))
	cmd.Flags().String("reportdb-password", "", L("Report database password. Randomly generated by default"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "reportdb", Title: L("Report DB Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "reportdb-name", "reportdb")
	_ = utils.AddFlagToHelpGroupID(cmd, "reportdb-host", "reportdb")
	_ = utils.AddFlagToHelpGroupID(cmd, "reportdb-port", "reportdb")
	_ = utils.AddFlagToHelpGroupID(cmd, "reportdb-user", "reportdb")
	_ = utils.AddFlagToHelpGroupID(cmd, "reportdb-password", "reportdb")

	// For generated CA and certificate
	cmd.Flags().StringSlice("ssl-cname", []string{}, L("SSL certificate cnames separated by commas"))
	cmd.Flags().String("ssl-country", "DE", L("SSL certificate country"))
	cmd.Flags().String("ssl-state", "Bayern", L("SSL certificate state"))
	cmd.Flags().String("ssl-city", "Nuernberg", L("SSL certificate city"))
	cmd.Flags().String("ssl-org", "SUSE", L("SSL certificate organization"))
	cmd.Flags().String("ssl-ou", "SUSE", L("SSL certificate organization unit"))
	cmd.Flags().String("ssl-password", "", L("Password for the CA key to generate"))
	cmd.Flags().String("ssl-email", "ca-admin@example.com", L("SSL certificate E-Mail"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "ssl", Title: L("SSL Certificate Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-cname", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-country", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-state", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-city", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-org", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-ou", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-password", "ssl")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-email", "ssl")

	// For SSL 3rd party certificates
	cmd.Flags().StringSlice("ssl-ca-intermediate", []string{}, L("Intermediate CA certificate path"))
	cmd.Flags().String("ssl-ca-root", "", L("Root CA certificate path"))
	cmd.Flags().String("ssl-server-cert", "", L("Server certificate path"))
	cmd.Flags().String("ssl-server-key", "", L("Server key path"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "ssl3rd", Title: L("3rd Party SSL Certificate Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-ca-intermediate", "ssl3rd")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-ca-root", "ssl3rd")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-server-cert", "ssl3rd")
	_ = utils.AddFlagToHelpGroupID(cmd, "ssl-server-key", "ssl3rd")

	cmd.Flags().String("scc-user", "", L("SUSE Customer Center username"))
	cmd.Flags().String("scc-password", "", L("SUSE Customer Center password"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "scc", Title: L("SUSE Customer Center Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "scc-user", "scc")
	_ = utils.AddFlagToHelpGroupID(cmd, "scc-password", "scc")

	cmd.Flags().Bool("debug-java", false, L("Enable tomcat and taskomatic remote debugging"))
	cmd_utils.AddImageFlag(cmd)

	cmd_utils.AddContainerImageFlags(cmd, "coco")
	cmd.Flags().Int("coco-replicas", 0, L("How many replicas of the confidential computing container should be started. (only 0 or 1 supported for now)"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "coco-container", Title: L("Confidential Computing Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "coco-replicas", "coco-container")
	_ = utils.AddFlagToHelpGroupID(cmd, "coco-image", "coco-container")
	_ = utils.AddFlagToHelpGroupID(cmd, "coco-tag", "coco-container")

	cmd.Flags().String("admin-login", "admin", L("Administrator user name"))
	cmd.Flags().String("admin-password", "", L("Administrator password"))
	cmd.Flags().String("admin-firstName", "Administrator", L("First name of the administrator"))
	cmd.Flags().String("admin-lastName", "McAdmin", L("Last name of the administrator"))
	cmd.Flags().String("admin-email", "", L("Administrator's email"))
	cmd.Flags().String("organization", "Organization", L("First organization name"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "first-user", Title: L("First User Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "admin-login", "first-user")
	_ = utils.AddFlagToHelpGroupID(cmd, "admin-password", "first-user")
	_ = utils.AddFlagToHelpGroupID(cmd, "admin-firstName", "first-user")
	_ = utils.AddFlagToHelpGroupID(cmd, "admin-lastName", "first-user")
	_ = utils.AddFlagToHelpGroupID(cmd, "admin-email", "first-user")
	_ = utils.AddFlagToHelpGroupID(cmd, "organization", "first-user")
}
07070100000051000081B4000000000000000000000001662A7528000003C7000000000000000000000000000000000000003400000000uyuni-tools/mgradm/cmd/install/shared/flags_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import "testing"

func TestIdChecker(t *testing.T) {
	data := map[string]bool{
		"foo":       true,
		"foo bar":   false,
		"\u798f":    false,
		"foo123._-": true,
		"foo+":      false,
		"foo&":      false,
		"foo'":      false,
		"foo\"":     false,
		"foo`":      false,
		"foo=":      false,
		"foo#":      false,
	}
	for value, expected := range data {
		actual := idChecker(value)
		if actual != expected {
			t.Errorf("%s: expected %v got %v", value, expected, actual)
		}
	}
}

func TestEmailChecker(t *testing.T) {
	data := map[string]bool{
		"root@localhost":           true,
		"joe.hacker@foo.bar.com":   true,
		"<joe.hacker@foo.bar.com>": false,
		"fooo":                     false,
	}
	for value, expected := range data {
		actual := emailChecker(value)
		if actual != expected {
			t.Errorf("%s: expected %v got %v", value, expected, actual)
		}
	}
}
07070100000052000081B4000000000000000000000001662A75280000102D000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/install/shared/shared.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/templates"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	"github.com/uyuni-project/uyuni-tools/shared/api/org"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const setup_name = "setup.sh"

// RunSetup execute the setup.
func RunSetup(cnx *shared.Connection, flags *InstallFlags, fqdn string, env map[string]string) error {
	tmpFolder := generateSetupScript(flags, fqdn, env)
	defer os.RemoveAll(tmpFolder)

	if err := cnx.Copy(filepath.Join(tmpFolder, setup_name), "server:/tmp/setup.sh", "root", "root"); err != nil {
		return fmt.Errorf(L("cannot copy /tmp/setup.sh: %s"), err)
	}

	err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, "/tmp/setup.sh")
	if err != nil {
		return fmt.Errorf(L("error running the setup script: %s"), err)
	}

	// Call the org.createFirst api if flags are passed
	// This should not happen since the password is queried and enforced
	if flags.Admin.Password != "" {
		apiCnx := api.ConnectionDetails{
			Server:   fqdn,
			Insecure: true, // TODO Get the CA Cert and toggle this to false
		}
		_, err := org.CreateFirst(&apiCnx, flags.Organization, &flags.Admin)
		if err != nil {
			return err
		}
	}

	log.Info().Msg(L("Server set up"))
	return nil
}

// generateSetupScript creates a temporary folder with the setup script to execute in the container.
// The script exports all the needed environment variables and calls uyuni's mgr-setup.
// Podman or kubernetes-specific variables can be passed using extraEnv parameter.
func generateSetupScript(flags *InstallFlags, fqdn string, extraEnv map[string]string) string {
	localHostValues := []string{
		"localhost",
		"127.0.0.1",
		"::1",
		fqdn,
	}

	localDb := utils.Contains(localHostValues, flags.Db.Host)

	dbHost := flags.Db.Host
	reportdbHost := flags.ReportDb.Host

	if localDb {
		dbHost = "localhost"
		if reportdbHost == "" {
			reportdbHost = "localhost"
		}
	}
	env := map[string]string{
		"UYUNI_FQDN":            fqdn,
		"MANAGER_USER":          flags.Db.User,
		"MANAGER_PASS":          flags.Db.Password,
		"MANAGER_ADMIN_EMAIL":   flags.Email,
		"MANAGER_MAIL_FROM":     flags.EmailFrom,
		"MANAGER_ENABLE_TFTP":   boolToString(flags.Tftp),
		"LOCAL_DB":              boolToString(localDb),
		"MANAGER_DB_NAME":       flags.Db.Name,
		"MANAGER_DB_HOST":       dbHost,
		"MANAGER_DB_PORT":       strconv.Itoa(flags.Db.Port),
		"MANAGER_DB_PROTOCOL":   flags.Db.Protocol,
		"REPORT_DB_NAME":        flags.ReportDb.Name,
		"REPORT_DB_HOST":        reportdbHost,
		"REPORT_DB_PORT":        strconv.Itoa(flags.ReportDb.Port),
		"REPORT_DB_USER":        flags.ReportDb.User,
		"REPORT_DB_PASS":        flags.ReportDb.Password,
		"EXTERNALDB_ADMIN_USER": flags.Db.Admin.User,
		"EXTERNALDB_ADMIN_PASS": flags.Db.Admin.Password,
		"EXTERNALDB_PROVIDER":   flags.Db.Provider,
		"ISS_PARENT":            flags.IssParent,
		"ACTIVATE_SLP":          "N", // Deprecated, will be removed soon
		"SCC_USER":              flags.Scc.User,
		"SCC_PASS":              flags.Scc.Password,
	}
	if flags.MirrorPath != "" {
		env["MIRROR_PATH"] = "/mirror"
	}

	// Add the extra environment variables
	for key, value := range extraEnv {
		env[key] = value
	}

	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	if err != nil {
		log.Fatal().Err(err).Msg(L("Failed to create temporary directory"))
	}

	dataTemplate := templates.MgrSetupScriptTemplateData{
		Env:       env,
		DebugJava: flags.Debug.Java,
	}

	scriptPath := filepath.Join(scriptDir, setup_name)
	if err = utils.WriteTemplateToFile(dataTemplate, scriptPath, 0555, true); err != nil {
		log.Fatal().Err(err).Msg(L("Failed to generate setup script"))
	}

	return scriptDir
}

func boolToString(value bool) string {
	if value {
		return "Y"
	}
	return "N"
}
07070100000053000041FD000000000000000000000005662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/migrate07070100000054000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/migrate/kubernetes07070100000055000081B4000000000000000000000001662A75280000082B000000000000000000000000000000000000003800000000uyuni-tools/mgradm/cmd/migrate/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate/shared"
	cmd_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type kubernetesMigrateFlags struct {
	shared.MigrateFlags `mapstructure:",squash"`
	Helm                cmd_utils.HelmFlags
	Ssl                 cmd_utils.SslCertFlags
}

// NewCommand for kubernetes migration.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	migrateCmd := &cobra.Command{
		Use:   "kubernetes [source server FQDN]",
		Short: L("Migrate a remote server to containers running on a kubernetes cluster"),
		Long: L(`Migrate a remote server to containers running on a kubernetes cluster

This migration command assumes a few things:
  * the SSH configuration for the source server is complete, including user and
    all needed options to connect to the machine,
  * an SSH agent is started and the key to use to connect to the server is added to it,
  * kubectl and helm are installed locally,
  * a working kubectl configuration should be set to connect to the cluster to deploy to

When migrating a server with a automatically generated SSL Root CA certificate, the private key
password will be required to convert it to RSA in a kubernetes secret.
This is not needed if the source server does not have a generated SSL CA certificate.

NOTE: migrating to a remote cluster is not supported yet!
`),
		Args: cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags kubernetesMigrateFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, migrateToKubernetes)
		},
	}

	shared.AddMigrateFlags(migrateCmd)
	cmd_utils.AddHelmInstallFlag(migrateCmd)
	migrateCmd.Flags().String("ssl-password", "", L("SSL CA generated private key password"))

	return migrateCmd
}
07070100000056000081B4000000000000000000000001662A752800000124000000000000000000000000000000000000003500000000uyuni-tools/mgradm/cmd/migrate/kubernetes/nobuild.go// SPDX-FileCopyrightText: 2023 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	return nil
}
07070100000057000081B4000000000000000000000001662A752800001BDF000000000000000000000000000000000000003300000000uyuni-tools/mgradm/cmd/migrate/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package kubernetes

import (
	"encoding/base64"
	"fmt"
	"os"
	"os/exec"
	"path"

	"github.com/rs/zerolog"
	"github.com/spf13/cobra"
	migration_shared "github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate/shared"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	shared_kubernetes "github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func migrateToKubernetes(
	globalFlags *types.GlobalFlags,
	flags *kubernetesMigrateFlags,
	cmd *cobra.Command,
	args []string,
) error {
	for _, binary := range []string{"kubectl", "helm"} {
		if _, err := exec.LookPath(binary); err != nil {
			return fmt.Errorf(L("install %s before running this command"), binary)
		}
	}
	cnx := shared.NewConnection("kubectl", "", shared_kubernetes.ServerFilter)

	serverImage, err := utils.ComputeImage(flags.Image.Name, flags.Image.Tag)
	if err != nil {
		return fmt.Errorf(L("failed to compute image URL: %s"), err)
	}

	fqdn := args[0]

	// Find the SSH Socket and paths for the migration
	sshAuthSocket := migration_shared.GetSshAuthSocket()
	sshConfigPath, sshKnownhostsPath := migration_shared.GetSshPaths()

	// Prepare the migration script and folder
	scriptDir, err := adm_utils.GenerateMigrationScript(fqdn, flags.User, true)
	if err != nil {
		return fmt.Errorf(L("failed to generate migration script: %s"), err)
	}

	defer os.RemoveAll(scriptDir)

	// We don't need the SSL certs at this point of the migration
	clusterInfos, err := shared_kubernetes.CheckCluster()
	if err != nil {
		return err
	}
	kubeconfig := clusterInfos.GetKubeconfig()
	//TODO: check if we need to handle SELinux policies, as we do in podman

	// Install Uyuni with generated CA cert: an empty struct means no 3rd party cert
	var sslFlags adm_utils.SslCertFlags

	// Deploy for running migration command
	if err := kubernetes.Deploy(cnx, &flags.Image, &flags.Helm, &sslFlags, clusterInfos, fqdn, false,
		"--set", "migration.ssh.agentSocket="+sshAuthSocket,
		"--set", "migration.ssh.configPath="+sshConfigPath,
		"--set", "migration.ssh.knownHostsPath="+sshKnownhostsPath,
		"--set", "migration.dataPath="+scriptDir); err != nil {
		return fmt.Errorf(L("cannot run deploy: %s"), err)
	}

	//this is needed because folder with script needs to be mounted
	//check the node before scaling down
	nodeName, err := shared_kubernetes.GetNode("uyuni")
	if err != nil {
		return fmt.Errorf(L("cannot find node running uyuni: %s"), err)
	}
	// Run the actual migration
	if err := adm_utils.RunMigration(cnx, scriptDir, "migrate.sh"); err != nil {
		return fmt.Errorf(L("cannot run migration: %s"), err)
	}

	tz, oldPgVersion, newPgVersion, err := adm_utils.ReadContainerData(scriptDir)
	if err != nil {
		return fmt.Errorf(L("cannot read data from container: %s"), err)
	}

	// After each command we want to scale to 0
	err = shared_kubernetes.ReplicasTo(shared_kubernetes.ServerFilter, 0)
	if err != nil {
		return fmt.Errorf(L("cannot set replicas to 0: %s"), err)
	}

	defer func() {
		// if something is running, we don't need to set replicas to 1
		if _, err = shared_kubernetes.GetNode("uyuni"); err != nil {
			err = shared_kubernetes.ReplicasTo(shared_kubernetes.ServerFilter, 1)
		}
	}()

	setupSslArray, err := setupSsl(&flags.Helm, kubeconfig, scriptDir, flags.Ssl.Password, flags.Image.PullPolicy)
	if err != nil {
		return fmt.Errorf(L("cannot setup SSL: %s"), err)
	}

	helmArgs := []string{
		"--reset-values",
		"--set", "timezone=" + tz,
	}
	helmArgs = append(helmArgs, setupSslArray...)

	// Run uyuni upgrade using the new ssl certificate
	err = kubernetes.UyuniUpgrade(serverImage, flags.Image.PullPolicy, &flags.Helm, kubeconfig, fqdn, clusterInfos.Ingress, helmArgs...)
	if err != nil {
		return fmt.Errorf(L("cannot upgrade helm chart to image %s using new SSL certificate: %s"), serverImage, err)
	}

	if err := shared_kubernetes.WaitForDeployment(flags.Helm.Uyuni.Namespace, "uyuni", "uyuni"); err != nil {
		return fmt.Errorf(L("cannot wait for deployment of %s: %s"), serverImage, err)
	}

	err = shared_kubernetes.ReplicasTo(shared_kubernetes.ServerFilter, 0)
	if err != nil {
		return fmt.Errorf(L("cannot set replicas to 0: %s"), err)
	}

	if oldPgVersion != newPgVersion {
		if err := kubernetes.RunPgsqlVersionUpgrade(flags.Image, flags.MigrationImage, nodeName, oldPgVersion, newPgVersion); err != nil {
			return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
		}
	}

	schemaUpdateRequired := oldPgVersion != newPgVersion
	if err := kubernetes.RunPgsqlFinalizeScript(serverImage, flags.Image.PullPolicy, nodeName, schemaUpdateRequired); err != nil {
		return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
	}

	if err := kubernetes.RunPostUpgradeScript(serverImage, flags.Image.PullPolicy, nodeName); err != nil {
		return fmt.Errorf(L("cannot run post upgrade script: %s"), err)
	}

	err = kubernetes.UyuniUpgrade(serverImage, flags.Image.PullPolicy, &flags.Helm, kubeconfig, fqdn, clusterInfos.Ingress, helmArgs...)
	if err != nil {
		return fmt.Errorf(L("cannot upgrade to image %s: %s"), serverImage, err)
	}

	return shared_kubernetes.WaitForDeployment(flags.Helm.Uyuni.Namespace, "uyuni", "uyuni")
}

// updateIssuer replaces the temporary SSL certificate issuer with the source server CA.
// Return additional helm args to use the SSL certificates.
func setupSsl(helm *adm_utils.HelmFlags, kubeconfig string, scriptDir string, password string, pullPolicy string) ([]string, error) {
	caCert := path.Join(scriptDir, "RHN-ORG-TRUSTED-SSL-CERT")
	caKey := path.Join(scriptDir, "RHN-ORG-PRIVATE-SSL-KEY")

	if utils.FileExists(caCert) && utils.FileExists(caKey) {
		key := base64.StdEncoding.EncodeToString(ssl.GetRsaKey(caKey, password))

		// Strip down the certificate text part
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "openssl", "x509", "-in", caCert)
		if err != nil {
			return []string{}, fmt.Errorf(L("failed to strip text part from CA certificate: %s"), err)
		}
		cert := base64.StdEncoding.EncodeToString(out)
		ca := ssl.SslPair{Cert: cert, Key: key}

		// An empty struct means no third party certificate
		sslFlags := adm_utils.SslCertFlags{}
		ret, err := kubernetes.DeployCertificate(helm, &sslFlags, cert, &ca, kubeconfig, "", pullPolicy)
		if err != nil {
			return []string{}, fmt.Errorf(L("cannot deploy certificate: %s"), err)
		}
		return ret, nil
	} else {
		// Handle third party certificates and CA
		sslFlags := adm_utils.SslCertFlags{
			Ca: ssl.CaChain{Root: caCert},
			Server: ssl.SslPair{
				Key:  path.Join(scriptDir, "spacewalk.key"),
				Cert: path.Join(scriptDir, "spacewalk.crt"),
			},
		}
		kubernetes.DeployExistingCertificate(helm, &sslFlags, kubeconfig)
	}
	return []string{}, nil
}
07070100000058000081B4000000000000000000000001662A75280000035A000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/migrate/migrate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package migrate

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand for migration.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	migrateCmd := &cobra.Command{
		Use:   "migrate [source server FQDN]",
		Short: L("Migrate a remote server to containers"),
		Long:  L("Migrate a remote server to containers"),
	}

	migrateCmd.AddCommand(podman.NewCommand(globalFlags))

	if kubernetesCmd := kubernetes.NewCommand(globalFlags); kubernetesCmd != nil {
		migrateCmd.AddCommand(kubernetesCmd)
	}

	return migrateCmd
}
07070100000059000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/migrate/podman0707010000005A000081B4000000000000000000000001662A752800000600000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/migrate/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	podman_utils "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type podmanMigrateFlags struct {
	shared.MigrateFlags `mapstructure:",squash"`
	Podman              podman_utils.PodmanFlags
}

// NewCommand for podman migration.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	migrateCmd := &cobra.Command{
		Use:   "podman [source server FQDN]",
		Short: L("Migrate a remote server to containers running on podman"),
		Long: L(`Migrate a remote server to containers running on podman

This migration command assumes a few things:
  * the SSH configuration for the source server is complete, including user and
    all needed options to connect to the machine,
  * an SSH agent is started and the key to use to connect to the server is added to it,
  * podman is installed locally

NOTE: migrating to a remote podman is not supported yet!
`),
		Args: cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags podmanMigrateFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, migrateToPodman)
		},
	}

	shared.AddMigrateFlags(migrateCmd)
	podman_utils.AddPodmanInstallFlag(migrateCmd)

	return migrateCmd
}
0707010000005B000081B4000000000000000000000001662A7528000009DC000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/migrate/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"fmt"
	"os/exec"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	migration_shared "github.com/uyuni-project/uyuni-tools/mgradm/cmd/migrate/shared"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/podman"
	podman_utils "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"

	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func migrateToPodman(globalFlags *types.GlobalFlags, flags *podmanMigrateFlags, cmd *cobra.Command, args []string) error {
	if _, err := exec.LookPath("podman"); err != nil {
		return fmt.Errorf(L("install podman before running this command"))
	}
	sourceFqdn := args[0]
	serverImage, err := utils.ComputeImage(flags.Image.Name, flags.Image.Tag)
	if err != nil {
		return fmt.Errorf(L("cannot compute image: %s"), err)
	}

	// Find the SSH Socket and paths for the migration
	sshAuthSocket := migration_shared.GetSshAuthSocket()
	sshConfigPath, sshKnownhostsPath := migration_shared.GetSshPaths()

	tz, oldPgVersion, newPgVersion, err := podman.RunMigration(serverImage, flags.Image.PullPolicy, sshAuthSocket, sshConfigPath, sshKnownhostsPath, sourceFqdn, flags.User)
	if err != nil {
		return fmt.Errorf(L("cannot run migration script: %s"), err)
	}

	if oldPgVersion != newPgVersion {
		if err := podman.RunPgsqlVersionUpgrade(flags.Image, flags.MigrationImage, oldPgVersion, newPgVersion); err != nil {
			return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
		}
	}

	schemaUpdateRequired := oldPgVersion != newPgVersion
	if err := podman.RunPgsqlFinalizeScript(serverImage, schemaUpdateRequired); err != nil {
		return fmt.Errorf(L("cannot run PostgreSQL finalize script: %s"), err)
	}

	if err := podman.RunPostUpgradeScript(serverImage); err != nil {
		return fmt.Errorf(L("cannot run post upgrade script: %s"), err)
	}

	if err := podman.GenerateSystemdService(tz, serverImage, false, viper.GetStringSlice("podman.arg")); err != nil {
		return fmt.Errorf(L("cannot generate systemd service file: %s"), err)
	}

	// Start the service
	if err := podman_utils.EnableService(podman_utils.ServerService); err != nil {
		return err
	}

	log.Info().Msg(L("Server migrated"))

	if err := podman_utils.EnablePodmanSocket(); err != nil {
		return fmt.Errorf(L("cannot enable podman socket: %s"), err)
	}

	return nil
}
0707010000005C000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/migrate/shared0707010000005D000081B4000000000000000000000001662A752800000352000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/migrate/shared/flags.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// MigrateFlags represents flag required by migration command.
type MigrateFlags struct {
	Image          types.ImageFlags `mapstructure:",squash"`
	MigrationImage types.ImageFlags `mapstructure:"migration"`
	User           string
}

// AddMigrateFlags add migration flags to a command.
func AddMigrateFlags(cmd *cobra.Command) {
	utils.AddImageFlag(cmd)
	utils.AddMigrationImageFlag(cmd)
	cmd.Flags().String("user", "root", L("User on the source server. Non-root user must have passwordless sudo privileges (NOPASSWD tag in /etc/sudoers)."))
}
0707010000005E000081B4000000000000000000000001662A75280000046D000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/migrate/shared/shared.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"os"
	"path/filepath"

	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// GetSshAuthSocket returns the SSH_AUTH_SOCK environment variable value.
func GetSshAuthSocket() string {
	path := os.Getenv("SSH_AUTH_SOCK")
	if len(path) == 0 {
		log.Fatal().Msg(L("SSH_AUTH_SOCK is not defined, start an SSH agent and try again"))
	}
	return path
}

// GetSshPaths returns the user SSH config and known_hosts paths.
func GetSshPaths() (string, string) {
	// Find ssh config to mount it in the container
	homedir, err := os.UserHomeDir()
	if err != nil {
		log.Fatal().Msg(L("Failed to find home directory to look for SSH config"))
	}
	sshConfigPath := filepath.Join(homedir, ".ssh", "config")
	sshKnownhostsPath := filepath.Join(homedir, ".ssh", "known_hosts")

	if !utils.FileExists(sshConfigPath) {
		sshConfigPath = ""
	}

	if !utils.FileExists(sshKnownhostsPath) {
		sshKnownhostsPath = ""
	}

	return sshConfigPath, sshKnownhostsPath
}
0707010000005F000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/restart07070100000060000081B4000000000000000000000001662A7528000001C1000000000000000000000000000000000000002D00000000uyuni-tools/mgradm/cmd/restart/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package restart

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesRestart(
	globalFlags *types.GlobalFlags,
	flags *restartFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Restart(kubernetes.ServerFilter)
}
07070100000061000081B4000000000000000000000001662A7528000001CD000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/restart/nokubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package restart

import (
	"errors"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesRestart(
	globalFlags *types.GlobalFlags,
	flags *restartFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return errors.New(L("built without kubernetes support"))
}
07070100000062000081B4000000000000000000000001662A75280000024F000000000000000000000000000000000000002900000000uyuni-tools/mgradm/cmd/restart/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package restart

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func podmanRestart(
	globalFlags *types.GlobalFlags,
	flags *restartFlags,
	cmd *cobra.Command,
	args []string,
) error {
	err := podman.RestartService(podman.ServerService)
	if err != nil {
		return err
	}
	if podman.HasService(podman.ServerAttestationService) {
		return podman.RestartService(podman.ServerAttestationService)
	}
	return nil
}
07070100000063000081B4000000000000000000000001662A7528000004C8000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/restart/restart.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package restart

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type restartFlags struct {
	Backend string
}

// NewCommand to restart server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	restartCmd := &cobra.Command{
		Use:   "restart",
		Short: L("Restart the server"),
		Long:  L("Restart the server"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags restartFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, restart)
		},
	}
	restartCmd.SetUsageTemplate(restartCmd.UsageTemplate())

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(restartCmd)
	}

	return restartCmd
}

func restart(globalFlags *types.GlobalFlags, flags *restartFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChoosePodmanOrKubernetes(cmd.Flags(), podmanRestart, kubernetesRestart)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
07070100000064000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001D00000000uyuni-tools/mgradm/cmd/start07070100000065000081B4000000000000000000000001662A7528000001B9000000000000000000000000000000000000002B00000000uyuni-tools/mgradm/cmd/start/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package start

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStart(
	globalFlags *types.GlobalFlags,
	flags *startFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Start(kubernetes.ServerFilter)
}
07070100000066000081B4000000000000000000000001662A7528000001C7000000000000000000000000000000000000002D00000000uyuni-tools/mgradm/cmd/start/nokubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package start

import (
	"errors"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStart(
	globalFlags *types.GlobalFlags,
	flags *startFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return errors.New(L("built without kubernetes support"))
}
07070100000067000081B4000000000000000000000001662A75280000023B000000000000000000000000000000000000002700000000uyuni-tools/mgradm/cmd/start/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package start

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func podmanStart(
	globalFlags *types.GlobalFlags,
	flags *startFlags,
	cmd *cobra.Command,
	args []string,
) error {
	if podman.HasService(podman.ServerAttestationService) {
		if err := podman.StartService(podman.ServerAttestationService); err != nil {
			return err
		}
	}
	return podman.StartService(podman.ServerService)
}
07070100000068000081B4000000000000000000000001662A7528000004A8000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/start/start.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package start

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type startFlags struct {
	Backend string
}

// NewCommand starts the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	startCmd := &cobra.Command{
		Use:   "start",
		Short: L("Start the server"),
		Long:  L("Start the server"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags startFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, start)
		},
	}
	startCmd.SetUsageTemplate(startCmd.UsageTemplate())

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(startCmd)
	}

	return startCmd
}

func start(globalFlags *types.GlobalFlags, flags *startFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChoosePodmanOrKubernetes(cmd.Flags(), podmanStart, kubernetesStart)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
07070100000069000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/mgradm/cmd/status0707010000006A000081B4000000000000000000000001662A752800000776000000000000000000000000000000000000002C00000000uyuni-tools/mgradm/cmd/status/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package status

import (
	"errors"
	"fmt"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStatus(
	globalFlags *types.GlobalFlags,
	flags *statusFlags,
	cmd *cobra.Command,
	args []string,
) error {
	// Do we have an uyuni helm release?
	clusterInfos, err := kubernetes.CheckCluster()
	if err != nil {
		return fmt.Errorf(L("failed to discover the cluster type: %s"), err)
	}

	kubeconfig := clusterInfos.GetKubeconfig()
	if !kubernetes.HasHelmRelease("uyuni", kubeconfig) {
		return errors.New(L("no uyuni helm release installed on the cluster"))
	}

	namespace, err := kubernetes.FindNamespace("uyuni", kubeconfig)
	if err != nil {
		return fmt.Errorf(L("failed to find the uyuni deployment namespace: %s"), err)
	}

	// Is the pod running? Do we have all the replicas?
	status, err := kubernetes.GetDeploymentStatus(namespace, "uyuni")
	if err != nil {
		return fmt.Errorf(L("failed to get deployment status: %s"), err)
	}
	if status.Replicas != status.ReadyReplicas {
		log.Warn().Msgf(L("Some replicas are not ready: %d / %d"), status.ReadyReplicas, status.Replicas)
	}

	if status.AvailableReplicas == 0 {
		return errors.New(L("the pod is not running"))
	}

	// Are the services running in the container?
	cnx := shared.NewConnection("kubectl", "", kubernetes.ServerFilter)
	if err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, "spacewalk-service", "status"); err != nil {
		return fmt.Errorf(L("failed to run spacewalk-service status: %s"), err)
	}
	return nil
}
0707010000006B000081B4000000000000000000000001662A7528000001CA000000000000000000000000000000000000002E00000000uyuni-tools/mgradm/cmd/status/nokubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package status

import (
	"errors"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStatus(
	globalFlags *types.GlobalFlags,
	flags *statusFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return errors.New(L("built without kubernetes support"))
}
0707010000006C000081B4000000000000000000000001662A752800000622000000000000000000000000000000000000002800000000uyuni-tools/mgradm/cmd/status/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package status

import (
	"fmt"

	"github.com/rs/zerolog"
	"github.com/spf13/cobra"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func podmanStatus(
	globalFlags *types.GlobalFlags,
	flags *statusFlags,
	cmd *cobra.Command,
	args []string,
) error {
	// Show the status and that's it if the service is not running
	if !podman.IsServiceRunning(podman.ServerService) {
		if err := utils.RunCmdStdMapping(zerolog.DebugLevel, "systemctl", "status", "-no-pager", podman.ServerService); err != nil {
			return fmt.Errorf(L("failed to get status of the server service: %s"), err)
		}
		return nil
	}

	// Run spacewalk-service status in the container
	cnx := shared.NewConnection("podman", podman.ServerContainerName, "")
	if err := adm_utils.ExecCommand(zerolog.InfoLevel, cnx, "spacewalk-service", "status"); err != nil {
		return fmt.Errorf(L("failed to run spacewalk-service status: %s"), err)
	}

	if !podman.IsServiceRunning(podman.ServerAttestationService) {
		if err := utils.RunCmdStdMapping(zerolog.DebugLevel, "systemctl", "status", podman.ServerAttestationService); err != nil {
			return fmt.Errorf(L("failed to get status of the server service: %s"), err)
		}
		return nil
	}

	return nil
}
0707010000006D000081B4000000000000000000000001662A7528000004EB000000000000000000000000000000000000002800000000uyuni-tools/mgradm/cmd/status/status.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package status

import (
	"errors"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type statusFlags struct {
}

// NewCommand to get the status of the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "status",
		Short: L("Get the server status"),
		Long:  L("Get the server status"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags statusFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, status)
		},
	}
	cmd.SetUsageTemplate(cmd.UsageTemplate())

	return cmd
}

func status(globalFlags *types.GlobalFlags, flags *statusFlags, cmd *cobra.Command, args []string) error {
	if podman.HasService(podman.ServerService) {
		return podmanStatus(globalFlags, flags, cmd, args)
	}

	if utils.IsInstalled("kubectl") && utils.IsInstalled("helm") {
		return kubernetesStatus(globalFlags, flags, cmd, args)
	}

	return errors.New(L("no installed server detected"))
}
0707010000006E000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001C00000000uyuni-tools/mgradm/cmd/stop0707010000006F000081B4000000000000000000000001662A7528000001B5000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/stop/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package stop

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStop(
	globalFlags *types.GlobalFlags,
	flags *stopFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Stop(kubernetes.ServerFilter)
}
07070100000070000081B4000000000000000000000001662A7528000001C4000000000000000000000000000000000000002C00000000uyuni-tools/mgradm/cmd/stop/nokubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package stop

import (
	"errors"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStop(
	globalFlags *types.GlobalFlags,
	flags *stopFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return errors.New(L("built without kubernetes support"))
}
07070100000071000081B4000000000000000000000001662A752800000236000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/stop/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package stop

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func podmanStop(
	globalFlags *types.GlobalFlags,
	flags *stopFlags,
	cmd *cobra.Command,
	args []string,
) error {
	if podman.HasService(podman.ServerAttestationService) {
		if err := podman.StopService(podman.ServerAttestationService); err != nil {
			return err
		}
	}
	return podman.StopService(podman.ServerService)
}
07070100000072000081B4000000000000000000000001662A752800000496000000000000000000000000000000000000002400000000uyuni-tools/mgradm/cmd/stop/stop.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package stop

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type stopFlags struct {
	Backend string
}

// NewCommand to stop server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	stopCmd := &cobra.Command{
		Use:   "stop",
		Short: L("Stop the server"),
		Long:  L("Stop the server"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags stopFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, stop)
		},
	}

	stopCmd.SetUsageTemplate(stopCmd.UsageTemplate())

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(stopCmd)
	}

	return stopCmd
}

func stop(globalFlags *types.GlobalFlags, flags *stopFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChoosePodmanOrKubernetes(cmd.Flags(), podmanStop, kubernetesStop)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
07070100000073000041FD000000000000000000000005662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/support07070100000074000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/support/config07070100000075000081B4000000000000000000000001662A752800000403000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/support/config/config.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package config

import (
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type configFlags struct {
	Output  string
	Backend string
}

// NewCommand is the command for creates supportconfig.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	configCmd := &cobra.Command{
		Use:   "config",
		Short: L("Extract configuration and logs"),
		Long: L(`Extract the host or cluster configuration and logs as well as those from 
the containers for support to help debugging.`),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags configFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, extract)
		},
	}

	configCmd.Flags().StringP("output", "o", "supportconfig.tar.gz", L("path where to extract the data"))
	utils.AddBackendFlag(configCmd)

	return configCmd
}
07070100000076000081B4000000000000000000000001662A752800000CB9000000000000000000000000000000000000003300000000uyuni-tools/mgradm/cmd/support/config/extractor.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package config

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path"
	"regexp"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func extract(globalFlags *types.GlobalFlags, flags *configFlags, cmd *cobra.Command, args []string) error {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)

	// Copy the generated file locally
	tmpDir, err := os.MkdirTemp("", "mgradm-*")
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}
	defer os.RemoveAll(tmpDir)

	var files []string
	extensions := []string{"", ".md5"}

	// Run supportconfig in the container if it's running
	log.Info().Msg(L("Running supportconfig in the container"))
	out, err := cnx.Exec("supportconfig")
	if err != nil {
		return errors.New(L("failed to run supportconfig"))
	} else {
		tarballPath := getSupportConfigPath(out)
		if tarballPath == "" {
			return fmt.Errorf(L("failed to find container supportconfig tarball from command output"))
		}

		// TODO Get the error from copy
		for _, ext := range extensions {
			containerTarball := path.Join(tmpDir, "container-supportconfig.txz"+ext)
			if err := cnx.Copy("server:"+tarballPath+ext, containerTarball, "", ""); err != nil {
				return fmt.Errorf(L("cannot copy tarball: %s"), err)
			}
			files = append(files, containerTarball)

			// Remove the generated file in the container
			if _, err := cnx.Exec("rm", tarballPath+ext); err != nil {
				return fmt.Errorf(L("failed to remove %s%s file in the container: %s"), tarballPath, ext, err)
			}
		}
	}

	// Run supportconfig on the host if installed
	if _, err := exec.LookPath("supportconfig"); err == nil {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "supportconfig")
		if err != nil {
			return fmt.Errorf(L("failed to run supportconfig on the host: %s"), err)
		}
		tarballPath := getSupportConfigPath(out)

		// Look for the generated supportconfig file
		if tarballPath != "" && utils.FileExists(tarballPath) {
			for _, ext := range extensions {
				files = append(files, tarballPath+ext)
			}
		} else {
			return errors.New(L("failed to find host supportconfig tarball from command output"))
		}
	} else {
		log.Warn().Msg(L("supportconfig is not available on the host, skipping it"))
	}

	// TODO Get cluster infos in case of kubernetes

	// Pack it all into a tarball
	log.Info().Msg(L("Preparing the tarball"))
	tarball, err := utils.NewTarGz(flags.Output)
	if err != nil {
		return err
	}

	for _, file := range files {
		if err := tarball.AddFile(file, path.Base(file)); err != nil {
			return fmt.Errorf(L("failed to add %s to tarball: %s"), path.Base(file), err)
		}
	}
	tarball.Close()

	return nil
}

func getSupportConfigPath(out []byte) string {
	re := regexp.MustCompile(`/var/log/scc_[^.]+\.txz`)
	return re.FindString(string(out))
}
07070100000077000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000002300000000uyuni-tools/mgradm/cmd/support/ptf07070100000078000081B4000000000000000000000001662A752800000150000000000000000000000000000000000000002C00000000uyuni-tools/mgradm/cmd/support/ptf/noptf.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build !ptf

package ptf

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand is the command for creates supportptf.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	return nil
}
07070100000079000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/support/ptf/podman0707010000007A000081B4000000000000000000000001662A752800000544000000000000000000000000000000000000003400000000uyuni-tools/mgradm/cmd/support/ptf/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package podman

import (
	"github.com/spf13/cobra"
	mgradm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type podmanPTFFlags struct {
	Image      types.ImageFlags `mapstructure:",squash"`
	PTFId      string           `mapstructure:"ptf"`
	TestId     string           `mapstructure:"test"`
	CustomerId string           `mapstructure:"user"`
}

// NewCommand for podman installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	podmanCmd := &cobra.Command{
		Use: "podman",

		Short: L("Install a PTF or Test package on podman"),
		Long: L(`Install a PTF or Test package on podman

The support ptf podman command assumes podman is installed locally and
the host machine is register to SCC.

NOTE: for now installing on a remote podman is not supported!
`),
		Args: cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags podmanPTFFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, ptfForPodman)
		},
	}

	mgradm_utils.AddImagePTFlag(podmanCmd)
	utils.AddPTFFlag(podmanCmd)

	return podmanCmd
}
0707010000007B000081B4000000000000000000000001662A75280000068C000000000000000000000000000000000000003300000000uyuni-tools/mgradm/cmd/support/ptf/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package podman

import (
	"errors"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	podman_shared "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func ptfForPodman(
	globalFlags *types.GlobalFlags,
	flags *podmanPTFFlags,
	cmd *cobra.Command,
	args []string,
) error {
	//we don't want to perform a postgres version upgrade when installing a PTF.
	//in that case, we can use the upgrade command.
	dummyMigration := types.ImageFlags{}
	if err := flags.checkParameters(); err != nil {
		return err
	}
	return podman.Upgrade(flags.Image, dummyMigration, args)
}

func (flags *podmanPTFFlags) checkParameters() error {
	if flags.TestId != "" && flags.PTFId != "" {
		return errors.New(L("ptf and test flags cannot be set simultaneously "))
	}
	if flags.TestId == "" && flags.PTFId == "" {
		return errors.New(L("ptf and test flags cannot be empty simultaneously "))
	}
	if flags.CustomerId == "" {
		return errors.New(L("user flag cannot be empty"))
	}
	serverImage, err := podman_shared.GetRunningImage(podman_shared.ServerContainerName)
	if err != nil {
		return err
	}

	suffix := "ptf"
	if flags.TestId != "" {
		suffix = "test"
	}
	flags.Image.Name, err = utils.ComputePTF(flags.CustomerId, flags.PTFId, serverImage, suffix)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("The image computed is: %s"), flags.Image.Name)
	return nil
}
0707010000007C000081B4000000000000000000000001662A7528000002A2000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/support/ptf/ptf.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package ptf

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/support/ptf/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// NewCommand is the command for creates supportptf.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	ptfCmd := &cobra.Command{
		Use:   "ptf",
		Short: L("Install a PTF"),
	}

	utils.AddBackendFlag(ptfCmd)

	ptfCmd.AddCommand(podman.NewCommand(globalFlags))

	return ptfCmd
}
0707010000007D000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002300000000uyuni-tools/mgradm/cmd/support/sql0707010000007E000081B4000000000000000000000001662A7528000015D9000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/support/sql/sql.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package sql

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func prepareSource(args []string, cnx *shared.Connection) (string, error) {
	source := "-"
	if len(args) > 0 {
		source = args[0]
		if !utils.FileExists(source) {
			return "", fmt.Errorf(L("source %s does not exists"), source)
		}
		randBytes := make([]byte, 16)
		if _, err := rand.Read(randBytes); err != nil {
			return "", fmt.Errorf(L("unable to get random file prefix: %s"), err)
		}
		source = hex.EncodeToString(randBytes) + source
		if err := cnx.Copy(args[0], "server:"+source, "", ""); err != nil {
			return "", err
		}
	}
	return source, nil
}

func cleanupSource(file string, cnx *shared.Connection) {
	if _, err := cnx.Exec("rm", file); err != nil {
		log.Error().Err(err).Msg(L("unable to cleanup source file"))
	}
}

func prepareOutput(flags *configFlags) (string, error) {
	output := "-"
	if flags.OutputFile != "" {
		output = flags.OutputFile
		if utils.FileExists(output) && !flags.ForceOverwrite {
			return "", fmt.Errorf(L("output file %s exists, use -f to force overwrite"), output)
		}
	}
	return output, nil
}

func getBaseCommand(keepStdin bool, flags *configFlags, cnx *shared.Connection) (string, []string, error) {
	podName, err := cnx.GetPodName()
	if err != nil {
		return "", nil, err
	}

	command, err := cnx.GetCommand()
	if err != nil {
		return "", nil, err
	}

	commandArgs := []string{"exec"}
	envs := []string{}
	if flags.Interactive {
		commandArgs = append(commandArgs, "-i")
		envs = append(envs, "ENV=/etc/sh.shrc.local")
		commandArgs = append(commandArgs, "-t")
		envs = append(envs, "TERM")
	} else if keepStdin {
		// To use STDIN source, we need to pass -i
		commandArgs = append(commandArgs, "-i")
	}
	commandArgs = append(commandArgs, podName)

	if command == "kubectl" {
		commandArgs = append(commandArgs, "-c", "uyuni", "--")
	}
	newEnv := []string{}
	for _, envValue := range envs {
		if !strings.Contains(envValue, "=") {
			if value, set := os.LookupEnv(envValue); set {
				newEnv = append(newEnv, fmt.Sprintf("%s=%s", envValue, value))
			}
		} else {
			newEnv = append(newEnv, envValue)
		}
	}
	if len(newEnv) > 0 {
		commandArgs = append(commandArgs, "env")
		commandArgs = append(commandArgs, newEnv...)
	}
	return command, commandArgs, nil
}

func doSql(globalFlags *types.GlobalFlags, flags *configFlags, cmd *cobra.Command, args []string) error {
	if flags.Interactive && flags.OutputFile != "" {
		return errors.New(L("interactive mode cannot work with a file output"))
	}

	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)

	// Validate options
	source, err := prepareSource(args, cnx)
	if err != nil {
		return err
	}
	if source != "" && source != "-" {
		defer cleanupSource(source, cnx)
	}
	output, err := prepareOutput(flags)
	if err != nil {
		return err
	}

	// For now do quick wrapper around spacewalk-sql tool.
	// TODO - ideally use sql directly, but will need some gateway to be able to connect to the database
	command, commandArgs, err := getBaseCommand(source == "-", flags, cnx)
	if err != nil {
		return err
	}
	commandArgs = append(commandArgs, "/usr/bin/spacewalk-sql")

	sqlArgs := []string{}
	if flags.Database == "reportdb" {
		sqlArgs = append(sqlArgs, "--reportdb")
	} else if flags.Database != "productdb" {
		return fmt.Errorf(L("unknown or unsupported database %s"), flags.Database)
	}

	if flags.Interactive {
		sqlArgs = append(sqlArgs, "-i")
	} else {
		sqlArgs = append(sqlArgs, "--select-mode", source)
	}
	commandArgs = append(commandArgs, sqlArgs...)

	err = runCmd(command, output, commandArgs)
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			log.Info().Err(err).Msg(L("Command failed"))
			os.Exit(exitErr.ExitCode())
		}
	}
	if output != "-" {
		log.Info().Msgf(L("Result is stored in the file '%s'"), output)
	}
	return nil
}

type copyWriter struct {
	Stream io.Writer
}

// Write writes an array of buffer in a stream.
func (l copyWriter) Write(p []byte) (n int, err error) {
	// Filter out kubectl line about terminated exit code
	if !strings.HasPrefix(string(p), "command terminated with exit code") {
		if _, err := l.Stream.Write(p); err != nil {
			return 0, fmt.Errorf(L("cannot write: %s"), err)
		}

		n = len(p)
		if n > 0 && p[n-1] == '\n' {
			// Trim CR added by stdlog.
			p = p[0 : n-1]
		}
		log.Debug().Msg(string(p))
	}
	return
}

func runCmd(command string, output string, args []string) error {
	log.Info().Msgf(L("Running: %s %s"), command, strings.Join(args, " "))

	runCmd := exec.Command(command, args...)
	runCmd.Stdin = os.Stdin

	if output == "" || output == "-" {
		runCmd.Stdout = copyWriter{Stream: os.Stdout}
	} else {
		log.Trace().Msgf("Output is FILE %s", output)
		f, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			return err
		}
		defer f.Close()
		runCmd.Stdout = copyWriter{Stream: f}
	}
	runCmd.Stderr = copyWriter{Stream: os.Stderr}

	if err := runCmd.Start(); err != nil {
		log.Debug().Err(err).Msg("error starting command")
		return err
	}

	return runCmd.Wait()
}
0707010000007F000081B4000000000000000000000001662A7528000006A1000000000000000000000000000000000000002E00000000uyuni-tools/mgradm/cmd/support/sql/sql_cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package sql

import (
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type configFlags struct {
	Database       string
	Interactive    bool
	ForceOverwrite bool   `mapstructure:"force"`
	OutputFile     string `mapstructure:"output"`
	Backend        string
}

// Add support sql command.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	configCmd := &cobra.Command{
		Use:   "sql [sql-file]",
		Short: L("Execute SQL query"),
		Long: L(`Execute SQL query either provided in sql-file or passed through standard input.

Examples:

  Run the 'select hostname from rhnserver;' query using echo:

  # echo 'select hostname from rhnserver;' | mgradm support sql

  Run in interative mode:

  # mgradm support sql -i

  Running the SQL queries in example.sql file and output them to out.log file

  # mgradm support sql example.sql -o out.log

`),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags configFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, doSql)
		},
	}

	configCmd.Flags().StringP("database", "d", "productdb", L("Target database, can be 'reportdb' or 'productdb'"))
	configCmd.Flags().BoolP("interactive", "i", false, L("Start in interactive mode"))
	configCmd.Flags().BoolP("force", "f", false, L("Force overwrite of output file if already exists"))
	configCmd.Flags().StringP("output", "o", "", L("Write output to the file instead of standard output"))
	utils.AddBackendFlag(configCmd)

	return configCmd
}
07070100000080000081B4000000000000000000000001662A75280000039D000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/support/support.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package support

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/support/config"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/support/ptf"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/support/sql"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand to export supportconfig.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	supportCmd := &cobra.Command{
		Use:   "support",
		Short: L("Commands for support operations"),
		Long:  L("Commands for support operations"),
	}
	supportCmd.AddCommand(config.NewCommand(globalFlags))
	supportCmd.AddCommand(sql.NewCommand(globalFlags))
	if ptfCommand := ptf.NewCommand(globalFlags); ptfCommand != nil {
		supportCmd.AddCommand(ptfCommand)
	}

	return supportCmd
}
07070100000081000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002100000000uyuni-tools/mgradm/cmd/uninstall07070100000082000081B4000000000000000000000001662A752800000AED000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/uninstall/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package uninstall

import (
	"fmt"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func uninstallForKubernetes(
	globalFlags *types.GlobalFlags,
	flags *uninstallFlags,
	cmd *cobra.Command,
	args []string,
) error {
	clusterInfos, err := kubernetes.CheckCluster()
	if err != nil {
		return err
	}
	kubeconfig := clusterInfos.GetKubeconfig()

	// TODO Find all the PVs related to the server if we want to delete them

	// Uninstall uyuni
	namespace, err := kubernetes.HelmUninstall(kubeconfig, "uyuni", "", !flags.Force)
	if err != nil {
		return err
	}

	// Remove the remaining configmap and secrets
	if namespace != "" {
		_, err := utils.RunCmdOutput(zerolog.TraceLevel, "kubectl", "-n", namespace, "get", "secret", "uyuni-ca")
		caSecret := "uyuni-ca"
		if err != nil {
			caSecret = ""
		}

		if !flags.Force {
			log.Info().Msgf(L("Would run %s"), fmt.Sprintf("kubectl delete -n %s configmap uyuni-ca", namespace))
			log.Info().Msgf(L("Would run %s"), fmt.Sprintf("kubectl delete -n %s secret uyuni-cert %s", namespace, caSecret))
		} else {
			log.Info().Msgf(L("Running %s"), fmt.Sprintf("kubectl delete -n %s configmap uyuni-ca", namespace))
			if err := utils.RunCmd("kubectl", "delete", "-n", namespace, "configmap", "uyuni-ca"); err != nil {
				log.Info().Err(err).Msgf(L("Failed deleting config map"))
			}

			log.Info().Msgf(L("Running %s"), fmt.Sprintf("kubectl delete -n %s secret uyuni-cert %s", namespace, caSecret))

			args := []string{"delete", "-n", namespace, "secret", "uyuni-cert"}
			if caSecret != "" {
				args = append(args, caSecret)
			}
			err := utils.RunCmd("kubectl", args...)
			if err != nil {
				log.Info().Err(err).Msgf(L("Failed deleting secret"))
			}
		}
	}

	// TODO Remove the PVs or wait for their automatic removal if purge is requested
	// Also wait if the PVs are dynamic with Delete reclaim policy but the user didn't ask to purge them
	// Since some storage plugins don't handle Delete policy, we may need to check for error events to avoid infinite loop

	// Uninstall cert-manager if we installed it
	if _, err := kubernetes.HelmUninstall(kubeconfig, "cert-manager", "-linstalledby=mgradm", !flags.Force); err != nil {
		return err
	}

	// Remove the K3s Traefik config
	if clusterInfos.IsK3s() {
		kubernetes.UninstallK3sTraefikConfig(!flags.Force)
	}

	// Remove the rke2 nginx config
	if clusterInfos.IsRke2() {
		kubernetes.UninstallRke2NginxConfig(!flags.Force)
	}
	return nil
}
07070100000083000081B4000000000000000000000001662A752800000182000000000000000000000000000000000000003100000000uyuni-tools/mgradm/cmd/uninstall/nokubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package uninstall

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func uninstallForKubernetes(
	globalFlags *types.GlobalFlags,
	flags *uninstallFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return nil
}

const kubernetesHelp = ""
07070100000084000081B4000000000000000000000001662A75280000056A000000000000000000000000000000000000002B00000000uyuni-tools/mgradm/cmd/uninstall/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package uninstall

import (
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func uninstallForPodman(
	globalFlags *types.GlobalFlags,
	flags *uninstallFlags,
	cmd *cobra.Command,
	args []string,
) error {
	// Uninstall the service
	podman.UninstallService("uyuni-server", !flags.Force)
	// Force stop the pod
	podman.DeleteContainer(podman.ServerContainerName, !flags.Force)

	if podman.HasService(podman.ServerAttestationService) {
		podman.UninstallService(podman.ServerAttestationService, !flags.Force)
		podman.DeleteContainer(podman.ServerAttestationService, !flags.Force)
	}

	// Remove the volumes
	if flags.PurgeVolumes {
		volumes := []string{"cgroup"}
		for _, volume := range utils.ServerVolumeMounts {
			volumes = append(volumes, volume.Name)
		}
		for _, volume := range volumes {
			if err := podman.DeleteVolume(volume, !flags.Force); err != nil {
				return fmt.Errorf(L("cannot delete volume %s: %s"), volume, err)
			}
		}
		log.Info().Msg(L("All volumes removed"))
	}

	podman.DeleteNetwork(!flags.Force)

	return podman.ReloadDaemon(!flags.Force)
}
07070100000085000081B4000000000000000000000001662A75280000067C000000000000000000000000000000000000002E00000000uyuni-tools/mgradm/cmd/uninstall/uninstall.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package uninstall

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type uninstallFlags struct {
	Backend      string
	Force        bool
	PurgeVolumes bool
}

// NewCommand uninstall a server and optionally the corresponding volumes.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	uninstallCmd := &cobra.Command{
		Use:   "uninstall",
		Short: L("Uninstall a server"),
		Long: L(`Uninstall a server and optionally the corresponding volumes.
By default it will only print what would be done, use --force to actually remove.`) + kubernetes.UninstallHelp(),
		Args: cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags uninstallFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, uninstall)
		},
	}
	uninstallCmd.Flags().BoolP("force", "f", false, L("Actually remove the server"))
	uninstallCmd.Flags().Bool("purgeVolumes", false, L("Also remove the volumes"))

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(uninstallCmd)
	}

	return uninstallCmd
}

func uninstall(
	globalFlags *types.GlobalFlags,
	flags *uninstallFlags,
	cmd *cobra.Command,
	args []string,
) error {
	fn, err := shared.ChoosePodmanOrKubernetes(cmd.Flags(), uninstallForPodman, uninstallForKubernetes)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
07070100000086000041FD000000000000000000000005662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgradm/cmd/upgrade07070100000087000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/upgrade/kubernetes07070100000088000081B4000000000000000000000001662A75280000048C000000000000000000000000000000000000003800000000uyuni-tools/mgradm/cmd/upgrade/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/upgrade/shared"
	cmd_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type kubernetesUpgradeFlags struct {
	shared.UpgradeFlags `mapstructure:",squash"`
	Helm                cmd_utils.HelmFlags
}

// NewCommand to upgrade a kubernetes server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	upgradeCmd := &cobra.Command{
		Use:   "kubernetes",
		Short: L("Upgrade a local server on kubernetes"),
		Long:  L("Upgrade a local server on kubernetes"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags kubernetesUpgradeFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, upgradeKubernetes)
		},
	}

	shared.AddUpgradeFlags(upgradeCmd)
	cmd_utils.AddHelmInstallFlag(upgradeCmd)

	return upgradeCmd
}
07070100000089000081B4000000000000000000000001662A752800000124000000000000000000000000000000000000003500000000uyuni-tools/mgradm/cmd/upgrade/kubernetes/nobuild.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	return nil
}
0707010000008A000081B4000000000000000000000001662A752800000205000000000000000000000000000000000000003300000000uyuni-tools/mgradm/cmd/upgrade/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func upgradeKubernetes(
	globalFlags *types.GlobalFlags,
	flags *kubernetesUpgradeFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Upgrade(globalFlags, &flags.Image, &flags.MigrationImage, flags.Helm, cmd, args)
}
0707010000008B000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/upgrade/podman0707010000008C000081B4000000000000000000000001662A7528000006E2000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/upgrade/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/upgrade/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type podmanUpgradeFlags struct {
	shared.UpgradeFlags `mapstructure:",squash"`
	Podman              podman.PodmanFlags
	MirrorPath          string
}

// NewCommand to upgrade a podman server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	upgradeCmd := &cobra.Command{
		Use:   "podman",
		Short: L("Upgrade a local server on podman"),
		Args:  cobra.RangeArgs(0, 1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags podmanUpgradeFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, upgradePodman)
		},
	}
	listCmd := &cobra.Command{
		Use:   "list",
		Short: L("List available tag for an image"),
		Args:  cobra.ExactArgs(0),
		Run: func(cmd *cobra.Command, args []string) {
			viper, _ := utils.ReadConfig(globalFlags.ConfigPath, cmd)

			var flags podmanUpgradeFlags
			if err := viper.Unmarshal(&flags); err != nil {
				log.Fatal().Err(err).Msg(L("Failed to unmarshall configuration"))
			}
			tags, _ := podman.ShowAvailableTag(flags.Image.Name)
			log.Info().Msgf(L("Available Tags for image: %s"), flags.Image.Name)
			for _, value := range tags {
				log.Info().Msgf(value)
			}
		},
	}
	shared.AddUpgradeListFlags(listCmd)
	upgradeCmd.AddCommand(listCmd)

	shared.AddUpgradeFlags(upgradeCmd)
	podman.AddPodmanArgFlag(upgradeCmd)

	return upgradeCmd
}
0707010000008D000081B4000000000000000000000001662A7528000001B7000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/upgrade/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func upgradePodman(globalFlags *types.GlobalFlags, flags *podmanUpgradeFlags, cmd *cobra.Command, args []string) error {
	return podman.Upgrade(flags.Image, flags.MigrationImage, args)
}
0707010000008E000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgradm/cmd/upgrade/shared0707010000008F000081B4000000000000000000000001662A7528000002FD000000000000000000000000000000000000002F00000000uyuni-tools/mgradm/cmd/upgrade/shared/flags.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// UpgradeFlags represents flags used for upgrading a server.
type UpgradeFlags struct {
	Image          types.ImageFlags `mapstructure:",squash"`
	MigrationImage types.ImageFlags `mapstructure:"migration"`
}

// AddUpgradeFlags add upgrade flags to a command.
func AddUpgradeFlags(cmd *cobra.Command) {
	utils.AddImageUpgradeFlag(cmd)
	utils.AddMigrationImageFlag(cmd)
}

// AddUpgradeListFlags add upgrade list flags to a command.
func AddUpgradeListFlags(cmd *cobra.Command) {
	utils.AddImageUpgradeFlag(cmd)
}
07070100000090000081B4000000000000000000000001662A752800000DF9000000000000000000000000000000000000003000000000uyuni-tools/mgradm/cmd/upgrade/shared/shared.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"errors"
	"fmt"

	"github.com/rs/zerolog/log"

	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func isUyuni(cnx *shared.Connection) (bool, error) {
	cnx_args := []string{"/etc/uyuni-release"}
	_, err := cnx.Exec("cat", cnx_args...)
	if err != nil {
		cnx_args := []string{"/etc/susemanager-release"}
		_, err := cnx.Exec("cat", cnx_args...)
		if err != nil {
			return false, errors.New(L("cannot find neither /etc/uyuni-release nor /etc/susemanager-release"))
		}
		return false, nil
	}
	return true, nil
}

// SanityCheck verifies if an upgrade can be run.
func SanityCheck(cnx *shared.Connection, inspectedValues map[string]string, serverImage string) error {
	isUyuni, err := isUyuni(cnx)
	if err != nil {
		return fmt.Errorf(L("cannot check server release: %s"), err)
	}
	_, isCurrentUyuni := inspectedValues["uyuni_release"]
	_, isCurrentSuma := inspectedValues["suse_manager_release"]

	if isUyuni && isCurrentSuma {
		return fmt.Errorf(L("currently SUSE Manager %s is installed, instead the image is Uyuni. Upgrade is not supported"), inspectedValues["suse_manager_release"])
	}

	if !isUyuni && isCurrentUyuni {
		return fmt.Errorf(L("currently Uyuni %s is installed, instead the image is SUSE Manager. Upgrade is not supported"), inspectedValues["uyuni_release"])
	}

	if isUyuni {
		cnx_args := []string{"s/Uyuni release //g", "/etc/uyuni-release"}
		current_uyuni_release, err := cnx.Exec("sed", cnx_args...)
		if err != nil {
			return fmt.Errorf(L("failed to read current uyuni release: %s"), err)
		}
		log.Debug().Msgf("Current release is %s", string(current_uyuni_release))
		if (len(inspectedValues["uyuni_release"])) <= 0 {
			return fmt.Errorf(L("cannot fetch release from image %s"), serverImage)
		}
		log.Debug().Msgf("Image %s is %s", serverImage, inspectedValues["uyuni_release"])
		if utils.CompareVersion(inspectedValues["uyuni_release"], string(current_uyuni_release)) < 0 {
			return fmt.Errorf(L("cannot downgrade from version %s to %s"), string(current_uyuni_release), inspectedValues["uyuni_release"])
		}
	} else {
		cnx_args := []string{"s/SUSE Manager release //g", "/etc/susemanager-release"}
		current_suse_manager_release, err := cnx.Exec("sed", cnx_args...)
		if err != nil {
			return fmt.Errorf(L("failed to read current susemanager release: %s"), err)
		}
		log.Debug().Msgf("Current release is %s", string(current_suse_manager_release))
		if (len(inspectedValues["suse_manager_release"])) <= 0 {
			return fmt.Errorf(L("cannot fetch release from image %s"), serverImage)
		}
		log.Debug().Msgf("Image %s is %s", serverImage, inspectedValues["suse_manager_release"])
		if utils.CompareVersion(inspectedValues["suse_manager_release"], string(current_suse_manager_release)) < 0 {
			return fmt.Errorf(L("cannot downgrade from version %s to %s"), string(current_suse_manager_release), inspectedValues["suse_manager_release"])
		}
	}

	if (len(inspectedValues["image_pg_version"])) <= 0 {
		return fmt.Errorf(L("cannot fetch postgresql version from %s"), serverImage)
	}
	log.Debug().Msgf("Image %s has PostgreSQL %s", serverImage, inspectedValues["image_pg_version"])
	if (len(inspectedValues["current_pg_version"])) <= 0 {
		return fmt.Errorf(L("PostgreSQL is not installed in the current deployment"))
	}
	log.Debug().Msgf("Current deployment has PostgreSQL %s", inspectedValues["current_pg_version"])

	return nil
}
07070100000091000081B4000000000000000000000001662A752800000338000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/cmd/upgrade/upgrade.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package upgrade

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/upgrade/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd/upgrade/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand for upgrading a local server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	upgradeCmd := &cobra.Command{
		Use:   "upgrade server",
		Short: L("Upgrade local server"),
		Long:  L("Upgrade local server"),
	}

	upgradeCmd.AddCommand(podman.NewCommand(globalFlags))

	if kubernetesCmd := kubernetes.NewCommand(globalFlags); kubernetesCmd != nil {
		upgradeCmd.AddCommand(kubernetesCmd)
	}

	return upgradeCmd
}
07070100000092000081B4000000000000000000000001662A75280000027C000000000000000000000000000000000000001B00000000uyuni-tools/mgradm/main.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package main

import (
	"os"

	"github.com/chai2010/gettext-go"
	"github.com/uyuni-project/uyuni-tools/mgradm/cmd"
	l10n_utils "github.com/uyuni-project/uyuni-tools/shared/l10n/utils"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// Run runs the `mgradm` root command.
func Run() error {
	gettext.BindLocale(gettext.New("mgradm", utils.LocaleRoot, l10n_utils.New(utils.LocaleRoot)))
	run, err := cmd.NewUyuniadmCommand()
	if err != nil {
		return err
	}
	return run.Execute()
}

func main() {
	if err := Run(); err != nil {
		os.Exit(1)
	}
}
07070100000093000041FD000000000000000000000007662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/mgradm/shared07070100000094000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002500000000uyuni-tools/mgradm/shared/kubernetes07070100000095000081B4000000000000000000000001662A752800001818000000000000000000000000000000000000003500000000uyuni-tools/mgradm/shared/kubernetes/certificates.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/templates"
	cmd_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func installTlsSecret(namespace string, serverCrt []byte, serverKey []byte, rootCaCrt []byte) {
	crdsDir, err := os.MkdirTemp("", "mgradm-*")
	if err != nil {
		log.Fatal().Err(err).Msgf(L("failed to create temporary directory: %s"), err)
	}
	defer os.RemoveAll(crdsDir)

	secretPath := filepath.Join(crdsDir, "secret.yaml")
	log.Info().Msg(L("Creating SSL server certificate secret"))
	tlsSecretData := templates.TlsSecretTemplateData{
		Namespace:   namespace,
		Name:        "uyuni-cert",
		Certificate: base64.StdEncoding.EncodeToString(serverCrt),
		Key:         base64.StdEncoding.EncodeToString(serverKey),
		RootCa:      base64.StdEncoding.EncodeToString(rootCaCrt),
	}

	if err = utils.WriteTemplateToFile(tlsSecretData, secretPath, 0500, true); err != nil {
		log.Fatal().Err(err).Msg(L("Failed to generate uyuni-crt secret definition"))
	}
	err = utils.RunCmd("kubectl", "apply", "-f", secretPath)
	if err != nil {
		log.Fatal().Err(err).Msg(L("Failed to create uyuni-crt TLS secret"))
	}

	createCaConfig(rootCaCrt)
}

// Install cert-manager and its CRDs using helm in the cert-manager namespace if needed
// and then create a self-signed CA and issuers.
// Returns helm arguments to be added to use the issuer.
func installSslIssuers(helmFlags *cmd_utils.HelmFlags, sslFlags *cmd_utils.SslCertFlags, rootCa string,
	tlsCert *ssl.SslPair, kubeconfig, fqdn string, imagePullPolicy string) ([]string, error) {
	// Install cert-manager if needed
	if err := installCertManager(helmFlags, kubeconfig, imagePullPolicy); err != nil {
		return []string{}, fmt.Errorf(L("cannot install cert manager: %s"), err)
	}

	log.Info().Msg(L("Creating SSL certificate issuer"))
	crdsDir, err := os.MkdirTemp("", "mgradm-*")
	if err != nil {
		return []string{}, fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}
	defer os.RemoveAll(crdsDir)

	issuerPath := filepath.Join(crdsDir, "issuer.yaml")

	issuerData := templates.IssuerTemplateData{
		Namespace:   helmFlags.Uyuni.Namespace,
		Country:     sslFlags.Country,
		State:       sslFlags.State,
		City:        sslFlags.City,
		Org:         sslFlags.Org,
		OrgUnit:     sslFlags.OU,
		Email:       sslFlags.Email,
		Fqdn:        fqdn,
		RootCa:      rootCa,
		Key:         tlsCert.Key,
		Certificate: tlsCert.Cert,
	}

	if err = utils.WriteTemplateToFile(issuerData, issuerPath, 0500, true); err != nil {
		return []string{}, fmt.Errorf(L("failed to generate issuer definition: %s"), err)
	}

	err = utils.RunCmd("kubectl", "apply", "-f", issuerPath)
	if err != nil {
		log.Fatal().Err(err).Msg(L("Failed to create issuer"))
	}

	// Wait for issuer to be ready
	for i := 0; i < 60; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "-o=jsonpath={.status.conditions[*].type}",
			"issuer", "uyuni-ca-issuer")
		if err == nil && string(out) == "Ready" {
			return []string{"--set-json", "ingressSslAnnotations={\"cert-manager.io/issuer\": \"uyuni-ca-issuer\"}"}, nil
		}
		time.Sleep(1 * time.Second)
	}
	log.Fatal().Msg(L("Issuer didn't turn ready after 60s"))
	return []string{}, nil
}

func installCertManager(helmFlags *cmd_utils.HelmFlags, kubeconfig string, imagePullPolicy string) error {
	if !kubernetes.IsDeploymentReady("", "cert-manager") {
		log.Info().Msg(L("Installing cert-manager"))
		repo := ""
		chart := helmFlags.CertManager.Chart
		version := helmFlags.CertManager.Version
		namespace := helmFlags.CertManager.Namespace

		args := []string{
			"--set", "installCRDs=true",
			"--set-json", "global.commonLabels={\"installedby\": \"mgradm\"}",
			"--set", "images.pullPolicy=" + kubernetes.GetPullPolicy(imagePullPolicy),
		}
		extraValues := helmFlags.CertManager.Values
		if extraValues != "" {
			args = append(args, "-f", extraValues)
		}

		// Use upstream chart if nothing defined
		if chart == "" {
			repo = "https://charts.jetstack.io"
			chart = "cert-manager"
		}
		// The installedby label will be used to only uninstall what we installed
		if err := kubernetes.HelmUpgrade(kubeconfig, namespace, true, repo, "cert-manager", chart, version, args...); err != nil {
			return fmt.Errorf(L("cannot run helm upgrade: %s"), err)
		}
	}

	// Wait for cert-manager to be ready
	err := kubernetes.WaitForDeployment("", "cert-manager-webhook", "webhook")
	if err != nil {
		return fmt.Errorf(L("cannot deploy: %s"), err)
	}

	return nil
}

func extractCaCertToConfig() {
	// TODO Replace with [trust-manager](https://cert-manager.io/docs/projects/trust-manager/) to automate this
	const jsonPath = "-o=jsonpath={.data.ca\\.crt}"

	log.Info().Msg(L("Extracting CA certificate to a configmap"))
	// Skip extracting if the configmap is already present
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "configmap", "uyuni-ca", jsonPath)
	log.Info().Msgf(L("CA cert: %s"), string(out))
	if err == nil && len(out) > 0 {
		log.Info().Msg(L("uyuni-ca configmap already existing, skipping extraction"))
		return
	}

	out, err = utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "secret", "uyuni-ca", jsonPath)
	if err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to get uyuni-ca certificate"))
	}

	decoded, err := base64.StdEncoding.DecodeString(string(out))
	if err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to base64 decode CA certificate"))
	}

	createCaConfig(decoded)
}

func createCaConfig(ca []byte) {
	valueArg := "--from-literal=ca.crt=" + string(ca)
	if err := utils.RunCmd("kubectl", "create", "configmap", "uyuni-ca", valueArg); err != nil {
		log.Fatal().Err(err).Msg(L("Failed to create uyuni-ca config map from certificate"))
	}
}
07070100000096000081B4000000000000000000000001662A752800001F22000000000000000000000000000000000000003000000000uyuni-tools/mgradm/shared/kubernetes/install.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl"
	cmd_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// HELM_APP_NAME is the Helm application name.
const HELM_APP_NAME = "uyuni"

// Deploy execute a deploy of a given image and helm to a cluster.
func Deploy(cnx *shared.Connection, imageFlags *types.ImageFlags,
	helmFlags *cmd_utils.HelmFlags, sslFlags *cmd_utils.SslCertFlags, clusterInfos *kubernetes.ClusterInfos,
	fqdn string, debug bool, helmArgs ...string) error {
	// If installing on k3s, install the traefik helm config in manifests
	isK3s := clusterInfos.IsK3s()
	IsRke2 := clusterInfos.IsRke2()
	if isK3s {
		InstallK3sTraefikConfig(debug)
	} else if IsRke2 {
		kubernetes.InstallRke2NginxConfig(utils.TCP_PORTS, utils.UDP_PORTS, helmFlags.Uyuni.Namespace)
	}

	serverImage, err := utils.ComputeImage(imageFlags.Name, imageFlags.Tag)
	if err != nil {
		return fmt.Errorf(L("failed to compute image URL: %s"), err)
	}

	// Install the uyuni server helm chart
	err = UyuniUpgrade(serverImage, imageFlags.PullPolicy, helmFlags, clusterInfos.GetKubeconfig(), fqdn, clusterInfos.Ingress, helmArgs...)
	if err != nil {
		return fmt.Errorf(L("cannot upgrade: %s"), err)
	}

	// Wait for the pod to be started
	err = kubernetes.WaitForDeployment(helmFlags.Uyuni.Namespace, HELM_APP_NAME, "uyuni")
	if err != nil {
		return fmt.Errorf(L("cannot deploy: %s"), err)
	}
	return cnx.WaitForServer()
}

// DeployCertificate executre a deploy a new certificate given an helm.
func DeployCertificate(helmFlags *cmd_utils.HelmFlags, sslFlags *cmd_utils.SslCertFlags, rootCa string,
	ca *ssl.SslPair, kubeconfig string, fqdn string, imagePullPolicy string) ([]string, error) {
	helmArgs := []string{}
	if sslFlags.UseExisting() {
		DeployExistingCertificate(helmFlags, sslFlags, kubeconfig)
	} else {
		// Install cert-manager and a self-signed issuer ready for use
		issuerArgs, err := installSslIssuers(helmFlags, sslFlags, rootCa, ca, kubeconfig, fqdn, imagePullPolicy)
		if err != nil {
			return []string{}, fmt.Errorf(L("cannot install cert-manager and self-sign issuer: %s"), err)
		}
		helmArgs = append(helmArgs, issuerArgs...)

		// Extract the CA cert into uyuni-ca config map as the container shouldn't have the CA secret
		extractCaCertToConfig()
	}

	return helmArgs, nil
}

// DeployExistingCertificate execute a deploy of an existing certificate.
func DeployExistingCertificate(helmFlags *cmd_utils.HelmFlags, sslFlags *cmd_utils.SslCertFlags, kubeconfig string) {
	// Deploy the SSL Certificate secret and CA configmap
	serverCrt, rootCaCrt := ssl.OrderCas(&sslFlags.Ca, &sslFlags.Server)
	serverKey := utils.ReadFile(sslFlags.Server.Key)
	installTlsSecret(helmFlags.Uyuni.Namespace, serverCrt, serverKey, rootCaCrt)

	// Extract the CA cert into uyuni-ca config map as the container shouldn't have the CA secret
	extractCaCertToConfig()
}

// UyuniUpgrade runs an helm upgrade using images and helm configuration as parameters.
func UyuniUpgrade(serverImage string, pullPolicy string, helmFlags *cmd_utils.HelmFlags, kubeconfig string,
	fqdn string, ingress string, helmArgs ...string) error {
	log.Info().Msg(L("Installing Uyuni"))

	// The guessed ingress is passed before the user's value to let the user override it in case we got it wrong.
	helmParams := []string{
		"--set", "ingress=" + ingress,
	}

	extraValues := helmFlags.Uyuni.Values
	if extraValues != "" {
		helmParams = append(helmParams, "-f", extraValues)
	}

	// The values computed from the command line need to be last to override what could be in the extras
	helmParams = append(helmParams,
		"--set", "images.server="+serverImage,
		"--set", "pullPolicy="+kubernetes.GetPullPolicy(pullPolicy),
		"--set", "fqdn="+fqdn)

	helmParams = append(helmParams, helmArgs...)

	namespace := helmFlags.Uyuni.Namespace
	chart := helmFlags.Uyuni.Chart
	version := helmFlags.Uyuni.Version
	return kubernetes.HelmUpgrade(kubeconfig, namespace, true, "", HELM_APP_NAME, chart, version, helmParams...)
}

// Upgrade will upgrade a server in a kubernetes cluster.
func Upgrade(
	globalFlags *types.GlobalFlags,
	image *types.ImageFlags,
	migrationImage *types.ImageFlags,
	helm cmd_utils.HelmFlags,
	cmd *cobra.Command,
	args []string,
) error {
	for _, binary := range []string{"kubectl", "helm"} {
		if _, err := exec.LookPath(binary); err != nil {
			return fmt.Errorf(L("install %s before running this command"), binary)
		}
	}
	cnx := shared.NewConnection("kubectl", "", kubernetes.ServerFilter)

	serverImage, err := utils.ComputeImage(image.Name, image.Tag)
	if err != nil {
		return fmt.Errorf(L("failed to compute image URL: %s"), err)
	}

	inspectedValues, err := kubernetes.InspectKubernetes(serverImage, image.PullPolicy)
	if err != nil {
		return fmt.Errorf(L("cannot inspect kubernetes values: %s"), err)
	}

	err = cmd_utils.SanityCheck(cnx, inspectedValues, serverImage)
	if err != nil {
		return err
	}

	fqdn, exist := inspectedValues["fqdn"]
	if !exist {
		return fmt.Errorf(L("inspect function did non return fqdn value"))
	}

	clusterInfos, err := kubernetes.CheckCluster()
	if err != nil {
		return err
	}
	kubeconfig := clusterInfos.GetKubeconfig()

	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}

	//this is needed because folder with script needs to be mounted
	//check the node before scaling down
	nodeName, err := kubernetes.GetNode("uyuni")
	if err != nil {
		return fmt.Errorf(L("cannot find node running uyuni: %s"), err)
	}

	err = kubernetes.ReplicasTo(kubernetes.ServerFilter, 0)
	if err != nil {
		return fmt.Errorf(L("cannot set replica to 0: %s"), err)
	}

	defer func() {
		// if something is running, we don't need to set replicas to 1
		if _, err = kubernetes.GetNode("uyuni"); err != nil {
			err = kubernetes.ReplicasTo(kubernetes.ServerFilter, 1)
		}
	}()
	if inspectedValues["image_pg_version"] > inspectedValues["current_pg_version"] {
		log.Info().Msgf(L("Previous PostgreSQL is %s, new one is %s. Performing a DB version upgrade..."), inspectedValues["current_pg_version"], inspectedValues["image_pg_version"])

		if err := RunPgsqlVersionUpgrade(*image, *migrationImage, nodeName, inspectedValues["current_pg_version"], inspectedValues["image_pg_version"]); err != nil {
			return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
		}
	} else if inspectedValues["image_pg_version"] == inspectedValues["current_pg_version"] {
		log.Info().Msgf(L("Upgrading to %s without changing PostgreSQL version"), inspectedValues["uyuni_release"])
	} else {
		return fmt.Errorf(L("trying to downgrade PostgreSQL from %s to %s"), inspectedValues["current_pg_version"], inspectedValues["image_pg_version"])
	}

	schemaUpdateRequired := inspectedValues["current_pg_version"] != inspectedValues["image_pg_version"]
	if err := RunPgsqlFinalizeScript(serverImage, image.PullPolicy, nodeName, schemaUpdateRequired); err != nil {
		return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
	}

	if err := RunPostUpgradeScript(serverImage, image.PullPolicy, nodeName); err != nil {
		return fmt.Errorf(L("cannot run post upgrade script: %s"), err)
	}

	err = UyuniUpgrade(serverImage, image.PullPolicy, &helm, kubeconfig, fqdn, clusterInfos.Ingress)
	if err != nil {
		return fmt.Errorf(L("cannot upgrade to image %s: %s"), serverImage, err)
	}

	return kubernetes.WaitForDeployment(helm.Uyuni.Namespace, "uyuni", "uyuni")
}
07070100000097000081B4000000000000000000000001662A752800001CB9000000000000000000000000000000000000002C00000000uyuni-tools/mgradm/shared/kubernetes/k3s.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"errors"
	"fmt"
	"os"

	"github.com/rs/zerolog/log"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// InstallK3sTraefikConfig installs the K3s Traefik configuration.
func InstallK3sTraefikConfig(debug bool) {
	tcpPorts := []types.PortMap{}
	tcpPorts = append(tcpPorts, utils.TCP_PORTS...)
	if debug {
		tcpPorts = append(tcpPorts, utils.DEBUG_PORTS...)
	}

	kubernetes.InstallK3sTraefikConfig(tcpPorts, utils.UDP_PORTS)
}

// RunPgsqlVersionUpgrade perform a PostgreSQL major upgrade.
func RunPgsqlVersionUpgrade(image types.ImageFlags, migrationImage types.ImageFlags, nodeName string, oldPgsql string, newPgsql string) error {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return errors.New(L("failed to create temporary directory: %s"))
	}
	if newPgsql > oldPgsql {
		log.Info().Msgf(L("Previous PostgreSQL is %s, new one is %s. Performing a DB version upgrade..."), oldPgsql, newPgsql)

		pgsqlVersionUpgradeContainer := "uyuni-upgrade-pgsql"

		migrationImageUrl := ""
		if migrationImage.Name == "" {
			migrationImageUrl, err = utils.ComputeImage(image.Name, image.Tag, fmt.Sprintf("-migration-%s-%s", oldPgsql, newPgsql))
			if err != nil {
				return fmt.Errorf(L("failed to compute image URL: %s"), err)
			}
		} else {
			migrationImageUrl, err = utils.ComputeImage(migrationImage.Name, image.Tag)
			if err != nil {
				return fmt.Errorf(L("failed to compute image URL: %s"), err)
			}
		}

		log.Info().Msgf(L("Using migration image %s"), migrationImageUrl)
		pgsqlVersionUpgradeScriptName, err := adm_utils.GeneratePgsqlVersionUpgradeScript(scriptDir, oldPgsql, newPgsql, true)
		if err != nil {
			return fmt.Errorf(L("cannot generate PostgreSQL database version upgrade script: %s"), err)
		}

		//delete pending pod and then check the node, because in presence of more than a pod GetNode return is wrong
		if err := kubernetes.DeletePod(pgsqlVersionUpgradeContainer, kubernetes.ServerFilter); err != nil {
			return fmt.Errorf(L("cannot delete %s: %s"), pgsqlVersionUpgradeContainer, err)
		}

		//generate deploy data
		pgsqlVersioUpgradeDeployData := types.Deployment{
			APIVersion: "v1",
			Spec: &types.Spec{
				RestartPolicy: "Never",
				NodeName:      nodeName,
				Containers: []types.Container{
					{
						Name: pgsqlVersionUpgradeContainer,
						VolumeMounts: append(utils.PgsqlRequiredVolumeMounts,
							types.VolumeMount{MountPath: "/var/lib/uyuni-tools", Name: "var-lib-uyuni-tools"}),
					},
				},
				Volumes: append(utils.PgsqlRequiredVolumes,
					types.Volume{Name: "var-lib-uyuni-tools", HostPath: &types.HostPath{Path: scriptDir, Type: "Directory"}}),
			},
		}

		//transform deploy in JSON
		overridePgsqlVersioUpgrade, err := kubernetes.GenerateOverrideDeployment(pgsqlVersioUpgradeDeployData)
		if err != nil {
			return err
		}

		err = kubernetes.RunPod(pgsqlVersionUpgradeContainer, kubernetes.ServerFilter, migrationImageUrl, image.PullPolicy, "/var/lib/uyuni-tools/"+pgsqlVersionUpgradeScriptName, overridePgsqlVersioUpgrade)
		if err != nil {
			return fmt.Errorf(L("error running container %s: %s"), pgsqlVersionUpgradeContainer, err)
		}
	}
	return nil
}

// RunPgsqlFinalizeScript run the script with all the action required to a db after upgrade.
func RunPgsqlFinalizeScript(serverImage string, pullPolicy string, nodeName string, schemaUpdateRequired bool) error {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"))
	}
	pgsqlFinalizeContainer := "uyuni-finalize-pgsql"
	pgsqlFinalizeScriptName, err := adm_utils.GenerateFinalizePostgresScript(scriptDir, true, schemaUpdateRequired, true, true, true)
	if err != nil {
		return fmt.Errorf(L("cannot generate PostgreSQL finalization script %s"), err)
	}
	//delete pending pod and then check the node, because in presence of more than a pod GetNode return is wrong
	if err := kubernetes.DeletePod(pgsqlFinalizeContainer, kubernetes.ServerFilter); err != nil {
		return fmt.Errorf(L("cannot delete %s: %s"), pgsqlFinalizeContainer, err)
	}
	//generate deploy data
	pgsqlFinalizeDeployData := types.Deployment{
		APIVersion: "v1",
		Spec: &types.Spec{
			RestartPolicy: "Never",
			NodeName:      nodeName,
			Containers: []types.Container{
				{
					Name: pgsqlFinalizeContainer,
					VolumeMounts: append(utils.PgsqlRequiredVolumeMounts,
						types.VolumeMount{MountPath: "/var/lib/uyuni-tools", Name: "var-lib-uyuni-tools"}),
				},
			},
			Volumes: append(utils.PgsqlRequiredVolumes,
				types.Volume{Name: "var-lib-uyuni-tools", HostPath: &types.HostPath{Path: scriptDir, Type: "Directory"}}),
		},
	}
	//transform deploy data in JSON
	overridePgsqlFinalize, err := kubernetes.GenerateOverrideDeployment(pgsqlFinalizeDeployData)
	if err != nil {
		return err
	}
	err = kubernetes.RunPod(pgsqlFinalizeContainer, kubernetes.ServerFilter, serverImage, pullPolicy, "/var/lib/uyuni-tools/"+pgsqlFinalizeScriptName, overridePgsqlFinalize)
	if err != nil {
		return fmt.Errorf(L("error running container %s: %s"), pgsqlFinalizeContainer, err)
	}
	return nil
}

// RunPostUpgradeScript run the script with the changes to apply after the upgrade.
func RunPostUpgradeScript(serverImage string, pullPolicy string, nodeName string) error {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"))
	}
	postUpgradeContainer := "uyuni-post-upgrade"
	postUpgradeScriptName, err := adm_utils.GeneratePostUpgradeScript(scriptDir, "localhost")
	if err != nil {
		return fmt.Errorf(L("cannot generate PostgreSQL finalization script %s"), err)
	}

	//delete pending pod and then check the node, because in presence of more than a pod GetNode return is wrong
	if err := kubernetes.DeletePod(postUpgradeContainer, kubernetes.ServerFilter); err != nil {
		return fmt.Errorf(L("cannot delete %s: %s"), postUpgradeContainer, err)
	}
	//generate deploy data
	postUpgradeDeployData := types.Deployment{
		APIVersion: "v1",
		Spec: &types.Spec{
			RestartPolicy: "Never",
			NodeName:      nodeName,
			Containers: []types.Container{
				{
					Name: postUpgradeContainer,
					VolumeMounts: append(utils.PgsqlRequiredVolumeMounts,
						types.VolumeMount{MountPath: "/var/lib/uyuni-tools", Name: "var-lib-uyuni-tools"}),
				},
			},
			Volumes: append(utils.PgsqlRequiredVolumes,
				types.Volume{Name: "var-lib-uyuni-tools", HostPath: &types.HostPath{Path: scriptDir, Type: "Directory"}}),
		},
	}
	//transform deploy data in JSON
	overridePostUpgrade, err := kubernetes.GenerateOverrideDeployment(postUpgradeDeployData)
	if err != nil {
		return err
	}

	err = kubernetes.RunPod(postUpgradeContainer, kubernetes.ServerFilter, serverImage, pullPolicy, "/var/lib/uyuni-tools/"+postUpgradeScriptName, overridePostUpgrade)
	if err != nil {
		return fmt.Errorf(L("error running container %s: %s"), postUpgradeContainer, err)
	}
	return nil
}
07070100000098000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002100000000uyuni-tools/mgradm/shared/podman07070100000099000081B4000000000000000000000001662A752800003D66000000000000000000000000000000000000002B00000000uyuni-tools/mgradm/shared/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"errors"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	install_shared "github.com/uyuni-project/uyuni-tools/mgradm/cmd/install/shared"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/templates"
	adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// GetExposedPorts returns the port exposed.
func GetExposedPorts(debug bool) []types.PortMap {
	ports := []types.PortMap{
		utils.NewPortMap("https", 443, 443),
		utils.NewPortMap("http", 80, 80),
	}
	ports = append(ports, utils.TCP_PORTS...)
	ports = append(ports, utils.UDP_PORTS...)

	if debug {
		ports = append(ports, utils.DEBUG_PORTS...)
	}

	return ports
}

// GenerateAttestationSystemdService creates the coco attestation systemd files.
func GenerateAttestationSystemdService(image string, db install_shared.DbFlags) error {
	attestationData := templates.AttestationServiceTemplateData{
		NamePrefix: "uyuni",
		Network:    podman.UyuniNetwork,
		Image:      image,
	}
	if err := utils.WriteTemplateToFile(attestationData, podman.GetServicePath(podman.ServerAttestationService), 0555, false); err != nil {
		return fmt.Errorf(L("failed to generate systemd service unit file: %s"), err)
	}

	environment := fmt.Sprintf(`Environment=UYUNI_IMAGE=%s
Environment=database_connection=jdbc:postgresql://uyuni-server.mgr.internal:%d/%s
Environment=database_user=%s
Environment=database_password=%s
	`, image, db.Port, db.Name, db.User, db.Password)
	if err := podman.GenerateSystemdConfFile(podman.ServerAttestationService, "Service", environment); err != nil {
		return fmt.Errorf(L("cannot generate systemd conf file: %s"), err)
	}

	return podman.ReloadDaemon(false)
}

// GenerateSystemdService creates a serverY systemd file.
func GenerateSystemdService(tz string, image string, debug bool, podmanArgs []string) error {
	if err := podman.SetupNetwork(); err != nil {
		return fmt.Errorf(L("cannot setup network: %s"), err)
	}

	log.Info().Msg(L("Enabling system service"))
	data := templates.PodmanServiceTemplateData{
		Volumes:    utils.ServerVolumeMounts,
		NamePrefix: "uyuni",
		Args:       strings.Join(podman.GetCommonParams(), " ") + strings.Join(podmanArgs, " "),
		Ports:      GetExposedPorts(debug),
		Timezone:   tz,
		Network:    podman.UyuniNetwork,
	}
	if err := utils.WriteTemplateToFile(data, podman.GetServicePath("uyuni-server"), 0555, false); err != nil {
		return fmt.Errorf(L("failed to generate systemd service unit file: %s"), err)
	}

	if err := podman.GenerateSystemdConfFile("uyuni-server", "Service", "Environment=UYUNI_IMAGE="+image); err != nil {
		return fmt.Errorf(L("cannot generate systemd conf file: %s"), err)
	}
	return podman.ReloadDaemon(false)
}

// UpdateSslCertificate update SSL certificate.
func UpdateSslCertificate(cnx *shared.Connection, chain *ssl.CaChain, serverPair *ssl.SslPair) error {
	ssl.CheckPaths(chain, serverPair)

	// Copy the CAs, certificate and key to the container
	const certDir = "/tmp/uyuni-tools"
	if err := utils.RunCmd("podman", "exec", podman.ServerContainerName, "mkdir", "-p", certDir); err != nil {
		return fmt.Errorf(L("failed to create temporary folder on container to copy certificates to"))
	}

	rootCaPath := path.Join(certDir, "root-ca.crt")
	serverCrtPath := path.Join(certDir, "server.crt")
	serverKeyPath := path.Join(certDir, "server.key")

	log.Debug().Msgf("Intermediate CA flags: %v", chain.Intermediate)

	args := []string{
		"exec",
		podman.ServerContainerName,
		"mgr-ssl-cert-setup",
		"-vvv",
		"--root-ca-file", rootCaPath,
		"--server-cert-file", serverCrtPath,
		"--server-key-file", serverKeyPath,
	}

	if err := cnx.Copy(chain.Root, "server:"+rootCaPath, "root", "root"); err != nil {
		return fmt.Errorf(L("cannot copy %s: %s"), rootCaPath, err)
	}
	if err := cnx.Copy(serverPair.Cert, "server:"+serverCrtPath, "root", "root"); err != nil {
		return fmt.Errorf(L("cannot copy %s: %s"), serverCrtPath, err)
	}
	if err := cnx.Copy(serverPair.Key, "server:"+serverKeyPath, "root", "root"); err != nil {
		return fmt.Errorf(L("cannot copy %s: %s"), serverKeyPath, err)
	}

	for i, ca := range chain.Intermediate {
		caFilename := fmt.Sprintf("ca-%d.crt", i)
		caPath := path.Join(certDir, caFilename)
		args = append(args, "--intermediate-ca-file", caPath)
		if err := cnx.Copy(ca, "server:"+caPath, "root", "root"); err != nil {
			return fmt.Errorf(L("cannot copy %s: %s"), caPath, err)
		}
	}

	// Check and install then using mgr-ssl-cert-setup
	if _, err := utils.RunCmdOutput(zerolog.InfoLevel, "podman", args...); err != nil {
		return errors.New(L("failed to update SSL certificate"))
	}

	// Clean the copied files and the now useless ssl-build
	if err := utils.RunCmd("podman", "exec", podman.ServerContainerName, "rm", "-rf", certDir); err != nil {
		return errors.New(L("failed to remove copied certificate files in the container"))
	}

	const sslbuildPath = "/root/ssl-build"
	if cnx.TestExistenceInPod(sslbuildPath) {
		if err := utils.RunCmd("podman", "exec", podman.ServerContainerName, "rm", "-rf", sslbuildPath); err != nil {
			return errors.New(L("failed to remove now useless ssl-build folder in the container"))
		}
	}

	// The services need to be restarted
	log.Info().Msg(L("Restarting services after updating the certificate"))
	return utils.RunCmdStdMapping(zerolog.DebugLevel, "podman", "exec", podman.ServerContainerName, "spacewalk-service", "restart")
}

// RunMigration migrate an existing remote server to a container.
func RunMigration(serverImage string, pullPolicy string, sshAuthSocket string, sshConfigPath string, sshKnownhostsPath string, sourceFqdn string, user string) (string, string, string, error) {
	scriptDir, err := adm_utils.GenerateMigrationScript(sourceFqdn, user, false)
	if err != nil {
		return "", "", "", fmt.Errorf(L("cannot generate migration script: %s"), err)
	}
	defer os.RemoveAll(scriptDir)

	extraArgs := []string{
		"--security-opt", "label:disable",
		"-e", "SSH_AUTH_SOCK",
		"-v", filepath.Dir(sshAuthSocket) + ":" + filepath.Dir(sshAuthSocket),
		"-v", scriptDir + ":/var/lib/uyuni-tools/",
	}

	if sshConfigPath != "" {
		extraArgs = append(extraArgs, "-v", sshConfigPath+":/tmp/ssh_config")
	}

	if sshKnownhostsPath != "" {
		extraArgs = append(extraArgs, "-v", sshKnownhostsPath+":/etc/ssh/ssh_known_hosts")
	}

	inspectedHostValues, err := utils.InspectHost()
	if err != nil {
		return "", "", "", fmt.Errorf(L("cannot inspect host values: %s"), err)
	}

	pullArgs := []string{}
	_, scc_user_exist := inspectedHostValues["host_scc_username"]
	_, scc_user_password := inspectedHostValues["host_scc_password"]
	if scc_user_exist && scc_user_password {
		pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
	}

	preparedImage, err := podman.PrepareImage(serverImage, pullPolicy, pullArgs...)
	if err != nil {
		return "", "", "", err
	}

	log.Info().Msg(L("Migrating server"))
	if err := podman.RunContainer("uyuni-migration", preparedImage, extraArgs,
		[]string{"/var/lib/uyuni-tools/migrate.sh"}); err != nil {
		return "", "", "", fmt.Errorf(L("cannot run uyuni migration container: %s"), err)
	}
	tz, oldPgVersion, newPgVersion, err := adm_utils.ReadContainerData(scriptDir)

	if err != nil {
		return "", "", "", fmt.Errorf(L("cannot read extracted data: %s"), err)
	}

	return tz, oldPgVersion, newPgVersion, nil
}

// RunPgsqlVersionUpgrade perform a PostgreSQL major upgrade.
func RunPgsqlVersionUpgrade(image types.ImageFlags, migrationImage types.ImageFlags, oldPgsql string, newPgsql string) error {
	log.Info().Msgf(L("Previous PostgreSQL is %s, new one is %s. Performing a DB version upgrade..."), oldPgsql, newPgsql)

	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}
	if newPgsql > oldPgsql {
		pgsqlVersionUpgradeContainer := "uyuni-upgrade-pgsql"
		extraArgs := []string{
			"-v", scriptDir + ":/var/lib/uyuni-tools/",
			"--security-opt", "label:disable",
		}

		migrationImageUrl := ""
		if migrationImage.Name == "" {
			migrationImageUrl, err = utils.ComputeImage(image.Name, image.Tag, fmt.Sprintf("-migration-%s-%s", oldPgsql, newPgsql))
			if err != nil {
				return fmt.Errorf(L("failed to compute image URL: %s"), err)
			}
		} else {
			migrationImageUrl, err = utils.ComputeImage(migrationImage.Name, image.Tag)
			if err != nil {
				return fmt.Errorf(L("failed to compute image URL: %s"), err)
			}
		}

		inspectedHostValues, err := utils.InspectHost()
		if err != nil {
			return fmt.Errorf(L("cannot inspect host values: %s"), err)
		}

		pullArgs := []string{}
		_, scc_user_exist := inspectedHostValues["host_scc_username"]
		_, scc_user_password := inspectedHostValues["host_scc_password"]
		if scc_user_exist && scc_user_password {
			pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
		}

		preparedImage, err := podman.PrepareImage(migrationImageUrl, image.PullPolicy, pullArgs...)
		if err != nil {
			return err
		}

		log.Info().Msgf(L("Using migration image %s"), preparedImage)

		pgsqlVersionUpgradeScriptName, err := adm_utils.GeneratePgsqlVersionUpgradeScript(scriptDir, oldPgsql, newPgsql, false)
		if err != nil {
			return fmt.Errorf(L("cannot generate PostgreSQL database version upgrade script %s"), err)
		}

		err = podman.RunContainer(pgsqlVersionUpgradeContainer, preparedImage, extraArgs,
			[]string{"/var/lib/uyuni-tools/" + pgsqlVersionUpgradeScriptName})
		if err != nil {
			return err
		}
	}
	return nil
}

// RunPgsqlFinalizeScript run the script with all the action required to a db after upgrade.
func RunPgsqlFinalizeScript(serverImage string, schemaUpdateRequired bool) error {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}

	extraArgs := []string{
		"-v", scriptDir + ":/var/lib/uyuni-tools/",
		"--security-opt", "label:disable",
	}
	pgsqlFinalizeContainer := "uyuni-finalize-pgsql"
	pgsqlFinalizeScriptName, err := adm_utils.GenerateFinalizePostgresScript(scriptDir, true, schemaUpdateRequired, true, true, false)
	if err != nil {
		return fmt.Errorf(L("cannot generate PostgreSQL finalization script: %s"), err)
	}
	err = podman.RunContainer(pgsqlFinalizeContainer, serverImage, extraArgs,
		[]string{"/var/lib/uyuni-tools/" + pgsqlFinalizeScriptName})
	if err != nil {
		return err
	}
	return nil
}

// RunPostUpgradeScript run the script with the changes to apply after the upgrade.
func RunPostUpgradeScript(serverImage string) error {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}
	postUpgradeContainer := "uyuni-post-upgrade"
	extraArgs := []string{
		"-v", scriptDir + ":/var/lib/uyuni-tools/",
		"--security-opt", "label:disable",
	}
	postUpgradeScriptName, err := adm_utils.GeneratePostUpgradeScript(scriptDir, "localhost")
	if err != nil {
		return fmt.Errorf(L("cannot generate PostgreSQL finalization script: %s"), err)
	}
	err = podman.RunContainer(postUpgradeContainer, serverImage, extraArgs,
		[]string{"/var/lib/uyuni-tools/" + postUpgradeScriptName})
	if err != nil {
		return err
	}
	return nil
}

// Upgrade will upgrade server to the image given as attribute.
func Upgrade(image types.ImageFlags, migrationImage types.ImageFlags, args []string) error {
	serverImage, err := utils.ComputeImage(image.Name, image.Tag)
	if err != nil {
		return fmt.Errorf(L("failed to compute image URL"))
	}

	inspectedValues, err := Inspect(serverImage, image.PullPolicy)
	if err != nil {
		return fmt.Errorf(L("cannot inspect podman values: %s"), err)
	}

	cnx := shared.NewConnection("podman", podman.ServerContainerName, "")

	if err := adm_utils.SanityCheck(cnx, inspectedValues, serverImage); err != nil {
		return err
	}

	if err := podman.StopService(podman.ServerService); err != nil {
		return fmt.Errorf(L("cannot stop service %s"), err)
	}

	defer func() {
		err = podman.StartService(podman.ServerService)
	}()
	if inspectedValues["image_pg_version"] > inspectedValues["current_pg_version"] {
		log.Info().Msgf(L("Previous postgresql is %s, instead new one is %s. Performing a DB version upgrade..."), inspectedValues["current_pg_version"], inspectedValues["image_pg_version"])
		if err := RunPgsqlVersionUpgrade(image, migrationImage, inspectedValues["current_pg_version"], inspectedValues["image_pg_version"]); err != nil {
			return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
		}
	} else if inspectedValues["image_pg_version"] == inspectedValues["current_pg_version"] {
		log.Info().Msgf(L("Upgrading to %s without changing PostgreSQL version"), inspectedValues["uyuni_release"])
	} else {
		return fmt.Errorf(L("trying to downgrade postgresql from %s to %s"), inspectedValues["current_pg_version"], inspectedValues["image_pg_version"])
	}

	schemaUpdateRequired := inspectedValues["current_pg_version"] != inspectedValues["image_pg_version"]
	if err := RunPgsqlFinalizeScript(serverImage, schemaUpdateRequired); err != nil {
		return fmt.Errorf(L("cannot run PostgreSQL version upgrade script: %s"), err)
	}

	if err := RunPostUpgradeScript(serverImage); err != nil {
		return fmt.Errorf(L("cannot run post upgrade script: %s"), err)
	}

	if err := podman.GenerateSystemdConfFile("uyuni-server", "Service", "Environment=UYUNI_IMAGE="+serverImage); err != nil {
		return err
	}
	log.Info().Msg(L("Waiting for the server to start..."))
	return podman.ReloadDaemon(false)
}

// Inspect check values on a given image and deploy.
func Inspect(serverImage string, pullPolicy string) (map[string]string, error) {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("failed to create temporary directory %s"), err)
	}

	inspectedHostValues, err := utils.InspectHost()
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot inspect host values: %s"), err)
	}

	pullArgs := []string{}
	_, scc_user_exist := inspectedHostValues["host_scc_username"]
	_, scc_user_password := inspectedHostValues["host_scc_password"]
	if scc_user_exist && scc_user_password {
		pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
	}

	preparedImage, err := podman.PrepareImage(serverImage, pullPolicy, pullArgs...)
	if err != nil {
		return map[string]string{}, err
	}

	if err := utils.GenerateInspectContainerScript(scriptDir); err != nil {
		return map[string]string{}, err
	}

	podmanArgs := []string{
		"-v", scriptDir + ":" + utils.InspectOutputFile.Directory,
		"--security-opt", "label:disable",
	}

	err = podman.RunContainer("uyuni-inspect", preparedImage, podmanArgs,
		[]string{utils.InspectOutputFile.Directory + "/" + utils.InspectScriptFilename})
	if err != nil {
		return map[string]string{}, err
	}

	inspectResult, err := utils.ReadInspectData(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot inspect data. %s"), err)
	}

	return inspectResult, err
}
0707010000009A000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/mgradm/shared/ssl0707010000009B000081B4000000000000000000000001662A752800001E08000000000000000000000000000000000000002500000000uyuni-tools/mgradm/shared/ssl/ssl.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package ssl

import (
	"bytes"
	"errors"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// CaChain is a type to store CA Chain.
type CaChain struct {
	Root         string
	Intermediate []string
}

// SslPait is a type for SSL Cert and Key.
type SslPair struct {
	Cert string
	Key  string
}

// Generate the server certificate with the CA chain.
// Returns the certificate chain and the root CA.
func OrderCas(chain *CaChain, serverPair *SslPair) ([]byte, []byte) {
	CheckPaths(chain, serverPair)

	// Extract all certificates and their data
	certs := readCertificates(chain.Root)
	for _, caPath := range chain.Intermediate {
		certs = append(certs, readCertificates(caPath)...)
	}
	serverCerts := readCertificates(serverPair.Cert)
	certs = append(certs, serverCerts...)

	serverCert, err := findServerCert(certs)
	if err != nil {
		log.Fatal().Msg(L("Failed to find a non-CA certificate"))
	}

	// Map all certificates using their hashes
	mapBySubjectHash := map[string]certificate{}
	if serverCert.subjectHash != "" {
		mapBySubjectHash[serverCert.subjectHash] = *serverCert
	}

	for _, caCert := range certs {
		if caCert.subjectHash != "" {
			mapBySubjectHash[caCert.subjectHash] = caCert
		}
	}

	// Sort from server certificate to RootCA
	return sortCertificates(mapBySubjectHash, serverCert.subjectHash)
}

type certificate struct {
	content      []byte
	subject      string
	subjectHash  string
	issuer       string
	issuerHash   string
	startDate    time.Time
	endDate      time.Time
	subjectKeyId string
	authKeyId    string
	isCa         bool
	isRoot       bool
}

func findServerCert(certs []certificate) (*certificate, error) {
	for _, cert := range certs {
		if !cert.isCa {
			return &cert, nil
		}
	}
	return nil, errors.New(L("expected to find a certificate, got none"))
}

func readCertificates(path string) []certificate {
	fd, err := os.Open(path)
	if err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to read certificate file %s"), path)
	}

	certs := []certificate{}
	for {
		log.Debug().Msgf("Running openssl x509 on %s", path)
		cmd := exec.Command("openssl", "x509")
		cmd.Stdin = fd
		out, err := cmd.Output()

		if err != nil {
			// openssl got an invalid certificate or the end of the file
			break
		}

		// Extract data from the certificate
		cert := extractCertificateData(out)
		certs = append(certs, cert)
	}
	return certs
}

// Extract data from the certificate to help ordering and verifying it.
func extractCertificateData(content []byte) certificate {
	args := []string{"x509", "-noout", "-subject", "-subject_hash", "-startdate", "-enddate",
		"-issuer", "-issuer_hash", "-ext", "subjectKeyIdentifier,authorityKeyIdentifier,basicConstraints"}
	log.Debug().Msg("Running command openssl " + strings.Join(args, " "))
	cmd := exec.Command("openssl", args...)

	log.Trace().Msgf("Extracting data from certificate:\n%s", string(content))

	reader := bytes.NewReader(content)
	cmd.Stdin = reader

	out, err := cmd.Output()
	if err != nil {
		log.Fatal().Err(err).Msg(L("Failed to extract data from certificate"))
	}
	lines := strings.Split(string(out), "\n")

	cert := certificate{content: content}

	const timeLayout = "Jan 2 15:04:05 2006 MST"

	nextVal := ""
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}
		if strings.HasPrefix(line, "subject=") {
			cert.subject = strings.SplitN(line, "=", 2)[1]
		} else if strings.HasPrefix(line, "issuer=") {
			cert.issuer = strings.SplitN(line, "=", 2)[1]
		} else if strings.HasPrefix(line, "notBefore=") {
			date := strings.SplitN(line, "=", 2)[1]
			cert.startDate, err = time.Parse(timeLayout, date)
			if err != nil {
				log.Fatal().Err(err).Msgf(L("Failed to parse start date: %s\n"), date)
			}
		} else if strings.HasPrefix(line, "notAfter=") {
			date := strings.SplitN(line, "=", 2)[1]
			cert.endDate, err = time.Parse(timeLayout, date)
			if err != nil {
				log.Fatal().Err(err).Msgf(L("Failed to parse end date: %s\n"), date)
			}
		} else if strings.HasPrefix(line, "X509v3 Subject Key Identifier") {
			nextVal = "subjectKeyId"
		} else if strings.HasPrefix(line, "X509v3 Authority Key Identifier") {
			nextVal = "authKeyId"
		} else if strings.HasPrefix(line, "X509v3 Basic Constraints") {
			nextVal = "basicConstraints"
		} else if strings.HasPrefix(line, "    ") {
			if nextVal == "subjectKeyId" {
				cert.subjectKeyId = strings.ToUpper(strings.TrimSpace(line))
			} else if nextVal == "authKeyId" && strings.HasPrefix(line, "    keyid:") {
				cert.authKeyId = strings.ToUpper(strings.TrimSpace(strings.SplitN(line, ":", 2)[1]))
			} else if nextVal == "basicConstraints" && strings.Contains(line, "CA:TRUE") {
				cert.isCa = true
			} else {
				// Unhandled extension value
				continue
			}
		} else if cert.subjectHash == "" {
			// subject_hash comes first without key to identify it
			cert.subjectHash = strings.TrimSpace(line)
		} else {
			// second issue_hash without key to identify this value
			cert.issuerHash = strings.TrimSpace(line)
		}
	}

	if cert.subject == cert.issuer {
		cert.isRoot = true
		// Some Root CAs might not have their authorityKeyIdentifier set to themself
		if cert.isCa && cert.authKeyId == "" {
			cert.authKeyId = cert.subjectKeyId
		}
	} else {
		cert.isRoot = false
	}
	return cert
}

// Prepare the certificate chain starting by the server up to the root CA.
// Returns the certificate chain and the root CA.
func sortCertificates(mapBySubjectHash map[string]certificate, serverCertHash string) ([]byte, []byte) {
	if len(mapBySubjectHash) == 0 {
		log.Fatal().Msg(L("No CA found"))
	}

	cert := mapBySubjectHash[serverCertHash]
	issuerHash := cert.issuerHash
	_, found := mapBySubjectHash[issuerHash]
	if issuerHash == "" || !found {
		log.Fatal().Msg(L("No CA found for server certificate"))
	}

	sortedChain := bytes.NewBuffer(mapBySubjectHash[serverCertHash].content)
	var rootCa []byte

	for {
		cert, found = mapBySubjectHash[issuerHash]
		if !found {
			log.Fatal().Msgf(L("Missing CA with subject hash %s"), issuerHash)
		}

		nextHash := cert.issuerHash
		if nextHash == issuerHash {
			// Found Root CA, we can exit
			rootCa = cert.content
			break
		}
		issuerHash = nextHash
		sortedChain.Write(cert.content)
	}
	return sortedChain.Bytes(), rootCa
}

// Ensures that all the passed path exists and the required files are available.
func CheckPaths(chain *CaChain, serverPair *SslPair) {
	mandatoryFile(chain.Root, "root CA")
	for _, ca := range chain.Intermediate {
		optionalFile(ca)
	}
	mandatoryFile(serverPair.Cert, L("server certificate is required"))
	mandatoryFile(serverPair.Key, L("server key is required"))
}

func mandatoryFile(file string, msg string) {
	if file == "" {
		log.Fatal().Msgf(msg)
	}
	optionalFile(file)
}

func optionalFile(file string) {
	if file != "" && !utils.FileExists(file) {
		log.Fatal().Msgf(L("%s file is not accessible"), file)
	}
}

// Converts an SSL key to RSA.
func GetRsaKey(keyPath string, password string) []byte {
	// Kubernetes only handles RSA private TLS keys, convert and strip password
	caPassword := password
	utils.AskPasswordIfMissing(&caPassword, L("Source server SSL CA private key password"), 0, 0)

	// Convert the key file to RSA format for kubectl to handle it
	cmd := exec.Command("openssl", "rsa", "-in", keyPath, "-passin", "env:pass")
	cmd.Env = append(cmd.Env, "pass="+caPassword)
	out, err := cmd.Output()
	if err != nil {
		log.Fatal().Err(err).Msg(L("Failed to convert CA private key to RSA"))
	}
	return out
}
0707010000009C000081B4000000000000000000000001662A7528000016C7000000000000000000000000000000000000002A00000000uyuni-tools/mgradm/shared/ssl/ssl_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package ssl

import (
	"strings"
	"testing"
)

func TestReadCertificatesRootCa(t *testing.T) {
	actual := readCertificates("testdata/chain1/root-ca.crt")
	if len(actual) != 1 {
		t.Errorf("readCertificates got %d certificates; want 1", len(actual))
	}

	if !actual[0].isRoot {
		t.Error("CA should be root")
	}
}

func TestReadCertificatesNoCa(t *testing.T) {
	actual := readCertificates("testdata/chain1/server.crt")
	if len(actual) != 1 {
		t.Errorf("readCertificates got %d certificates; want 1", len(actual))
	}

	if actual[0].isCa {
		t.Error("Shouldn't be a CA certificate")
	}
}

func TestReadCertificatesMultiple(t *testing.T) {
	actual := readCertificates("testdata/chain1/intermediate-ca.crt")
	if len(actual) != 2 {
		t.Errorf("readCertificates got %d certificates; want 2", len(actual))
	}

	content := string(actual[0].content)
	if !strings.HasPrefix(content, "-----BEGIN CERTIFICATE-----\nMIIEXjCCA0agA") ||
		!strings.HasSuffix(content, "nrUN5m7Y0taw4qrOVOZRmGXu\n-----END CERTIFICATE-----\n") {
		t.Errorf("Wrong certificate content:\n%s", content)
	}

	if actual[1].subject != "C = DE, ST = STATE, O = ORG, OU = ORGUNIT, CN = TeamCA" {
		t.Errorf("Wrong certificate subject: %s", actual[1].subject)
	}

	if actual[1].subjectHash != "85a51924" {
		t.Errorf("Wrong subject hash: %s", actual[1].subjectHash)
	}

	if actual[0].issuer != "C = DE, ST = STATE, L = CITY, O = ORG, OU = ORGUNIT, CN = RootCA" {
		t.Errorf("Wrong certificate issuer: %s", actual[0].issuer)
	}

	if actual[0].issuerHash != "e96ab651" {
		t.Errorf("Wrong issuer hash: %s", actual[0].issuerHash)
	}

	if actual[0].isRoot {
		t.Error("CA shouldn't be root")
	}

	if !actual[0].isCa {
		t.Error("Should be a CA")
	}

	if actual[1].subjectKeyId != "62:00:25:E4:EE:70:E5:37:2D:1E:9E:AE:4E:B7:3E:FC:62:08:BF:27" {
		t.Errorf("Wrong subject key id: %s", actual[1].subjectKeyId)
	}

	if actual[0].authKeyId != "6E:6D:4B:35:22:23:3E:13:18:A5:93:61:0E:9C:BE:1E:D2:B8:1B:D4" {
		t.Errorf("Wrong auth key id: %s", actual[0].authKeyId)
	}
}

func TestOrderCas(t *testing.T) {
	chain := CaChain{Root: "testdata/chain1/root-ca.crt", Intermediate: []string{"testdata/chain1/intermediate-ca.crt"}}
	server := SslPair{Cert: "testdata/chain1/server.crt", Key: "testdata/chain1/server.key"}

	certs, rootCa := OrderCas(&chain, &server)
	ordered := strings.Split(string(certs), "-----BEGIN CERTIFICATE-----\n")

	if ordered[0] != "" {
		t.Errorf("Found unknown content before first certificate: %s", ordered[0])
	}
	onlyCerts := ordered[1:]

	expected := []struct {
		Begin string
		End   string
	}{
		{Begin: "MIIEdDCCA1ygAwIBAgIUZ2P1Ka9Eun", End: "JtS8rmkQpYyJciifX0PxYzTg=="},
		{Begin: "MIIETzCCAzegAwIBAgIUZ2P1Ka9Eun", End: "s3DjcCbkzyTUCKh9Po4\nmoUf"},
		{Begin: "MIIEXjCCA0agAwIBAgIUZ2P1Ka9Eunnv3dy/", End: "nrUN5m7Y0taw4qrOVOZRmGXu"},
	}

	// Do not count the empty first item
	if len(onlyCerts) != len(expected) {
		t.Errorf("Wrong number of certificates in the chain: got %d; want %d", len(onlyCerts), len(expected))
	}

	for i, data := range expected {
		if !strings.HasPrefix(onlyCerts[i], data.Begin) ||
			!strings.HasSuffix(onlyCerts[i], data.End+"\n-----END CERTIFICATE-----\n") {
			t.Errorf("Invalid certificate #%d, got:\n:%s", i, onlyCerts[i])
		}
	}

	rootCert := string(rootCa)
	if !strings.HasPrefix(rootCert, "-----BEGIN CERTIFICATE-----\nMIIEVjCCAz6gAwIBAgIUSZYESIXLDe") ||
		!strings.HasSuffix(rootCert, "5c7cfxV\nkABuj9PJxnNnFQ==\n-----END CERTIFICATE-----\n") {
		t.Errorf("Invalid root CA certificate, got:\n:%s", rootCert)
	}
}

func TestFindServerCertificate(t *testing.T) {
	certsList := readCertificates("testdata/chain2/spacewalk.crt")
	actual, err := findServerCert(certsList)

	if err != nil {
		t.Error("Expected to find a certificate, got none")
	}

	if actual.subjectHash != "78b716a6" {
		t.Errorf("Wrong subject hash, got %s", actual.subjectHash)
	}
}

// Test a CA chain with all the chain in the server certificate file.
func TestOrderCasChain2(t *testing.T) {
	chain := CaChain{Root: "testdata/chain2/RHN-ORG-TRUSTED-SSL-CERT", Intermediate: []string{}}
	server := SslPair{Cert: "testdata/chain2/spacewalk.crt", Key: "testdata/chain2/spacewalk.key"}

	certs, rootCa := OrderCas(&chain, &server)
	ordered := strings.Split(string(certs), "-----BEGIN CERTIFICATE-----\n")

	if ordered[0] != "" {
		t.Errorf("Found unknown content before first certificate: %s", ordered[0])
	}
	onlyCerts := ordered[1:]

	expected := []struct {
		Begin string
		End   string
	}{
		{Begin: "MIIEejCCA2KgAwIBAgIUEbWzxg57E", End: "Ur+fgZpBNvbkjD8b+S0ECQA6Dg=="},
		{Begin: "MIIETzCCAzegAwIBAgIUEbWzxg57E", End: "TT2Sljt0YfkmWfdXA\nwOUt"},
		{Begin: "MIIEXjCCA0agAwIBAgIUEbWzxg57E", End: "ivyvRvlwCUNstG6u8Y7IxHHn"},
	}

	// Do not count the empty first item
	if len(onlyCerts) != len(expected) {
		t.Errorf("Wrong number of certificates in the chain: got %d; want %d", len(onlyCerts), len(expected))
	}

	for i, data := range expected {
		if !strings.HasPrefix(onlyCerts[i], data.Begin) ||
			!strings.HasSuffix(onlyCerts[i], data.End+"\n-----END CERTIFICATE-----\n") {
			t.Errorf("Invalid certificate #%d, got:\n:%s", i, onlyCerts[i])
		}
	}

	rootCert := string(rootCa)
	if !strings.HasPrefix(rootCert, "-----BEGIN CERTIFICATE-----\nMIIEVjCCAz6gAwIBAgIUA12e94NK") ||
		!strings.HasSuffix(rootCert, "AQKotV5y5qBInw==\n-----END CERTIFICATE-----\n") {
		t.Errorf("Invalid root CA certificate, got:\n:%s", rootCert)
	}
}

func TestGetRsaKey(t *testing.T) {
	actual := string(GetRsaKey("testdata/RootCA.key", "secret"))
	if !strings.HasPrefix(actual, "-----BEGIN PRIVATE KEY-----\nMIIEugIBADANBgkqhkiG9w0BAQEFAAS") ||
		!strings.HasSuffix(actual, "DKY9SmW6QD+RJwbMc4M=\n-----END PRIVATE KEY-----\n") {
		t.Errorf("Unexpected generated RSA key: %s", actual)
	}
}
0707010000009D000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000002700000000uyuni-tools/mgradm/shared/ssl/testdata0707010000009E000081B4000000000000000000000001662A75280000073E000000000000000000000000000000000000003200000000uyuni-tools/mgradm/shared/ssl/testdata/RootCA.key-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIZDERQAIgS+ACAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBA0CZ/wEFCBHg0mYxZucHcwBIIE
wI40HqL0NoUUuxx0qo+UVoXuTPp5bWEmv4be+0v3+ya0ucCLx41l7HiYSwL/fYvt
tStWm9ArOlLW48uxF54eyA3Nwvt59xnZBGN0YokJR1heeYzns1xadgHwiFjl4CLI
IfpUoQvGmnph51301YRgwuTRGJsih8EH8g24TQ5qy1uRMS1BZLd7elUuHzVFyu6y
PMfxpqcygBvjnufgOye4aAkTQIeGlJBDk5DwVx56T5VEUA8/v9VXVFy4took8Gwr
B08ZVszbR7AeUpa16aqKXFV1hQPcEle/n/0zsqPTE0drRxAGKufdpvK1rfDRDSQx
LudQJ31NJkCBRe+sP+rv4BA+zme4xFEjAI12IVQ6sUaTCW/qMnPBcSC5iSvVLXen
iSB519BJgLPmJi2gNXsuluHddwRp1QFUOoxnyPHDyGFkQ4zNwnG/pPvxASoBQRPA
PR/JsZC9p+oGHBds0HzZETuU5oKWdrZwyawQaAWEeuZEeICsrkrKi9PkumY5SMCT
47tn0vyQTQEGexKag/UTaC5PaGX8SxpWBXrskxgLcYnUAQnpkEhnU92CJQSI36uL
pGC7kQKY8vPjCOw33lLrEkWDxIn3oFX76NuHtMBdKgNT/Qlxb1tqIfuaNI80MrGV
zAzeK6dw4ZakI6aSlOBa4l71JB3HfWZjWAldhS33GvWAdsnPef5c5jMEsDqMFFnw
lZh2hrdfUTkAb+v2tJ13CUccSqIy3i9DmOU/ijdEGGte3E1ws+qyouHsFC09ETcy
XC4YYXF4ccGNZemcqpsQhi5iQEL+HjaNRkv16+qyXRUG49TGRe8nlEA2mBXV8Wzf
mkEdzp9Oc3iOPqeQYgnhtHbYaj0iHjiwaUmKiisadD7Jo76z2CAG0YJdWw4FNNUI
tpM2eADVHXcFGLDzvmFWtPnfkBnhn09GhOJJXOIEsoNyhczv6TqMrtGx8wYvbNqc
YOgl42mDYn6v0P+uEDWcHWFQiHNDNPHUCT/LmjYcVRTOPlCqnv5Fh+yKS50pGtFy
8h53QURP7e4cJxE8CuBZAAGiEkoXEbXGoslnrtpQbB0bVpMDKrLpJMrMjSr4/pKK
zWRgVo7wOuUJN+o30lcQ/RM8NzJRii6tnHRG1eOijNOLqRYmyEHFSeTB7+lhjE5A
xyXotHOml32pW4lEu7Ks18fmTQtyI1opx4ocVLNMeRvesxCf3z5eMUciXG+frxnF
a9UScTR1rIbVVP2V7PiHVCXY6WXZqiefWU/Sn+oVndm/9GGH9f2WVW1xn6YX3L+e
pTpzZScReh7B0gd3cVtwmfAbQU3xuA8IFJnrLuDwQCs19WweImgPm/rf7ITBuxeO
3vdvVd8GfCsN3sTJ/9G5XJJstM0eXoXjoXhthQ4OuTyVSiI3tHROYW+iggAJsaVH
HwzTnGE5m5WBvQ9GziSThxDz4vfDtwVlNS1K0+wkt9stt9ruCpXxntQV2FEin0GM
iWptJuYpCMOH9gkALDYoYv/jG0PbMBs63FRLeZg2ehtYw6kU5KtjHkLZTiccuwSq
W010SdsstOrwrZKEJAuRNQHLHRFmjyoskz0nCtmyJBCTkP46Awu5PFVwBE9auiIe
Y0UUre0B4tPsoPewpvgRJio=
-----END ENCRYPTED PRIVATE KEY-----
0707010000009F000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002E00000000uyuni-tools/mgradm/shared/ssl/testdata/chain1070701000000A0000081B4000000000000000000000001662A7528000027CA000000000000000000000000000000000000004200000000uyuni-tools/mgradm/shared/ssl/testdata/chain1/intermediate-ca.crtCertificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            67:63:f5:29:af:44:ba:79:ef:dd:dc:bf:bb:ab:3e:36:22:b8:71:26
        Signature Algorithm: sha384WithRSAEncryption
        Issuer: C=DE, ST=STATE, L=CITY, O=ORG, OU=ORGUNIT, CN=RootCA
        Validity
            Not Before: Oct  2 13:09:11 2023 GMT
            Not After : Feb 13 13:09:11 2025 GMT
        Subject: C=DE, ST=STATE, O=ORG, OU=ORGUNIT, CN=OrgCa
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:e0:25:1f:af:4d:59:23:82:a6:f1:c9:33:d1:c6:
                    10:47:ad:f4:f2:b6:96:aa:79:2b:45:97:d3:d8:a5:
                    29:fe:9c:b9:2c:26:30:37:5f:ae:69:6a:ac:85:e0:
                    28:d3:d3:7e:83:c8:87:2a:70:e5:a6:78:99:d9:42:
                    f9:d3:17:c0:e1:bc:4c:51:af:f0:aa:a8:fb:19:65:
                    27:91:35:80:5a:3d:fd:90:0b:fc:af:a1:d3:af:26:
                    02:ba:7c:26:bc:aa:08:6e:cf:d5:5d:ed:c9:8c:33:
                    9f:63:51:45:49:1f:f2:1f:12:c2:7c:e4:42:05:8a:
                    ef:33:d3:0e:e5:44:58:99:88:aa:2f:e3:5f:37:d8:
                    36:fb:21:ac:90:0f:82:b9:55:bc:9e:ba:23:70:4a:
                    83:c0:44:37:c2:0a:9a:03:fb:1d:4a:d2:67:a8:70:
                    e0:8c:b2:c1:d7:d8:e7:c9:bd:ee:6f:f6:4e:f7:25:
                    f2:4b:9b:93:33:28:40:18:c6:f1:47:78:0d:84:fa:
                    7f:f4:82:9b:37:f0:37:84:25:b5:ae:f5:88:4f:d2:
                    d9:7e:61:c0:8e:92:24:c8:32:55:cb:c4:8c:e6:be:
                    1f:e8:32:e2:9f:18:1e:2f:a6:8f:80:27:d4:77:7b:
                    5d:2d:cb:eb:a4:b8:2f:28:a0:38:34:a5:91:c8:6e:
                    b6:71
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:TRUE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                9B:C1:07:5E:AB:5C:7E:6B:9E:E4:23:7B:18:61:34:CB:D0:06:91:3B
            X509v3 Authority Key Identifier: 
                keyid:6E:6D:4B:35:22:23:3E:13:18:A5:93:61:0E:9C:BE:1E:D2:B8:1B:D4
                DirName:/C=DE/ST=STATE/L=CITY/O=ORG/OU=ORGUNIT/CN=RootCA
                serial:49:96:04:48:85:CB:0D:EC:2C:31:FE:EF:E9:CB:12:2B:DB:80:F8:71
    Signature Algorithm: sha384WithRSAEncryption
    Signature Value:
        78:5a:ac:de:87:a3:fb:d5:e1:29:4f:c3:1b:39:2b:da:29:78:
        1c:07:3d:e3:db:da:8a:40:3d:c3:d4:51:9d:21:59:d2:37:66:
        f5:47:69:b8:96:2a:2e:f0:35:1a:5b:5b:23:cd:d5:ac:88:49:
        97:e1:5b:91:e4:b8:7a:2d:ab:46:17:c4:61:a9:1a:b1:29:d3:
        50:52:af:0d:c2:4a:e1:2f:aa:00:1b:07:5a:7d:f9:d1:57:19:
        66:49:52:c5:74:3c:1e:3d:a3:1f:49:64:60:92:48:03:a2:37:
        52:26:69:24:34:d7:a4:68:fd:ea:b7:a6:d6:c2:b0:46:19:a7:
        2c:b9:cc:a3:0f:87:4c:cb:fb:69:d1:a9:c5:93:73:69:7c:34:
        aa:3d:f0:98:83:88:14:48:29:2d:ed:f9:c0:96:22:ae:03:7a:
        2f:09:ad:43:6d:a3:12:d5:8c:48:e6:65:ca:e1:97:b4:ec:d7:
        aa:fc:db:e2:cf:16:30:2c:46:f3:dd:a5:37:db:d9:0f:99:c5:
        74:e7:21:2d:ca:2b:c5:4b:50:56:0a:2c:0d:25:30:56:39:87:
        33:b2:ae:d7:98:74:e9:3d:ce:78:ca:1b:bd:ea:f8:a6:3f:2a:
        a4:21:3b:19:9e:b5:0d:e6:6e:d8:d2:d6:b0:e2:aa:ce:54:e6:
        51:98:65:ee
-----BEGIN CERTIFICATE-----
MIIEXjCCA0agAwIBAgIUZ2P1Ka9Eunnv3dy/u6s+NiK4cSYwDQYJKoZIhvcNAQEM
BQAwXTELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQ0wCwYDVQQHDARDSVRZ
MQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlJvb3RD
QTAeFw0yMzEwMDIxMzA5MTFaFw0yNTAyMTMxMzA5MTFaME0xCzAJBgNVBAYTAkRF
MQ4wDAYDVQQIDAVTVEFURTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklU
MQ4wDAYDVQQDDAVPcmdDYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AOAlH69NWSOCpvHJM9HGEEet9PK2lqp5K0WX09ilKf6cuSwmMDdfrmlqrIXgKNPT
foPIhypw5aZ4mdlC+dMXwOG8TFGv8Kqo+xllJ5E1gFo9/ZAL/K+h068mArp8Jryq
CG7P1V3tyYwzn2NRRUkf8h8SwnzkQgWK7zPTDuVEWJmIqi/jXzfYNvshrJAPgrlV
vJ66I3BKg8BEN8IKmgP7HUrSZ6hw4IyywdfY58m97m/2Tvcl8kubkzMoQBjG8Ud4
DYT6f/SCmzfwN4Qlta71iE/S2X5hwI6SJMgyVcvEjOa+H+gy4p8YHi+mj4An1Hd7
XS3L66S4LyigODSlkchutnECAwEAAaOCASQwggEgMAwGA1UdEwQFMAMBAf8wCwYD
VR0PBAQDAgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAoBglghkgB
hvhCAQ0EGxYZU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUm8EH
Xqtcfmue5CN7GGE0y9AGkTswgZoGA1UdIwSBkjCBj4AUbm1LNSIjPhMYpZNhDpy+
HtK4G9ShYaRfMF0xCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVTVEFURTENMAsGA1UE
BwwEQ0lUWTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMQ8wDQYDVQQD
DAZSb290Q0GCFEmWBEiFyw3sLDH+7+nLEivbgPhxMA0GCSqGSIb3DQEBDAUAA4IB
AQB4Wqzeh6P71eEpT8MbOSvaKXgcBz3j29qKQD3D1FGdIVnSN2b1R2m4liou8DUa
W1sjzdWsiEmX4VuR5Lh6LatGF8RhqRqxKdNQUq8NwkrhL6oAGwdaffnRVxlmSVLF
dDwePaMfSWRgkkgDojdSJmkkNNekaP3qt6bWwrBGGacsucyjD4dMy/tp0anFk3Np
fDSqPfCYg4gUSCkt7fnAliKuA3ovCa1DbaMS1YxI5mXK4Ze07Neq/NvizxYwLEbz
3aU329kPmcV05yEtyivFS1BWCiwNJTBWOYczsq7XmHTpPc54yhu96vimPyqkITsZ
nrUN5m7Y0taw4qrOVOZRmGXu
-----END CERTIFICATE-----
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            67:63:f5:29:af:44:ba:79:ef:dd:dc:bf:bb:ab:3e:36:22:b8:71:27
        Signature Algorithm: sha384WithRSAEncryption
        Issuer: C=DE, ST=STATE, O=ORG, OU=ORGUNIT, CN=OrgCa
        Validity
            Not Before: Oct  2 13:09:11 2023 GMT
            Not After : Nov  5 13:09:11 2024 GMT
        Subject: C=DE, ST=STATE, O=ORG, OU=ORGUNIT, CN=TeamCA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:eb:35:fe:18:8f:59:de:23:4a:84:bc:de:7b:f1:
                    79:f8:1a:5d:94:95:54:2c:00:bd:42:c1:e6:f5:c6:
                    ca:25:da:97:cd:5b:85:d6:89:8a:7c:45:11:9e:df:
                    65:10:68:e4:49:6a:cf:fd:76:48:08:c7:09:aa:e3:
                    88:c2:7e:2f:f9:85:b4:df:d4:00:ec:a9:71:38:1d:
                    ff:8d:d4:d1:84:2a:f9:9b:e7:7f:e1:61:3e:75:06:
                    7f:18:66:59:23:96:e6:c2:75:11:e7:f4:f3:47:7b:
                    e6:17:8c:25:d9:ee:da:01:d6:cd:94:9e:a7:8e:35:
                    f6:d8:24:d6:cf:58:4f:29:36:42:18:96:aa:87:ca:
                    ad:af:05:a2:e5:a6:6b:4f:42:98:e3:4e:86:b4:d7:
                    1f:2f:db:c3:5b:bd:e9:da:7d:d0:d9:8d:83:c9:28:
                    56:27:e7:0d:a2:15:88:99:af:eb:a3:85:73:9e:3d:
                    64:70:01:be:cb:71:c0:d8:ca:e7:6e:25:b7:3b:fe:
                    73:0a:92:d2:23:2d:f5:f4:9c:0e:d6:65:c6:ef:6c:
                    9a:c5:c5:af:70:10:ba:fc:2d:b1:29:26:88:9e:06:
                    e5:63:5f:d4:25:0c:98:18:f0:46:77:86:f5:98:00:
                    63:38:3a:36:81:27:94:2a:cc:84:24:75:01:54:ed:
                    a4:d7
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:TRUE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                62:00:25:E4:EE:70:E5:37:2D:1E:9E:AE:4E:B7:3E:FC:62:08:BF:27
            X509v3 Authority Key Identifier: 
                keyid:9B:C1:07:5E:AB:5C:7E:6B:9E:E4:23:7B:18:61:34:CB:D0:06:91:3B
                DirName:/C=DE/ST=STATE/L=CITY/O=ORG/OU=ORGUNIT/CN=RootCA
                serial:67:63:F5:29:AF:44:BA:79:EF:DD:DC:BF:BB:AB:3E:36:22:B8:71:26
    Signature Algorithm: sha384WithRSAEncryption
    Signature Value:
        26:a0:7e:98:44:58:ab:81:9f:f9:a6:04:dc:08:59:d8:b4:5a:
        11:47:8e:9c:23:7f:53:66:f9:b9:93:5b:df:50:d6:2a:11:a1:
        cb:1c:d5:2e:cd:5d:f3:eb:45:b5:fe:01:9f:0c:d0:f0:1d:8f:
        57:ac:0f:2a:5b:a4:6a:57:a7:0e:25:e1:69:25:f5:ef:2f:3c:
        60:9c:26:ac:e8:cd:3a:89:fa:84:18:da:bb:83:f6:f5:02:53:
        51:f2:ab:76:e8:fb:d0:63:dc:5c:09:c5:f7:de:68:90:c0:50:
        80:ec:88:ff:16:95:a0:c1:97:69:fb:1f:9d:43:32:0c:5d:f9:
        bc:5e:48:c4:52:f2:f3:43:1f:ff:c5:bb:58:6c:ee:11:cb:0c:
        22:45:29:1c:62:26:78:9c:31:10:d8:14:24:17:17:4a:7d:a9:
        1d:3b:5b:64:8e:b2:84:91:66:fb:f2:e6:37:3d:c2:1b:db:98:
        11:10:6d:67:9c:95:a5:d9:a4:8a:e1:b6:c4:ab:2d:f7:48:3a:
        66:9b:9c:af:9b:b7:a5:27:cc:4b:53:e6:21:0c:2b:6c:c8:b2:
        cc:6c:51:58:df:b2:bc:53:ed:25:0f:4a:e6:44:6c:be:74:46:
        0b:6a:df:46:76:cd:c3:8d:c0:9b:93:3c:93:50:22:a1:f4:fa:
        38:9a:85:1f
-----BEGIN CERTIFICATE-----
MIIETzCCAzegAwIBAgIUZ2P1Ka9Eunnv3dy/u6s+NiK4cScwDQYJKoZIhvcNAQEM
BQAwTTELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQwwCgYDVQQKDANPUkcx
EDAOBgNVBAsMB09SR1VOSVQxDjAMBgNVBAMMBU9yZ0NhMB4XDTIzMTAwMjEzMDkx
MVoXDTI0MTEwNTEzMDkxMVowTjELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRF
MQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlRlYW1D
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOs1/hiPWd4jSoS83nvx
efgaXZSVVCwAvULB5vXGyiXal81bhdaJinxFEZ7fZRBo5Elqz/12SAjHCarjiMJ+
L/mFtN/UAOypcTgd/43U0YQq+Zvnf+FhPnUGfxhmWSOW5sJ1Eef080d75heMJdnu
2gHWzZSep4419tgk1s9YTyk2QhiWqofKra8FouWma09CmONOhrTXHy/bw1u96dp9
0NmNg8koVifnDaIViJmv66OFc549ZHABvstxwNjK524ltzv+cwqS0iMt9fScDtZl
xu9smsXFr3AQuvwtsSkmiJ4G5WNf1CUMmBjwRneG9ZgAYzg6NoEnlCrMhCR1AVTt
pNcCAwEAAaOCASQwggEgMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgKkMB0GA1Ud
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAoBglghkgBhvhCAQ0EGxYZU1NMIEdl
bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUYgAl5O5w5TctHp6uTrc+/GII
vycwgZoGA1UdIwSBkjCBj4AUm8EHXqtcfmue5CN7GGE0y9AGkTuhYaRfMF0xCzAJ
BgNVBAYTAkRFMQ4wDAYDVQQIDAVTVEFURTENMAsGA1UEBwwEQ0lUWTEMMAoGA1UE
CgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMQ8wDQYDVQQDDAZSb290Q0GCFGdj9Smv
RLp5793cv7urPjYiuHEmMA0GCSqGSIb3DQEBDAUAA4IBAQAmoH6YRFirgZ/5pgTc
CFnYtFoRR46cI39TZvm5k1vfUNYqEaHLHNUuzV3z60W1/gGfDNDwHY9XrA8qW6Rq
V6cOJeFpJfXvLzxgnCas6M06ifqEGNq7g/b1AlNR8qt26PvQY9xcCcX33miQwFCA
7Ij/FpWgwZdp+x+dQzIMXfm8XkjEUvLzQx//xbtYbO4RywwiRSkcYiZ4nDEQ2BQk
FxdKfakdO1tkjrKEkWb78uY3PcIb25gREG1nnJWl2aSK4bbEqy33SDpmm5yvm7el
J8xLU+YhDCtsyLLMbFFY37K8U+0lD0rmRGy+dEYLat9Gds3DjcCbkzyTUCKh9Po4
moUf
-----END CERTIFICATE-----
070701000000A1000081B4000000000000000000000001662A7528000013BA000000000000000000000000000000000000003A00000000uyuni-tools/mgradm/shared/ssl/testdata/chain1/root-ca.crtCertificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            49:96:04:48:85:cb:0d:ec:2c:31:fe:ef:e9:cb:12:2b:db:80:f8:71
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = DE, ST = STATE, L = CITY, O = ORG, OU = ORGUNIT, CN = RootCA
        Validity
            Not Before: Oct  2 13:09:10 2023 GMT
            Not After : Jul 22 13:09:10 2026 GMT
        Subject: C = DE, ST = STATE, L = CITY, O = ORG, OU = ORGUNIT, CN = RootCA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:f3:67:90:0c:b8:98:e1:5c:8d:00:32:13:26:95:
                    95:6f:7c:2f:96:34:b8:6e:2f:5a:61:da:e2:bd:2e:
                    9e:8a:ad:5e:4e:27:a9:1c:08:06:7c:36:26:28:5e:
                    a7:6e:bc:04:68:eb:2c:97:b6:4b:ca:f0:0d:9c:5a:
                    47:ee:9e:15:1e:c0:62:3c:72:1b:80:01:07:67:51:
                    64:34:0f:41:50:73:21:09:d9:79:ac:73:51:db:5c:
                    a0:30:fa:79:49:02:a4:8e:cb:8f:15:dd:99:4c:b7:
                    9e:d1:ec:18:f9:6f:d2:73:27:d1:ff:c9:07:07:4e:
                    8a:6e:02:2d:6d:ab:5b:5f:5b:a2:4a:4d:c7:d6:7b:
                    26:6e:b7:a0:44:0d:82:18:43:f8:3a:49:f7:47:40:
                    d0:ed:72:dd:f2:8b:c4:9f:e5:64:24:49:f0:0d:e8:
                    5b:21:66:89:31:4a:3e:1e:9c:9b:11:89:91:9d:57:
                    af:73:64:19:bf:ed:02:8f:3f:0b:5f:aa:2c:5c:93:
                    9b:03:08:c4:a1:72:58:7a:df:cb:f2:00:8c:71:7e:
                    76:23:29:ac:c6:6a:46:2a:a9:b2:6e:f4:14:2a:16:
                    e8:7b:3c:f4:c3:14:89:11:54:d3:10:70:6c:98:c8:
                    66:e3:f7:31:cf:fd:78:76:e2:eb:2a:3e:37:a6:ce:
                    4c:07
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:TRUE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                6E:6D:4B:35:22:23:3E:13:18:A5:93:61:0E:9C:BE:1E:D2:B8:1B:D4
            X509v3 Authority Key Identifier: 
                DirName:/C=DE/ST=STATE/L=CITY/O=ORG/OU=ORGUNIT/CN=RootCA
                serial:49:96:04:48:85:CB:0D:EC:2C:31:FE:EF:E9:CB:12:2B:DB:80:F8:71
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        82:e6:ae:cb:60:cf:85:3e:00:70:37:c7:dc:c9:51:b9:70:36:
        25:e2:f5:bc:e0:8f:34:3d:67:1f:09:8e:48:e9:de:b5:78:b5:
        b5:97:f6:75:fa:fc:0f:05:c4:e1:33:ab:f5:f9:b1:32:9f:75:
        b3:c4:fd:a9:6d:c6:88:c6:a5:35:68:28:04:1d:c0:1d:92:9a:
        9b:be:52:e9:b9:9c:0d:01:b1:a8:0d:42:89:f7:f3:43:58:99:
        98:6c:0d:9f:ff:9d:10:29:68:9f:db:41:e7:b7:c6:43:67:79:
        ec:a6:f2:5b:ce:b7:d9:17:90:c2:f4:ac:56:8f:9a:af:fb:85:
        85:59:95:d1:5e:37:f6:40:2a:16:cf:53:fa:55:8b:35:48:31:
        10:c6:c4:b9:85:07:96:48:c3:dd:35:d0:04:e3:c8:fd:7e:8e:
        a2:ab:60:6b:b4:cc:f5:33:44:f8:bc:e6:b1:1b:86:f0:6d:a1:
        23:48:62:63:de:e3:27:d7:8c:9b:58:a2:10:ed:11:b6:b8:4c:
        ee:83:4a:be:0b:ee:6a:80:1d:02:91:21:4d:84:64:5f:a5:1e:
        1b:c5:8c:c6:d9:7d:0c:43:da:45:c9:13:e5:47:46:8d:bb:36:
        51:f8:72:70:d7:40:43:97:3b:71:fc:55:90:00:6e:8f:d3:c9:
        c6:73:67:15
-----BEGIN CERTIFICATE-----
MIIEVjCCAz6gAwIBAgIUSZYESIXLDewsMf7v6csSK9uA+HEwDQYJKoZIhvcNAQEL
BQAwXTELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQ0wCwYDVQQHDARDSVRZ
MQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlJvb3RD
QTAeFw0yMzEwMDIxMzA5MTBaFw0yNjA3MjIxMzA5MTBaMF0xCzAJBgNVBAYTAkRF
MQ4wDAYDVQQIDAVTVEFURTENMAsGA1UEBwwEQ0lUWTEMMAoGA1UECgwDT1JHMRAw
DgYDVQQLDAdPUkdVTklUMQ8wDQYDVQQDDAZSb290Q0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDzZ5AMuJjhXI0AMhMmlZVvfC+WNLhuL1ph2uK9Lp6K
rV5OJ6kcCAZ8NiYoXqduvARo6yyXtkvK8A2cWkfunhUewGI8chuAAQdnUWQ0D0FQ
cyEJ2Xmsc1HbXKAw+nlJAqSOy48V3ZlMt57R7Bj5b9JzJ9H/yQcHTopuAi1tq1tf
W6JKTcfWeyZut6BEDYIYQ/g6SfdHQNDtct3yi8Sf5WQkSfAN6FshZokxSj4enJsR
iZGdV69zZBm/7QKPPwtfqixck5sDCMShclh638vyAIxxfnYjKazGakYqqbJu9BQq
Fuh7PPTDFIkRVNMQcGyYyGbj9zHP/Xh24usqPjemzkwHAgMBAAGjggEMMIIBCDAM
BgNVHRMEBTADAQH/MAsGA1UdDwQEAwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwKAYJYIZIAYb4QgENBBsWGVNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh
dGUwHQYDVR0OBBYEFG5tSzUiIz4TGKWTYQ6cvh7SuBvUMIGCBgNVHSMEezB5oWGk
XzBdMQswCQYDVQQGEwJERTEOMAwGA1UECAwFU1RBVEUxDTALBgNVBAcMBENJVFkx
DDAKBgNVBAoMA09SRzEQMA4GA1UECwwHT1JHVU5JVDEPMA0GA1UEAwwGUm9vdENB
ghRJlgRIhcsN7Cwx/u/pyxIr24D4cTANBgkqhkiG9w0BAQsFAAOCAQEAguauy2DP
hT4AcDfH3MlRuXA2JeL1vOCPND1nHwmOSOnetXi1tZf2dfr8DwXE4TOr9fmxMp91
s8T9qW3GiMalNWgoBB3AHZKam75S6bmcDQGxqA1CiffzQ1iZmGwNn/+dEClon9tB
57fGQ2d57KbyW8632ReQwvSsVo+ar/uFhVmV0V439kAqFs9T+lWLNUgxEMbEuYUH
lkjD3TXQBOPI/X6Ooqtga7TM9TNE+LzmsRuG8G2hI0hiY97jJ9eMm1iiEO0RtrhM
7oNKvgvuaoAdApEhTYRkX6UeG8WMxtl9DEPaRckT5UdGjbs2UfhycNdAQ5c7cfxV
kABuj9PJxnNnFQ==
-----END CERTIFICATE-----
070701000000A2000081B4000000000000000000000001662A75280000148A000000000000000000000000000000000000003900000000uyuni-tools/mgradm/shared/ssl/testdata/chain1/server.crtCertificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            67:63:f5:29:af:44:ba:79:ef:dd:dc:bf:bb:ab:3e:36:22:b8:71:28
        Signature Algorithm: sha384WithRSAEncryption
        Issuer: C=DE, ST=STATE, O=ORG, OU=ORGUNIT, CN=TeamCA
        Validity
            Not Before: Oct  2 13:09:12 2023 GMT
            Not After : Oct  1 13:09:12 2024 GMT
        Subject: C=DE, ST=STATE, O=ORG, OU=ORGUNIT, CN=uyuni-server-cert
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:83:51:78:0e:31:92:ea:d9:51:6e:9d:02:ed:d8:
                    55:dc:53:5f:7e:0c:a5:26:df:e1:c1:86:e0:38:9d:
                    67:59:e2:42:21:37:4d:5d:2c:f7:28:f1:d4:7f:00:
                    91:e4:0e:fa:eb:c8:bb:2d:f2:cd:37:62:5e:94:67:
                    83:0a:e0:69:4d:86:f4:39:be:1b:59:ec:64:65:41:
                    0c:5a:3a:7e:4b:98:8e:62:4c:4b:2c:b7:68:3c:36:
                    e2:ea:7e:58:70:e7:3e:7e:0a:b4:7f:32:b7:d0:0f:
                    15:b4:ac:24:ce:a8:f4:13:9e:62:44:7d:f6:3e:fd:
                    a5:67:5a:3d:67:54:40:89:6f:51:f0:4a:60:35:d4:
                    51:27:ca:bb:a1:5e:32:12:a5:3f:b4:e3:d0:7d:9e:
                    b2:84:4f:4e:84:db:52:00:9e:46:bf:c8:31:3b:79:
                    30:09:fe:ba:01:b7:3e:c4:9b:c4:cc:18:8b:f0:42:
                    fd:88:71:8c:d6:4c:d2:99:c8:7a:bf:7e:d6:dc:18:
                    d2:97:57:ca:0c:0c:66:52:2e:38:1c:8a:56:13:44:
                    a5:84:3d:95:70:e5:aa:94:2f:59:48:3d:22:74:8d:
                    f8:5d:ef:42:9c:e1:cf:ca:f5:24:d8:a8:8c:4e:1d:
                    75:9d:ac:15:a9:6f:f7:81:ab:5e:11:61:f7:8f:e3:
                    a5:c1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Cert Type: 
                SSL Server
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                FD:F8:AA:26:08:7F:F7:FA:92:1E:68:3F:4A:29:54:E5:7A:A6:0D:42
            X509v3 Authority Key Identifier: 
                keyid:62:00:25:E4:EE:70:E5:37:2D:1E:9E:AE:4E:B7:3E:FC:62:08:BF:27
                DirName:/C=DE/ST=STATE/O=ORG/OU=ORGUNIT/CN=OrgCa
                serial:67:63:F5:29:AF:44:BA:79:EF:DD:DC:BF:BB:AB:3E:36:22:B8:71:27
            X509v3 Subject Alternative Name: 
                DNS:*.example.com
    Signature Algorithm: sha384WithRSAEncryption
    Signature Value:
        24:4e:b4:4d:29:e6:ad:12:e6:39:9d:95:0e:fc:b7:af:e3:55:
        60:cc:f2:57:1c:38:05:fb:1f:8c:95:40:40:b3:23:c3:11:1b:
        5f:7b:99:01:0d:fe:3a:05:7e:d0:b1:9a:c8:fc:6a:78:41:fb:
        3f:5a:6a:26:0d:dd:2e:f3:ab:d5:16:45:99:51:3e:94:87:3a:
        a7:67:e4:25:43:c9:1a:e5:84:df:15:ba:f3:11:64:99:1d:22:
        0e:44:35:9c:9b:52:e8:b0:a8:a9:04:d2:4b:cf:10:14:35:6c:
        1f:a3:ec:81:4f:2b:98:c0:02:8a:b9:03:50:f3:97:25:25:05:
        ba:c4:e0:5b:b2:34:7e:d3:d6:e1:69:d8:72:38:9e:0a:50:f6:
        25:d0:97:c3:58:37:49:40:08:11:9d:39:b1:4b:4d:e9:18:08:
        38:9d:b5:b4:a2:8b:d8:24:94:b3:b5:41:e9:17:b8:22:17:6e:
        70:33:a2:a3:ee:8e:ae:be:ee:c4:dd:6c:2c:c2:ae:8b:31:8f:
        5d:ca:9d:01:83:5d:89:59:cf:f6:30:3a:59:4d:17:82:ab:6e:
        a3:bf:4d:61:98:9a:f6:29:20:9b:eb:c5:3a:cd:06:b6:82:8c:
        24:34:65:60:12:6d:4b:ca:e6:91:0a:58:c8:97:22:89:f5:f4:
        3f:16:33:4e
-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIUZ2P1Ka9Eunnv3dy/u6s+NiK4cSgwDQYJKoZIhvcNAQEM
BQAwTjELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQwwCgYDVQQKDANPUkcx
EDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlRlYW1DQTAeFw0yMzEwMDIxMzA5
MTJaFw0yNDEwMDExMzA5MTJaMFkxCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVTVEFU
RTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMRowGAYDVQQDDBF1eXVu
aS1zZXJ2ZXItY2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAINR
eA4xkurZUW6dAu3YVdxTX34MpSbf4cGG4DidZ1niQiE3TV0s9yjx1H8AkeQO+uvI
uy3yzTdiXpRngwrgaU2G9Dm+G1nsZGVBDFo6fkuYjmJMSyy3aDw24up+WHDnPn4K
tH8yt9APFbSsJM6o9BOeYkR99j79pWdaPWdUQIlvUfBKYDXUUSfKu6FeMhKlP7Tj
0H2esoRPToTbUgCeRr/IMTt5MAn+ugG3PsSbxMwYi/BC/YhxjNZM0pnIer9+1twY
0pdXygwMZlIuOByKVhNEpYQ9lXDlqpQvWUg9InSN+F3vQpzhz8r1JNiojE4ddZ2s
Falv94GrXhFh94/jpcECAwEAAaOCAT0wggE5MAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjARBglghkgBhvhCAQEE
BAMCBkAwKAYJYIZIAYb4QgENBBsWGVNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUw
HQYDVR0OBBYEFP34qiYIf/f6kh5oP0opVOV6pg1CMIGJBgNVHSMEgYEwf4AUYgAl
5O5w5TctHp6uTrc+/GIIvyehUaRPME0xCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVT
VEFURTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMQ4wDAYDVQQDDAVP
cmdDYYIUZ2P1Ka9Eunnv3dy/u6s+NiK4cScwGAYDVR0RBBEwD4INKi5leGFtcGxl
LmNvbTANBgkqhkiG9w0BAQwFAAOCAQEAJE60TSnmrRLmOZ2VDvy3r+NVYMzyVxw4
BfsfjJVAQLMjwxEbX3uZAQ3+OgV+0LGayPxqeEH7P1pqJg3dLvOr1RZFmVE+lIc6
p2fkJUPJGuWE3xW68xFkmR0iDkQ1nJtS6LCoqQTSS88QFDVsH6PsgU8rmMACirkD
UPOXJSUFusTgW7I0ftPW4WnYcjieClD2JdCXw1g3SUAIEZ05sUtN6RgIOJ21tKKL
2CSUs7VB6Re4IhducDOio+6Orr7uxN1sLMKuizGPXcqdAYNdiVnP9jA6WU0Xgqtu
o79NYZia9ikgm+vFOs0GtoKMJDRlYBJtS8rmkQpYyJciifX0PxYzTg==
-----END CERTIFICATE-----
070701000000A3000081B4000000000000000000000001662A7528000006A8000000000000000000000000000000000000003900000000uyuni-tools/mgradm/shared/ssl/testdata/chain1/server.key-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCDUXgOMZLq2VFu
nQLt2FXcU19+DKUm3+HBhuA4nWdZ4kIhN01dLPco8dR/AJHkDvrryLst8s03Yl6U
Z4MK4GlNhvQ5vhtZ7GRlQQxaOn5LmI5iTEsst2g8NuLqflhw5z5+CrR/MrfQDxW0
rCTOqPQTnmJEffY+/aVnWj1nVECJb1HwSmA11FEnyruhXjISpT+049B9nrKET06E
21IAnka/yDE7eTAJ/roBtz7Em8TMGIvwQv2IcYzWTNKZyHq/ftbcGNKXV8oMDGZS
LjgcilYTRKWEPZVw5aqUL1lIPSJ0jfhd70Kc4c/K9STYqIxOHXWdrBWpb/eBq14R
YfeP46XBAgMBAAECggEAHjz9Sy9pKEEAelsXWJNvOfvMyma5BNmaz4hySzcbnFv4
ZFOqseDvzPLavp/v+Dbm2rJvP6ZgUPeK1dt8Fl4UgXCo/j7jZ3KCr7op0QEVIe0w
JDxzNwnIq8zrtZmAXgcxoa5vX7bbEsLWebMGCrxm77mR4TmsIVcg5kqmRwvkjIDM
SZxAJSMYVWlmyI695fMPng8f4nOxRWPBgW73XzMvlyr68OUtji9+JqI8C9mJ471S
F2qL+ubaovZM83EQ1gAol5RX8rDdkQD+/OlkLaVvdKOpp3VLKx1KszYyqhR/wYqD
4FUjK5Abxz8JlVOZkPOaF3Y9IgAuAhK45lqyUjGtvQKBgQC3wTzcpB2zjmw07iAP
B0/REU1RGPvkehsV3KnjsSvWAzrgAJMgS1l5pWJLLBRJDguX3jbDMiQ05Z9RSLdL
+IbvL75+QhbXLRiv7aGkM1xIaBrGJpCANZmHKjjy/T7LfewSwyNqlP/FdMOy+tat
2/t/IrmpArryd/cP/Pw8rfKS0wKBgQC28oixIJP3uAtQzpiqKnIovw57s8MXcMzh
QaxR00v8ss4d82T6BmJolF+gjotsYq4lVnOmL1+E/74dN8p0xL8ITIrr8AAO2Kp2
j0qWVgagvjqTq+5H7er9dRwCpKTo2dt2/JM6MCrKyNICdZdljYJiUVijTenckzeS
K78RD3BAmwKBgQCu0PNjAeuT4IJHVOhBA/bGcsx4w+kYs6ZDBTzHds26fDYt174g
8i58kX/S/muKGQekgu7cgz546J/KSADCEP3mXii/m4Z5Tdj3vn6SZZ588DXQn+3H
W7blJaEqYw2zsOe/7dAq3Pf8VZq9EvDcVLWOfW3eQc+zT7hHiKo73E0zqwKBgHa+
0a52gNRnJyEaF8lLp7F+4T21nkmWs8T5xYmO5mFtBZA3LTGD91f+BlvGagS9wF8H
0CTr1soS3SlFzykfkwcl933Q15jLVUmDFFykFcU78/VpwU36xW4iFz4387oXvfVr
V3yLSxs4Yeeqv8vwn9KFDk1hAwximc1Mi8XdCXVFAoGAVIYfKAO9KgshtbxRE3ql
kC7DhT2iZ7du8F2qLZf5tEV4WcWtxy5vI89MYHzg1MhToKPGauvOxDSqdLLuzeKa
0MWuGiV02z4nA5xv40OVWI5zylcwPeV7drCoitjvbFCpv4bKcagdVOOF8SjB25GA
yBPHa/QCsfYNCPpWHS7DYvk=
-----END PRIVATE KEY-----
070701000000A4000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002E00000000uyuni-tools/mgradm/shared/ssl/testdata/chain2070701000000A5000081B4000000000000000000000001662A75280000061E000000000000000000000000000000000000004700000000uyuni-tools/mgradm/shared/ssl/testdata/chain2/RHN-ORG-TRUSTED-SSL-CERT-----BEGIN CERTIFICATE-----
MIIEVjCCAz6gAwIBAgIUA12e94NKtyrGIZpdEYgrqkjHXN8wDQYJKoZIhvcNAQEL
BQAwXTELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQ0wCwYDVQQHDARDSVRZ
MQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlJvb3RD
QTAeFw0yMzEwMDYxNTQyMzBaFw0yNjA3MjYxNTQyMzBaMF0xCzAJBgNVBAYTAkRF
MQ4wDAYDVQQIDAVTVEFURTENMAsGA1UEBwwEQ0lUWTEMMAoGA1UECgwDT1JHMRAw
DgYDVQQLDAdPUkdVTklUMQ8wDQYDVQQDDAZSb290Q0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDDbR3+UOxtw6KO8s/XsvkjukqSAFggAjSJxOw+KJL4
tOykM4lBXkC3nLiV6ve5Np2koi9bX1At/nk1Fxftwy37WbeVAFs6wprkI0sDbK6z
ZfT/qRoNChpYnzMFs28VCgftsOv1q5aLEUHfnSgEIK3lH3lMvDaEO6VgTDa84Y3h
DlNbj5bssq3mMHsKE5DRCSM0wXP8ZlnwfY8S/LMxf8FN8S+c3fwg6/+dUKiAHU8Q
goXQliH/NvZZPvvYTiADTY+xt6fEeZ4OdVVV31V7so3v6cIN4WwaOtGAzWKOrB4r
Oa4ZybhmEMW7rLOnSUvl+r1UyWfh/8rH+ATSYQSynI/TAgMBAAGjggEMMIIBCDAM
BgNVHRMEBTADAQH/MAsGA1UdDwQEAwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwKAYJYIZIAYb4QgENBBsWGVNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh
dGUwHQYDVR0OBBYEFDOCM0j6AGn+E4QnDh2Y9rR5Mc1DMIGCBgNVHSMEezB5oWGk
XzBdMQswCQYDVQQGEwJERTEOMAwGA1UECAwFU1RBVEUxDTALBgNVBAcMBENJVFkx
DDAKBgNVBAoMA09SRzEQMA4GA1UECwwHT1JHVU5JVDEPMA0GA1UEAwwGUm9vdENB
ghQDXZ73g0q3KsYhml0RiCuqSMdc3zANBgkqhkiG9w0BAQsFAAOCAQEAbvvh+GyX
KwFC8xaGAAHBsz0yg43LS5W9TNNOospC1qwbgCpSZJ9nWBbF2UWTKUgzjSPUpCjJ
0kMUvkpFnwFujE8IgJiP0Tha3KE3D14kj91Vfs5jDSyBsexUi8GMTP4caTMbXnU5
+q1iVhigbtOh2gSBKQvTIIdhzhghp9iFX7f68WERRVlSG/xGCSt6DXO5sgUyQb2U
ArHMJZkROIrVeGY6pXp1dWB/j6iguRUTC3GJ0JfRgx5E+pgpFnjItDQ1e2/pxsQr
ikqyFuc2CAHkhlEl0oWz+yWwCQrKkZNLABWyPWtnMecIoCqZQ79EoeQ59JZzQPrg
AQKotV5y5qBInw==
-----END CERTIFICATE-----
070701000000A6000081B4000000000000000000000001662A752800003C9D000000000000000000000000000000000000003C00000000uyuni-tools/mgradm/shared/ssl/testdata/chain2/spacewalk.crtCertificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            11:b5:b3:c6:0e:7b:13:28:c9:e6:28:3c:f1:40:25:6d:cb:14:eb:3e
        Signature Algorithm: sha384WithRSAEncryption
        Issuer: C = DE, ST = STATE, O = ORG, OU = ORGUNIT, CN = TeamCA
        Validity
            Not Before: Oct  6 15:42:31 2023 GMT
            Not After : Oct  5 15:42:31 2024 GMT
        Subject: C = DE, ST = STATE, O = ORG, OU = ORGUNIT, CN = uyuni.world-co.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b8:3f:47:da:13:fe:f4:7b:af:ff:75:fc:0b:dd:
                    e7:26:d6:34:7b:19:33:80:7d:b9:20:40:17:b8:34:
                    a4:80:3a:4c:bb:25:0c:8a:40:65:47:32:04:af:ef:
                    2d:b6:97:70:66:1e:23:28:b0:8f:98:d4:f0:2c:b6:
                    c0:40:6a:29:06:c2:8d:5e:81:5b:60:66:54:54:9f:
                    fd:77:ae:b4:62:63:87:f1:5b:fb:aa:41:cc:82:16:
                    10:3e:35:9d:99:98:63:c1:ad:2c:7b:2d:02:0e:0a:
                    af:1d:75:6d:5c:44:c1:3d:a8:28:a5:a4:53:35:10:
                    5b:58:a8:ab:54:77:ad:f4:f4:e7:5a:51:5f:75:6f:
                    05:37:fd:55:56:a2:4d:2e:3a:58:3a:a4:d6:ad:20:
                    6d:4f:7e:1d:a2:83:94:a2:6c:0c:b8:03:ba:39:55:
                    05:93:ad:7c:9f:7a:12:99:28:3e:53:9d:3a:83:bc:
                    4a:3e:6e:2e:52:e6:63:a2:fa:e7:d9:12:90:2c:5b:
                    78:52:34:92:19:19:ac:28:84:c3:25:4f:8f:f9:0d:
                    64:ef:eb:e4:bc:cd:87:89:1c:74:01:6f:e2:1a:78:
                    92:e2:2e:15:d0:8e:2b:94:69:6d:87:f4:91:f1:5d:
                    f3:47:73:95:e3:d6:80:87:93:15:6a:f7:ae:af:83:
                    b6:55
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Cert Type: 
                SSL Server
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                93:88:1D:52:E3:27:6E:73:35:9E:0E:AE:20:9F:E2:2E:58:41:CA:90
            X509v3 Authority Key Identifier: 
                keyid:C6:C6:FD:7F:A3:EA:C1:50:0A:7E:33:1D:50:F7:E0:94:3F:93:EA:B7
                DirName:/C=DE/ST=STATE/O=ORG/OU=ORGUNIT/CN=OrgCa
                serial:11:B5:B3:C6:0E:7B:13:28:C9:E6:28:3C:F1:40:25:6D:CB:14:EB:3D

            X509v3 Subject Alternative Name: 
                DNS:uyuni.world-co.com
    Signature Algorithm: sha384WithRSAEncryption
         75:04:ec:e4:e7:cc:3b:00:df:a3:5e:42:70:f4:30:91:17:8c:
         2a:19:58:6d:0c:0c:ff:8a:5e:b8:1f:03:e5:c5:01:7e:7f:c1:
         9d:25:3d:5a:89:df:0b:97:a4:f6:94:3b:ce:fc:11:f2:db:b2:
         4f:76:5a:4e:9a:6d:ef:b9:b5:69:db:c7:33:27:d8:8b:ce:a7:
         45:e5:12:84:38:48:b3:f3:54:6f:bf:fb:35:3f:ae:26:1a:03:
         b2:e1:55:45:97:eb:d2:b3:7e:d3:bd:f3:21:d0:34:56:51:15:
         88:e6:49:e3:ea:ac:e8:aa:5c:16:d2:95:fa:f2:d6:f6:f0:5a:
         e7:8b:c1:7e:f6:54:c5:a4:36:99:0c:ef:d9:c3:9d:d4:22:f9:
         55:d1:2b:10:ed:6c:9d:84:87:88:c2:b3:bf:ac:54:fa:3e:3d:
         42:5d:76:83:cb:9a:b9:a2:88:b3:99:31:ac:05:f2:d6:16:be:
         73:85:bd:56:49:17:6a:f6:81:e4:f7:ec:2a:38:50:11:b6:c6:
         af:6e:df:8a:97:57:f6:36:b6:ca:3c:04:e0:c6:2b:20:c2:c0:
         50:f7:21:ec:46:23:e5:3c:5d:e3:37:19:48:88:3c:40:10:fb:
         bd:86:40:52:bf:9f:81:9a:41:36:f6:e4:8c:3f:1b:f9:2d:04:
         09:00:3a:0e
-----BEGIN CERTIFICATE-----
MIIEejCCA2KgAwIBAgIUEbWzxg57EyjJ5ig88UAlbcsU6z4wDQYJKoZIhvcNAQEM
BQAwTjELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQwwCgYDVQQKDANPUkcx
EDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlRlYW1DQTAeFw0yMzEwMDYxNTQy
MzFaFw0yNDEwMDUxNTQyMzFaMFoxCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVTVEFU
RTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMRswGQYDVQQDDBJ1eXVu
aS53b3JsZC1jby5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4
P0faE/70e6//dfwL3ecm1jR7GTOAfbkgQBe4NKSAOky7JQyKQGVHMgSv7y22l3Bm
HiMosI+Y1PAstsBAaikGwo1egVtgZlRUn/13rrRiY4fxW/uqQcyCFhA+NZ2ZmGPB
rSx7LQIOCq8ddW1cRME9qCilpFM1EFtYqKtUd6309OdaUV91bwU3/VVWok0uOlg6
pNatIG1Pfh2ig5SibAy4A7o5VQWTrXyfehKZKD5TnTqDvEo+bi5S5mOi+ufZEpAs
W3hSNJIZGawohMMlT4/5DWTv6+S8zYeJHHQBb+IaeJLiLhXQjiuUaW2H9JHxXfNH
c5Xj1oCHkxVq966vg7ZVAgMBAAGjggFCMIIBPjAJBgNVHRMEAjAAMAsGA1UdDwQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEQYJYIZIAYb4QgEB
BAQDAgZAMCgGCWCGSAGG+EIBDQQbFhlTU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
MB0GA1UdDgQWBBSTiB1S4yduczWeDq4gn+IuWEHKkDCBiQYDVR0jBIGBMH+AFMbG
/X+j6sFQCn4zHVD34JQ/k+q3oVGkTzBNMQswCQYDVQQGEwJERTEOMAwGA1UECAwF
U1RBVEUxDDAKBgNVBAoMA09SRzEQMA4GA1UECwwHT1JHVU5JVDEOMAwGA1UEAwwF
T3JnQ2GCFBG1s8YOexMoyeYoPPFAJW3LFOs9MB0GA1UdEQQWMBSCEnV5dW5pLndv
cmxkLWNvLmNvbTANBgkqhkiG9w0BAQwFAAOCAQEAdQTs5OfMOwDfo15CcPQwkReM
KhlYbQwM/4peuB8D5cUBfn/BnSU9WonfC5ek9pQ7zvwR8tuyT3ZaTppt77m1advH
MyfYi86nReUShDhIs/NUb7/7NT+uJhoDsuFVRZfr0rN+073zIdA0VlEViOZJ4+qs
6KpcFtKV+vLW9vBa54vBfvZUxaQ2mQzv2cOd1CL5VdErEO1snYSHiMKzv6xU+j49
Ql12g8uauaKIs5kxrAXy1ha+c4W9VkkXavaB5PfsKjhQEbbGr27fipdX9ja2yjwE
4MYrIMLAUPch7EYj5Txd4zcZSIg8QBD7vYZAUr+fgZpBNvbkjD8b+S0ECQA6Dg==
-----END CERTIFICATE-----
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            11:b5:b3:c6:0e:7b:13:28:c9:e6:28:3c:f1:40:25:6d:cb:14:eb:3d
        Signature Algorithm: sha384WithRSAEncryption
        Issuer: C = DE, ST = STATE, O = ORG, OU = ORGUNIT, CN = OrgCa
        Validity
            Not Before: Oct  6 15:42:30 2023 GMT
            Not After : Nov  9 15:42:30 2024 GMT
        Subject: C = DE, ST = STATE, O = ORG, OU = ORGUNIT, CN = TeamCA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:87:88:e3:ca:8e:8a:f1:5e:1e:b4:78:1d:32:79:
                    ef:bd:51:74:fb:40:8d:85:01:98:a4:b3:73:fa:18:
                    f5:5f:7c:6c:fb:56:ad:30:ee:df:da:19:cb:db:d2:
                    f8:59:8b:15:52:6a:46:c2:1c:12:4d:ed:83:a5:67:
                    97:47:9e:98:94:78:e2:fd:e4:7e:18:48:12:92:29:
                    54:49:ac:bb:8e:de:db:c2:22:37:a9:4f:0d:ff:39:
                    5a:ca:98:2b:fd:b5:ec:e2:e1:88:2a:cf:b6:3a:60:
                    1b:11:74:a2:af:fa:e6:a7:b4:71:21:f7:d9:6c:2f:
                    c5:33:d4:e2:fd:b1:93:8d:de:ff:2c:86:52:e9:84:
                    19:dd:ba:a6:0b:85:f4:64:ef:15:97:79:21:a9:da:
                    46:ef:b5:89:00:01:e0:6d:72:21:6b:ea:a3:7c:d1:
                    42:8a:26:ca:7c:f2:47:a8:8e:86:2b:a9:1b:61:66:
                    02:93:ab:57:cf:e4:7b:94:08:7f:71:62:f1:29:23:
                    35:a4:33:6c:e1:84:4c:c4:91:aa:45:b3:d4:a7:6b:
                    83:80:6b:ec:03:27:73:ff:10:20:1c:fd:aa:3d:79:
                    f7:4f:cf:c4:83:bd:4c:b1:5e:55:1c:f4:49:34:23:
                    c3:01:fc:25:b0:45:81:da:cc:10:84:66:e1:9b:c2:
                    4a:57
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:TRUE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                C6:C6:FD:7F:A3:EA:C1:50:0A:7E:33:1D:50:F7:E0:94:3F:93:EA:B7
            X509v3 Authority Key Identifier: 
                keyid:5B:5B:28:4B:13:37:60:B5:95:D4:5B:47:09:97:59:DF:16:63:AF:D9
                DirName:/C=DE/ST=STATE/L=CITY/O=ORG/OU=ORGUNIT/CN=RootCA
                serial:11:B5:B3:C6:0E:7B:13:28:C9:E6:28:3C:F1:40:25:6D:CB:14:EB:3C

    Signature Algorithm: sha384WithRSAEncryption
         92:16:c7:1b:6c:7a:f9:a1:dc:57:bd:24:45:26:0a:72:91:75:
         38:bc:f0:2c:d3:9f:ab:7a:bf:11:c0:1a:60:f3:5d:6a:ba:fe:
         f7:83:c4:21:f9:72:02:eb:47:85:16:6b:c9:38:58:6b:06:5f:
         c8:55:c1:ac:6e:9d:3c:ca:20:d1:94:15:d8:86:ed:b6:58:fc:
         56:81:15:8d:53:8f:62:da:5a:15:74:b0:78:41:da:fc:c1:69:
         fb:8d:cc:65:86:de:e5:79:f8:2d:53:1c:5c:c8:76:50:07:fe:
         f5:31:46:73:ba:e8:be:bb:f3:63:09:ae:f4:91:22:99:68:f6:
         82:b3:52:e5:92:4c:91:c8:12:b2:df:48:71:cc:ee:44:22:db:
         7e:52:97:5f:99:13:96:06:31:67:8c:00:c0:31:62:57:9a:aa:
         82:fe:e5:d9:56:07:fa:2a:15:fa:47:01:e4:ce:b0:98:fe:c0:
         ab:67:6d:7b:dc:30:d5:51:f5:13:7e:44:48:2a:d5:6f:4d:ab:
         22:0c:1a:45:bb:d3:36:37:aa:5c:f6:d3:6e:6e:d7:83:8c:3d:
         60:f5:b4:c9:67:f0:f3:a0:3a:6a:9e:7d:8c:e0:8e:75:9c:9c:
         9b:25:9b:cc:71:a2:53:4f:64:a5:8e:dd:18:7e:49:96:7d:d5:
         c0:c0:e5:2d
-----BEGIN CERTIFICATE-----
MIIETzCCAzegAwIBAgIUEbWzxg57EyjJ5ig88UAlbcsU6z0wDQYJKoZIhvcNAQEM
BQAwTTELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQwwCgYDVQQKDANPUkcx
EDAOBgNVBAsMB09SR1VOSVQxDjAMBgNVBAMMBU9yZ0NhMB4XDTIzMTAwNjE1NDIz
MFoXDTI0MTEwOTE1NDIzMFowTjELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRF
MQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlRlYW1D
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeI48qOivFeHrR4HTJ5
771RdPtAjYUBmKSzc/oY9V98bPtWrTDu39oZy9vS+FmLFVJqRsIcEk3tg6Vnl0ee
mJR44v3kfhhIEpIpVEmsu47e28IiN6lPDf85WsqYK/217OLhiCrPtjpgGxF0oq/6
5qe0cSH32WwvxTPU4v2xk43e/yyGUumEGd26pguF9GTvFZd5IanaRu+1iQAB4G1y
IWvqo3zRQoomynzyR6iOhiupG2FmApOrV8/ke5QIf3Fi8SkjNaQzbOGETMSRqkWz
1Kdrg4Br7AMnc/8QIBz9qj1590/PxIO9TLFeVRz0STQjwwH8JbBFgdrMEIRm4ZvC
SlcCAwEAAaOCASQwggEgMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgKkMB0GA1Ud
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAoBglghkgBhvhCAQ0EGxYZU1NMIEdl
bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUxsb9f6PqwVAKfjMdUPfglD+T
6rcwgZoGA1UdIwSBkjCBj4AUW1soSxM3YLWV1FtHCZdZ3xZjr9mhYaRfMF0xCzAJ
BgNVBAYTAkRFMQ4wDAYDVQQIDAVTVEFURTENMAsGA1UEBwwEQ0lUWTEMMAoGA1UE
CgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMQ8wDQYDVQQDDAZSb290Q0GCFBG1s8YO
exMoyeYoPPFAJW3LFOs8MA0GCSqGSIb3DQEBDAUAA4IBAQCSFscbbHr5odxXvSRF
JgpykXU4vPAs05+rer8RwBpg811quv73g8Qh+XIC60eFFmvJOFhrBl/IVcGsbp08
yiDRlBXYhu22WPxWgRWNU49i2loVdLB4Qdr8wWn7jcxlht7lefgtUxxcyHZQB/71
MUZzuui+u/NjCa70kSKZaPaCs1LlkkyRyBKy30hxzO5EItt+UpdfmROWBjFnjADA
MWJXmqqC/uXZVgf6KhX6RwHkzrCY/sCrZ2173DDVUfUTfkRIKtVvTasiDBpFu9M2
N6pc9tNubteDjD1g9bTJZ/DzoDpqnn2M4I51nJybJZvMcaJTT2Sljt0YfkmWfdXA
wOUt
-----END CERTIFICATE-----
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            11:b5:b3:c6:0e:7b:13:28:c9:e6:28:3c:f1:40:25:6d:cb:14:eb:3c
        Signature Algorithm: sha384WithRSAEncryption
        Issuer: C = DE, ST = STATE, L = CITY, O = ORG, OU = ORGUNIT, CN = RootCA
        Validity
            Not Before: Oct  6 15:42:30 2023 GMT
            Not After : Feb 17 15:42:30 2025 GMT
        Subject: C = DE, ST = STATE, O = ORG, OU = ORGUNIT, CN = OrgCa
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:c5:6a:66:7e:44:c7:79:2c:85:ab:72:48:b8:d3:
                    c2:a4:33:a9:ec:4d:4a:4c:9a:cb:f7:d8:a9:38:81:
                    70:12:f2:6c:b5:31:f4:f9:2b:5c:d3:e6:d1:d3:7e:
                    97:a7:ab:30:06:6a:82:13:15:3b:bf:1c:b5:9a:81:
                    c6:da:ab:8b:10:b7:3e:ec:21:63:72:fd:6e:cd:6e:
                    83:53:af:aa:d1:1e:66:79:42:03:50:aa:71:a5:6e:
                    ac:8f:5d:1a:b1:21:35:65:10:56:7f:fb:59:f7:f7:
                    3c:1c:41:1d:a3:bd:98:a5:df:a6:00:9a:9f:a9:f4:
                    f4:10:3f:1d:63:9e:dc:ab:44:3d:8a:2a:bc:70:7f:
                    56:e0:bd:ca:1b:45:54:94:72:db:12:02:22:c9:07:
                    f5:cf:60:6f:a8:b5:b0:cc:8e:16:25:33:21:f3:3a:
                    ac:7a:12:2c:f4:f6:25:55:be:98:4a:d0:cc:5a:25:
                    82:16:27:70:6b:d3:4d:f6:10:0f:2d:75:03:1f:90:
                    a9:31:28:24:78:c3:4a:af:54:69:46:a4:5c:c0:3a:
                    6b:94:5f:3e:b8:86:b9:40:ce:c1:0f:bf:de:5b:cf:
                    59:14:49:49:cb:d7:27:d5:d0:d2:14:b9:5b:4d:0a:
                    90:1c:e3:2b:8c:c4:d5:8d:a7:9c:db:2b:60:36:45:
                    e7:3d
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:TRUE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Comment: 
                SSL Generated Certificate
            X509v3 Subject Key Identifier: 
                5B:5B:28:4B:13:37:60:B5:95:D4:5B:47:09:97:59:DF:16:63:AF:D9
            X509v3 Authority Key Identifier: 
                keyid:33:82:33:48:FA:00:69:FE:13:84:27:0E:1D:98:F6:B4:79:31:CD:43
                DirName:/C=DE/ST=STATE/L=CITY/O=ORG/OU=ORGUNIT/CN=RootCA
                serial:03:5D:9E:F7:83:4A:B7:2A:C6:21:9A:5D:11:88:2B:AA:48:C7:5C:DF

    Signature Algorithm: sha384WithRSAEncryption
         9e:36:a2:c3:9f:7c:88:74:be:19:70:3f:bd:bf:44:93:86:1e:
         e7:72:96:66:62:d6:45:ee:c3:22:d8:09:13:2c:96:26:e8:dc:
         72:8d:6d:c4:51:68:32:58:ec:4b:45:1b:59:58:fb:ef:bc:91:
         c2:f3:ed:c6:4f:70:8b:e1:48:f7:7e:b4:b6:91:98:1d:a1:0e:
         e0:08:36:ff:d7:8f:79:d8:5d:76:f6:49:d7:c1:9e:24:58:dd:
         48:77:69:8e:80:82:ec:f5:5a:44:0d:b8:7d:5c:8e:ce:b0:1d:
         e7:3c:b4:73:10:e6:1b:9e:fb:45:42:34:64:98:58:a2:da:4b:
         b9:3f:df:61:c2:1e:25:f8:8e:84:3d:2c:e7:a9:43:54:2d:39:
         3b:9c:f9:9b:22:e1:37:dd:46:25:11:a1:c6:3a:60:18:56:56:
         8d:e0:99:31:8a:5b:ad:a5:4f:4b:b5:d4:cf:ca:91:93:1b:d4:
         41:16:56:85:fd:99:df:0d:48:c1:0c:af:4a:60:e0:d2:9e:9b:
         18:81:58:fe:54:f2:42:bc:60:70:d4:f8:0c:70:4a:b3:3f:90:
         0b:63:f3:1b:b1:2e:40:c2:ef:59:ab:49:9b:26:22:c2:09:8e:
         ec:39:d0:95:8a:fc:af:46:f9:70:09:43:6c:b4:6e:ae:f1:8e:
         c8:c4:71:e7
-----BEGIN CERTIFICATE-----
MIIEXjCCA0agAwIBAgIUEbWzxg57EyjJ5ig88UAlbcsU6zwwDQYJKoZIhvcNAQEM
BQAwXTELMAkGA1UEBhMCREUxDjAMBgNVBAgMBVNUQVRFMQ0wCwYDVQQHDARDSVRZ
MQwwCgYDVQQKDANPUkcxEDAOBgNVBAsMB09SR1VOSVQxDzANBgNVBAMMBlJvb3RD
QTAeFw0yMzEwMDYxNTQyMzBaFw0yNTAyMTcxNTQyMzBaME0xCzAJBgNVBAYTAkRF
MQ4wDAYDVQQIDAVTVEFURTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklU
MQ4wDAYDVQQDDAVPcmdDYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMVqZn5Ex3kshatySLjTwqQzqexNSkyay/fYqTiBcBLybLUx9PkrXNPm0dN+l6er
MAZqghMVO78ctZqBxtqrixC3PuwhY3L9bs1ug1OvqtEeZnlCA1CqcaVurI9dGrEh
NWUQVn/7Wff3PBxBHaO9mKXfpgCan6n09BA/HWOe3KtEPYoqvHB/VuC9yhtFVJRy
2xICIskH9c9gb6i1sMyOFiUzIfM6rHoSLPT2JVW+mErQzFolghYncGvTTfYQDy11
Ax+QqTEoJHjDSq9UaUakXMA6a5RfPriGuUDOwQ+/3lvPWRRJScvXJ9XQ0hS5W00K
kBzjK4zE1Y2nnNsrYDZF5z0CAwEAAaOCASQwggEgMAwGA1UdEwQFMAMBAf8wCwYD
VR0PBAQDAgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAoBglghkgB
hvhCAQ0EGxYZU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUW1so
SxM3YLWV1FtHCZdZ3xZjr9kwgZoGA1UdIwSBkjCBj4AUM4IzSPoAaf4ThCcOHZj2
tHkxzUOhYaRfMF0xCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVTVEFURTENMAsGA1UE
BwwEQ0lUWTEMMAoGA1UECgwDT1JHMRAwDgYDVQQLDAdPUkdVTklUMQ8wDQYDVQQD
DAZSb290Q0GCFANdnveDSrcqxiGaXRGIK6pIx1zfMA0GCSqGSIb3DQEBDAUAA4IB
AQCeNqLDn3yIdL4ZcD+9v0SThh7ncpZmYtZF7sMi2AkTLJYm6NxyjW3EUWgyWOxL
RRtZWPvvvJHC8+3GT3CL4Uj3frS2kZgdoQ7gCDb/14952F129knXwZ4kWN1Id2mO
gILs9VpEDbh9XI7OsB3nPLRzEOYbnvtFQjRkmFii2ku5P99hwh4l+I6EPSznqUNU
LTk7nPmbIuE33UYlEaHGOmAYVlaN4JkxilutpU9LtdTPypGTG9RBFlaF/ZnfDUjB
DK9KYODSnpsYgVj+VPJCvGBw1PgMcEqzP5ALY/MbsS5Awu9Zq0mbJiLCCY7sOdCV
ivyvRvlwCUNstG6u8Y7IxHHn
-----END CERTIFICATE-----
070701000000A7000081B4000000000000000000000001662A7528000006A8000000000000000000000000000000000000003C00000000uyuni-tools/mgradm/shared/ssl/testdata/chain2/spacewalk.key-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4P0faE/70e6//
dfwL3ecm1jR7GTOAfbkgQBe4NKSAOky7JQyKQGVHMgSv7y22l3BmHiMosI+Y1PAs
tsBAaikGwo1egVtgZlRUn/13rrRiY4fxW/uqQcyCFhA+NZ2ZmGPBrSx7LQIOCq8d
dW1cRME9qCilpFM1EFtYqKtUd6309OdaUV91bwU3/VVWok0uOlg6pNatIG1Pfh2i
g5SibAy4A7o5VQWTrXyfehKZKD5TnTqDvEo+bi5S5mOi+ufZEpAsW3hSNJIZGawo
hMMlT4/5DWTv6+S8zYeJHHQBb+IaeJLiLhXQjiuUaW2H9JHxXfNHc5Xj1oCHkxVq
966vg7ZVAgMBAAECggEAErkNgGX5Sdda2GshKISNdYcdcKfsMaG1Awe4UVX6JHSo
MPlQF6l5ET3ON6Gmw9AKUjo8SOl+QiHbYTPWAAW5sw/opUKgakCjz7CtZXDZsEjc
euSlw5Spp0t+LZAtunq/omIKa970P0CLMINrEE4FVBJnRQPYl8MYgT8sn+oEgaiI
J0p1MaxvvLWJfCF9niFWevCxjWwFNP5nYA7XdfdG8yKO7AXIlE3Xte174juTQ5+Q
neAEYnh2bp+uqEgfgvhp830NsBmegvqn7USSUqWXO++aTEwlDv70t5YWtD6GvSu2
8SWEPdAsLZkH1tzD+0jgMhdiJdcsY48fSnOxP1LHSQKBgQDI+SBLwtvAtEHI1lY7
LQ+lonJSNY/MtFEkPBAgDtU97PzF7ucoQdYitoLOyAu2d6TN7ze/H8crKjLSbKXn
X5DcID+7fLWbAeF4jDUfB9cm/zMkywxHcWL3K+aHzD08NVdTcGpIxViUZ/7ekBOr
9l+32+tRkYLumH9roUsSnLN0DQKBgQDqscbR8OtzwGi2ic/cjJvyZ1Vkbr19yeea
2WM0RiSO3vU7NY7aBSWkoHOAECKdKqJ0J7VhhMYYP+LKGFQbQQge+LmQpDRIJaM9
tq5QpOW6YYpwvGLwsbHSwfz93iRR+SO9+X9mx/FeoyOv9qKBvxv3UzdNpO5W5BKt
3oJiqbVRaQKBgQCC8sB2XNru7wTGJdI98Jh3ZidzJW8zBHKyV2hyWvfax6XUGlwH
wQ4TxDPrJDFtjPuXKz15jO0rVO2UajKXVY9/vouIUDPMcidFcqXSODuaL0JVwO+Z
RWokfzhQV2W261KhDWhTTjLvT+ujfOE0dO3dULA9j8BuUnMD4C6YS/4pqQKBgQC3
2oSyOlV43CYruVIIqG4SOzj98HKpc93nxJyeesRw1+CsfYxm5tlSWg+hJwK2tIuH
CwRgXK8CmCmFwAFDSHKgMKDN2pTKYBG9arqrmkIM/BSDtFCd1dZEEIusJLW3McD6
NdXEIqXHSW3PjxpHIfs6iQot3SKJFyo64rCpseDE4QKBgHHp6c9UEJjq+IYT6hXg
kvxS87ZWWVqFRAjS93WRS7ZllFyX9+9GfUAEaOrArJf8gvVl+8QOqVn9spjUoV0o
VIWMPlh9VRS9nQGYNTTg3vYRRCdNE0SGwNg4CL7oW1kvoPOVMk7nST/9AnGAMpkF
LY7A0vS56vx3wZuWfPbqcK1s
-----END PRIVATE KEY-----
070701000000A8000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002400000000uyuni-tools/mgradm/shared/templates070701000000A9000081B4000000000000000000000001662A7528000006C8000000000000000000000000000000000000004200000000uyuni-tools/mgradm/shared/templates/attestationServiceTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const attestationServiceTemplate = `# uyuni-server-attestation.service, generated by mgradm
# Use an uyuni-server-attestation.service.d/local.conf file to override

[Unit]
Description=Uyuni server attestation container service
Wants=network.target
After=network-online.target

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-server-attestation.pid %t/%n.ctr-id
ExecStartPre=/usr/bin/podman rm --ignore --force -t 10 {{ .NamePrefix }}-server-attestation
ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-server-attestation.pid \
	--cidfile=%t/%n.ctr-id \
	--cgroups=no-conmon \
	--sdnotify=conmon \
	-d \
	-e database_connection  \
	-e database_user \
	-e database_password \
	--replace \
	--name {{ .NamePrefix }}-server-attestation \
	--hostname {{ .NamePrefix }}-server-attestation.mgr.internal \
	--network {{ .Network }} \
	${UYUNI_IMAGE}

ExecStop=/usr/bin/podman stop --ignore -t 10 --cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm -f --ignore -t 10 --cidfile=%t/%n.ctr-id
PIDFile=%t/uyuni-server-attestation.pid
TimeoutStopSec=60
TimeoutStartSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// PodmanServiceTemplateData POD information to create systemd file.
type AttestationServiceTemplateData struct {
	NamePrefix string
	Image      string
	Network    string
}

// Render will create the systemd configuration file.
func (data AttestationServiceTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(attestationServiceTemplate))
	return t.Execute(wr, data)
}
070701000000AA000081B4000000000000000000000001662A752800000811000000000000000000000000000000000000003600000000uyuni-tools/mgradm/shared/templates/issuerTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

// Deploy self-signed issuer or CA Certificate and key.
const issuerTemplate = `{{if and .Certificate .Key -}}
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
  name: uyuni-ca
  namespace: {{ .Namespace }}
data:
  ca.crt: {{ .RootCa }}
  tls.crt: {{ .Certificate }}
  tls.key: {{ .Key }}
{{- else }}
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: uyuni-issuer
  namespace: {{ .Namespace }}
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: uyuni-ca
  namespace: {{ .Namespace }}
spec:
  isCA: true
{{- if or .Country .State .City .Org .OrgUnit }}
  subject:
	{{- if .Country }}
    countries: ["{{ .Country }}"]
	{{- end }}
	{{- if .State }}
    provinces: ["{{ .State }}"]
	{{- end }}
	{{- if .City }}
    localities: ["{{ .City }}"]
	{{- end }}
	{{- if .Org }}
    organizations: ["{{ .Org }}"]
	{{- end }}
	{{- if .OrgUnit }}
    organizationalUnits: ["{{ .OrgUnit }}"]
	{{- end }}
{{- end }}
{{- if .Email }}
  emailAddresses:
    - {{ .Email }}
{{- end }}
  commonName: {{ .Fqdn }}
  dnsNames:
    - {{ .Fqdn }}
  secretName: uyuni-ca
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: uyuni-issuer
    kind: Issuer
    group: cert-manager.io
{{- end }}
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: uyuni-ca-issuer
  namespace: {{ .Namespace }}
spec:
  ca:
    secretName:
      uyuni-ca
`

// IssuerTemplateData represents information used to create issuer file.
type IssuerTemplateData struct {
	Namespace   string
	Country     string
	State       string
	City        string
	Org         string
	OrgUnit     string
	Email       string
	Fqdn        string
	RootCa      string
	Certificate string
	Key         string
}

// Render creates issuer file.
func (data IssuerTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("issuer").Parse(issuerTemplate))
	return t.Execute(wr, data)
}
070701000000AB000081B4000000000000000000000001662A75280000049D000000000000000000000000000000000000003E00000000uyuni-tools/mgradm/shared/templates/mgrSetupScriptTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const mgrSetupScriptTemplate = `#!/bin/sh
{{- range $name, $value := .Env }}
export {{ $name }}={{ $value }}
{{- end }}

{{- if .DebugJava }}
echo 'JAVA_OPTS=" $JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=*:8003,server=y,suspend=n" ' >> /etc/tomcat/conf.d/remote_debug.conf
echo 'JAVA_OPTS=" $JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=*:8001,server=y,suspend=n" ' >> /etc/rhn/taskomatic.conf
echo 'JAVA_OPTS=" $JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=*:8002,server=y,suspend=n" ' >> /usr/share/rhn/config-defaults/rhn_search_daemon.conf
{{- end }}

/usr/lib/susemanager/bin/mgr-setup -s -n

# clean before leaving
rm $0`

// MgrSetupScriptTemplateData represents information used to create setup script.
type MgrSetupScriptTemplateData struct {
	Env       map[string]string
	DebugJava bool
}

// Render will create setup script.
func (data MgrSetupScriptTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("script").Parse(mgrSetupScriptTemplate))
	return t.Execute(wr, data)
}
070701000000AC000081B4000000000000000000000001662A75280000161A000000000000000000000000000000000000003D00000000uyuni-tools/mgradm/shared/templates/migrateScriptTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

const migrationScriptTemplate = `#!/bin/bash
set -e
SSH_CONFIG=""
if test -e /tmp/ssh_config; then
  SSH_CONFIG="-F /tmp/ssh_config"
fi
SSH="ssh -o User={{ .User }} -A $SSH_CONFIG "

echo "Stopping spacewalk service..."
$SSH {{ .SourceFqdn }} "sudo spacewalk-service stop ; sudo systemctl start postgresql.service"

$SSH {{ .SourceFqdn }} \
 "echo \"COPY (SELECT MIN(CONCAT(org_id, '-', label)) AS target, base_path FROM rhnKickstartableTree GROUP BY base_path) TO STDOUT WITH CSV;\" \
 |sudo spacewalk-sql --select-mode - " > distros

echo "Stopping posgresql service..."
$SSH {{ .SourceFqdn }} "sudo systemctl stop postgresql.service"

while IFS="," read -r target path ; do
    echo "-/ $path"
done < distros > exclude_list

# exclude all config files which already exist and are not marked noreplace
rpm -qa --qf '[%{fileflags},%{filenames}\n]' |grep ",/etc/" | while IFS="," read -r flags path ; do
    # config(noreplace) is 1<<4 (from lib/rpmlib.h)
    if [ $(( $flags & 16 )) -eq 0 -a -f "$path" ] ; then
        echo "-/ $path" >> exclude_list
    fi
done

# exclude schema migration files
echo "-/ /etc/sysconfig/rhn/reportdb-schema-upgrade" >> exclude_list
echo "-/ /etc/sysconfig/rhn/schema-upgrade" >> exclude_list


for folder in {{ range .Volumes }}{{ .MountPath }} {{ end }};
do
  if $SSH {{ .SourceFqdn }} test -e $folder; then
    echo "Copying $folder..."
    rsync -e "$SSH" --rsync-path='sudo rsync' -avz -f "merge exclude_list" {{ .SourceFqdn }}:$folder/ $folder;
  else
    echo "Skipping missing $folder..."
  fi
done;

sed -i -e 's|appBase="webapps"|appBase="/usr/share/susemanager/www/tomcat/webapps"|' /etc/tomcat/server.xml
sed -i -e 's|DocumentRoot\s*"/srv/www/htdocs"|DocumentRoot "/usr/share/susemanager/www/htdocs"|' /etc/apache2/vhosts.d/vhost-ssl.conf

echo "Migrating auto-installable distributions..."

while IFS="," read -r target path ; do
  if $SSH -n {{ .SourceFqdn }} test -e $path ; then
    echo "Copying distribution $target from $path"
    mkdir -p "/srv/www/distributions/$target"
    rsync -e "$SSH" --rsync-path='sudo rsync' -avz "{{ .SourceFqdn }}:$path/" "/srv/www/distributions/$target"
  else
    echo "Skipping missing distribution $path..."
  fi
done < distros

rm -f /srv/www/htdocs/pub/RHN-ORG-TRUSTED-SSL-CERT;
ln -s /etc/pki/trust/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT /srv/www/htdocs/pub/RHN-ORG-TRUSTED-SSL-CERT;

echo "Extracting time zone..."
$SSH {{ .SourceFqdn }} timedatectl show -p Timezone >/var/lib/uyuni-tools/data

echo "Extracting postgresql versions..."
echo "new_pg_version=$(rpm -qa --qf '%{VERSION}\n' 'name=postgresql[0-8][0-9]-server'  | cut -d. -f1 | sort -n | tail -1)" >> /var/lib/uyuni-tools/data
echo "old_pg_version=$(cat /var/lib/pgsql/data/PG_VERSION)" >> /var/lib/uyuni-tools/data

echo "Altering configuration for domain resolution..."
sed 's/report_db_host = {{ .SourceFqdn }}/report_db_host = localhost/' -i /etc/rhn/rhn.conf;
sed 's/server\.jabber_server/java\.hostname/' -i /etc/rhn/rhn.conf;
sed 's/client_use_localhost: false/client_use_localhost: true/' -i /etc/cobbler/settings.yaml;

echo "Altering configuration for container environment..."
sed 's/address=[^:]*:/address=*:/' -i /etc/rhn/taskomatic.conf;

if test ! -f /etc/tomcat/conf.d/remote_debug.conf -a -f /etc/sysconfig/tomcat; then
  mv /etc/sysconfig/tomcat /etc/tomcat/conf.d/remote_debug.conf
fi

sed 's/address=[^:]*:/address=*:/' -i /etc/tomcat/conf.d/remote_debug.conf

{{ if .Kubernetes }}
echo 'server.no_ssl = 1' >> /etc/rhn/rhn.conf;
echo "Extracting SSL certificate and authority"
extractedSSL=
if test -d /root/ssl-build; then
  # We may have an old unused ssl-build folder, check if the CA matches the deployed one
  buildCaFingerprint=
  if test -e /root/ssl-build/RHN-ORG-TRUSTED-SSL-CERT; then
    buildCaFingerprint=$(openssl x509 -in /root/ssl-build/RHN-ORG-TRUSTED-SSL-CERT -noout -fingerprint)
  fi
  caFingerprint=$(openssl x509 -in /etc/pki/trust/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT -noout -fingerprint)

  if test "$buildCaFingerprint" == "$caFingerprint"; then
    echo "Extracting SSL Root CA key..."
    # Extract the SSL CA certificate and key.
    # The server certificate will be auto-generated by cert-manager using it, so no need to copy it.
    cp /root/ssl-build/RHN-ORG-PRIVATE-SSL-KEY /var/lib/uyuni-tools/

    extractedSSL="1"
  fi
fi

# This Root CA file is common to both cases
cp /etc/pki/trust/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT /var/lib/uyuni-tools/RHN-ORG-TRUSTED-SSL-CERT

if test "extractedSSL" != "1"; then
  # For third party certificates, the CA chain is in the certificate file.
  rsync -e "$SSH" --rsync-path='sudo rsync' -avz {{ .SourceFqdn }}:/etc/pki/tls/private/spacewalk.key /var/lib/uyuni-tools/
  rsync -e "$SSH" --rsync-path='sudo rsync' -avz {{ .SourceFqdn }}:/etc/pki/tls/certs/spacewalk.crt /var/lib/uyuni-tools/

fi

echo "Removing useless ssl-build folder..."
rm -rf /root/ssl-build

# The content of this folder will be a RO mount from a configmap
rm /etc/pki/trust/anchors/*
{{ end }}

echo "DONE"`

// MigrateScriptTemplateData represents migration information used to create migration script.
type MigrateScriptTemplateData struct {
	Volumes    []types.VolumeMount
	SourceFqdn string
	User       string
	Kubernetes bool
}

// Render will create migration script.
func (data MigrateScriptTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("script").Parse(migrationScriptTemplate))
	return t.Execute(wr, data)
}
070701000000AD000081B4000000000000000000000001662A7528000008F6000000000000000000000000000000000000004300000000uyuni-tools/mgradm/shared/templates/pgsqlFinalizeScriptTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const postgresFinalizeScriptTemplate = `#!/bin/bash
set -e

{{ if .RunAutotune }}
echo "Running smdba system-check autotuning..."
smdba system-check autotuning
{{ end }}
echo "Starting Postgresql..."
su -s /bin/bash - postgres -c "/usr/share/postgresql/postgresql-script start"
{{ if .RunReindex }}
echo "Reindexing database. This may take a while, please do not cancel it!"
database=$(sed -n "s/^\s*db_name\s*=\s*\([^ ]*\)\s*$/\1/p" /etc/rhn/rhn.conf)
spacewalk-sql --select-mode - <<<"REINDEX DATABASE \"${database}\";"
{{ end }}

{{ if .RunSchemaUpdate }}
echo "Schema update..."
/usr/sbin/spacewalk-startup-helper check-database
{{ end }}

{{ if .RunDistroMigration }}
echo "Updating auto-installable distributions..."
spacewalk-sql --select-mode - <<EOT
SELECT MIN(CONCAT(org_id, '-', label)) AS target, base_path INTO TEMP TABLE dist_map FROM rhnKickstartableTree GROUP BY base_path;
UPDATE rhnKickstartableTree SET base_path = CONCAT('/srv/www/distributions/', target)
    from dist_map WHERE dist_map.base_path = rhnKickstartableTree.base_path;
DROP TABLE dist_map;
EOT
{{ end }}

echo "Schedule a system list update task..."
spacewalk-sql --select-mode - <<EOT
insert into rhnTaskQueue (id, org_id, task_name, task_data)
SELECT nextval('rhn_task_queue_id_seq'), 1, 'update_system_overview', s.id
from rhnserver s
where not exists (select 1 from rhntaskorun r join rhntaskotemplate t on r.template_id = t.id
join rhntaskobunch b on t.bunch_id = b.id where b.name='update-system-overview-bunch' limit 1);
EOT


echo "Stopping Postgresql..."
su -s /bin/bash - postgres -c "/usr/share/postgresql/postgresql-script stop"
echo "DONE"
`

// FinalizePostgresTemplateData represents information used to create PostgreSQL migration script.
type FinalizePostgresTemplateData struct {
	RunAutotune        bool
	RunReindex         bool
	RunSchemaUpdate    bool
	RunDistroMigration bool
	Kubernetes         bool
}

// Render will create script for finalizing PostgreSQL upgrade.
func (data FinalizePostgresTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("script").Parse(postgresFinalizeScriptTemplate))
	return t.Execute(wr, data)
}
070701000000AE000081B4000000000000000000000001662A7528000008CD000000000000000000000000000000000000004900000000uyuni-tools/mgradm/shared/templates/pgsqlVersionUpgradeScriptTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const postgreSQLVersionUpgradeScriptTemplate = `#!/bin/bash
set -e
echo "PostgreSQL version upgrade"

OLD_VERSION={{ .OldVersion }}
NEW_VERSION={{ .NewVersion }}
FAST_UPGRADE=--link

echo "Testing presence of postgresql$NEW_VERSION..."
test -d /usr/lib/postgresql$NEW_VERSION/bin
echo "Testing presence of postgresql$OLD_VERSION..."
test -d /usr/lib/postgresql$OLD_VERSION/bin

echo "Create a backup at /var/lib/pgsql/data-pg$OLD_VERSION..."
mv /var/lib/pgsql/data /var/lib/pgsql/data-pg$OLD_VERSION
echo "Create new database directory..."
mkdir -p /var/lib/pgsql/data
chown -R postgres:postgres /var/lib/pgsql
echo "Enforce key permission"
chown -R postgres:postgres /etc/pki/tls/private/pg-spacewalk.key
chown -R postgres:postgres /etc/pki/tls/certs/spacewalk.crt

echo "Initialize new postgresql $NEW_VERSION database..."
. /etc/sysconfig/postgresql 2>/dev/null # Load locale for SUSE
PGHOME=$(getent passwd postgres | cut -d ":" -f6)
#. $PGHOME/.i18n 2>/dev/null # Load locale for Enterprise Linux
if [ -z $POSTGRES_LANG ]; then
    POSTGRES_LANG="en_US.UTF-8"
    [ ! -z $LC_CTYPE ] && POSTGRES_LANG=$LC_CTYPE
fi

echo "Running initdb using postgres user"
echo "Any suggested command from the console should be run using postgres user"
su -s /bin/bash - postgres -c "initdb -D /var/lib/pgsql/data --locale=$POSTGRES_LANG"
echo "Successfully initialized new postgresql $NEW_VERSION database."
su -s /bin/bash - postgres -c "pg_upgrade --old-bindir=/usr/lib/postgresql$OLD_VERSION/bin --new-bindir=/usr/lib/postgresql$NEW_VERSION/bin --old-datadir=/var/lib/pgsql/data-pg$OLD_VERSION --new-datadir=/var/lib/pgsql/data $FAST_UPGRADE"

echo "DONE"`

// PostgreSQLVersionUpgradeTemplateData represents information used to create PostgreSQL migration script.
type PostgreSQLVersionUpgradeTemplateData struct {
	OldVersion string
	NewVersion string
	Kubernetes bool
}

// Render will create PostgreSQL migration script.
func (data PostgreSQLVersionUpgradeTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("script").Parse(postgreSQLVersionUpgradeScriptTemplate))
	return t.Execute(wr, data)
}
070701000000AF000081B4000000000000000000000001662A7528000004AA000000000000000000000000000000000000004100000000uyuni-tools/mgradm/shared/templates/postUpgradeScriptTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const postUpgradeScriptTemplate = `#!/bin/bash
{{ if .CobblerHost }}
sed 's/cobbler\.host.*/cobbler\.host = {{ .CobblerHost }}/' -i /etc/rhn/rhn.conf;
grep uyuni_authentication_endpoint /etc/cobbler/settings.yaml
if [ $? -eq 1 ]; then
	echo 'uyuni_authentication_endpoint: "http://localhost"' >> /etc/cobbler/settings.yaml
else
	sed 's/uyuni_authentication_endpoint.*/uyuni_authentication_endpoint: http:\/\/localhost/' -i /etc/cobbler/settings.yaml;
fi
{{ end }}

grep pam_auth_service /etc/rhn/rhn.conf
if [ $? -eq 1 ]; then
	echo 'pam_auth_service = susemanager' >> /etc/rhn/rhn.conf
else
	sed 's/pam_auth_service.*/pam_auth_service = susemanager/' -i /etc/rhn/rhn.conf;
fi
`

// PostUpgradeTemplateData represents information used to create post upgrade.
type PostUpgradeTemplateData struct {
	CobblerHost string
}

// Render will create script for finalizing PostgreSQL upgrade.
func (data PostUpgradeTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("script").Parse(postUpgradeScriptTemplate))
	return t.Execute(wr, data)
}
070701000000B0000081B4000000000000000000000001662A752800000845000000000000000000000000000000000000003700000000uyuni-tools/mgradm/shared/templates/serviceTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

const serviceTemplate = `# uyuni-server.service, generated by mgradm
# Use an uyuni-server.service.d/local.conf file to override

[Unit]
Description=Uyuni server image container service
Wants=network.target
After=network-online.target
RequiresMountsFor=%t/containers

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=TZ={{ .Timezone }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-server.pid %t/%n.ctr-id
ExecStartPre=/usr/bin/podman rm --ignore --force -t 10 {{ .NamePrefix }}-server
ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-server.pid \
	--cidfile=%t/%n.ctr-id \
	--cgroups=no-conmon \
	--shm-size=0 \
	--shm-size-systemd=0 \
	--sdnotify=conmon \
	-d \
	--name {{ .NamePrefix }}-server \
	--hostname {{ .NamePrefix }}-server.mgr.internal \
	{{ .Args }} \
	{{- range .Ports }}
	-p {{ .Exposed }}:{{ .Port }}{{if .Protocol}}/{{ .Protocol }}{{end}} \
	{{- end }}
	{{- range .Volumes }}
	-v {{ .Name }}:{{ .MountPath }} \
	{{- end }}
	-e TZ=${TZ} \
	--network {{ .Network }} \
	${UYUNI_IMAGE}
ExecStop=/usr/bin/podman exec \
    uyuni-server \
    /bin/bash -c 'spacewalk-service stop && systemctl stop postgresql'
ExecStop=/usr/bin/podman stop \
	--ignore -t 10 \
	--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
	-f \
	--ignore -t 10 \
	--cidfile=%t/%n.ctr-id

PIDFile=%t/uyuni-server.pid
TimeoutStopSec=180
TimeoutStartSec=900
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// PodmanServiceTemplateData POD information to create systemd file.
type PodmanServiceTemplateData struct {
	Volumes    []types.VolumeMount
	NamePrefix string
	Args       string
	Ports      []types.PortMap
	Timezone   string
	Image      string
	Network    string
}

// Render will create the systemd configuration file.
func (data PodmanServiceTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(serviceTemplate))
	return t.Execute(wr, data)
}
070701000000B1000081B4000000000000000000000001662A75280000034F000000000000000000000000000000000000003100000000uyuni-tools/mgradm/shared/templates/tlsSecret.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

// Deploy self-signed issuer or CA Certificate and key.
const tlsSecretTemplate = `apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
  name: {{ .Name }}
  namespace: {{ .Namespace }}
data:
  ca.crt: {{ .RootCa }}
  tls.crt: {{ .Certificate }}
  tls.key: {{ .Key }}
`

// TlsSecretTemplateData contains information to create secret configuration file.
type TlsSecretTemplateData struct {
	Name        string
	Namespace   string
	RootCa      string
	Certificate string
	Key         string
}

// Render creates secret configuration file.
func (data TlsSecretTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("secret").Parse(tlsSecretTemplate))
	return t.Execute(wr, data)
}
070701000000B2000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002000000000uyuni-tools/mgradm/shared/utils070701000000B3000081B4000000000000000000000001662A7528000015C2000000000000000000000000000000000000002D00000000uyuni-tools/mgradm/shared/utils/cmd_utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"
	"path"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

var defaultImage = path.Join(utils.DefaultNamespace, "server")

// HelmFrags stores Uyuni and Cert Manager Helm information.
type HelmFlags struct {
	Uyuni       types.ChartFlags
	CertManager types.ChartFlags
}

// SslCertFlags can store SSL Certs information.
type SslCertFlags struct {
	Cnames   []string `mapstructure:"cname"`
	Country  string
	State    string
	City     string
	Org      string
	OU       string
	Password string
	Email    string
	Ca       ssl.CaChain
	Server   ssl.SslPair
}

// UseExisting return true if existing SSL Cert can be used.
func (f *SslCertFlags) UseExisting() bool {
	return f.Server.Cert != "" && f.Server.Key != "" && f.Ca.Root != ""
}

// Checks that all the required flags are passed if using 3rd party certificates.
func (f *SslCertFlags) CheckParameters() {
	if !f.UseExisting() && (f.Server.Cert != "" || f.Server.Key != "" || f.Ca.Root != "") {
		log.Fatal().Msg(L("Server certificate, key and root CA need to be all provided"))
	}
}

// AddHelmInstallFlag add Helm install flags to a command.
func AddHelmInstallFlag(cmd *cobra.Command) {
	defaultChart := fmt.Sprintf("oci://%s/server-helm", utils.DefaultNamespace)

	cmd.Flags().String("helm-uyuni-namespace", "default", L("Kubernetes namespace where to install uyuni"))
	cmd.Flags().String("helm-uyuni-chart", defaultChart, L("URL to the uyuni helm chart"))
	cmd.Flags().String("helm-uyuni-version", "", L("Version of the uyuni helm chart"))
	cmd.Flags().String("helm-uyuni-values", "", L("Path to a values YAML file to use for Uyuni helm install"))
	cmd.Flags().String("helm-certmanager-namespace", "cert-manager", L("Kubernetes namespace where to install cert-manager"))
	cmd.Flags().String("helm-certmanager-chart", "", L("URL to the cert-manager helm chart. To be used for offline installations"))
	cmd.Flags().String("helm-certmanager-version", "", L("Version of the cert-manager helm chart"))
	cmd.Flags().String("helm-certmanager-values", "", L("Path to a values YAML file to use for cert-manager helm install"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "helm", Title: L("Helm Chart Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-uyuni-namespace", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-uyuni-chart", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-uyuni-version", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-uyuni-values", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-certmanager-namespace", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-certmanager-chart", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-certmanager-version", "helm")
	_ = utils.AddFlagToHelpGroupID(cmd, "helm-certmanager-values", "helm")
}

// AddContainerImageFlags add container image flags to command.
func AddContainerImageFlags(cmd *cobra.Command, container string) {
	cmd.Flags().String(container+"-image", "",
		fmt.Sprintf(L("Image for %s container, overrides the namespace if set"), container))
	cmd.Flags().String(container+"-tag", "",
		fmt.Sprintf(L("Tag for %s container, overrides the global value if set"), container))
}

// AddImageFlag add Image flags to a command.
func AddImageFlag(cmd *cobra.Command) {
	cmd.Flags().String("image", defaultImage, L("Image"))
	cmd.Flags().String("tag", utils.DefaultTag, L("Tag Image"))

	utils.AddPullPolicyFlag(cmd)

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "image", Title: L("Image Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "image", "image")
	_ = utils.AddFlagToHelpGroupID(cmd, "tag", "image")
	_ = utils.AddFlagToHelpGroupID(cmd, "pullPolicy", "image")
}

// AddImageUpgradeFlag add Image flags to an upgrade command, where pullPolicy default is always.
func AddImageUpgradeFlag(cmd *cobra.Command) {
	cmd.Flags().String("image", defaultImage, L("Image"))
	cmd.Flags().String("tag", utils.DefaultTag, L("Tag Image"))
	cmd.Flags().String("pullPolicy", "Always",
		L("set whether to pull the images or not during upgrade. The value can be one of 'Never', 'IfNotPresent' or 'Always'"))
}

// AddImagePTFFlag add Image flags to an support ptf command, where pullPolicy default is always.
func AddImagePTFlag(cmd *cobra.Command) {
	cmd.Flags().String("image", "", L("Image"))
	cmd.Flags().String("tag", utils.DefaultTag, L("Tag Image"))
	cmd.Flags().String("pullPolicy", "Always",
		L("set whether to pull the images or not during upgrade. The value can be one of 'Never', 'IfNotPresent' or 'Always'"))
}

// AddMigrationImageFlag add Migration Image flags to a command.
func AddMigrationImageFlag(cmd *cobra.Command) {
	cmd.Flags().String("migration-image", "", L("Migration image"))
	cmd.Flags().String("migration-tag", utils.DefaultTag, L("Migration image tag"))
	cmd.Flags().String("migration-pullPolicy", "IfNotPresent",
		L("set whether to pull the migration images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "migration-image", Title: L("Migration Image Flags")})
	_ = utils.AddFlagToHelpGroupID(cmd, "migration-image", "migration-image")
	_ = utils.AddFlagToHelpGroupID(cmd, "migration-tag", "migration-image")
	_ = utils.AddFlagToHelpGroupID(cmd, "migration-pullPolicy", "migration-image")
}
070701000000B4000081B4000000000000000000000001662A752800002532000000000000000000000000000000000000002800000000uyuni-tools/mgradm/shared/utils/exec.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/viper"
	"github.com/uyuni-project/uyuni-tools/mgradm/shared/templates"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// ExecCommand execute commands passed as argument in the current system.
func ExecCommand(logLevel zerolog.Level, cnx *shared.Connection, args ...string) error {
	podName, err := cnx.GetPodName()
	if err != nil {
		return fmt.Errorf(L("exec command failed: %s"), err)
	}

	commandArgs := []string{"exec", podName}

	command, err := cnx.GetCommand()
	if err != nil {
		log.Fatal().Err(err)
	}

	if command == "kubectl" {
		commandArgs = append(commandArgs, "-c", "uyuni", "--")
	}

	commandArgs = append(commandArgs, "sh", "-c", strings.Join(args, " "))

	runCmd := exec.Command(command, commandArgs...)
	logger := utils.OutputLogWriter{Logger: log.Logger, LogLevel: logLevel}
	runCmd.Stdout = logger
	runCmd.Stderr = logger
	return runCmd.Run()
}

// GeneratePgsqlVersionUpgradeScript generates the PostgreSQL version upgrade script.
func GeneratePgsqlVersionUpgradeScript(scriptDir string, oldPgVersion string, newPgVersion string, kubernetes bool) (string, error) {
	data := templates.PostgreSQLVersionUpgradeTemplateData{
		OldVersion: oldPgVersion,
		NewVersion: newPgVersion,
		Kubernetes: kubernetes,
	}

	scriptName := "pgsqlVersionUpgrade.sh"
	scriptPath := filepath.Join(scriptDir, scriptName)
	if err := utils.WriteTemplateToFile(data, scriptPath, 0555, true); err != nil {
		return "", fmt.Errorf(L("failed to generate %s"), scriptName)
	}
	return scriptName, nil
}

// GenerateFinalizePostgresScript generates the script to finalize PostgreSQL upgrade.
func GenerateFinalizePostgresScript(scriptDir string, RunAutotune bool, RunReindex bool, RunSchemaUpdate bool, RunDistroMigration bool, kubernetes bool) (string, error) {
	data := templates.FinalizePostgresTemplateData{
		RunAutotune:        RunAutotune,
		RunReindex:         RunReindex,
		RunSchemaUpdate:    RunSchemaUpdate,
		RunDistroMigration: RunDistroMigration,
		Kubernetes:         kubernetes,
	}

	scriptName := "pgsqlFinalize.sh"
	scriptPath := filepath.Join(scriptDir, scriptName)
	if err := utils.WriteTemplateToFile(data, scriptPath, 0555, true); err != nil {
		return "", fmt.Errorf(L("failed to generate %s"), scriptName)
	}
	return scriptName, nil
}

// GeneratePostUpgradeScript generates the script to be run after upgrade.
func GeneratePostUpgradeScript(scriptDir string, cobblerHost string) (string, error) {
	data := templates.PostUpgradeTemplateData{
		CobblerHost: cobblerHost,
	}

	scriptName := "postUpgrade.sh"
	scriptPath := filepath.Join(scriptDir, scriptName)
	if err := utils.WriteTemplateToFile(data, scriptPath, 0555, true); err != nil {
		return "", fmt.Errorf(L("failed to generate %s"), scriptName)
	}
	return scriptName, nil
}

// ReadContainerData returns values used to perform migration.
func ReadContainerData(scriptDir string) (string, string, string, error) {
	data, err := os.ReadFile(filepath.Join(scriptDir, "data"))
	if err != nil {
		return "", "", "", errors.New(L("failed to read data extracted from source host"))
	}
	viper.SetConfigType("env")
	if err := viper.ReadConfig(bytes.NewBuffer(data)); err != nil {
		return "", "", "", fmt.Errorf(L("cannot read config: %s"), err)
	}
	if len(viper.GetString("Timezone")) <= 0 {
		return "", "", "", errors.New(L("cannot retrieve timezone"))
	}
	if len(viper.GetString("old_pg_version")) <= 0 {
		return "", "", "", errors.New(L("cannot retrieve source PostgreSQL version"))
	}
	if len(viper.GetString("new_pg_version")) <= 0 {
		return "", "", "", errors.New(L("cannot retrieve image PostgreSQL version"))
	}
	return viper.GetString("Timezone"), viper.GetString("old_pg_version"), viper.GetString("new_pg_version"), nil
}

// RunMigration execute the migration script.
func RunMigration(cnx *shared.Connection, tmpPath string, scriptName string) error {
	log.Info().Msg(L("Migrating server"))
	err := ExecCommand(zerolog.InfoLevel, cnx, "/var/lib/uyuni-tools/"+scriptName)
	if err != nil {
		return fmt.Errorf(L("error running the migration script: %s"), err)
	}
	return nil
}

// GenerateMigrationScript generates the script that perform migration.
func GenerateMigrationScript(sourceFqdn string, user string, kubernetes bool) (string, error) {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	if err != nil {
		return "", fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}

	data := templates.MigrateScriptTemplateData{
		Volumes:    utils.ServerVolumeMounts,
		SourceFqdn: sourceFqdn,
		User:       user,
		Kubernetes: kubernetes,
	}

	scriptPath := filepath.Join(scriptDir, "migrate.sh")
	if err = utils.WriteTemplateToFile(data, scriptPath, 0555, true); err != nil {
		return "", fmt.Errorf(L("failed to generate migration script: %s"), err)
	}

	return scriptDir, nil
}

// RunningImage returns the image running in the current system.
func RunningImage(cnx *shared.Connection, containerName string) (string, error) {
	command, err := cnx.GetCommand()

	switch command {
	case "podman":
		args := []string{"ps", "--format", "{{.Image}}", "--noheading"}
		image, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", args...)
		if err != nil {
			return "", err
		}
		return strings.Trim(string(image), "\n"), nil

	case "kubectl":

		//FIXME this will work until containers 0 is uyuni. Then jsonpath should be something like
		// {.items[0].spec.containers[?(@.name=="` + containerName + `")].image but there are problems
		// using RunCmdOutput with an arguments with round brackets
		args := []string{"get", "pods", kubernetes.ServerFilter, "-o", "jsonpath={.items[0].spec.containers[0].image}"}
		image, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", args...)

		log.Info().Msgf(L("Image is: %s"), image)
		if err != nil {
			return "", err
		}
		return strings.Trim(string(image), "\n"), nil
	}

	return command, err
}

// SanityCheck verifies if an upgrade can be run.
func SanityCheck(cnx *shared.Connection, inspectedValues map[string]string, serverImage string) error {
	isUyuni, err := isUyuni(cnx)
	if err != nil {
		return fmt.Errorf(L("cannot check server release: %s"), err)
	}
	_, isCurrentUyuni := inspectedValues["uyuni_release"]
	_, isCurrentSuma := inspectedValues["suse_manager_release"]

	if isUyuni && isCurrentSuma {
		return fmt.Errorf(L("currently SUSE Manager %s is installed, instead the image is Uyuni. Upgrade is not supported"), inspectedValues["suse_manager_release"])
	}

	if !isUyuni && isCurrentUyuni {
		return fmt.Errorf(L("currently Uyuni %s is installed, instead the image is SUSE Manager. Upgrade is not supported"), inspectedValues["uyuni_release"])
	}

	if isUyuni {
		cnx_args := []string{"s/Uyuni release //g", "/etc/uyuni-release"}
		current_uyuni_release, err := cnx.Exec("sed", cnx_args...)
		if err != nil {
			return fmt.Errorf(L("failed to read current uyuni release: %s"), err)
		}
		log.Debug().Msgf("Current release is %s", string(current_uyuni_release))
		if (len(inspectedValues["uyuni_release"])) <= 0 {
			return fmt.Errorf(L("cannot fetch release from image %s"), serverImage)
		}
		log.Debug().Msgf("Image %s is %s", serverImage, inspectedValues["uyuni_release"])
		if utils.CompareVersion(inspectedValues["uyuni_release"], string(current_uyuni_release)) < 0 {
			return fmt.Errorf(L("cannot downgrade from version %s to %s"), string(current_uyuni_release), inspectedValues["uyuni_release"])
		}
	} else {
		cnx_args := []string{"s/SUSE Manager release //g", "/etc/susemanager-release"}
		current_suse_manager_release, err := cnx.Exec("sed", cnx_args...)
		if err != nil {
			return fmt.Errorf(L("failed to read current susemanager release: %s"), err)
		}
		log.Debug().Msgf("Current release is %s", string(current_suse_manager_release))
		if (len(inspectedValues["suse_manager_release"])) <= 0 {
			return fmt.Errorf(L("cannot fetch release from image %s"), serverImage)
		}
		log.Debug().Msgf("Image %s is %s", serverImage, inspectedValues["suse_manager_release"])
		if utils.CompareVersion(inspectedValues["suse_manager_release"], string(current_suse_manager_release)) < 0 {
			return fmt.Errorf(L("cannot downgrade from version %s to %s"), string(current_suse_manager_release), inspectedValues["suse_manager_release"])
		}
	}

	if (len(inspectedValues["image_pg_version"])) <= 0 {
		return fmt.Errorf(L("cannot fetch postgresql version from %s"), serverImage)
	}
	log.Debug().Msgf("Image %s has PostgreSQL %s", serverImage, inspectedValues["image_pg_version"])
	if (len(inspectedValues["current_pg_version"])) <= 0 {
		return fmt.Errorf(L("posgresql is not installed in the current deployment"))
	}
	log.Debug().Msgf("Current deployment has PostgreSQL %s", inspectedValues["current_pg_version"])

	return nil
}

func isUyuni(cnx *shared.Connection) (bool, error) {
	cnx_args := []string{"/etc/uyuni-release"}
	_, err := cnx.Exec("cat", cnx_args...)
	if err != nil {
		cnx_args := []string{"/etc/susemanager-release"}
		_, err := cnx.Exec("cat", cnx_args...)
		if err != nil {
			return false, errors.New(L("cannot find neither /etc/uyuni-release nor /etc/susemanagere-release"))
		}
		return false, nil
	}
	return true, nil
}
070701000000B5000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001300000000uyuni-tools/mgrctl070701000000B6000041FD000000000000000000000007662A752800000000000000000000000000000000000000000000001700000000uyuni-tools/mgrctl/cmd070701000000B7000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001B00000000uyuni-tools/mgrctl/cmd/api070701000000B8000081B4000000000000000000000001662A752800000679000000000000000000000000000000000000002200000000uyuni-tools/mgrctl/cmd/api/api.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type apiFlags struct {
	api.ConnectionDetails `mapstructure:"api"`
}

// NewCommand generates a JSON over HTTP API helper tool command.
func NewCommand(globalFlags *types.GlobalFlags) (*cobra.Command, error) {
	var flags apiFlags

	apiCmd := &cobra.Command{
		Use:   "api",
		Short: L("JSON over HTTP API helper tool"),
	}

	apiGet := &cobra.Command{
		Use:   "get path [parameters]...",
		Short: L("Call API GET request"),
		Long:  L("Takes an API path and optional parameters and then issues GET request with them. If user and password are provided, calls login before API call"),
		RunE: func(cmd *cobra.Command, args []string) error {
			return utils.CommandHelper(globalFlags, cmd, args, &flags, runGet)
		},
	}

	apiPost := &cobra.Command{
		Use:   "post path parameters...",
		Short: L("Call API POST request"),
		Long:  L("Takes an API path and parameters and then issues POST request with them. User and password are mandatory. Parameters can be either JSON encoded string or one or more key=value pairs."),
		RunE: func(cmd *cobra.Command, args []string) error {
			return utils.CommandHelper(globalFlags, cmd, args, &flags, runPost)
		},
	}

	apiCmd.AddCommand(apiGet)
	apiCmd.AddCommand(apiPost)

	if err := api.AddAPIFlags(apiCmd, false); err != nil {
		return apiCmd, err
	}
	return apiCmd, nil
}
070701000000B9000081B4000000000000000000000001662A7528000001A2000000000000000000000000000000000000002700000000uyuni-tools/mgrctl/cmd/api/api_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"testing"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func TestNewCommand(t *testing.T) {
	var globalflags types.GlobalFlags
	cmd, err := NewCommand(&globalflags)
	if err != nil {
		t.Errorf("Unexpected error creating command: %s", err)
	}
	if cmd == nil {
		t.Error("Unexpected nil command")
	}
}
070701000000BA000081B4000000000000000000000001662A752800000462000000000000000000000000000000000000002200000000uyuni-tools/mgrctl/cmd/api/get.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"

	"github.com/uyuni-project/uyuni-tools/shared/api"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func runGet(globalFlags *types.GlobalFlags, flags *apiFlags, cmd *cobra.Command, args []string) error {
	log.Debug().Msgf("Running GET command %s", args[0])
	client, err := api.Init(&flags.ConnectionDetails)

	if err != nil {
		return fmt.Errorf(L("unable to login to the server: %s"), err)
	}
	path := args[0]
	options := args[1:]

	res, err := api.Get[interface{}](client, fmt.Sprintf("%s?%s", path, strings.Join(options, "&")))
	if err != nil {
		return fmt.Errorf(L("error in query %s: %s"), path, err)
	}

	// TODO do this only when result is JSON or TEXT. Watchout for binary data
	// Decode JSON to the string and pretty print it
	out, err := json.MarshalIndent(res.Result, "", "  ")
	if err != nil {
		return err
	}
	fmt.Print(string(out))

	return nil
}
070701000000BB000081B4000000000000000000000001662A7528000005B1000000000000000000000000000000000000002300000000uyuni-tools/mgrctl/cmd/api/post.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"

	"github.com/uyuni-project/uyuni-tools/shared/api"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func runPost(globalFlags *types.GlobalFlags, flags *apiFlags, cmd *cobra.Command, args []string) error {
	log.Debug().Msgf("Running POST command %s", args[0])
	client, err := api.Init(&flags.ConnectionDetails)

	if err != nil {
		return fmt.Errorf(L("unable to login to the server: %s"), err)
	}

	path := args[0]
	options := args[1:]

	var data map[string]interface{}

	if len(options) > 1 {
		log.Debug().Msg("Multiple options specified, assuming non JSON data")
		data = map[string]interface{}{}
		for _, o := range options {
			s := strings.SplitN(o, "=", 2)
			data[s[0]] = s[1]
		}
	} else {
		if err := json.NewDecoder(strings.NewReader(args[1])).Decode(&data); err != nil {
			log.Debug().Msg("Failed to decode parameters as JSON, assuming key=value pairs")
		}
	}

	res, err := api.Post[interface{}](client, path, data)
	if err != nil {
		return fmt.Errorf(L("error in query %s: %s"), path, err)
	}

	if !res.Success {
		log.Error().Msg(res.Message)
	}
	out, err := json.MarshalIndent(res.Result, "", "  ")
	if err != nil {
		log.Fatal().Err(err)
	}
	fmt.Print(string(out))

	return nil
}
070701000000BC000081B4000000000000000000000001662A752800000999000000000000000000000000000000000000001E00000000uyuni-tools/mgrctl/cmd/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
	"os"
	"path"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/api"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/cp"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/exec"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/org"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/term"
	"github.com/uyuni-project/uyuni-tools/shared/completion"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// NewCommand returns a new cobra.Command implementing the root command for kinder.
func NewUyunictlCommand() (*cobra.Command, error) {
	globalFlags := &types.GlobalFlags{}
	name := path.Base(os.Args[0])
	rootCmd := &cobra.Command{
		Use:          name,
		Short:        L("Uyuni control tool"),
		Long:         L("Tool to help managing Uyuni servers mainly through their API"),
		Version:      utils.Version,
		SilenceUsage: true, // Don't show usage help on errors
	}

	rootCmd.SetUsageTemplate(utils.GetLocalizedUsageTemplate())

	rootCmd.PersistentFlags().StringVarP(&globalFlags.ConfigPath, "config", "c", "", L("configuration file path"))
	rootCmd.PersistentFlags().StringVar(&globalFlags.LogLevel, "logLevel", "", L("application log level")+"(trace|debug|info|warn|error|fatal|panic)")

	rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
		utils.LogInit(cmd.Name() != "exec" && cmd.Name() != "term")
		utils.SetLogLevel(globalFlags.LogLevel)

		// do not log if running the completion cmd as the output is redirect to create a file to source
		if cmd.Name() != "completion" {
			log.Info().Msgf(L("Welcome to %s"), name)
			log.Info().Msgf(L("Executing command: %s"), cmd.Name())
		}
	}

	apiCmd, err := api.NewCommand(globalFlags)
	if err != nil {
		log.Err(err).Msg(L("Failed to create api command"))
	}
	rootCmd.AddCommand(apiCmd)
	rootCmd.AddCommand(exec.NewCommand(globalFlags))
	rootCmd.AddCommand(term.NewCommand(globalFlags))
	rootCmd.AddCommand(cp.NewCommand(globalFlags))
	rootCmd.AddCommand(completion.NewCommand(globalFlags))
	orgCmd, err := org.NewCommand(globalFlags)
	if err != nil {
		log.Err(err).Msg(L("Failed to create org command"))
	}
	rootCmd.AddCommand(orgCmd)

	rootCmd.AddCommand(utils.GetConfigHelpCommand())

	return rootCmd, nil
}
070701000000BD000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/mgrctl/cmd/cp070701000000BE000081B4000000000000000000000001662A7528000006F2000000000000000000000000000000000000002000000000uyuni-tools/mgrctl/cmd/cp/cp.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package cp

import (
	"fmt"

	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type flagpole struct {
	User    string
	Group   string
	Backend string
}

// NewCommand copy file to and from the containers.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	flags := &flagpole{}

	cpCmd := &cobra.Command{
		Use:   "cp [path/to/source.file] [path/to/destination.file]",
		Short: L("Copy files to and from the containers"),
		Long: L(`Takes a source and destination parameters.
	One of them can be prefixed with 'server:' to indicate the path is within the server pod.`),
		Args: cobra.ExactArgs(2),
		RunE: func(cmd *cobra.Command, args []string) error {
			viper, err := utils.ReadConfig(globalFlags.ConfigPath, cmd)
			if err != nil {
				return err
			}
			if err := viper.Unmarshal(&flags); err != nil {
				return fmt.Errorf(L("failed to unmarshall configuration")+": %s", err)
			}
			return run(flags, cmd, args)
		},
	}

	cpCmd.Flags().String("user", "", L("User or UID to set on the destination file"))
	cpCmd.Flags().String("group", "susemanager", L("Group or GID to set on the destination file"))

	utils.AddBackendFlag(cpCmd)
	return cpCmd
}

func run(flags *flagpole, cmd *cobra.Command, args []string) error {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)
	return cnx.Copy(args[0], args[1], flags.User, flags.Group)
}
070701000000BF000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001C00000000uyuni-tools/mgrctl/cmd/exec070701000000C0000081B4000000000000000000000001662A752800000F66000000000000000000000000000000000000002400000000uyuni-tools/mgrctl/cmd/exec/exec.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package exec

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type flagpole struct {
	Envs        []string `mapstructure:"env"`
	Interactive bool
	Tty         bool
	Backend     string
}

// NewCommand returns a new cobra.Command for exec.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	var flags flagpole

	execCmd := &cobra.Command{
		Use:   "exec '[command-to-run --with-args]'",
		Short: L("Execute commands inside the uyuni containers using 'sh -c'"),
		RunE: func(cmd *cobra.Command, args []string) error {
			return utils.CommandHelper(globalFlags, cmd, args, &flags, run)
		},
	}

	execCmd.Flags().StringSliceP("env", "e", []string{}, L("environment variables to pass to the command, separated by commas"))
	execCmd.Flags().BoolP("interactive", "i", false, L("Pass stdin to the container"))
	execCmd.Flags().BoolP("tty", "t", false, L("Stdin is a TTY"))

	utils.AddBackendFlag(execCmd)
	return execCmd
}

func run(globalFlags *types.GlobalFlags, flags *flagpole, cmd *cobra.Command, args []string) error {
	cnx := shared.NewConnection(flags.Backend, podman.ServerContainerName, kubernetes.ServerFilter)
	podName, err := cnx.GetPodName()
	if err != nil {
		log.Fatal().Err(err)
	}

	command, err := cnx.GetCommand()
	if err != nil {
		log.Fatal().Err(err)
	}

	commandArgs := []string{"exec"}
	envs := []string{}
	envs = append(envs, flags.Envs...)
	if flags.Interactive {
		commandArgs = append(commandArgs, "-i")
		envs = append(envs, "ENV=/etc/sh.shrc.local")
	}
	if flags.Tty {
		commandArgs = append(commandArgs, "-t")
		envs = append(envs, "TERM")
	}
	commandArgs = append(commandArgs, podName)

	if command == "kubectl" {
		commandArgs = append(commandArgs, "-c", "uyuni", "--")
	}

	newEnv := []string{}
	for _, envValue := range envs {
		if !strings.Contains(envValue, "=") {
			if value, set := os.LookupEnv(envValue); set {
				newEnv = append(newEnv, fmt.Sprintf("%s=%s", envValue, value))
			}
		} else {
			newEnv = append(newEnv, envValue)
		}
	}
	if len(newEnv) > 0 {
		commandArgs = append(commandArgs, "env")
		commandArgs = append(commandArgs, newEnv...)
	}
	commandArgs = append(commandArgs, "sh", "-c", strings.Join(args, " "))
	err = RunRawCmd(command, commandArgs)
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			log.Info().Err(err).Msg(L("Command failed"))
			os.Exit(exitErr.ExitCode())
		}
	}
	log.Info().Msg(L("Command returned with exit code 0"))

	return nil
}

type copyWriter struct {
	Stream io.Writer
}

// Write writes an array of buffer in a stream.
func (l copyWriter) Write(p []byte) (n int, err error) {
	// Filter out kubectl line about terminated exit code
	if !strings.HasPrefix(string(p), "command terminated with exit code") {
		if _, err := l.Stream.Write(p); err != nil {
			return 0, fmt.Errorf(L("cannot write: %s"), err)
		}

		n = len(p)
		if n > 0 && p[n-1] == '\n' {
			// Trim CR added by stdlog.
			p = p[0 : n-1]
		}
		log.Debug().Msg(string(p))
	}
	return
}

// RunRawCmd runs a command, mapping stdout and start error, waiting and checking return code.
func RunRawCmd(command string, args []string) error {
	log.Info().Msgf(L("Running: %s %s"), command, strings.Join(args, " "))

	runCmd := exec.Command(command, args...)
	runCmd.Stdin = os.Stdin

	runCmd.Stdout = copyWriter{Stream: os.Stdout}
	runCmd.Stderr = copyWriter{Stream: os.Stderr}

	if err := runCmd.Start(); err != nil {
		log.Debug().Err(err).Msg("error starting command")
		return err
	}

	return runCmd.Wait()
}
070701000000C1000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001B00000000uyuni-tools/mgrctl/cmd/org070701000000C2000081B4000000000000000000000001662A752800000713000000000000000000000000000000000000002A00000000uyuni-tools/mgrctl/cmd/org/createFirst.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package org

import (
	"fmt"

	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	"github.com/uyuni-project/uyuni-tools/shared/api/org"
	apiTypes "github.com/uyuni-project/uyuni-tools/shared/api/types"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type createFirstFlags struct {
	api.ConnectionDetails `mapstructure:"api"`
	Organization          string
	Admin                 apiTypes.User
}

func createFirstCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "createFirst",
		Short: L("Create the first user and organization"),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags createFirstFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, createFirst)
		},
	}

	cmd.Flags().String("admin-login", "admin", L("Administrator user name"))
	cmd.Flags().String("admin-password", "", L("Administrator password"))
	cmd.Flags().String("admin-firstName", "Administrator", L("The first name of the administrator"))
	cmd.Flags().String("admin-lastName", "McAdmin", L("The last name of the administrator"))
	cmd.Flags().String("admin-email", "root@localhost", L("The administrator's email"))
	cmd.Flags().String("organization", "Organization", L("The first organization name"))

	return cmd
}

func createFirst(globalFlags *types.GlobalFlags, flags *createFirstFlags, cmd *cobra.Command, args []string) error {
	org, err := org.CreateFirst(&flags.ConnectionDetails, flags.Organization, &flags.Admin)
	if err != nil {
		return err
	}

	fmt.Printf(L("Organization %s created with id %d"), org.Name, org.Id)

	return nil
}
070701000000C3000081B4000000000000000000000001662A752800000288000000000000000000000000000000000000002200000000uyuni-tools/mgrctl/cmd/org/org.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package org

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/api"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand  command for APIs.
func NewCommand(globalFlags *types.GlobalFlags) (*cobra.Command, error) {
	orgCmd := &cobra.Command{
		Use:   "org",
		Short: L("Organization-related commands"),
	}

	if err := api.AddAPIFlags(orgCmd, false); err != nil {
		return orgCmd, err
	}

	orgCmd.AddCommand(createFirstCommand(globalFlags))

	return orgCmd, nil
}
070701000000C4000081B4000000000000000000000001662A7528000001A2000000000000000000000000000000000000002700000000uyuni-tools/mgrctl/cmd/org/org_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package org

import (
	"testing"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func TestNewCommand(t *testing.T) {
	var globalflags types.GlobalFlags
	cmd, err := NewCommand(&globalflags)
	if err != nil {
		t.Errorf("Unexpected error creating command: %s", err)
	}
	if cmd == nil {
		t.Error("Unexpected nil command")
	}
}
070701000000C5000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001C00000000uyuni-tools/mgrctl/cmd/term070701000000C6000081B4000000000000000000000001662A752800000411000000000000000000000000000000000000002400000000uyuni-tools/mgrctl/cmd/term/term.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package term

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/exec"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

var newExecCmd = exec.NewCommand

// NewCommand returns a new cobra.Command for term.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "term",
		Short: L("Run a terminal inside the server container"),
		RunE: func(cmd *cobra.Command, args []string) error {
			execCmd := newExecCmd(globalFlags)
			execArgs := []string{"-i", "-t"}
			backend, err := cmd.Flags().GetString("backend")
			if err == nil {
				execArgs = append(execArgs, "--backend", backend)
			}
			if err := execCmd.Flags().Parse(execArgs); err != nil {
				return err
			}
			return execCmd.RunE(execCmd, []string{"bash"})
		},
	}

	utils.AddBackendFlag(cmd)
	return cmd
}
070701000000C7000081B4000000000000000000000001662A75280000050C000000000000000000000000000000000000002900000000uyuni-tools/mgrctl/cmd/term/term_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package term

import (
	"errors"
	"testing"

	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd/exec"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// Ensure the term command properly delegates to the exec one.
func TestExecute(t *testing.T) {
	var globalFlags types.GlobalFlags

	newExecCmd = func(globalFlags *types.GlobalFlags) *cobra.Command {
		execCmd := exec.NewCommand(globalFlags)
		execCmd.RunE = func(cmd *cobra.Command, args []string) error {
			if interactive, err := cmd.Flags().GetBool("interactive"); err != nil || !interactive {
				t.Error("interactive flag not passed")
			}
			if tty, err := cmd.Flags().GetBool("tty"); err != nil || !tty {
				t.Error("tty flag not passed")
			}
			if backend, err := cmd.Flags().GetString("backend"); err != nil || backend != "mybackend" {
				t.Error("backend flag not passed")
			}
			return errors.New("some error")
		}
		return execCmd
	}

	cmd := NewCommand(&globalFlags)
	if err := cmd.Flags().Parse([]string{"--backend", "mybackend"}); err != nil {
		t.Errorf("failed to parse flags: %s", err)
	}
	if err := cmd.RunE(cmd, []string{}); err.Error() != "some error" {
		t.Errorf("Unexpected error returned")
	}
}
070701000000C8000081B4000000000000000000000001662A75280000027C000000000000000000000000000000000000001B00000000uyuni-tools/mgrctl/main.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package main

import (
	"os"

	"github.com/chai2010/gettext-go"
	"github.com/uyuni-project/uyuni-tools/mgrctl/cmd"
	l10n_utils "github.com/uyuni-project/uyuni-tools/shared/l10n/utils"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// Run runs the `mgrctl` root command.
func Run() error {
	gettext.BindLocale(gettext.New("mgrctl", utils.LocaleRoot, l10n_utils.New(utils.LocaleRoot)))
	run, err := cmd.NewUyunictlCommand()
	if err != nil {
		return err
	}
	return run.Execute()
}

func main() {
	if err := Run(); err != nil {
		os.Exit(1)
	}
}
070701000000C9000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000001300000000uyuni-tools/mgrpxy070701000000CA000041FD00000000000000000000000A662A752800000000000000000000000000000000000000000000001700000000uyuni-tools/mgrpxy/cmd070701000000CB000081B4000000000000000000000001662A752800000B07000000000000000000000000000000000000001E00000000uyuni-tools/mgrpxy/cmd/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
	"os"
	"path"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/install"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/restart"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/start"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/status"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/stop"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/uninstall"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/upgrade"
	"github.com/uyuni-project/uyuni-tools/shared/completion"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// NewCommand returns a new cobra.Command implementing the root command for kinder.
func NewUyuniproxyCommand() (*cobra.Command, error) {
	globalFlags := &types.GlobalFlags{}
	name := path.Base(os.Args[0])
	rootCmd := &cobra.Command{
		Use:          name,
		Short:        L("Uyuni proxy administration tool"),
		Long:         L("Tool to help administering Uyuni proxies in containers"),
		Version:      utils.Version,
		SilenceUsage: true, // Don't show usage help on errors
	}

	rootCmd.SetUsageTemplate(utils.GetLocalizedUsageTemplate())

	rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
		utils.LogInit(true)
		utils.SetLogLevel(globalFlags.LogLevel)

		// do not log if running the completion cmd as the output is redirected to create a file to source
		if cmd.Name() != "completion" {
			log.Info().Msgf(L("Welcome to %s"), name)
			log.Info().Msgf(L("Executing command: %s"), cmd.Name())
		}
	}

	rootCmd.PersistentFlags().StringVarP(&globalFlags.ConfigPath, "config", "c", "", L("configuration file path"))
	rootCmd.PersistentFlags().StringVar(&globalFlags.LogLevel, "logLevel", "", L("application log level")+"(trace|debug|info|warn|error|fatal|panic)")

	installCmd := install.NewCommand(globalFlags)
	rootCmd.AddCommand(installCmd)
	uninstallCmd, err := uninstall.NewCommand(globalFlags)
	if err != nil {
		return rootCmd, err
	}
	rootCmd.AddCommand(uninstallCmd)
	rootCmd.AddCommand(completion.NewCommand(globalFlags))
	rootCmd.AddCommand(status.NewCommand(globalFlags))
	rootCmd.AddCommand(start.NewCommand(globalFlags))
	rootCmd.AddCommand(stop.NewCommand(globalFlags))
	rootCmd.AddCommand(restart.NewCommand(globalFlags))
	rootCmd.AddCommand(upgrade.NewCommand(globalFlags))

	if supportCommand := support.NewCommand(globalFlags); supportCommand != nil {
		rootCmd.AddCommand(supportCommand)
	}

	rootCmd.AddCommand(utils.GetConfigHelpCommand())
	rootCmd.AddCommand(support.NewCommand(globalFlags))

	return rootCmd, nil
}
070701000000CC000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgrpxy/cmd/install070701000000CD000081B4000000000000000000000001662A752800000315000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/install/install.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package install

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/install/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/install/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand install a new proxy from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install [fqdn]",
		Short: L("Install a new proxy from scratch"),
		Long:  L("Install a new proxy from scratch"),
	}

	installCmd.AddCommand(podman.NewCommand(globalFlags))
	installCmd.AddCommand(kubernetes.NewCommand(globalFlags))

	return installCmd
}
070701000000CE000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/install/kubernetes070701000000CF000081B4000000000000000000000001662A7528000005AA000000000000000000000000000000000000003800000000uyuni-tools/mgrpxy/cmd/install/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
	pxy_utils "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type kubernetesProxyInstallFlags struct {
	pxy_utils.ProxyImageFlags `mapstructure:",squash"`
	Helm                      kubernetes.HelmFlags
}

// NewCommand install a new proxy on a running kubernetes cluster.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "kubernetes [path/to/config.tar.gz]",
		Short: L("Install a new proxy on a running kubernetes cluster"),
		Long: L(`Install a new proxy on a running kubernetes cluster.

It only takes the path to the configuration tarball generated by the server
as parameter.

The install kubernetes command assumes kubectl is installed locally.

NOTE: for now installing on a remote kubernetes cluster is not supported!
`),
		Args: cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags kubernetesProxyInstallFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, installForKubernetes)
		},
	}

	pxy_utils.AddImageFlags(cmd)

	kubernetes.AddHelmFlags(cmd)

	return cmd
}
070701000000D0000081B4000000000000000000000001662A7528000007FA000000000000000000000000000000000000003300000000uyuni-tools/mgrpxy/cmd/install/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	shared_kubernetes "github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)

func installForKubernetes(globalFlags *types.GlobalFlags,
	flags *kubernetesProxyInstallFlags, cmd *cobra.Command, args []string,
) error {
	for _, binary := range []string{"kubectl", "helm"} {
		if _, err := exec.LookPath(binary); err != nil {
			return fmt.Errorf(L("install %s before running this command"), binary)
		}
	}

	// Unpack the tarball
	configPath := utils.GetConfigPath(args)

	tmpDir, err := os.MkdirTemp("", "mgrpxy-*")
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}
	defer os.RemoveAll(tmpDir)

	if err := shared_utils.ExtractTarGz(configPath, tmpDir); err != nil {
		return fmt.Errorf(L("failed to extract configuration"))
	}

	// Check the kubernetes cluster setup
	clusterInfos, err := shared_kubernetes.CheckCluster()
	if err != nil {
		return err
	}

	// If installing on k3s, install the traefik helm config in manifests
	isK3s := clusterInfos.IsK3s()
	IsRke2 := clusterInfos.IsRke2()
	if isK3s {
		shared_kubernetes.InstallK3sTraefikConfig(shared_utils.PROXY_TCP_PORTS, shared_utils.UDP_PORTS)
	} else if IsRke2 {
		shared_kubernetes.InstallRke2NginxConfig(shared_utils.PROXY_TCP_PORTS, shared_utils.UDP_PORTS,
			flags.Helm.Proxy.Namespace)
	}

	// Install the uyuni proxy helm chart
	if err := kubernetes.Deploy(&flags.ProxyImageFlags, &flags.Helm, tmpDir, clusterInfos.GetKubeconfig(),
		"--set", "ingress="+clusterInfos.Ingress); err != nil {
		return fmt.Errorf(L("cannot deploy proxy helm chart: %s"), err)
	}

	return nil
}
070701000000D1000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgrpxy/cmd/install/podman070701000000D2000081B4000000000000000000000001662A752800000557000000000000000000000000000000000000003000000000uyuni-tools/mgrpxy/cmd/install/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)

type podmanProxyInstallFlags struct {
	utils.ProxyImageFlags `mapstructure:",squash"`
	Podman                podman.PodmanFlags
}

// NewCommand install a new proxy on podman from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	podmanCmd := &cobra.Command{
		Use:   "podman [path/to/config.tar.gz]",
		Short: L("Install a new proxy on podman"),
		Long: L(`Install a new proxy on podman

It only takes the path to the configuration tarball generated by the server
as parameter.

The install podman command assumes podman is installed locally.

NOTE: for now installing on a remote podman is not supported!
`),
		Args: cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags podmanProxyInstallFlags
			return shared_utils.CommandHelper(globalFlags, cmd, args, &flags, installForPodman)
		},
	}

	utils.AddImageFlags(podmanCmd)
	podman.AddPodmanArgFlag(podmanCmd)

	return podmanCmd
}
070701000000D3000081B4000000000000000000000001662A7528000007AF000000000000000000000000000000000000002F00000000uyuni-tools/mgrpxy/cmd/install/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"fmt"
	"os/exec"

	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// Start the proxy services.
func startPod() error {
	ret := shared_podman.IsServiceRunning(shared_podman.ProxyService)
	if ret {
		return shared_podman.RestartService(shared_podman.ProxyService)
	} else {
		return shared_podman.EnableService(shared_podman.ProxyService)
	}
}

func installForPodman(globalFlags *types.GlobalFlags, flags *podmanProxyInstallFlags, cmd *cobra.Command, args []string) error {
	if _, err := exec.LookPath("podman"); err != nil {
		return fmt.Errorf(L("install podman before running this command"))
	}

	configPath := utils.GetConfigPath(args)
	if err := podman.UnpackConfig(configPath); err != nil {
		return fmt.Errorf(L("failed to extract proxy config from %s file: %s"), configPath, err)
	}

	httpdImage, err := podman.GetContainerImage(&flags.ProxyImageFlags, "httpd")
	if err != nil {
		return err
	}
	saltBrokerImage, err := podman.GetContainerImage(&flags.ProxyImageFlags, "salt-broker")
	if err != nil {
		return err
	}
	squidImage, err := podman.GetContainerImage(&flags.ProxyImageFlags, "squid")
	if err != nil {
		return err
	}
	sshImage, err := podman.GetContainerImage(&flags.ProxyImageFlags, "ssh")
	if err != nil {
		return err
	}
	tftpdImage, err := podman.GetContainerImage(&flags.ProxyImageFlags, "tftpd")
	if err != nil {
		return err
	}

	// Setup the systemd service configuration options
	if err := podman.GenerateSystemdService(httpdImage, saltBrokerImage, squidImage, sshImage, tftpdImage, flags.Podman.Args); err != nil {
		return err
	}

	return startPod()
}
070701000000D4000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgrpxy/cmd/restart070701000000D5000081B4000000000000000000000001662A7528000001AD000000000000000000000000000000000000002D00000000uyuni-tools/mgrpxy/cmd/restart/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package restart

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesRestart(
	globalFlags *types.GlobalFlags,
	flags *restartFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Restart(kubernetes.ProxyFilter)
}
070701000000D6000081B4000000000000000000000001662A7528000001A5000000000000000000000000000000000000002900000000uyuni-tools/mgrpxy/cmd/restart/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package restart

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func podmanRestart(
	globalFlags *types.GlobalFlags,
	flags *restartFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return podman.RestartService(podman.ProxyService)
}
070701000000D7000081B4000000000000000000000001662A7528000004AB000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/restart/restart.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package restart

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type restartFlags struct {
	Backend string
}

// NewCommand to restart server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	restartCmd := &cobra.Command{
		Use:   "restart",
		Short: L("Restart the proxy"),
		Long:  L("Restart the proxy"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags restartFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, restart)
		},
	}
	restartCmd.SetUsageTemplate(restartCmd.UsageTemplate())

	utils.AddBackendFlag(restartCmd)

	return restartCmd
}

func restart(globalFlags *types.GlobalFlags, flags *restartFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanRestart, kubernetesRestart)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
070701000000D8000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001D00000000uyuni-tools/mgrpxy/cmd/start070701000000D9000081B4000000000000000000000001662A7528000001A5000000000000000000000000000000000000002B00000000uyuni-tools/mgrpxy/cmd/start/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package start

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStart(
	globalFlags *types.GlobalFlags,
	flags *startFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Start(kubernetes.ProxyFilter)
}
070701000000DA000081B4000000000000000000000001662A75280000019D000000000000000000000000000000000000002700000000uyuni-tools/mgrpxy/cmd/start/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package start

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func podmanStart(
	globalFlags *types.GlobalFlags,
	flags *startFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return podman.StartService(podman.ProxyService)
}
070701000000DB000081B4000000000000000000000001662A7528000004AB000000000000000000000000000000000000002600000000uyuni-tools/mgrpxy/cmd/start/start.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package start

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type startFlags struct {
	Backend string
}

// NewCommand starts the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	startCmd := &cobra.Command{
		Use:   "start",
		Short: L("Start the proxy"),
		Long:  L("Start the proxy"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags startFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, start)
		},
	}
	startCmd.SetUsageTemplate(startCmd.UsageTemplate())

	if utils.KubernetesBuilt {
		utils.AddBackendFlag(startCmd)
	}

	return startCmd
}

func start(globalFlags *types.GlobalFlags, flags *startFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanStart, kubernetesStart)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
070701000000DC000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/mgrpxy/cmd/status070701000000DD000081B4000000000000000000000001662A752800000603000000000000000000000000000000000000002C00000000uyuni-tools/mgrpxy/cmd/status/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package status

import (
	"errors"
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStatus(
	globalFlags *types.GlobalFlags,
	flags *statusFlags,
	cmd *cobra.Command,
	args []string,
) error {
	// Do we have an uyuni helm release?
	clusterInfos, err := kubernetes.CheckCluster()
	if err != nil {
		return fmt.Errorf(L("failed to discover the cluster type: %s"), err)
	}

	kubeconfig := clusterInfos.GetKubeconfig()
	if !kubernetes.HasHelmRelease("uyuni-proxy", kubeconfig) {
		return errors.New(L("no uyuni-proxy helm release installed on the cluster"))
	}

	namespace, err := kubernetes.FindNamespace("uyuni-proxy", kubeconfig)
	if err != nil {
		return fmt.Errorf(L("failed to find the uyuni-proxy deployment namespace: %s"), err)
	}

	// Is the pod running? Do we have all the replicas?
	status, err := kubernetes.GetDeploymentStatus(namespace, "uyuni-proxy")
	if err != nil {
		return fmt.Errorf(L("failed to get deployment status: %s"), err)
	}
	if status.Replicas != status.ReadyReplicas {
		log.Warn().Msgf(L("Some replicas are not ready: %d / %d"), status.ReadyReplicas, status.Replicas)
	}

	if status.AvailableReplicas == 0 {
		return errors.New(L("the pod is not running"))
	}

	log.Info().Msg(L("Proxy containers up and running"))

	return nil
}
070701000000DE000081B4000000000000000000000001662A7528000003E3000000000000000000000000000000000000002800000000uyuni-tools/mgrpxy/cmd/status/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package status

import (
	"errors"
	"fmt"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func podmanStatus(
	globalFlags *types.GlobalFlags,
	flags *statusFlags,
	cmd *cobra.Command,
	args []string,
) error {
	var returnErr error
	services := []string{"httpd", "salt-broker", "squid", "ssh", "tftpd", "pod"}
	for _, service := range services {
		serviceName := fmt.Sprintf("uyuni-proxy-%s", service)
		if err := utils.RunCmdStdMapping(zerolog.DebugLevel, "systemctl", "status", "--no-pager", serviceName); err != nil {
			log.Error().Err(err).Msgf(L("Failed to get status of the %s service"), serviceName)
			returnErr = errors.New(L("failed to get the status of at least one service"))
		}
	}
	return returnErr
}
070701000000DF000081B4000000000000000000000001662A7528000004E7000000000000000000000000000000000000002800000000uyuni-tools/mgrpxy/cmd/status/status.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package status

import (
	"errors"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type statusFlags struct {
}

// NewCommand to get the status of the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "status",
		Short: L("Get the proxy status"),
		Long:  L("Get the proxy status"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags statusFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, status)
		},
	}
	cmd.SetUsageTemplate(cmd.UsageTemplate())

	return cmd
}

func status(globalFlags *types.GlobalFlags, flags *statusFlags, cmd *cobra.Command, args []string) error {
	if podman.HasService(podman.ProxyService) {
		return podmanStatus(globalFlags, flags, cmd, args)
	}

	if utils.IsInstalled("kubectl") && utils.IsInstalled("helm") {
		return kubernetesStatus(globalFlags, flags, cmd, args)
	}

	return errors.New(L("no installed proxy detected"))
}
070701000000E0000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001C00000000uyuni-tools/mgrpxy/cmd/stop070701000000E1000081B4000000000000000000000001662A7528000001A1000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/stop/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package stop

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func kubernetesStop(
	globalFlags *types.GlobalFlags,
	flags *stopFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return kubernetes.Stop(kubernetes.ProxyFilter)
}
070701000000E2000081B4000000000000000000000001662A752800000199000000000000000000000000000000000000002600000000uyuni-tools/mgrpxy/cmd/stop/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package stop

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func podmanStop(
	globalFlags *types.GlobalFlags,
	flags *stopFlags,
	cmd *cobra.Command,
	args []string,
) error {
	return podman.StopService(podman.ProxyService)
}
070701000000E3000081B4000000000000000000000001662A752800000479000000000000000000000000000000000000002400000000uyuni-tools/mgrpxy/cmd/stop/stop.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package stop

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type stopFlags struct {
	Backend string
}

// NewCommand to stop server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	stopCmd := &cobra.Command{
		Use:   "stop",
		Short: L("Stop the proxy"),
		Long:  L("Stop the proxy"),
		Args:  cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags stopFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, stop)
		},
	}

	stopCmd.SetUsageTemplate(stopCmd.UsageTemplate())

	utils.AddBackendFlag(stopCmd)

	return stopCmd
}

func stop(globalFlags *types.GlobalFlags, flags *stopFlags, cmd *cobra.Command, args []string) error {
	fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanStop, kubernetesStop)
	if err != nil {
		return err
	}

	return fn(globalFlags, flags, cmd, args)
}
070701000000E4000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgrpxy/cmd/support070701000000E5000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000002300000000uyuni-tools/mgrpxy/cmd/support/ptf070701000000E6000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002E00000000uyuni-tools/mgrpxy/cmd/support/ptf/kubernetes070701000000E7000081B4000000000000000000000001662A7528000005FE000000000000000000000000000000000000003C00000000uyuni-tools/mgrpxy/cmd/support/ptf/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
	pxy_utils "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

type kubernetesPTFFlags struct {
	UpgradeFlags kubernetes.KubernetesProxyUpgradeFlags `mapstructure:",squash"`
}

// NewCommand for kubernetes installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	kubernetesCmd := &cobra.Command{
		Use:   "kubernetes",
		Short: L("Install a PTF or Test package on a kubernetes cluster"),
		Long: L(`Install a PTR of Test package on a kubernetes cluster

The support ptf command assumes the following:
  * kubectl and helm are installed locally
  * a working kubectl configuration should be set to connect to the cluster to deploy to

The helm values file will be overridden with the values from the command parameters or configuration.

NOTE: installing on a remote cluster is not supported yet!
`),

		Args: cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags kubernetesPTFFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, ptfForKubernetes)
		},
	}

	pxy_utils.AddImageUpgradeFlags(kubernetesCmd)

	kubernetes.AddHelmFlags(kubernetesCmd)

	return kubernetesCmd
}
070701000000E8000081B4000000000000000000000001662A7528000001D2000000000000000000000000000000000000003700000000uyuni-tools/mgrpxy/cmd/support/ptf/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func ptfForKubernetes(globalFlags *types.GlobalFlags,
	flags *kubernetesPTFFlags,
	cmd *cobra.Command,
	args []string,
) error {

	return kubernetes.Upgrade(&flags.UpgradeFlags, cmd, args)
}
070701000000E9000081B4000000000000000000000001662A752800000150000000000000000000000000000000000000002C00000000uyuni-tools/mgrpxy/cmd/support/ptf/noptf.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build !ptf

package ptf

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand is the command for creates supportptf.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	return nil
}
070701000000EA000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/support/ptf/podman070701000000EB000081B4000000000000000000000001662A7528000005C7000000000000000000000000000000000000003400000000uyuni-tools/mgrpxy/cmd/support/ptf/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)

type podmanPTFFlags struct {
	UpgradeFlags podman.PodmanProxyUpgradeFlags `mapstructure:",squash"`
	PTFId        string                         `mapstructure:"ptf"`
	TestId       string                         `mapstructure:"test"`
	CustomerId   string                         `mapstructure:"user"`
}

// NewCommand for podman installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	var flags podmanPTFFlags
	podmanCmd := &cobra.Command{
		Use: "podman",

		Short: L("Install a PTF or Test package on podman"),
		Long: L(`Install a PTF or Test package on podman

The support ptf podman command assumes podman is installed locally and
the host machine is register to SCC.

NOTE: for now installing on a remote podman is not supported!
`),
		Args: cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return shared_utils.CommandHelper(globalFlags, cmd, args, &flags, ptfForPodman)
		},
	}

	utils.AddImagePTFFlags(podmanCmd)
	shared_utils.AddPTFFlag(podmanCmd)
	return podmanCmd
}
070701000000EC000081B4000000000000000000000001662A752800000B15000000000000000000000000000000000000003300000000uyuni-tools/mgrpxy/cmd/support/ptf/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package podman

import (
	"errors"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	podman_shared "github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func ptfForPodman(
	globalFlags *types.GlobalFlags,
	flags *podmanPTFFlags,
	cmd *cobra.Command,
	args []string,
) error {
	if err := flags.checkParameters(); err != nil {
		return err
	}
	return podman.Upgrade(globalFlags, &flags.UpgradeFlags, cmd, args)
}

func (flags *podmanPTFFlags) checkParameters() error {
	if flags.TestId != "" && flags.PTFId != "" {
		return errors.New(L("ptf and test flags cannot be set simultaneously "))
	}
	if flags.TestId == "" && flags.PTFId == "" {
		return errors.New(L("ptf and test flags cannot be empty simultaneously "))
	}
	if flags.CustomerId == "" {
		return errors.New(L("user flag cannot be empty"))
	}
	suffix := "ptf"
	if flags.TestId != "" {
		suffix = "test"
	}
	httpdImage, err := podman_shared.GetRunningImage("httpd")
	if err != nil {
		return err
	}
	flags.UpgradeFlags.Httpd.Name, err = utils.ComputePTF(flags.CustomerId, flags.PTFId, httpdImage, suffix)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("The httpd ptf image computed is: %s"), flags.UpgradeFlags.Httpd.Name)

	sshImage, err := podman_shared.GetRunningImage("ssh")
	if err != nil {
		return err
	}
	flags.UpgradeFlags.Ssh.Name, err = utils.ComputePTF(flags.CustomerId, flags.PTFId, sshImage, suffix)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("The ssh ptf image computed is: %s"), flags.UpgradeFlags.Ssh.Name)

	tftpdImage, err := podman_shared.GetRunningImage("tftpd")
	if err != nil {
		return err
	}
	flags.UpgradeFlags.Tftpd.Name, err = utils.ComputePTF(flags.CustomerId, flags.PTFId, tftpdImage, suffix)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("The tftpd ptf image computed is: %s"), flags.UpgradeFlags.Tftpd.Name)

	saltBrokerImage, err := podman_shared.GetRunningImage("salt-broker")
	if err != nil {
		return err
	}
	flags.UpgradeFlags.SaltBroker.Name, err = utils.ComputePTF(flags.CustomerId, flags.PTFId, saltBrokerImage, suffix)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("The salt-broker ptf image computed is: %s"), flags.UpgradeFlags.SaltBroker.Name)

	squidImage, err := podman_shared.GetRunningImage("squid")
	if err != nil {
		return err
	}
	flags.UpgradeFlags.Squid.Name, err = utils.ComputePTF(flags.CustomerId, flags.PTFId, squidImage, suffix)
	if err != nil {
		return err
	}
	log.Info().Msgf(L("The squid ptf image computed is: %s"), flags.UpgradeFlags.Squid.Name)

	return nil
}
070701000000ED000081B4000000000000000000000001662A75280000030F000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/support/ptf/ptf.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf

package ptf

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/ptf/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/ptf/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand is the command for creates supportptf.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	ptfCmd := &cobra.Command{
		Use:   "ptf",
		Short: L("Install a PTF"),
	}

	ptfCmd.AddCommand(podman.NewCommand(globalFlags))

	if kubernetesCmd := kubernetes.NewCommand(globalFlags); kubernetesCmd != nil {
		ptfCmd.AddCommand(kubernetesCmd)
	}

	return ptfCmd
}
070701000000EE000081B4000000000000000000000001662A7528000002BE000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/support/support.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package support

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/ptf"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand to export supportconfig.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	supportCmd := &cobra.Command{
		Use:   "support",
		Short: L("Commands for support operations"),
		Long:  L("Commands for support operations"),
	}

	if ptfCommand := ptf.NewCommand(globalFlags); ptfCommand != nil {
		supportCmd.AddCommand(ptfCommand)
		return supportCmd
	}
	return nil
}
070701000000EF000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002100000000uyuni-tools/mgrpxy/cmd/uninstall070701000000F0000081B4000000000000000000000001662A752800000445000000000000000000000000000000000000002F00000000uyuni-tools/mgrpxy/cmd/uninstall/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package uninstall

import (
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
)

func uninstallForKubernetes(dryRun bool) error {
	clusterInfos, err := kubernetes.CheckCluster()
	if err != nil {
		return err
	}
	kubeconfig := clusterInfos.GetKubeconfig()

	// TODO Find all the PVs related to the server if we want to delete them

	// Uninstall uyuni
	if _, err := kubernetes.HelmUninstall(kubeconfig, "uyuni-proxy", "", dryRun); err != nil {
		return err
	}

	// TODO Remove the PVs or wait for their automatic removal if purge is requested
	// Also wait if the PVs are dynamic with Delete reclaim policy but the user didn't ask to purge them
	// Since some storage plugins don't handle Delete policy, we may need to check for error events to avoid infinite loop

	// Remove the K3s Traefik config
	if clusterInfos.IsK3s() {
		kubernetes.UninstallK3sTraefikConfig(dryRun)
	}

	// Remove the rke2 nginx config
	if clusterInfos.IsRke2() {
		kubernetes.UninstallRke2NginxConfig(dryRun)
	}
	return nil
}
070701000000F1000081B4000000000000000000000001662A752800000614000000000000000000000000000000000000002B00000000uyuni-tools/mgrpxy/cmd/uninstall/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package uninstall

import (
	"fmt"

	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

func uninstallForPodman(dryRun bool, purge bool) error {
	// Uninstall the service
	podman.UninstallService("uyuni-proxy-pod", dryRun)
	podman.UninstallService("uyuni-proxy-httpd", dryRun)
	podman.UninstallService("uyuni-proxy-salt-broker", dryRun)
	podman.UninstallService("uyuni-proxy-squid", dryRun)
	podman.UninstallService("uyuni-proxy-ssh", dryRun)
	podman.UninstallService("uyuni-proxy-tftpd", dryRun)

	// Force stop the pod
	for _, containerName := range podman.ProxyContainerNames {
		podman.DeleteContainer(containerName, dryRun)
	}

	// Remove the volumes
	if purge {
		// Merge all proxy containers volumes into a map
		volumes := map[string]string{}
		allProxyVolumes := []map[string]string{
			utils.PROXY_HTTPD_VOLUMES,
			utils.PROXY_SQUID_VOLUMES,
			utils.PROXY_TFTPD_VOLUMES,
		}
		for _, volumesList := range allProxyVolumes {
			for volume, mount := range volumesList {
				volumes[volume] = mount
			}
		}

		// Delete each volume
		for volume := range volumes {
			if err := podman.DeleteVolume(volume, dryRun); err != nil {
				return fmt.Errorf(L("cannot delete volume %s: %s"), volume, err)
			}
		}
		log.Info().Msg(L("All volumes removed"))
	}

	podman.DeleteNetwork(dryRun)

	return podman.ReloadDaemon(dryRun)
}
070701000000F2000081B4000000000000000000000001662A7528000006FA000000000000000000000000000000000000002E00000000uyuni-tools/mgrpxy/cmd/uninstall/uninstall.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package uninstall

import (
	"fmt"

	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/shared"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// NewCommand for uninstall proxy.
func NewCommand(globalFlags *types.GlobalFlags) (*cobra.Command, error) {
	uninstallCmd := &cobra.Command{
		Use:   "uninstall",
		Short: L("Uninstall a proxy"),
		Long: L(`Uninstall a proxy and optionally the corresponding volumes.
By default it will only print what would be done, use --force to actually remove.`) + kubernetes.UninstallHelp(),
		Args: cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			force, _ := cmd.Flags().GetBool("force")
			purge, _ := cmd.Flags().GetBool("purgeVolumes")

			backend, _ := cmd.Flags().GetString("backend")

			cnx := shared.NewConnection(backend, podman.ProxyContainerNames[0], kubernetes.ProxyFilter)
			command, err := cnx.GetCommand()
			if err != nil {
				return fmt.Errorf(L("failed to determine suitable backend: %s"), err)
			}
			switch command {
			case "podman":
				if err := uninstallForPodman(!force, purge); err != nil {
					return err
				}
			case "kubectl":
				if err := uninstallForKubernetes(!force); err != nil {
					return err
				}
			}
			return nil
		},
	}
	uninstallCmd.Flags().BoolP("force", "f", false, L("Actually remove the proxy"))
	uninstallCmd.Flags().Bool("purgeVolumes", false, L("Also remove the volumes"))

	utils.AddBackendFlag(uninstallCmd)

	return uninstallCmd, nil
}
070701000000F3000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000001F00000000uyuni-tools/mgrpxy/cmd/upgrade070701000000F4000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/upgrade/kubernetes070701000000F5000081B4000000000000000000000001662A7528000004A5000000000000000000000000000000000000003800000000uyuni-tools/mgrpxy/cmd/upgrade/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
	pxy_utils "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// NewCommand install a new proxy on a running kubernetes cluster.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "kubernetes",
		Short: L("Upgrade a proxy on a running kubernetes cluster"),
		Long: L(`Upgrade a proxy on a running kubernetes cluster.

The upgrade kubernetes command assumes kubectl is installed locally.

NOTE: for now upgrading on a remote kubernetes cluster is not supported!
`),
		Args: cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags kubernetes.KubernetesProxyUpgradeFlags
			return utils.CommandHelper(globalFlags, cmd, args, &flags, upgradeKubernetes)
		},
	}

	pxy_utils.AddImageFlags(cmd)

	kubernetes.AddHelmFlags(cmd)

	return cmd
}
070701000000F6000081B4000000000000000000000001662A7528000001C7000000000000000000000000000000000000003300000000uyuni-tools/mgrpxy/cmd/upgrade/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func upgradeKubernetes(globalFlags *types.GlobalFlags,
	flags *kubernetes.KubernetesProxyUpgradeFlags, cmd *cobra.Command, args []string,
) error {
	return kubernetes.Upgrade(flags, cmd, args)
}
070701000000F7000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002600000000uyuni-tools/mgrpxy/cmd/upgrade/podman070701000000F8000081B4000000000000000000000001662A752800000445000000000000000000000000000000000000003000000000uyuni-tools/mgrpxy/cmd/upgrade/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)

// NewCommand install a new proxy on podman from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	podmanCmd := &cobra.Command{
		Use:   "podman",
		Short: L("Upgrade a proxy on podman"),
		Long: L(`Upgrade a proxy on podman

The upgrade podman command assumes podman is upgraded locally.

NOTE: for now upgrading on a remote podman is not supported!
`),
		Args: cobra.ExactArgs(0),
		RunE: func(cmd *cobra.Command, args []string) error {
			var flags podman.PodmanProxyUpgradeFlags
			return shared_utils.CommandHelper(globalFlags, cmd, args, &flags, upgradePodman)
		},
	}

	utils.AddImageUpgradeFlags(podmanCmd)

	return podmanCmd
}
070701000000F9000081B4000000000000000000000001662A7528000001B9000000000000000000000000000000000000002F00000000uyuni-tools/mgrpxy/cmd/upgrade/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

func upgradePodman(globalFlags *types.GlobalFlags, flags *podman.PodmanProxyUpgradeFlags, cmd *cobra.Command, args []string) error {
	return podman.Upgrade(globalFlags, flags, cmd, args)
}
070701000000FA000081B4000000000000000000000001662A7528000002EC000000000000000000000000000000000000002A00000000uyuni-tools/mgrpxy/cmd/upgrade/upgrade.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package upgrade

import (
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/upgrade/kubernetes"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/upgrade/podman"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand install a new proxy from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	upgradeCmd := &cobra.Command{
		Use:   "upgrade",
		Short: L("Upgrade a proxy"),
		Long:  L("Upgrade a proxy"),
	}

	upgradeCmd.AddCommand(podman.NewCommand(globalFlags))
	upgradeCmd.AddCommand(kubernetes.NewCommand(globalFlags))

	return upgradeCmd
}
070701000000FB000081B4000000000000000000000001662A75280000027E000000000000000000000000000000000000001B00000000uyuni-tools/mgrpxy/main.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package main

import (
	"os"

	"github.com/chai2010/gettext-go"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd"
	l10n_utils "github.com/uyuni-project/uyuni-tools/shared/l10n/utils"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// Run runs the `mgrpxy` root command.
func Run() error {
	gettext.BindLocale(gettext.New("mgrpxy", utils.LocaleRoot, l10n_utils.New(utils.LocaleRoot)))
	run, err := cmd.NewUyuniproxyCommand()
	if err != nil {
		return err
	}
	return run.Execute()
}

func main() {
	if err := Run(); err != nil {
		os.Exit(1)
	}
}
070701000000FC000041FD000000000000000000000006662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/mgrpxy/shared070701000000FD000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002500000000uyuni-tools/mgrpxy/shared/kubernetes070701000000FE000081B4000000000000000000000001662A7528000003BF000000000000000000000000000000000000002C00000000uyuni-tools/mgrpxy/shared/kubernetes/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"fmt"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// HelmFlags it's used for helm chart flags.
type HelmFlags struct {
	Proxy types.ChartFlags
}

// AddHelmFlags add helm flags to a command.
func AddHelmFlags(cmd *cobra.Command) {
	defaultChart := fmt.Sprintf("oci://%s/proxy-helm", utils.DefaultNamespace)

	cmd.Flags().String("helm-proxy-namespace", "default", L("Kubernetes namespace where to install the proxy"))
	cmd.Flags().String("helm-proxy-chart", defaultChart, L("URL to the proxy helm chart"))
	cmd.Flags().String("helm-proxy-version", "", L("Version of the proxy helm chart"))
	cmd.Flags().String("helm-proxy-values", "", L("Path to a values YAML file to use for proxy helm install"))
}
070701000000FF000081B4000000000000000000000001662A7528000014D8000000000000000000000000000000000000002F00000000uyuni-tools/mgrpxy/shared/kubernetes/deploy.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)

const helmAppName = "uyuni-proxy"

// KubernetesProxyUpgradeFlags represents the flags for the mgrpxy upgrade kubernetes command.
type KubernetesProxyUpgradeFlags struct {
	utils.ProxyImageFlags `mapstructure:",squash"`
	Helm                  HelmFlags
}

// Deploy will deploy proxy in kubernetes.
func Deploy(imageFlags *utils.ProxyImageFlags, helmFlags *HelmFlags, configDir string,
	kubeconfig string, helmArgs ...string,
) error {
	log.Info().Msg(L("Installing Uyuni proxy"))

	helmParams := []string{}

	// Pass the user-provided values file
	extraValues := helmFlags.Proxy.Values
	if extraValues != "" {
		helmParams = append(helmParams, "-f", extraValues)
	}

	if !shared_utils.FileExists(path.Join(configDir, "httpd.yaml")) {
		if _, err := getHTTPDYaml(configDir); err != nil {
			return err
		}
	}
	helmParams = append(helmParams, "-f", path.Join(configDir, "httpd.yaml"))

	if !shared_utils.FileExists(path.Join(configDir, "ssh.yaml")) {
		if _, err := getSSHYaml(configDir); err != nil {
			return err
		}
	}
	helmParams = append(helmParams, "-f", path.Join(configDir, "ssh.yaml"))

	if !shared_utils.FileExists(path.Join(configDir, "config.yaml")) {
		if _, err := getConfigYaml(configDir); err != nil {
			return err
		}
	}
	helmParams = append(helmParams, "-f", path.Join(configDir, "config.yaml"))

	helmParams = append(helmParams,
		"--set", "images.proxy-httpd="+imageFlags.GetContainerImage("httpd"),
		"--set", "images.proxy-salt-broker="+imageFlags.GetContainerImage("salt-broker"),
		"--set", "images.proxy-squid="+imageFlags.GetContainerImage("squid"),
		"--set", "images.proxy-ssh="+imageFlags.GetContainerImage("ssh"),
		"--set", "images.proxy-tftpd="+imageFlags.GetContainerImage("tftpd"),
		"--set", "repository="+imageFlags.ImagesLocation,
		"--set", "version="+imageFlags.Tag,
		"--set", "pullPolicy="+kubernetes.GetPullPolicy(imageFlags.PullPolicy))

	helmParams = append(helmParams, helmArgs...)

	// Install the helm chart
	if err := kubernetes.HelmUpgrade(kubeconfig, helmFlags.Proxy.Namespace, true, "", helmAppName, helmFlags.Proxy.Chart,
		helmFlags.Proxy.Version, helmParams...); err != nil {
		return fmt.Errorf(L("cannot run helm upgrade: %s"), err)
	}

	// Wait for the pod to be started
	return kubernetes.WaitForDeployment(helmFlags.Proxy.Namespace, helmAppName, "uyuni-proxy")
}

func getSSHYaml(directory string) (string, error) {
	sshPayload, err := kubernetes.GetSecret("proxy-secret", "-o=jsonpath={.data.ssh\\.yaml}")
	if err != nil {
		return "", err
	}

	sshYamlFilename := filepath.Join(directory, "ssh.yaml")
	err = os.WriteFile(sshYamlFilename, []byte(sshPayload), 0644)
	if err != nil {
		return "", fmt.Errorf(L("failed to write in file %s: %s"), sshYamlFilename, err)
	}

	return sshYamlFilename, nil
}

func getHTTPDYaml(directory string) (string, error) {
	httpdPayload, err := kubernetes.GetSecret("proxy-secret", "-o=jsonpath={.data.httpd\\.yaml}")
	if err != nil {
		return "", err
	}

	httpdYamlFilename := filepath.Join(directory, "httpd.yaml")
	err = os.WriteFile(httpdYamlFilename, []byte(httpdPayload), 0644)
	if err != nil {
		return "", fmt.Errorf(L("failed to write in file %s: %s"), httpdYamlFilename, err)
	}

	return httpdYamlFilename, nil
}

func getConfigYaml(directory string) (string, error) {
	configPayload, err := kubernetes.GetConfigMap("proxy-configMap", "-o=jsonpath={.data.config\\.yaml}")
	if err != nil {
		return "", err
	}

	configYamlFilename := filepath.Join(directory, "config.yaml")
	err = os.WriteFile(configYamlFilename, []byte(configPayload), 0644)
	if err != nil {
		return "", fmt.Errorf(L("failed to write in file %s: %s"), configYamlFilename, err)
	}

	return configYamlFilename, nil
}

// Upgrade will upgrade the current kubernetes proxy.
func Upgrade(flags *KubernetesProxyUpgradeFlags, cmd *cobra.Command, args []string,
) error {
	for _, binary := range []string{"kubectl", "helm"} {
		if _, err := exec.LookPath(binary); err != nil {
			return fmt.Errorf(L("install %s before running this command"), binary)
		}
	}

	tmpDir, err := os.MkdirTemp("", "mgrpxy-*")
	if err != nil {
		return fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}
	defer os.RemoveAll(tmpDir)

	// Check the kubernetes cluster setup
	clusterInfos, err := kubernetes.CheckCluster()
	if err != nil {
		return err
	}

	err = kubernetes.ReplicasTo(kubernetes.ProxyFilter, 0)
	if err != nil {
		return err
	}

	defer func() {
		// if something is running, we don't need to set replicas to 1
		if _, err = kubernetes.GetNode("uyuni"); err != nil {
			err = kubernetes.ReplicasTo(kubernetes.ProxyFilter, 1)
		}
	}()

	// Install the uyuni proxy helm chart
	if err := Deploy(&flags.ProxyImageFlags, &flags.Helm, tmpDir, clusterInfos.GetKubeconfig(),
		"--set", "ingress="+clusterInfos.Ingress); err != nil {
		return fmt.Errorf(L("cannot deploy proxy helm chart: %s"), err)
	}

	return nil
}
07070100000100000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002100000000uyuni-tools/mgrpxy/shared/podman07070100000101000081B4000000000000000000000001662A752800001B9F000000000000000000000000000000000000002B00000000uyuni-tools/mgrpxy/shared/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/templates"
	"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)

// ProxyImageFlags are the flags used by pormdna proxy upgrade command.
type PodmanProxyUpgradeFlags struct {
	utils.ProxyImageFlags `mapstructure:",squash"`
	Podman                podman.PodmanFlags
}

// GenerateSystemdService generates all the systemd files required by proxy.
func GenerateSystemdService(httpdImage string, saltBrokerImage string, squidImage string, sshImage string,
	tftpdImage string, podmanArgs []string) error {
	if err := podman.SetupNetwork(); err != nil {
		return fmt.Errorf(L("cannot setup network: %s"), err)
	}

	log.Info().Msg(L("Generating systemd services"))
	httpProxyConfig := getHttpProxyConfig()

	ports := []types.PortMap{}
	ports = append(ports, shared_utils.PROXY_TCP_PORTS...)
	ports = append(ports, shared_utils.PROXY_PODMAN_PORTS...)
	ports = append(ports, shared_utils.UDP_PORTS...)

	// Pod
	dataPod := templates.PodTemplateData{
		Ports:         ports,
		HttpProxyFile: httpProxyConfig,
		Args:          strings.Join(podmanArgs, " "),
		Network:       podman.UyuniNetwork,
	}
	if err := generateSystemdFile(dataPod, "pod"); err != nil {
		return err
	}

	// Httpd
	dataHttpd := templates.HttpdTemplateData{
		Volumes:       shared_utils.PROXY_HTTPD_VOLUMES,
		HttpProxyFile: httpProxyConfig,
		Image:         httpdImage,
	}
	if err := generateSystemdFile(dataHttpd, "httpd"); err != nil {
		return err
	}

	// Salt broker
	dataSaltBroker := templates.SaltBrokerTemplateData{
		HttpProxyFile: httpProxyConfig,
		Image:         saltBrokerImage,
	}
	if err := generateSystemdFile(dataSaltBroker, "salt-broker"); err != nil {
		return err
	}

	// Squid
	dataSquid := templates.SquidTemplateData{
		Volumes:       shared_utils.PROXY_SQUID_VOLUMES,
		HttpProxyFile: httpProxyConfig,
		Image:         squidImage,
	}
	if err := generateSystemdFile(dataSquid, "squid"); err != nil {
		return err
	}

	// SSH
	dataSSH := templates.SSHTemplateData{
		HttpProxyFile: httpProxyConfig,
		Image:         sshImage,
	}
	if err := generateSystemdFile(dataSSH, "ssh"); err != nil {
		return err
	}

	// Tftpd
	dataTftpd := templates.TFTPDTemplateData{
		Volumes:       shared_utils.PROXY_TFTPD_VOLUMES,
		HttpProxyFile: httpProxyConfig,
		Image:         tftpdImage,
	}
	if err := generateSystemdFile(dataTftpd, "tftpd"); err != nil {
		return err
	}

	return podman.ReloadDaemon(false)
}

func generateSystemdFile(template shared_utils.Template, service string) error {
	name := fmt.Sprintf("uyuni-proxy-%s.service", service)

	const systemdPath = "/etc/systemd/system"
	path := path.Join(systemdPath, name)
	if err := shared_utils.WriteTemplateToFile(template, path, 0644, true); err != nil {
		return fmt.Errorf(L("failed to generate systemd file '%s': %s"), path, err)
	}
	return nil
}

func getHttpProxyConfig() string {
	const httpProxyConfigPath = "/etc/sysconfig/proxy"

	// Only SUSE distros seem to have such a file for HTTP proxy settings
	if shared_utils.FileExists(httpProxyConfigPath) {
		return httpProxyConfigPath
	}
	return ""
}

// GetContainerImage returns a proxy image URL.
func GetContainerImage(flags *utils.ProxyImageFlags, name string) (string, error) {
	image := flags.GetContainerImage(name)
	inspectedHostValues, err := shared_utils.InspectHost()
	if err != nil {
		return "", fmt.Errorf(L("cannot inspect host values: %s"), err)
	}

	pullArgs := []string{}
	_, scc_user_exist := inspectedHostValues["host_scc_username"]
	_, scc_user_password := inspectedHostValues["host_scc_password"]
	if scc_user_exist && scc_user_password {
		pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
	}

	preparedImage, err := podman.PrepareImage(image, flags.PullPolicy, pullArgs...)
	if err != nil {
		return "", err
	}

	return preparedImage, nil
}

// UnpackConfig uncompress the config.tar.gz containing proxy configuration.
func UnpackConfig(configPath string) error {
	log.Info().Msgf(L("Setting up proxy with configuration %s"), configPath)
	const proxyConfigDir = "/etc/uyuni/proxy"
	if err := os.MkdirAll(proxyConfigDir, 0755); err != nil {
		return err
	}

	if err := shared_utils.ExtractTarGz(configPath, proxyConfigDir); err != nil {
		return err
	}
	return nil
}

// Upgrade will upgrade the proxy podman deploy.
func Upgrade(globalFlags *types.GlobalFlags, flags *PodmanProxyUpgradeFlags, cmd *cobra.Command, args []string) error {
	if _, err := exec.LookPath("podman"); err != nil {
		return fmt.Errorf(L("install podman before running this command"))
	}

	httpdImage, err := getContainerImage(&flags.ProxyImageFlags, "httpd")
	if err != nil {
		log.Info().Msgf(L("cannot find httpd image: it will no be upgraded"))
	}
	saltBrokerImage, err := getContainerImage(&flags.ProxyImageFlags, "salt-broker")
	if err != nil {
		log.Info().Msgf(L("cannot find salt-broker image: it will no be upgraded"))
	}
	squidImage, err := getContainerImage(&flags.ProxyImageFlags, "squid")
	if err != nil {
		log.Info().Msgf(L("cannot find squid image: it will no be upgraded"))
	}
	sshImage, err := getContainerImage(&flags.ProxyImageFlags, "ssh")
	if err != nil {
		log.Info().Msgf(L("cannot find ssh image: it will no be upgraded"))
	}
	tftpdImage, err := getContainerImage(&flags.ProxyImageFlags, "tftpd")
	if err != nil {
		log.Info().Msgf(L("cannot find tftpd image: it will no be upgraded"))
	}

	// Setup the systemd service configuration options
	if err := GenerateSystemdService(httpdImage, saltBrokerImage, squidImage, sshImage, tftpdImage, flags.Podman.Args); err != nil {
		return err
	}

	return startPod()
}

func getContainerImage(flags *utils.ProxyImageFlags, name string) (string, error) {
	image := flags.GetContainerImage(name)
	inspectedHostValues, err := shared_utils.InspectHost()
	if err != nil {
		return "", fmt.Errorf(L("cannot inspect host values: %s"), err)
	}

	pullArgs := []string{}
	_, scc_user_exist := inspectedHostValues["host_scc_username"]
	_, scc_user_password := inspectedHostValues["host_scc_password"]
	if scc_user_exist && scc_user_password {
		pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
	}

	preparedImage, err := podman.PrepareImage(image, flags.PullPolicy, pullArgs...)
	if err != nil {
		return "", err
	}

	return preparedImage, nil
}

// Start the proxy services.
func startPod() error {
	ret := podman.IsServiceRunning(podman.ProxyService)
	if ret {
		return podman.RestartService(podman.ProxyService)
	} else {
		return podman.EnableService(podman.ProxyService)
	}
}
07070100000102000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002400000000uyuni-tools/mgrpxy/shared/templates07070100000103000081B4000000000000000000000001662A7528000006CE000000000000000000000000000000000000002D00000000uyuni-tools/mgrpxy/shared/templates/httpd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const httpdTemplate = `# uyuni-proxy-httpd.service, generated by mgrpxy
# Use an uyuni-proxy-httpd.service.d/local.conf file to override

[Unit]
Description=Uyuni proxy httpd container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=UYUNI_IMAGE={{ .Image }}
{{- if .HttpProxyFile }}
EnvironmentFile={{ .HttpProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-httpd.pid %t/uyuni-proxy-httpd.ctr-id

ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-proxy-httpd.pid \
	--cidfile %t/uyuni-proxy-httpd.ctr-id \
	--cgroups=no-conmon \
	--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
	--replace -dt \
	-v /etc/uyuni/proxy:/etc/uyuni:ro \
	{{- range $name, $path := .Volumes }}
	-v {{ $name }}:{{ $path }} \
	{{- end }}
	--name uyuni-proxy-httpd \
	${UYUNI_IMAGE}

ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-httpd.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-httpd.ctr-id
PIDFile=%t/uyuni-proxy-httpd.pid
TimeoutStopSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// HttpdTemplateData represents HTTPD information to create systemd file.
type HttpdTemplateData struct {
	Volumes       map[string]string
	HttpProxyFile string
	Image         string
}

// Render will create the systemd configuration file.
func (data HttpdTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(httpdTemplate))
	return t.Execute(wr, data)
}
07070100000104000081B4000000000000000000000001662A7528000007A9000000000000000000000000000000000000002B00000000uyuni-tools/mgrpxy/shared/templates/pod.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

const podTemplate = `# uyuni-proxy-pod.service, generated by mgrpxy

[Unit]
Description=Podman uyuni-proxy-pod.service
Wants=network.target
After=network-online.target
Requires=uyuni-proxy-httpd.service uyuni-proxy-salt-broker.service uyuni-proxy-squid.service uyuni-proxy-ssh.service uyuni-proxy-tftpd.service
Before=uyuni-proxy-httpd.service uyuni-proxy-salt-broker.service uyuni-proxy-squid.service uyuni-proxy-ssh.service uyuni-proxy-tftpd.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HttpProxyFile }}
EnvironmentFile={{ .HttpProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-pod.pid %t/uyuni-proxy-pod.pod-id

ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/uyuni-proxy-pod.pid \
		--pod-id-file %t/uyuni-proxy-pod.pod-id --name uyuni-proxy-pod \
		--network {{ .Network }} \
        {{- range .Ports }}
        -p {{ .Exposed }}:{{ .Port }}{{ if .Protocol }}/{{ .Protocol }}{{ end }} \
        {{- end }}
		--replace {{ .Args }}

ExecStart=/usr/bin/podman pod start --pod-id-file %t/uyuni-proxy-pod.pod-id
ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/uyuni-proxy-pod.pod-id -t 10
ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/uyuni-proxy-pod.pod-id

PIDFile=%t/uyuni-proxy-pod.pid
TimeoutStopSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// PodTemplateData POD information to create systemd file.
type PodTemplateData struct {
	Ports         []types.PortMap
	HttpProxyFile string
	Args          string
	Network       string
}

// Render will create the systemd configuration file.
func (data PodTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(podTemplate))
	return t.Execute(wr, data)
}
07070100000105000081B4000000000000000000000001662A7528000006BD000000000000000000000000000000000000003300000000uyuni-tools/mgrpxy/shared/templates/salt-broker.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const saltBrokerTemplate = `# uyuni-proxy-salt-broker.service, generated by mgrpxy
# Use an uyuni-proxy-salt-broker.service.d/local.conf file to override

[Unit]
Description=Uyuni proxy Salt broker container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=UYUNI_IMAGE={{ .Image }}
{{- if .HttpProxyFile }}
EnvironmentFile={{ .HttpProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-salt-broker.pid %t/uyuni-proxy-salt-broker.ctr-id

ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-proxy-salt-broker.pid \
	--cidfile %t/uyuni-proxy-salt-broker.ctr-id \
	--cgroups=no-conmon \
	--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
	--replace -dt \
	-v /etc/uyuni/proxy:/etc/uyuni:ro \
	--name uyuni-proxy-salt-broker \
	${UYUNI_IMAGE}

ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-salt-broker.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-salt-broker.ctr-id
PIDFile=%t/uyuni-proxy-salt-broker.pid
TimeoutStopSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// SaltBrokerTemplateData represents Salt Broker information to create systemd file.
type SaltBrokerTemplateData struct {
	HttpProxyFile string
	Image         string
}

// Render will create the systemd configuration file.
func (data SaltBrokerTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(saltBrokerTemplate))
	return t.Execute(wr, data)
}
07070100000106000081B4000000000000000000000001662A7528000006C3000000000000000000000000000000000000002D00000000uyuni-tools/mgrpxy/shared/templates/squid.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const squidTemplate = `# uyuni-proxy-squid.service, generated by mgrpxy
# Use an uyuni-proxy-squid.service.d/local.conf file to override

[Unit]
Description=Uyuni proxy squid container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=UYUNI_IMAGE={{ .Image }}
{{- if .HttpProxyFile }}
EnvironmentFile={{ .HttpProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-squid.pid %t/uyuni-proxy-squid.ctr-id

ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-proxy-squid.pid \
	--cidfile %t/uyuni-proxy-squid.ctr-id \
	--cgroups=no-conmon \
	--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
	--replace -dt \
	-v /etc/uyuni/proxy:/etc/uyuni:ro \
	{{- range $name, $path := .Volumes }}
	-v {{ $name }}:{{ $path }} \
	{{- end }}
	--name uyuni-proxy-squid \
	${UYUNI_IMAGE}

ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-squid.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-squid.ctr-id
PIDFile=%t/uyuni-proxy-squid.pid
TimeoutStopSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// SquidTemplateData Squid information to create systemd file.
type SquidTemplateData struct {
	Volumes       map[string]string
	HttpProxyFile string
	Image         string
}

// Render will create the systemd configuration file.
func (data SquidTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(squidTemplate))
	return t.Execute(wr, data)
}
07070100000107000081B4000000000000000000000001662A75280000062F000000000000000000000000000000000000002B00000000uyuni-tools/mgrpxy/shared/templates/ssh.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const sshTemplate = `# uyuni-proxy-ssh.service, generated by mgrpxy
# Use an uyuni-proxy-ssh.service.d/local.conf file to override

[Unit]
Description=Uyuni proxy ssh container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=UYUNI_IMAGE={{ .Image }}
{{- if .HttpProxyFile }}
EnvironmentFile={{ .HttpProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-ssh.pid %t/uyuni-proxy-ssh.ctr-id

ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-proxy-ssh.pid \
	--cidfile %t/uyuni-proxy-ssh.ctr-id \
	--cgroups=no-conmon \
	--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
	--replace -dt \
	-v /etc/uyuni/proxy:/etc/uyuni:ro \
	--name uyuni-proxy-ssh \
	${UYUNI_IMAGE}

ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-ssh.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-ssh.ctr-id
PIDFile=%t/uyuni-proxy-ssh.pid
TimeoutStopSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// SSHTemplateData SSH information to create systemd file.
type SSHTemplateData struct {
	HttpProxyFile string
	Image         string
}

// Render will create the systemd configuration file.
func (data SSHTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(sshTemplate))
	return t.Execute(wr, data)
}
07070100000108000081B4000000000000000000000001662A7528000006E8000000000000000000000000000000000000002D00000000uyuni-tools/mgrpxy/shared/templates/tftpd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"
)

const tftpdTemplate = `# uyuni-proxy-tftpd.service, generated by mgrpxy
# Use an uyuni-proxy-tftpd.service.d/local.conf file to override

[Unit]
Description=Uyuni proxy tftpd container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=UYUNI_IMAGE={{ .Image }}
{{- if .HttpProxyFile }}
EnvironmentFile={{ .HttpProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-tftpd.pid %t/uyuni-proxy-tftpd.ctr-id

ExecStart=/usr/bin/podman run \
	--conmon-pidfile %t/uyuni-proxy-tftpd.pid \
	--cidfile %t/uyuni-proxy-tftpd.ctr-id \
	--cgroups=no-conmon \
	--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
	--replace -dt \
	-v /etc/uyuni/proxy:/etc/uyuni:ro \
	{{- range $name, $path := .Volumes }}
	 -v {{ $name }}:{{ $path }} \
	{{- end }}
	--name uyuni-proxy-tftpd \
	${UYUNI_IMAGE}

ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-tftpd.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-tftpd.ctr-id
PIDFile=%t/uyuni-proxy-tftpd.pid
TimeoutStopSec=60
Type=forking

[Install]
WantedBy=multi-user.target default.target
`

// TFTPDTemplateData represents information used to create TFTPD systemd configuration file.
type TFTPDTemplateData struct {
	Volumes       map[string]string
	HttpProxyFile string
	Image         string
}

// Render will create the TFTPD systemd configuration file.
func (data TFTPDTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("service").Parse(tftpdTemplate))
	return t.Execute(wr, data)
}
07070100000109000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000002000000000uyuni-tools/mgrpxy/shared/utils0707010000010A000081B4000000000000000000000001662A7528000001FA000000000000000000000000000000000000002700000000uyuni-tools/mgrpxy/shared/utils/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// GetConfigPath returns the configuration path if exists.
func GetConfigPath(args []string) string {
	configPath := args[0]
	if !utils.FileExists(configPath) {
		log.Fatal().Msgf(L("argument is not an existing file: %s"), configPath)
	}
	return configPath
}
0707010000010B000081B4000000000000000000000001662A752800000E81000000000000000000000000000000000000002900000000uyuni-tools/mgrpxy/shared/utils/flags.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// ProxyImageFlags are the flags used by install proxy command.
type ProxyImageFlags struct {
	ImagesLocation string           `mapstructure:"imagesLocation"`
	Tag            string           `namespace:"tag"`
	PullPolicy     string           `mapstructure:"pullPolicy"`
	Httpd          types.ImageFlags `mapstructure:"httpd"`
	SaltBroker     types.ImageFlags `mapstructure:"saltBroker"`
	Squid          types.ImageFlags `mapstructure:"squid"`
	Ssh            types.ImageFlags `mapstructure:"ssh"`
	Tftpd          types.ImageFlags `mapstructure:"tftpd"`
}

// Get the full container image name and tag for a container name.
func (f *ProxyImageFlags) GetContainerImage(name string) string {
	imageName := "proxy-" + name
	image := fmt.Sprintf("%s/%s", f.ImagesLocation, imageName)
	tag := f.Tag

	var containerImage *types.ImageFlags
	switch name {
	case "httpd":
		containerImage = &f.Httpd
	case "salt-broker":
		containerImage = &f.SaltBroker
	case "squid":
		containerImage = &f.Squid
	case "ssh":
		containerImage = &f.Ssh
	case "tftpd":
		containerImage = &f.Tftpd
	default:
		log.Warn().Msgf(L("Invalid proxy container name: %s"), name)
	}

	if containerImage != nil {
		if containerImage.Name != "" {
			image = containerImage.Name
		}
		if containerImage.Tag != "" {
			tag = containerImage.Tag
		}
	}

	imageUrl, err := utils.ComputeImage(image, tag)
	if err != nil {
		log.Fatal().Err(err).Msg(L("Failed to compute image URL"))
	}
	return imageUrl
}

// AddImageFlags will add the proxy install flags to a command.
func AddImageFlags(cmd *cobra.Command) {
	cmd.Flags().String("imagesLocation", utils.DefaultNamespace,
		L("registry URL prefix containing the all the container images"))
	cmd.Flags().String("tag", utils.DefaultTag, L("image tag"))
	utils.AddPullPolicyFlag(cmd)

	addContainerImageFlags(cmd, "httpd")
	addContainerImageFlags(cmd, "saltBroker")
	addContainerImageFlags(cmd, "squid")
	addContainerImageFlags(cmd, "ssh")
	addContainerImageFlags(cmd, "tftpd")
}

// AddImageUpgradeFlags will add the proxy upgrade flags to a command.
func AddImageUpgradeFlags(cmd *cobra.Command) {
	cmd.Flags().String("imagesLocation", utils.DefaultNamespace,
		L("registry URL prefix containing the all the container images"))
	cmd.Flags().String("tag", utils.DefaultTag, L("image tag"))
	utils.AddPullPolicyUpgradeFlag(cmd)

	addContainerImageFlags(cmd, "httpd")
	addContainerImageFlags(cmd, "saltBroker")
	addContainerImageFlags(cmd, "squid")
	addContainerImageFlags(cmd, "ssh")
	addContainerImageFlags(cmd, "tftpd")
}

// AddImagePTFFlags will add the proxy support ptf flags to a command.
func AddImagePTFFlags(cmd *cobra.Command) {
	cmd.Flags().String("imagesLocation", "",
		L("registry URL prefix containing the all the container images"))
	cmd.Flags().String("tag", utils.DefaultTag, L("image tag"))
	utils.AddPullPolicyUpgradeFlag(cmd)

	addContainerImageFlags(cmd, "httpd")
	addContainerImageFlags(cmd, "saltBroker")
	addContainerImageFlags(cmd, "squid")
	addContainerImageFlags(cmd, "ssh")
	addContainerImageFlags(cmd, "tftpd")
}

func addContainerImageFlags(cmd *cobra.Command, container string) {
	cmd.Flags().String(container+"-image", "",
		fmt.Sprintf(L("Image for %s container, overrides the namespace if set"), container))
	cmd.Flags().String(container+"-tag", "",
		fmt.Sprintf(L("Tag for %s container, overrides the global value if set"), container))
}
0707010000010C000081B4000000000000000000000001662A752800000AD7000000000000000000000000000000000000001700000000uyuni-tools/modules.md<!--
SPDX-FileCopyrightText: 2023 SUSE LLC

SPDX-License-Identifier: Apache-2.0
-->

The goal of this content is to set a high-level overview of each tool available.

For tools that depend on the backend we should explicitly specify which one we want to use. Backend can also be defined in the configuration file to be used by the user.

## Tools definition

In case one wants to add a new sub-command it should decide to in which tool it should be placed.

If the new sub-command needs access to the host OS of direct access to a running container then it should be added to MGRADM.

Commands in MGRCTL should use the API only. 

Any command to manage the proxy deployment must be placed in MGRPXY.

MGRDEV is focused on utility commands to be used during the development process.


## MGRADM

**Goals and definition:**

Install, update, and maintain a containerized Uyuni Server. Commands placed here will have/need access to the container runtime environment and also to the HOST OS.

Any new command that needs direct access to the host OS or any running container must be added to these tools.

**Target Stakeholder:** Uyuni administrator
**Where to install:** System where Uyuni Server should be deployed
**Sub-commands Naming:** verb -> backend

## MGRCTL

**Goals and definition:**
Helper tool for day-to-day operations and integration with other tools.
Sub-commands in this tool should use the API calls (although case-by-case exceptions can be considered if there are valid reasons).

**Target Stakeholder:** Uyuni operators
**Where to install:** System where the Uyuni Server is deployed, or in the operator machine (supporting the same Operating Systems we already support for `spacecmd`).
**Sub-commands Naming:** subcommand -> verb

## MGRDEV

**Goals and definition:**
Utility commands to be used during development process. This tool can have commands that run remotely on the host OS or on running containers. These commands can use SSH and podman-socket.
Examples of sub-commands are `cp` and `exec`.

**Target Stakeholder:** Uyuni Developers
**Where to install:** Any machine that needs remote access to running containers.
**Sub-commands Naming:** verb -> backend


## MGRPXY

**Goals and definition:**
Install and manage a containerized Uyuni Proxy. This new command is a proposal to solve the problem of managing the Proxy using the same tool that manages the server, and how that can lead to confusion and errors.

**Target Stakeholder:** Uyuni administrator
**Where to install:** System where the Uyuni Proxy should be deployed
**Sub-commands Naming:** verb -> backend

This command is to be developed in a later stage since it would be better to redefine how we deploy containerized proxy and follow the same approach we have provided in the server.
0707010000010D000081B4000000000000000000000001662A7528000004C3000000000000000000000000000000000000001400000000uyuni-tools/push.sh#!/usr/bin/bash

# SPDX-FileCopyrightText: 2024 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

# This script is called by push-packages-to-obs

OSCAPI=$1
GIT_DIR=$2
PKG_NAME=$3

SRPM_PKG_DIR=$(dirname "$0")

pushd ${GIT_DIR}
REMOTE_BRANCH=$(git for-each-ref --format='%(upstream:lstrip=-1)' $(git rev-parse --symbolic-full-name HEAD))
COMMIT_ID=$(git rev-parse --short HEAD)
popd

if [ "${OSCAPI}" == "https://api.suse.de" ]; then
  VERSION="HEAD"
  case ${REMOTE_BRANCH} in Manager-*)
    VERSION="${REMOTE_BRANCH#Manager-}"
  esac
  
  sed 's/^tag=%{!?_default_tag:latest}/tag=5.0.0-beta2/' -i ${SRPM_PKG_DIR}/uyuni-tools.spec
  sed "s/namespace='%{_default_namespace}'/namespace='%{_default_namespace}\/%{_arch}'/" -i ${SRPM_PKG_DIR}/uyuni-tools.spec

else

  pushd ${GIT_DIR}
  VERSION=$(git tag --points-at HEAD Uyuni-*)
  popd

  if test -z "${VERSION}"; then
      case ${REMOTE_BRANCH} in Uyuni-*)
        VERSION="${REMOTE_BRANCH#Uyuni-}"
      esac
  
      if test -z "${VERSION}"; then
        VERSION="Master"
      fi
  fi
fi

# Add the version_details value for use in the version tag
sed "/^%global productname.*$/a%global version_details '${VERSION} $COMMIT_ID'" -i ${SRPM_PKG_DIR}/uyuni-tools.spec
0707010000010E000081FD000000000000000000000001662A7528000000C6000000000000000000000000000000000000001500000000uyuni-tools/setup.sh# SPDX-FileCopyrightText: 2023 SUSE LLC
#
# SPDX-License-Identifier: Apache-2.0

set -euxo pipefail

go mod vendor && tar czvf vendor.tar.gz vendor >/dev/null && rm -rf vendor

echo "vendor.tar.gz"
0707010000010F000041FD00000000000000000000000A662A752800000000000000000000000000000000000000000000001300000000uyuni-tools/shared07070100000110000041FD000000000000000000000004662A752800000000000000000000000000000000000000000000001700000000uyuni-tools/shared/api07070100000111000081B4000000000000000000000001662A752800001E90000000000000000000000000000000000000001E00000000uyuni-tools/shared/api/api.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"os"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"

	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

const root_path_apiv1 = "/rhn/manager/api"

// HTTP Client is an API entrypoint.
type HTTPClient struct {

	// URL to the API endpoint of the target host
	BaseURL string

	// net/http client
	Client *http.Client

	// Authentication cookie storage
	AuthCookie *http.Cookie
}

// Connection details for initial API connection.
type ConnectionDetails struct {

	// FQDN of the target host.
	Server string

	// User to login under.
	User string

	// Password for the user.
	Password string

	// CA certificate used for target host validation.
	// Provided certificate is used together with system certificates.
	CAcert string

	// Disable certificate validation, unsecure and not recommended.
	Insecure bool
}

// API response where T is the type of the result.
type ApiResponse[T interface{}] struct {
	Result  T
	Success bool
	Message string
}

// AddAPIFlags is a helper to include api details for the provided command tree.
//
// If the api support is only optional for the command, set optional parameter to true.
func AddAPIFlags(cmd *cobra.Command, optional bool) error {
	cmd.PersistentFlags().String("api-server", "", L("FQDN of the server to connect to"))
	cmd.PersistentFlags().String("api-user", "", L("API user username"))
	cmd.PersistentFlags().String("api-password", "", L("Password for the API user"))
	cmd.PersistentFlags().String("api-cacert", "", L("Path to a cert file of the CA"))
	cmd.PersistentFlags().Bool("api-insecure", false, L("If set, server certificate will not be checked for validity"))

	if !optional {
		if err := cmd.MarkPersistentFlagRequired("api-server"); err != nil {
			return err
		}
		if err := cmd.MarkPersistentFlagRequired("api-user"); err != nil {
			return err
		}
		if err := cmd.MarkPersistentFlagRequired("api-password"); err != nil {
			return err
		}
	}
	return nil
}

func prettyPrint(v interface{}) string {
	b, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return ""
	}
	return fmt.Sprintln(string(b))
}

func (c *HTTPClient) sendRequest(req *http.Request) (*http.Response, error) {
	log.Debug().Msgf("Sending %s request %s", req.Method, req.URL)
	req.Header.Set("Content-Type", "application/json; charset=utf-8")
	req.Header.Set("Accept", "application/json; charset=utf-8")
	if c.AuthCookie != nil {
		req.AddCookie(c.AuthCookie)
	}

	log.Trace().Msg(prettyPrint(req.Header))
	log.Trace().Msg(prettyPrint(req.Body))

	res, err := c.Client.Do(req)
	if err != nil {
		log.Trace().Msgf("Request failed: %s", err)
		return nil, err
	}

	log.Trace().Msg(prettyPrint(res.Header))
	log.Trace().Msg(prettyPrint(res.Body))

	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
		var errResponse map[string]string
		if err = json.NewDecoder(res.Body).Decode(&errResponse); err == nil {
			return nil, fmt.Errorf(errResponse["message"])
		}
		return nil, fmt.Errorf(L("unknown error: %d"), res.StatusCode)
	}
	log.Debug().Msgf("Received response with code %d", res.StatusCode)

	return res, nil
}

// Init returns a HTTPClient object for further API use.
//
// Provided connectionDetails must have Server specified with FQDN to the
// target host.
//
// Optionaly connectionDetails can have user name and password set and Init
// will try to login to the host.
// caCert can be set to use custom CA certificate to validate target host.
func Init(conn *ConnectionDetails) (*HTTPClient, error) {
	caCertPool, err := x509.SystemCertPool()
	if err != nil {
		log.Warn().Msg(err.Error())
	}
	if conn.CAcert != "" {
		caCert, err := os.ReadFile(conn.CAcert)
		if err != nil {
			log.Fatal().Msg(err.Error())
		}
		caCertPool.AppendCertsFromPEM(caCert)
	}
	client := &HTTPClient{
		BaseURL: fmt.Sprintf("https://%s%s", conn.Server, root_path_apiv1),
		Client: &http.Client{
			Timeout: time.Minute,
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					RootCAs:            caCertPool,
					InsecureSkipVerify: conn.Insecure,
				},
			},
		},
	}

	if len(conn.User) > 0 {
		if len(conn.Password) == 0 {
			utils.AskPasswordIfMissing(&conn.Password, L("API server password"), 0, 0)
		}
		err = client.login(conn)
	}
	return client, err
}

func (c *HTTPClient) login(conn *ConnectionDetails) error {
	url := fmt.Sprintf("%s/%s", c.BaseURL, "auth/login")
	data := map[string]string{
		"login":    conn.User,
		"password": conn.Password,
	}
	jsonData, err := json.Marshal(data)
	if err != nil {
		log.Error().Err(err).Msg(L("Unable to create login data"))
		return err
	}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}

	res, err := c.sendRequest(req)
	if err != nil {
		return err
	}

	var response map[string]interface{}
	if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
		return err
	}
	if !response["success"].(bool) {
		return fmt.Errorf(response["messages"].(string))
	}

	cookies := res.Cookies()
	for _, cookie := range cookies {
		if cookie.Name == "pxt-session-cookie" && cookie.MaxAge > 0 {
			c.AuthCookie = cookie
			break
		}
	}

	if c.AuthCookie == nil {
		return errors.New(L("auth cookie not found in login response"))
	}

	return nil
}

// Post issues a POST HTTP request to the API target
//
// `path` specifies an API endpoint
// `data` contains a map of values to add to the POST query. `data` are serialized to the JSON
//
// returns a raw HTTP Response.
func (c *HTTPClient) Post(path string, data map[string]interface{}) (*http.Response, error) {
	url := fmt.Sprintf("%s/%s", c.BaseURL, path)
	jsonData, err := json.Marshal(data)
	if err != nil {
		log.Error().Err(err).Msg(L("Unable to convert data to JSON"))
		return nil, err
	}

	log.Trace().Msg(string(jsonData))

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}

	res, err := c.sendRequest(req)
	if err != nil {
		return nil, err
	}

	return res, nil
}

// Get issues GET HTTP request to the API target
//
// `path` specifies API endpoint together with query options
//
// returns a raw HTTP Response.
func (c *HTTPClient) Get(path string) (*http.Response, error) {
	url := fmt.Sprintf("%s/%s", c.BaseURL, path)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	res, err := c.sendRequest(req)
	if err != nil {
		return nil, err
	}

	return res, nil
}

// Post issues a POST HTTP request to the API target using the client and decodes the response.
//
// `path` specifies an API endpoint
// `data` contains a map of values to add to the POST query. `data` are serialized to the JSON
//
// returns a deserialized JSON data to the map.
func Post[T interface{}](client *HTTPClient, path string, data map[string]interface{}) (*ApiResponse[T], error) {
	res, err := client.Post(path, data)
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	var response ApiResponse[T]
	if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
		return nil, err
	}

	return &response, nil
}

// Get issues an HTTP GET request to the API using the client and decodes the response.
//
// `path` specifies API endpoint together with query options
//
// returns an ApiResponse with the decoded result.
func Get[T interface{}](client *HTTPClient, path string) (*ApiResponse[T], error) {
	res, err := client.Get(path)
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	var response ApiResponse[T]
	if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
		return nil, err
	}

	return &response, nil
}
07070100000112000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001B00000000uyuni-tools/shared/api/org07070100000113000081B4000000000000000000000001662A7528000004BC000000000000000000000000000000000000002A00000000uyuni-tools/shared/api/org/createFirst.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package org

import (
	"errors"
	"fmt"

	"github.com/uyuni-project/uyuni-tools/shared/api"
	"github.com/uyuni-project/uyuni-tools/shared/api/types"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

// Create first organization and user after initial setup without authentication.
// orgName is the name of the first organization to create and admin the user to create.
func CreateFirst(cnxDetails *api.ConnectionDetails, orgName string, admin *types.User) (*types.Organization, error) {
	client, err := api.Init(cnxDetails)
	if err != nil {
		return nil, fmt.Errorf(L("failed to connect to the server: %s"), err)
	}

	data := map[string]interface{}{
		"orgName":       orgName,
		"adminLogin":    admin.Login,
		"adminPassword": admin.Password,
		"firstName":     admin.FirstName,
		"lastName":      admin.LastName,
		"email":         admin.Email,
	}

	res, err := api.Post[types.Organization](client, "org/createFirst", data)
	if err != nil {
		return nil, fmt.Errorf(L("failed to create first user and organization: %s"), err)
	}

	if !res.Success {
		return nil, errors.New(res.Message)
	}

	return &res.Result, nil
}
07070100000114000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001D00000000uyuni-tools/shared/api/types07070100000115000081B4000000000000000000000001662A75280000029E000000000000000000000000000000000000002D00000000uyuni-tools/shared/api/types/organization.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// Organization describe an organization in the API.
type Organization struct {
	Id                    int
	Name                  string
	ActiveUsers           int `mapstructure:"active_users"`
	Systems               int
	Trusts                int
	SystemGroups          int  `mapstructure:"system_groups"`
	ActivationKeys        int  `mapstructure:"activation_keys"`
	KickstartProfiles     int  `mapstructure:"kickstart_profiles"`
	ConfigurationChannels int  `mapstructure:"configuration_channels"`
	StagingContentEnabled bool `mapstructure:"staging_content_enabled"`
}
07070100000116000081B4000000000000000000000001662A7528000000FE000000000000000000000000000000000000002500000000uyuni-tools/shared/api/types/user.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// User describes an Uyuni user in the API.
type User struct {
	Login     string
	Password  string
	FirstName string
	LastName  string
	Email     string
}
07070100000117000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/shared/completion07070100000118000081B4000000000000000000000001662A752800000590000000000000000000000000000000000000002C00000000uyuni-tools/shared/completion/completion.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package completion

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// NewCommand  command for generates completion script.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
	shellCompletionCmd := &cobra.Command{
		Use:                   "completion [bash|zsh|fish|powershell]",
		Short:                 L("Generate shell completion script"),
		Long:                  L("Generate shell completion script"),
		DisableFlagsInUseLine: true,
		ValidArgs:             []string{"bash", "zsh", "fish"},
		Args:                  cobra.ExactValidArgs(1),
		Hidden:                true,
		RunE: func(cmd *cobra.Command, args []string) error {
			switch args[0] {
			case "bash":
				if err := cmd.Root().GenBashCompletion(os.Stdout); err != nil {
					return fmt.Errorf(L("cannot generate %s completion: %s"), args[0], err)
				}
			case "zsh":
				if err := cmd.Root().GenZshCompletion(os.Stdout); err != nil {
					return fmt.Errorf(L("cannot generate %s completion: %s"), args[0], err)
				}
			case "fish":
				if err := cmd.Root().GenFishCompletion(os.Stdout, true); err != nil {
					return fmt.Errorf(L("cannot generate %s completion: %s"), args[0], err)
				}
			}
			return nil
		},
	}
	return shellCompletionCmd
}
07070100000119000081B4000000000000000000000001662A752800002524000000000000000000000000000000000000002100000000uyuni-tools/shared/connection.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package shared

import (
	"bytes"
	"errors"
	"fmt"
	"os/exec"
	"strings"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/pflag"
	"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/podman"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// Connection contains information about how to connect to the server.
type Connection struct {
	backend          string
	command          string
	podName          string
	podmanContainer  string
	kubernetesFilter string
}

// Create a new connection object.
// The backend is either the command to use to connect to the container or the empty string.
//
// The empty strings means automatic detection of the backend where the uyuni container is running.
// podmanContainer is the name of a podman container to look for when detecting the command.
// kubernetesFilter is a filter parameter to use to match a pod.
func NewConnection(backend string, podmanContainer string, kubernetesFilter string) *Connection {
	cnx := Connection{backend: backend, podmanContainer: podmanContainer, kubernetesFilter: kubernetesFilter}

	return &cnx
}

// GetCommand validates or guesses the connection backend command.
func (c *Connection) GetCommand() (string, error) {
	var err error
	if c.command == "" {
		switch c.backend {
		case "podman":
			fallthrough
		case "podman-remote":
			fallthrough
		case "kubectl":
			if _, err = exec.LookPath(c.backend); err != nil {
				err = fmt.Errorf(L("backend command not found in PATH: %s"), c.backend)
			}
			c.command = c.backend
		case "":
			hasPodman := false
			hasKubectl := false

			// Check kubectl with a timeout in case the configured cluster is not responding
			_, err = exec.LookPath("kubectl")
			if err == nil {
				hasKubectl = true
				if out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "--request-timeout=30s", "get", "pod", c.kubernetesFilter, "-A", "-o=jsonpath={.items[*].metadata.name}"); err != nil {
					log.Info().Msg(L("kubectl not configured to connect to a cluster, ignoring"))
				} else if len(bytes.TrimSpace(out)) != 0 {
					c.command = "kubectl"
					return c.command, err
				}
			}

			// Search for other backends
			bins := []string{"podman", "podman-remote"}
			for _, bin := range bins {
				if _, err = exec.LookPath(bin); err == nil {
					hasPodman = true
					if checkErr := utils.RunCmd(bin, "inspect", c.podmanContainer, "--format", "{{.Name}}"); checkErr == nil {
						c.command = bin
						break
					}
				}
			}
			if c.command == "" {
				// Check for uyuni-server.service or helm release
				if hasPodman && podman.HasService("uyuni-server") {
					c.command = "podman"
				} else if hasKubectl {
					clusterInfos, err := kubernetes.CheckCluster()
					if err != nil {
						return c.command, err
					}
					if kubernetes.HasHelmRelease("uyuni", clusterInfos.GetKubeconfig()) {
						c.command = "kubectl"
					}
				}
			}
			if c.command == "" {
				err = errors.New(L("uyuni container is not accessible with one of podman, podman-remote or kubectl"))
			}
		default:
			err = fmt.Errorf(L("unsupported backend %s"), c.backend)
		}
	}
	return c.command, err
}

// GetPodName finds the name of the running pod.
func (c *Connection) GetPodName() (string, error) {
	var err error

	if c.podName == "" {
		command, cmdErr := c.GetCommand()
		if cmdErr != nil {
			log.Fatal().Err(cmdErr)
		}

		switch command {
		case "podman-remote":
			fallthrough
		case "podman":
			if out, _ := utils.RunCmdOutput(zerolog.DebugLevel, c.command, "ps", "-q", "-f", "name="+c.podmanContainer); len(out) == 0 {
				err = fmt.Errorf(L("container %s is not running on podman"), c.podmanContainer)
			} else {
				c.podName = c.podmanContainer
			}
		case "kubectl":
			// We try the first item on purpose to make the command fail if not available
			podName, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "pod", c.kubernetesFilter, "-A",
				"-o=jsonpath={.items[0].metadata.name}")
			if err == nil {
				c.podName = string(podName[:])
			}
		}
	}

	return c.podName, err
}

// Exec runs command inside the container within an sh shell.
func (c *Connection) Exec(command string, args ...string) ([]byte, error) {
	if c.podName == "" {
		if _, err := c.GetPodName(); c.podName == "" {
			return nil, fmt.Errorf(L("the container is not running, %s %s command not executed: %s"),
				command, strings.Join(args, " "), err)
		}
	}

	cmd, cmdErr := c.GetCommand()
	if cmdErr != nil {
		return nil, cmdErr
	}

	cmdArgs := []string{"exec", c.podName}
	if cmd == "kubectl" {
		cmdArgs = append(cmdArgs, "-c", "uyuni", "--")
	}
	shellArgs := append([]string{command}, args...)
	cmdArgs = append(cmdArgs, shellArgs...)

	return utils.RunCmdOutput(zerolog.DebugLevel, cmd, cmdArgs...)
}

// WaitForServer waits at most 60s for multi-user systemd target to be reached.
func (c *Connection) WaitForServer() error {
	// Wait for the system to be up
	for i := 0; i < 60; i++ {
		podName, err := c.GetPodName()
		if err != nil {
			log.Fatal().Err(err)
		}

		args := []string{"exec", podName}
		command, err := c.GetCommand()
		if err != nil {
			log.Fatal().Err(err)
		}

		if command == "kubectl" {
			args = append(args, "--")
		}
		args = append(args, "systemctl", "is-active", "-q", "multi-user.target")
		output := utils.RunCmd(command, args...)
		isActive := output == nil

		if isActive {
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return errors.New(L("server didn't start within 60s. Check for the service status"))
}

// Copy transfers a file to or from the container.
// Prefix one of src or dst parameters with `server:` to designate the path is in the container
// user and group parameters are used to set the owner of a file transferred in the container.
func (c *Connection) Copy(src string, dst string, user string, group string) error {
	podName, err := c.GetPodName()
	if err != nil {
		return err
	}
	var commandArgs []string
	extraArgs := []string{}
	srcExpanded := strings.Replace(src, "server:", podName+":", 1)
	dstExpanded := strings.Replace(dst, "server:", podName+":", 1)

	command, err := c.GetCommand()
	if err != nil {
		return err
	}

	switch command {
	case "podman-remote":
		fallthrough
	case "podman":
		commandArgs = []string{"cp", srcExpanded, dstExpanded}
	case "kubectl":
		commandArgs = []string{"cp", "-c", "uyuni", srcExpanded, dstExpanded}
		extraArgs = []string{"-c", "uyuni", "--"}
	default:
		return fmt.Errorf(L("unknown container kind: %s"), command)
	}

	if err := utils.RunCmdStdMapping(zerolog.DebugLevel, command, commandArgs...); err != nil {
		return err
	}

	if user != "" && strings.HasPrefix(dst, "server:") {
		execArgs := []string{"exec", podName}
		execArgs = append(execArgs, extraArgs...)
		owner := user
		if group != "" {
			owner = user + ":" + group
		}
		execArgs = append(execArgs, "chown", owner, strings.Replace(dst, "server:", "", 1))
		return utils.RunCmdStdMapping(zerolog.DebugLevel, command, execArgs...)
	}
	return nil
}

// TestExistenceInPod returns true if dstpath exists in the pod.
func (c *Connection) TestExistenceInPod(dstpath string) bool {
	podName, err := c.GetPodName()
	if err != nil {
		log.Fatal().Err(err)
	}
	commandArgs := []string{"exec", podName}

	command, err := c.GetCommand()
	if err != nil {
		log.Fatal().Err(err)
	}

	switch command {
	case "podman":
		commandArgs = append(commandArgs, "test", "-e", dstpath)
	case "kubectl":
		commandArgs = append(commandArgs, "-c", "uyuni", "test", "-e", dstpath)
	default:
		log.Fatal().Msgf(L("unknown container kind: %s"), command)
	}

	if _, err := utils.RunCmdOutput(zerolog.DebugLevel, command, commandArgs...); err != nil {
		return false
	}
	return true
}

// ChoosePodmanOrKubernetes selects either the podman or the kubernetes function based on the backend.
// This function automatically detects the backend if compiled with kubernetes support and the backend flag is not passed.
func ChoosePodmanOrKubernetes[F interface{}](
	flags *pflag.FlagSet,
	podmanFn utils.CommandFunc[F],
	kubernetesFn utils.CommandFunc[F],
) (utils.CommandFunc[F], error) {
	backend := "podman"
	if utils.KubernetesBuilt {
		backend, _ = flags.GetString("backend")
	}

	cnx := NewConnection(backend, podman.ServerContainerName, kubernetes.ServerFilter)
	return chooseBackend(cnx, podmanFn, kubernetesFn)
}

// ChooseProxyPodmanOrKubernetes selects either the podman or the kubernetes function based on the backend for the proxy.
func ChooseProxyPodmanOrKubernetes[F interface{}](
	flags *pflag.FlagSet,
	podmanFn utils.CommandFunc[F],
	kubernetesFn utils.CommandFunc[F],
) (utils.CommandFunc[F], error) {
	backend, _ := flags.GetString("backend")

	cnx := NewConnection(backend, podman.ProxyContainerNames[0], kubernetes.ProxyFilter)
	return chooseBackend(cnx, podmanFn, kubernetesFn)
}

func chooseBackend[F interface{}](
	cnx *Connection,
	podmanFn utils.CommandFunc[F],
	kubernetesFn utils.CommandFunc[F],
) (utils.CommandFunc[F], error) {
	command, err := cnx.GetCommand()
	if err != nil {
		return nil, errors.New(L("failed to determine suitable backend"))
	}
	switch command {
	case "podman":
		return podmanFn, nil
	case "kubectl":
		return kubernetesFn, nil
	}

	// Should never happen if the commands are the same than those handled in GetCommand()
	return nil, errors.New(L("no supported backend found"))
}
0707010000011A000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/shared/kubernetes0707010000011B000081B4000000000000000000000001662A7528000010AB000000000000000000000000000000000000002600000000uyuni-tools/shared/kubernetes/helm.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os/exec"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// HelmUpgrade runs the helm upgrade command.
//
// To perform an installation, set the install parameter to true: helm would get the --install parameter.
// If repo is not empty, the --repo parameter will be passed.
// If version is not empty, the --version parameter will be passed.
func HelmUpgrade(kubeconfig string, namespace string, install bool,
	repo string, name string, chart string, version string, args ...string) error {
	helmArgs := []string{
		"upgrade",
		"-n", namespace,
		"--create-namespace",
		name,
		chart,
	}
	if kubeconfig != "" {
		helmArgs = append(helmArgs, "--kubeconfig", kubeconfig)
	}

	if repo != "" {
		helmArgs = append(helmArgs, "--repo", repo)
	}
	if version != "" {
		helmArgs = append(helmArgs, "--version", version)
	}
	if install {
		helmArgs = append(helmArgs, "--install")
	}

	helmArgs = append(helmArgs, args...)

	command := "upgrade"
	if install {
		command = "install"
	}
	if err := utils.RunCmdStdMapping(zerolog.DebugLevel, "helm", helmArgs...); err != nil {
		return fmt.Errorf(L("failed to %s helm chart %s in namespace %s")+": %s", command, chart, namespace, err)
	}
	return nil
}

// HelmUninstall runs the helm uninstall command to remove a deployment.
func HelmUninstall(kubeconfig string, deployment string, filter string, dryRun bool) (string, error) {
	helmArgs := []string{}
	if kubeconfig != "" {
		helmArgs = append(helmArgs, "--kubeconfig", kubeconfig)
	}

	jsonpath := fmt.Sprintf("jsonpath={.items[?(@.metadata.name==\"%s\")].metadata.namespace}", deployment)
	args := []string{"get", "-A", "deploy", "-o", jsonpath}
	if filter != "" {
		args = append(args, filter)
	}

	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", args...)
	if err != nil {
		log.Info().Err(err).Msgf(L("Failed to find %s's namespace, skipping removal"), deployment)
	}

	namespace := string(out)
	if namespace == "" {
		log.Debug().Msgf("Pod is not running, trying to find the namespace using the helm release")
		namespace, err = FindNamespace(deployment, kubeconfig)
		if err != nil {
			log.Info().Err(err).Msgf(L("Cannot guess namespace"))
			return "", nil
		}
	}

	if namespace != "" {
		helmArgs = append(helmArgs, "uninstall", "-n", namespace, deployment)

		if dryRun {
			log.Info().Msgf(L("Would run %s"), "helm "+strings.Join(helmArgs, " "))
		} else {
			log.Info().Msgf(L("Uninstalling %s"), deployment)
			if err := utils.RunCmd("helm", helmArgs...); err != nil {
				return namespace, fmt.Errorf(L("failed to run helm %s: %s"), strings.Join(helmArgs, " "), err)
			}
		}
	}
	return namespace, nil
}

// FindNamespace tries to find the deployment namespace using helm.
func FindNamespace(deployment string, kubeconfig string) (string, error) {
	args := []string{}
	if kubeconfig != "" {
		args = append(args, "--kubeconfig", kubeconfig)
	}
	args = append(args, "list", "-aA", "-f", deployment, "-o", "json")
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "helm", args...)
	if err != nil {
		return "", fmt.Errorf(L("failed to detect %s's namespace using helm: %s"), deployment, err)
	}
	var data []releaseInfo
	if err = json.Unmarshal(out, &data); err != nil {
		return "", fmt.Errorf(L("helm provided an invalid JSON output: %s"), err)
	}

	if len(data) == 1 {
		return data[0].Namespace, nil
	}
	return "", errors.New(L("found no or more than one deployment"))
}

// HasHelmRelease returns whether a helm release is installed or not, even if it failed.
func HasHelmRelease(release string, kubeconfig string) bool {
	if _, err := exec.LookPath("helm"); err == nil {
		args := []string{}
		if kubeconfig != "" {
			args = append(args, "--kubeconfig", kubeconfig)
		}
		args = append(args, "list", "-aAq", "--no-headers", "-f", release)
		out, err := utils.RunCmdOutput(zerolog.TraceLevel, "helm", args...)
		return len(bytes.TrimSpace(out)) != 0 && err == nil
	}
	return false
}

type releaseInfo struct {
	Namespace string `mapstructure:"namespace"`
}
0707010000011C000081B4000000000000000000000001662A752800000F0D000000000000000000000000000000000000002500000000uyuni-tools/shared/kubernetes/k3s.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const k3sTraefikConfigPath = "/var/lib/rancher/k3s/server/manifests/k3s-traefik-config.yaml"

// InstallK3sTraefikConfig install K3s Traefik configuration.
func InstallK3sTraefikConfig(tcpPorts []types.PortMap, udpPorts []types.PortMap) {
	log.Info().Msg(L("Installing K3s Traefik configuration"))

	data := K3sTraefikConfigTemplateData{
		TcpPorts: tcpPorts,
		UdpPorts: udpPorts,
	}
	if err := utils.WriteTemplateToFile(data, k3sTraefikConfigPath, 0600, false); err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to write K3s Traefik configuration"))
	}

	// Wait for traefik to be back
	log.Info().Msg(L("Waiting for Traefik to be reloaded"))
	for i := 0; i < 60; i++ {
		out, err := utils.RunCmdOutput(zerolog.TraceLevel, "kubectl", "get", "job", "-A",
			"-o", "jsonpath={.status.completionTime}", "helm-install-traefik")
		if err == nil {
			completionTime, err := time.Parse(time.RFC3339, string(out))
			if err == nil && time.Since(completionTime).Seconds() < 60 {
				break
			}
		}
	}
}

// UninstallK3sTraefikConfig uninstall K3s Traefik configuration.
func UninstallK3sTraefikConfig(dryRun bool) {
	utils.UninstallFile(k3sTraefikConfigPath, dryRun)
}

// InspectKubernetes check values on a given image and deploy.
func InspectKubernetes(serverImage string, pullPolicy string) (map[string]string, error) {
	for _, binary := range []string{"kubectl", "helm"} {
		if _, err := exec.LookPath(binary); err != nil {
			return map[string]string{}, fmt.Errorf(L("install %s before running this command"), binary)
		}
	}

	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}

	if err := utils.GenerateInspectContainerScript(scriptDir); err != nil {
		return map[string]string{}, err
	}

	command := path.Join(utils.InspectOutputFile.Directory, utils.InspectScriptFilename)

	const podName = "inspector"

	//delete pending pod and then check the node, because in presence of more than a pod GetNode return is wrong
	if err := DeletePod(podName, ServerFilter); err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot delete %s: %s"), podName, err)
	}

	//this is needed because folder with script needs to be mounted
	nodeName, err := GetNode("uyuni")
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot find node running uyuni: %s"), err)
	}

	//generate deploy data
	deployData := types.Deployment{
		APIVersion: "v1",
		Spec: &types.Spec{
			RestartPolicy: "Never",
			NodeName:      nodeName,
			Containers: []types.Container{
				{
					Name: podName,
					VolumeMounts: append(utils.PgsqlRequiredVolumeMounts,
						types.VolumeMount{MountPath: "/var/lib/uyuni-tools", Name: "var-lib-uyuni-tools"}),
					Image: serverImage,
				},
			},
			Volumes: append(utils.PgsqlRequiredVolumes,
				types.Volume{Name: "var-lib-uyuni-tools", HostPath: &types.HostPath{Path: scriptDir, Type: "Directory"}}),
		},
	}
	//transform deploy data in JSON
	override, err := GenerateOverrideDeployment(deployData)
	if err != nil {
		return map[string]string{}, err
	}
	err = RunPod(podName, ServerFilter, serverImage, pullPolicy, command, override)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot run inspect pod: %s"), err)
	}

	inspectResult, err := utils.ReadInspectData(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot inspect data: %s"), err)
	}

	return inspectResult, err
}
0707010000011D000081B4000000000000000000000001662A75280000046A000000000000000000000000000000000000003400000000uyuni-tools/shared/kubernetes/k3sTraefikTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"io"
	"text/template"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

const k3sTraefikConfigTemplate = `apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    ports:
{{- range .TcpPorts }}
      {{ .Name }}:
        port: {{ .Port }}
        expose: true
        exposedPort: {{ .Exposed }}
        protocol: TCP
{{- end }}
{{- range .UdpPorts }}
      {{ .Name }}:
        port: {{ .Port }}
        expose: true
        exposedPort: {{ .Exposed }}
        protocol: UDP
{{- end }}
`

// K3sTraefikConfigTemplateData represents information used to create K3s Traefik helm chart.
type K3sTraefikConfigTemplateData struct {
	TcpPorts []types.PortMap
	UdpPorts []types.PortMap
}

// Render will create the helm chart configuation for K3sTraefik.
func (data K3sTraefikConfigTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("k3sTraefikConfig").Parse(k3sTraefikConfigTemplate))
	return t.Execute(wr, data)
}
0707010000011E000081B4000000000000000000000001662A7528000010BE000000000000000000000000000000000000002C00000000uyuni-tools/shared/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"encoding/base64"
	"fmt"
	"os"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// ClusterInfos represent cluster information.
type ClusterInfos struct {
	KubeletVersion string
	Ingress        string
}

// IsK3s is true if it's a K3s Cluster.
func (infos ClusterInfos) IsK3s() bool {
	return strings.Contains(infos.KubeletVersion, "k3s")
}

// IsRKE2 is true if it's a RKE2 Cluster.
func (infos ClusterInfos) IsRke2() bool {
	return strings.Contains(infos.KubeletVersion, "rke2")
}

// GetKubeconfig returns the path to the default kubeconfig file or "" if none.
func (infos ClusterInfos) GetKubeconfig() string {
	var kubeconfig string
	if infos.IsK3s() {
		// If the user didn't provide a KUBECONFIG value or file, use the k3s default
		kubeconfigPath := os.ExpandEnv("${HOME}/.kube/config")
		if os.Getenv("KUBECONFIG") == "" || !utils.FileExists(kubeconfigPath) {
			kubeconfig = "/etc/rancher/k3s/k3s.yaml"
		}
	}
	// Since even kubectl doesn't work without a trick on rke2, we assume the user has set kubeconfig
	return kubeconfig
}

// CheckCluster return cluster information.
func CheckCluster() (*ClusterInfos, error) {
	// Get the kubelet version
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "node",
		"-o", "jsonpath={.items[0].status.nodeInfo.kubeletVersion}")
	if err != nil {
		return nil, fmt.Errorf(L("failed to get kubelet version: %s"), err)
	}

	var infos ClusterInfos
	infos.KubeletVersion = string(out)
	infos.Ingress, err = guessIngress()
	if err != nil {
		return nil, err
	}

	return &infos, nil
}

func guessIngress() (string, error) {
	// Check for a traefik resource
	err := utils.RunCmd("kubectl", "explain", "ingressroutetcp")
	if err == nil {
		return "traefik", nil
	} else {
		log.Debug().Err(err).Msg("No ingressroutetcp resource deployed")
	}

	// Look for a pod running the nginx-ingress-controller: there is no other common way to find out
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "pod", "-A",
		"-o", "jsonpath={range .items[*]}{.spec.containers[*].args[0]}{.spec.containers[*].command}{end}")
	if err != nil {
		return "", fmt.Errorf(L("failed to get pod commands to look for nginx controller: %s"), err)
	}

	const nginxController = "/nginx-ingress-controller"
	if strings.Contains(string(out), nginxController) {
		return "nginx", nil
	}

	return "", nil
}

// Restart restarts the pod.
func Restart(filter string) error {
	if err := Stop(filter); err != nil {
		return fmt.Errorf(L("cannot stop %s: %s"), filter, err)
	}
	return Start(filter)
}

// Start starts the pod.
func Start(filter string) error {
	// if something is running, we don't need to set replicas to 1
	if _, err := GetNode(filter); err != nil {
		return ReplicasTo(filter, 1)
	}
	log.Debug().Msgf("Already running")
	return nil
}

// Stop stop the pod.
func Stop(filter string) error {
	return ReplicasTo(filter, 0)
}

func get(component string, componentName string, args ...string) ([]byte, error) {
	kubectlArgs := []string{
		"get",
		component,
		componentName,
	}

	kubectlArgs = append(kubectlArgs, args...)

	output, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", kubectlArgs...)
	if err != nil {
		return []byte{}, err
	}
	return output, nil
}

// GetConfigMap returns the value of a given config map.
func GetConfigMap(configMapName string, filter string) (string, error) {
	out, err := get("configMap", configMapName, filter)
	if err != nil {
		return "", fmt.Errorf(L("failed to kubectl get configMap %s %s")+": %s", configMapName, filter, err)
	}

	return string(out), nil
}

// GetSecret returns the value of a given secret.
func GetSecret(secretName string, filter string) (string, error) {
	out, err := get("secret", secretName, filter)
	if err != nil {
		return "", fmt.Errorf(L("failed to kubectl get secret %s %s")+": %s", secretName, filter, err)
	}
	decoded, err := base64.StdEncoding.DecodeString(string(out))
	if err != nil {
		return "", fmt.Errorf(L("Failed to base64 decode configMap %s: %s"), secretName, err)
	}

	return string(decoded), nil
}
0707010000011F000081B4000000000000000000000001662A7528000005F2000000000000000000000000000000000000002600000000uyuni-tools/shared/kubernetes/rke2.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"strconv"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const rke2NginxConfigPath = "/var/lib/rancher/rke2/server/manifests/rke2-ingress-nginx-config.yaml"

// InstallRke2NgixConfig install Rke2 Nginx configuration.
func InstallRke2NginxConfig(tcpPorts []types.PortMap, udpPorts []types.PortMap, namespace string) {
	log.Info().Msg(L("Installing RKE2 Nginx configuration"))

	data := Rke2NginxConfigTemplateData{
		Namespace: namespace,
		TcpPorts:  tcpPorts,
		UdpPorts:  udpPorts,
	}
	if err := utils.WriteTemplateToFile(data, rke2NginxConfigPath, 0600, false); err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to write Rke2 nginx configuration"))
	}

	// Wait for the nginx controller to be back
	log.Info().Msg(L("Waiting for Nginx controller to be reloaded"))
	for i := 0; i < 60; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "daemonset", "-A",
			"-o", "jsonpath={.status.numberReady}", "rke2-ingress-nginx-controller")
		if err == nil {
			if count, err := strconv.Atoi(string(out)); err == nil && count > 0 {
				break
			}
		}
	}
}

// UninstallRke2NgixConfig uninstall Rke2 Nginx configuration.
func UninstallRke2NginxConfig(dryRun bool) {
	utils.UninstallFile(rke2NginxConfigPath, dryRun)
}
07070100000120000081B4000000000000000000000001662A752800000444000000000000000000000000000000000000003300000000uyuni-tools/shared/kubernetes/rke2NginxTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"io"
	"text/template"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

const rke2NginxConfigTemplate = `apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: rke2-ingress-nginx
  namespace: kube-system
spec:
  valuesContent: |-
    controller:
      config:
        hsts: "false"
    tcp:
{{- range .TcpPorts }}
      {{ .Exposed }}: "{{ $.Namespace }}/uyuni-tcp:{{ .Port }}"
{{- end }}
    udp:
{{- range .UdpPorts }}
      {{ .Exposed }}: "{{ $.Namespace }}/uyuni-udp:{{ .Port }}"
{{- end }}
`

// Rke2NginxConfigTemplateData represents information used to create Rke2 Ngix helm chart.
type Rke2NginxConfigTemplateData struct {
	Namespace string
	TcpPorts  []types.PortMap
	UdpPorts  []types.PortMap
}

// Render will create the helm chart configuation for Rke2 Nginx.
func (data Rke2NginxConfigTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("rke2NginxConfig").Parse(rke2NginxConfigTemplate))
	return t.Execute(wr, data)
}
07070100000121000081B4000000000000000000000001662A75280000025D000000000000000000000000000000000000002B00000000uyuni-tools/shared/kubernetes/uninstall.go// SPDX-FileCopyrightText: 2023 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

// Message appended in the uninstall commands for kubernetes.
func UninstallHelp() string {
	return L(`
Note that removing the volumes could also be handled automatically depending on the StorageClass used
when installed on a kubernetes cluster.

For instance on a default K3S install, the local-path-provider storage volumes will
be automatically removed when deleting the deployment even if --purge-volumes argument is not used.`)
}
07070100000122000081B4000000000000000000000001662A7528000030A2000000000000000000000000000000000000002700000000uyuni-tools/shared/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package kubernetes

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// ServerFilter represents filter used to check server app.
const ServerFilter = "-lapp=uyuni"

// ServerFilter represents filter used to check proxy app.
const ProxyFilter = "-lapp=uyuni-proxy"

// waitForDeployment waits at most 60s for a kubernetes deployment to have at least one replica.
// See [isDeploymentReady] for more details.
func WaitForDeployment(namespace string, name string, appName string) error {
	// Find the name of a replica pod
	// Using the app label is a shortcut, not the 100% acurate way to get from deployment to pod
	podName := ""
	jsonpath := fmt.Sprintf("jsonpath={.items[?(@.metadata.labels.app==\"%s\")].metadata.name}", appName)
	cmdArgs := []string{"get", "pod", "-o", jsonpath}
	cmdArgs = addNamespace(cmdArgs, namespace)

	for i := 0; i < 60; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", cmdArgs...)
		if err == nil {
			podName = string(out)
			break
		}
	}

	// We need to wait for the image to be pulled as this can add quite some time
	// Setting a timeout on this is very hard since it hightly depends on network speed and image size
	// List the Pulled events from the pod as we may not see the Pulling if the image was already downloaded
	err := WaitForPulledImage(namespace, podName)
	if err != nil {
		return fmt.Errorf(L("failed to pull image: %s"), err)
	}

	log.Info().Msgf(L("Waiting for %s deployment to be ready in %s namespace\n"), name, namespace)
	// Wait for a replica to be ready
	for i := 0; i < 60; i++ {
		// TODO Look for pod failures
		if IsDeploymentReady(namespace, name) {
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return fmt.Errorf(L("failed to find a ready replica for deployment %s in namespace %s after 60s"), name, namespace)
}

// WaitForPulledImage wait that image is pulled.
func WaitForPulledImage(namespace string, podName string) error {
	log.Info().Msgf(L("Waiting for image of %s pod in %s namespace to be pulled"), podName, namespace)
	pulledArgs := []string{"get", "event",
		"-o", "jsonpath={.items[?(@.reason==\"Pulled\")].message}",
		"--field-selector", "involvedObject.name=" + podName}
	pulledArgs = addNamespace(pulledArgs, namespace)
	failedArgs := []string{"get", "event",
		"-o", "jsonpath={range .items[?(@.reason==\"Failed\")]}{.message}{\"\\n\"}{end}",
		"--field-selector", "involvedObject.name=" + podName}
	failedArgs = addNamespace(failedArgs, namespace)
	for {
		// Look for events indicating an image pull issue
		out, err := utils.RunCmdOutput(zerolog.TraceLevel, "kubectl", failedArgs...)
		if err != nil {
			return fmt.Errorf(L("failed to get failed events for pod %s"), podName)
		}
		lines := strings.Split(string(out), "\n")
		for _, line := range lines {
			if strings.HasPrefix(line, "Failed to pull image") {
				return errors.New(L("failed to pull image"))
			}
		}

		// Has the image pull finished?
		out, err = utils.RunCmdOutput(zerolog.TraceLevel, "kubectl", pulledArgs...)
		if err != nil {
			return fmt.Errorf(L("failed to get events for pod %s"), podName)
		}
		if len(out) > 0 {
			break
		}
		time.Sleep(1 * time.Second)
	}
	return nil
}

// IsDeploymentReady returns true if a kubernetes deployment has at least one ready replica.
// The name can also be a filter parameter like -lapp=uyuni.
// An empty namespace means searching through all the namespaces.
func IsDeploymentReady(namespace string, name string) bool {
	jsonpath := fmt.Sprintf("jsonpath={.items[?(@.metadata.name==\"%s\")].status.readyReplicas}", name)
	args := []string{"get", "-o", jsonpath, "deploy"}
	args = addNamespace(args, namespace)

	out, err := utils.RunCmdOutput(zerolog.TraceLevel, "kubectl", args...)
	// kubectl errors out if the deployment or namespace doesn't exist
	if err == nil {
		if replicas, _ := strconv.Atoi(string(out)); replicas > 0 {
			return true
		}
	}
	return false
}

// DeploymentStatus represents the kubernetes deployment status.
type DeploymentStatus struct {
	AvailableReplicas int
	ReadyReplicas     int
	UpdatedReplicas   int
	Replicas          int
}

// GetDeploymentStatus returns the replicas status of the deployment.
func GetDeploymentStatus(namespace string, name string) (*DeploymentStatus, error) {
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", "get", "deploy", "-n", namespace,
		name, "-o", "jsonpath={.status}")
	if err != nil {
		return nil, err
	}

	var status DeploymentStatus
	if err = json.Unmarshal(out, &status); err != nil {
		return nil, fmt.Errorf(L("failed to parse deployment status: %s"), err)
	}
	return &status, nil
}

// ReplicasTo set the replica for an app to the given value.
// Scale the number of replicas of the server.
func ReplicasTo(filter string, replica uint) error {
	args := []string{"scale", "deploy", filter, "--replicas"}
	log.Debug().Msgf("Setting replicas for pod in %s to %d", filter, replica)
	args = append(args, fmt.Sprint(replica))

	_, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", args...)
	if err != nil {
		return fmt.Errorf(L("cannot run kubectl %s: %s"), args, err)
	}

	pods, err := getPods(filter)
	if err != nil {
		return fmt.Errorf(L("cannot get pods for %s: %s"), filter, err)
	}

	for _, pod := range pods {
		if len(pod) > 0 {
			err = waitForReplica(pod, replica)
			if err != nil {
				return fmt.Errorf(L("replica to %d failed: %s"), replica, err)
			}
		}
	}

	log.Debug().Msgf("Replicas for pod in %s are now %d", filter, replica)

	return err
}

func isPodRunning(podname string, filter string) (bool, error) {
	pods, err := getPods(filter)
	if err != nil {
		return false, fmt.Errorf(L("cannot check if pod %s is running in app %s: %s"), podname, filter, err)
	}
	return utils.Contains(pods, podname), nil
}

func getPods(filter string) (pods []string, err error) {
	log.Debug().Msgf("Checking all pods for %s", filter)
	cmdArgs := []string{"get", "pods", filter, "--output=custom-columns=:.metadata.name", "--no-headers"}
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", cmdArgs...)
	if err != nil {
		return pods, fmt.Errorf(L("cannot execute %s: %s"), strings.Join(cmdArgs, string(" ")), err)
	}
	lines := strings.Split(string(out), "\n")
	pods = append(pods, lines...)
	log.Debug().Msgf("Pods in %s are %s", filter, pods)

	return pods, err
}

func waitForReplicaZero(podname string) error {
	waitSeconds := 120
	cmdArgs := []string{"get", "pod", podname}

	for i := 0; i < waitSeconds; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", cmdArgs...)
		/* Assume that if the command return an error at the first iteration, it's because it failed,
		* next iteration because the pod was actually deleted
		 */
		if err != nil && i == 0 {
			return fmt.Errorf(L("cannot get pod informations %s: %s"), podname, err)
		}
		outStr := strings.TrimSuffix(string(out), "\n")
		if len(outStr) == 0 {
			log.Debug().Msgf("Pod %s has been deleted", podname)
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return fmt.Errorf(L("cannot set replicas for %s to zero"), podname)
}

func waitForReplica(podname string, replica uint) error {
	waitSeconds := 120
	log.Debug().Msgf("Checking replica for %s ready to %d", podname, replica)
	if replica == 0 {
		return waitForReplicaZero(podname)
	}
	cmdArgs := []string{"get", "pod", podname, "--output=custom-columns=STATUS:.status.phase", "--no-headers"}

	var err error

	for i := 0; i < waitSeconds; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", cmdArgs...)
		outStr := strings.TrimSuffix(string(out), "\n")
		if err != nil {
			return fmt.Errorf(L("cannot execute %s: %s"), strings.Join(cmdArgs, string(" ")), err)
		}
		if string(outStr) == "Running" {
			log.Debug().Msgf("%s pod replica is now %d", podname, replica)
			break
		}
		log.Debug().Msgf("Pod %s replica is %s in %d seconds.", podname, string(out), i)
		time.Sleep(1 * time.Second)
	}
	if err != nil {
		return fmt.Errorf(L("pod %s replica is not %d in %s seconds: %s"), podname, replica, strconv.Itoa(waitSeconds), err)
	}
	return nil
}

func addNamespace(args []string, namespace string) []string {
	if namespace != "" {
		args = append(args, "-n", namespace)
	} else {
		args = append(args, "-A")
	}
	return args
}

// GetPullPolicy return pullpolicy in lower case, if exists.
func GetPullPolicy(name string) string {
	policies := map[string]string{
		"always":       "Always",
		"never":        "Never",
		"ifnotpresent": "IfNotPresent",
	}
	policy := policies[strings.ToLower(name)]
	if policy == "" {
		log.Fatal().Msgf(L("%s is not a valid image pull policy value"), name)
	}
	return policy
}

// RunPod runs a pod, waiting for its execution and deleting it.
func RunPod(podname string, filter string, image string, pullPolicy string, command string, override ...string) error {
	arguments := []string{"run", podname, "--image", image, "--image-pull-policy", pullPolicy, filter}

	if len(override) > 0 {
		arguments = append(arguments, `--override-type=strategic`)
		for _, arg := range override {
			overrideParam := "--overrides=" + arg
			arguments = append(arguments, overrideParam)
		}
	}

	arguments = append(arguments, "--command", "--", command)
	err := utils.RunCmdStdMapping(zerolog.DebugLevel, "kubectl", arguments...)
	if err != nil {
		return fmt.Errorf(L("cannot run %s using image %s: %s"), command, image, err)
	}
	err = waitForPod(podname)
	if err != nil {
		return fmt.Errorf(L("deleting pod %s. Status fails with error %s"), podname, err)
	}

	defer func() {
		err = DeletePod(podname, filter)
	}()
	return nil
}

// Delete a kubernetes pod named podname.
func DeletePod(podname string, filter string) error {
	isRunning, err := isPodRunning(podname, filter)
	if err != nil {
		return fmt.Errorf(L("cannot delete pod %s: %s"), podname, err)
	}
	if !isRunning {
		log.Debug().Msgf("no need to delete pod %s because is not running", podname)
		return nil
	}
	arguments := []string{"delete", "pod", podname}
	_, err = utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", arguments...)
	if err != nil {
		return fmt.Errorf(L("cannot delete pod %s: %s"), podname, err)
	}
	return nil
}

func waitForPod(podname string) error {
	status := "Succeeded"
	waitSeconds := 120
	log.Debug().Msgf("Checking status for %s pod. Waiting %s seconds until status is %s", podname, strconv.Itoa(waitSeconds), status)
	cmdArgs := []string{"get", "pod", podname, "--output=custom-columns=STATUS:.status.phase", "--no-headers"}
	var err error
	for i := 0; i < waitSeconds; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", cmdArgs...)
		outStr := strings.TrimSuffix(string(out), "\n")
		if err != nil {
			return fmt.Errorf(L("cannot execute %s: %s"), strings.Join(cmdArgs, string(" ")), err)
		}
		if strings.EqualFold(outStr, status) {
			log.Debug().Msgf("%s pod status is %s", podname, status)
			return nil
		}
		if strings.EqualFold(outStr, "Failed") {
			return fmt.Errorf(L("error during execution of %s: %s"), strings.Join(cmdArgs, string(" ")), err)
		}
		log.Debug().Msgf("Pod %s status is %s for %d seconds.", podname, outStr, i)
		time.Sleep(1 * time.Second)
	}
	return fmt.Errorf(L("pod %s status is not %s in %s seconds: %s"), podname, status, strconv.Itoa(waitSeconds), err)
}

// GetNode return the node where the app is running.
func GetNode(filter string) (string, error) {
	nodeName := ""
	cmdArgs := []string{"get", "pod", filter, "-o", "jsonpath={.items[*].spec.nodeName}"}
	for i := 0; i < 60; i++ {
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", cmdArgs...)
		if err == nil {
			nodeName = string(out)
			break
		}
	}
	if len(nodeName) > 0 {
		log.Debug().Msgf("Node name matching filter %s is: %s", filter, nodeName)
	} else {
		return "", fmt.Errorf(L("cannot find node name matching filter %s"), filter)
	}
	return nodeName, nil
}

// GenerateOverrideDeployment generate a JSON files represents the deployment information.
func GenerateOverrideDeployment(deployData types.Deployment) (string, error) {
	ret, err := json.Marshal(deployData)
	if err != nil {
		return "", fmt.Errorf(L("cannot serialize pod definition override: %s"), err)
	}
	return string(ret), nil
}
07070100000123000041FD000000000000000000000003662A752800000000000000000000000000000000000000000000001800000000uyuni-tools/shared/l10n07070100000124000081B4000000000000000000000001662A75280000021B000000000000000000000000000000000000002300000000uyuni-tools/shared/l10n/gettext.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package l10n

import "github.com/chai2010/gettext-go"

// L localizes a string using the set up gettext domain and locale.
// This is an alias for gettext.Gettext().
func L(message string) string {
	return gettext.Gettext(message)
}

// NL returns a localized message depending on the value of count.
// This is an alias for gettext.NGettext().
func NL(message string, plural string, count int) string {
	return gettext.NGettext(message, plural, count)
}
07070100000125000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001E00000000uyuni-tools/shared/l10n/utils07070100000126000081B4000000000000000000000001662A75280000051D000000000000000000000000000000000000002B00000000uyuni-tools/shared/l10n/utils/defaultfs.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package l10n

import "github.com/chai2010/gettext-go"

// DefaultFS providing a empty data if no data is found.
type DefaultFS struct {
	osFs gettext.FileSystem
	gettext.FileSystem
}

// New creates a new DefaultFS delegating to an OS FileSystem.
func New(path string) *DefaultFS {
	return &DefaultFS{
		osFs: gettext.OS(path),
	}
}

// LocaleList gets the list of locales from the underlying os FileSystem.
func (f *DefaultFS) LocaleList() []string {
	return f.osFs.LocaleList()
}

// LoadMessagesFile loads a messages or returns the content of an empty json file.
func (f *DefaultFS) LoadMessagesFile(domain, lang, ext string) ([]byte, error) {
	osFile, err := f.osFs.LoadMessagesFile(domain, lang, ext)
	// Return an empty file by default
	if err != nil {
		return []byte("[]"), nil
	}
	return osFile, nil
}

// LoadResourceFile loads the resource file or returns empty data.
func (f *DefaultFS) LoadResourceFile(domain, lang, ext string) ([]byte, error) {
	osFile, err := f.osFs.LoadResourceFile(domain, lang, ext)
	// Return an empty file by default
	if err != nil {
		return []byte{}, nil
	}
	return osFile, nil
}

// String returns a name of the FileSystem.
func (f *DefaultFS) String() string {
	return "DefaultFS"
}
07070100000127000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001A00000000uyuni-tools/shared/podman07070100000128000081B4000000000000000000000001662A752800001EB0000000000000000000000000000000000000002400000000uyuni-tools/shared/podman/images.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const rpmImageDir = "/usr/share/suse-docker-images/native/"

var registries = []string{
	"registry.suse.com",
	"registry.opensuse.com",
}

// Ensure the container image is pulled or pull it if the pull policy allows it.
//
// Returns the image name to use. Note that it may be changed if the image has been loaded from a local RPM package.
func PrepareImage(image string, pullPolicy string, args ...string) (string, error) {
	if strings.ToLower(pullPolicy) != "always" {
		log.Info().Msgf(L("Ensure image %s is available"), image)

		presentImage, err := IsImagePresent(image)
		if err != nil {
			return image, err
		}

		if len(presentImage) > 0 {
			log.Debug().Msgf("Image %s already present", presentImage)
			return presentImage, nil
		}
	}

	rpmImageFile := GetRpmImagePath(image)

	if len(rpmImageFile) > 0 {
		log.Debug().Msgf("Image %s present as RPM. Loading it", image)
		loadedImage, err := loadRpmImage(rpmImageFile)
		if err != nil {
			log.Warn().Msgf(L("Cannot use RPM image for %s: %s"), image, err)
		} else {
			log.Info().Msgf(L("Using the %s image loaded from the RPM instead of its online version %s"), strings.TrimSpace(loadedImage), image)
			return loadedImage, nil
		}
	} else {
		log.Info().Msgf(L("Cannot find RPM image for %s"), image)
	}

	if strings.ToLower(pullPolicy) != "never" {
		log.Debug().Msgf("Pulling image %s because it is missing and pull policy is not 'never'", image)
		return image, pullImage(image, args...)
	}

	return image, fmt.Errorf(L("image %s is missing and cannot be fetched"), image)
}

// GetRpmImageName return the RPM Image name and the tag, given an image.
func GetRpmImageName(image string) (rpmImageFile string, tag string) {
	for _, registry := range registries {
		if strings.HasPrefix(image, registry) {
			rpmImageFile = strings.ReplaceAll(image, registry+"/", "")
			rpmImageFile = strings.ReplaceAll(rpmImageFile, "/", "-")
			parts := strings.Split(rpmImageFile, ":")
			tag = "latest"
			if len(parts) > 1 {
				tag = parts[1]
			}
			rpmImageFile = parts[0]
			return rpmImageFile, tag
		}
	}
	return "", ""
}

// BuildRpmImagePath checks the image metadata and returns the RPM Image path.
func BuildRpmImagePath(byteValue []byte, rpmImageFile string, tag string) (string, error) {
	var data types.Metadata
	if err := json.Unmarshal(byteValue, &data); err != nil {
		return "", fmt.Errorf(L("cannot unmarshal image RPM metadata: %s"), err)
	}
	fullPathFile := rpmImageDir + data.Image.File
	if data.Image.Name == rpmImageFile {
		for _, metadataTag := range data.Image.Tags {
			if metadataTag == tag {
				return fullPathFile, nil
			}
		}
	}
	return "", nil
}

// GetRpmImagePath return the RPM image path.
func GetRpmImagePath(image string) string {
	log.Debug().Msgf("Looking for installed RPM package containing %s image", image)

	rpmImageFile, tag := GetRpmImageName(image)

	files, err := os.ReadDir(rpmImageDir)
	if err != nil {
		log.Debug().Msgf("Cannot read directory %s: %s", rpmImageDir, err)
		return ""
	}

	for _, file := range files {
		if !strings.HasSuffix(file.Name(), "metadata") {
			continue
		}
		fullPathFileName := path.Join(rpmImageDir, file.Name())
		log.Debug().Msgf("Parsing metadata file %s", fullPathFileName)
		fileHandler, err := os.Open(fullPathFileName)
		if err != nil {
			log.Debug().Msgf("Error opening metadata file %s: %s", fullPathFileName, err)
			continue
		}
		defer fileHandler.Close()
		byteValue, err := io.ReadAll(fileHandler)
		if err != nil {
			log.Debug().Msgf("Error reading metadata file %s: %s", fullPathFileName, err)
			continue
		}

		fullPathFile, err := BuildRpmImagePath(byteValue, rpmImageFile, tag)
		if err != nil {
			log.Warn().Msgf(L("Cannot unmarshal metadata file %s: %s"), fullPathFileName, err)
			return ""
		}
		if len(fullPathFile) > 0 {
			log.Debug().Msgf("%s match with %s", fullPathFileName, image)
			return fullPathFile
		}
		log.Debug().Msgf("%s does not match with %s", fullPathFileName, image)
	}
	log.Debug().Msgf("No installed RPM package containing %s image", image)
	return ""
}

func loadRpmImage(rpmImageBasePath string) (string, error) {
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "load", "--quiet", "--input", rpmImageBasePath)
	if err != nil {
		return "", err
	}
	parseOutput := strings.SplitN(string(out), ":", 2)
	if len(parseOutput) == 2 {
		return strings.TrimSpace(parseOutput[1]), nil
	}
	return "", fmt.Errorf(L("error parsing: %s"), string(out))
}

// IsImagePresent return true if the image is present.
func IsImagePresent(image string) (string, error) {
	log.Debug().Msgf("Checking for %s", image)
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "images", "--format={{ .Repository }}", image)
	if err != nil {
		return "", fmt.Errorf(L("failed to check if image %s has already been pulled"), image)
	}

	if len(bytes.TrimSpace(out)) > 0 {
		return image, nil
	}

	splitImage := strings.SplitN(string(image), "/", 2)
	if len(splitImage) < 2 {
		return "", nil
	}
	log.Debug().Msgf("Checking for local image of %s", image)
	out, err = utils.RunCmdOutput(zerolog.DebugLevel, "podman", "images", "--quiet", "localhost/"+splitImage[1])
	if err != nil {
		return "", fmt.Errorf(L("failed to check if image %s has already been pulled"), image)
	}
	if len(bytes.TrimSpace(out)) > 0 {
		return "localhost/" + splitImage[1], nil
	}

	return "", nil
}

// GetPulledImageName returns the fullname of a pulled image.
func GetPulledImageName(image string) (string, error) {
	parts := strings.Split(image, "/")
	imageWithTag := parts[len(parts)-1]
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "images", imageWithTag, "--format", "{{.Repository}}")
	if err != nil {
		return "", fmt.Errorf(L("failed to check if image %s has already been pulled"), parts[len(parts)-1])
	}
	return string(bytes.TrimSpace(out)), nil
}

func pullImage(image string, args ...string) error {
	if utils.ContainsUpperCase(image) {
		return fmt.Errorf(L("%s should contains just lower case character, otherwise podman pull would fails"), image)
	}
	log.Info().Msgf(L("Running podman pull %s"), image)
	podmanImageArgs := []string{"pull", image}
	podmanArgs := append(podmanImageArgs, args...)

	loglevel := zerolog.DebugLevel
	if len(args) > 0 {
		loglevel = zerolog.Disabled
		log.Debug().Msg("Additional arguments for pull command will not be shown.")
	}

	return utils.RunCmdStdMapping(loglevel, "podman", podmanArgs...)
}

// ShowAvailableTag  returns the list of available tag for a given image.
func ShowAvailableTag(image string) ([]string, error) {
	log.Info().Msgf(L("Running podman image search --list-tags %s --format={{.Tag}}"), image)

	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "image", "search", "--list-tags", image, "--format={{.Tag}}")
	if err != nil {
		return []string{}, fmt.Errorf(L("cannot find any tag for image %s: %s"), image, err)
	}

	tags := strings.Split(string(out), "\n")
	return tags, nil
}

// GetRunningImage given a container name, return the image name.
func GetRunningImage(container string) (string, error) {
	log.Info().Msgf(L("Running podman ps --filter=name=%s --format={{ .Image }}"), container)

	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "ps", fmt.Sprintf("--filter=name=%s", container), "--format='{{ .Image }}'")
	if err != nil {
		return "", fmt.Errorf(L("cannot find any running image for container %s: %s"), container, err)
	}

	image := strings.TrimSpace(string(out))
	return image, nil
}
07070100000129000081B4000000000000000000000001662A7528000009B1000000000000000000000000000000000000002900000000uyuni-tools/shared/podman/images_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"testing"
)

func TestGetRpmImageName(t *testing.T) {
	data := [][]string{
		{"suse-manager-5.0-x86_64-proxy-httpd", "latest", "registry.suse.com/suse/manager/5.0/x86_64/proxy-httpd"},
		{"suse-manager-5.0-x86_64-proxy-httpd", "latest", "registry.suse.com/suse/manager/5.0/x86_64/proxy-httpd:latest"},
		{"suse-manager-5.0-x86_64-proxy-httpd", "beta1", "registry.suse.com/suse/manager/5.0/x86_64/proxy-httpd:beta1"},
	}

	for i, testCase := range data {
		rpmImage := testCase[0]
		tag := testCase[1]
		image := testCase[2]

		rpmImageResult, tagResult := GetRpmImageName(image)

		if rpmImage != rpmImageResult {
			t.Errorf("Testcase %d: Expected %s got %s when computing RPM for image %s", i, rpmImage, rpmImageResult, image)
		}
		if tag != tagResult {
			t.Errorf("Testcase %d: Expected %s got %s when computing RPM for image %s", i, tag, tagResult, image)
		}
	}
}

func TestMatchingMetadata(t *testing.T) {
	jsonData := []byte(`{
		"image": {
			"name": "suse-manager-5.0-x86_64-proxy-tftpd",
			"tags": ["latest", "5.0.0-beta1", "5.0.0-beta1.59.128"],
			"file": "suse-manager-5.0-x86_64-proxy-tftpd-latest.x86_64-59.128.tar"
		}
	}`)

	data := [][]string{
		{"/usr/share/suse-docker-images/native/suse-manager-5.0-x86_64-proxy-tftpd-latest.x86_64-59.128.tar", "suse-manager-5.0-x86_64-proxy-httpd", "latest"},
		{"/usr/share/suse-docker-images/native/suse-manager-5.0-x86_64-proxy-tftpd-latest.x86_64-59.128.tar", "suse-manager-5.0-x86_64-proxy-httpd", "5.0.0-beta1.59.128"},
		{"", "suse-manager-5.0-x86_64-proxy-httpd", "missing_tag"},
		{"", "missing_image", "missing_tag"},
		{"", "missing_image", "latest"},
	}

	for i, testCase := range data {
		expectedResult := testCase[0]
		rpmImage := testCase[1]
		tag := testCase[2]

		testResult, err := BuildRpmImagePath(jsonData, rpmImage, tag)

		if err != nil && expectedResult != testResult {
			t.Errorf("Testcase %d: Expected %s got %s when computing RPM for image %s with tag %s", i, expectedResult, testResult, rpmImage, tag)
		}
	}

	jsonDataInvalidWithTypo := []byte(`{
		"image: {
			"name": "suse-manager-5.0-x86_64-proxy-tftpd",
			"tags": ["latest", "5.0.0-beta1", "5.0.0-beta1.59.128"],
			"file": "suse-manager-5.0-x86_64-proxy-tftpd-latest.x86_64-59.128.tar"
		}
	}`)

	_, err := BuildRpmImagePath(jsonDataInvalidWithTypo, "", "")
	if err == nil {
		t.Error("typo in json: this should fail")
	}
}
0707010000012A000081B4000000000000000000000001662A752800000E2F000000000000000000000000000000000000002500000000uyuni-tools/shared/podman/network.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"fmt"
	"os/exec"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

// The name of the podman network for Uyuni and its proxies.
const UyuniNetwork = "uyuni"

// SetupNetwork creates the podman network.
func SetupNetwork() error {
	log.Info().Msgf(L("Setting up %s network"), UyuniNetwork)

	ipv6Enabled := isIpv6Enabled()

	// check if network exists before trying to get the IPV6 information
	networkExists := IsNetworkPresent(UyuniNetwork)
	if networkExists {
		log.Debug().Msgf("%s network already present", UyuniNetwork)
		// Check if the uyuni network exists and is IPv6 enabled
		hasIpv6, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "network", "inspect", "--format", "{{.IPv6Enabled}}", UyuniNetwork)
		if err == nil {
			if string(hasIpv6) != "true" && ipv6Enabled {
				log.Info().Msgf(L("%s network doesn't have IPv6, deleting existing network to enable IPv6 on it"), UyuniNetwork)
				err := utils.RunCmd("podman", "network", "rm", UyuniNetwork,
					"--log-level", log.Logger.GetLevel().String())
				if err != nil {
					return fmt.Errorf(L("failed to remove %s podman network: %s"), UyuniNetwork, err)
				}
			} else {
				log.Info().Msgf(L("Reusing existing %s network"), UyuniNetwork)
				return nil
			}
		}
	}

	args := []string{"network", "create"}
	if ipv6Enabled {
		// An IPv6 network on a host where IPv6 is disabled doesn't work: don't try it.
		// Check if the networkd backend is netavark
		out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "info", "--format", "{{.Host.NetworkBackend}}")
		backend := strings.Trim(string(out), "\n")
		if err != nil {
			return fmt.Errorf(L("failed to find podman's network backend: %s"), err)
		} else if backend != "netavark" {
			log.Info().Msgf(L("Podman's network backend (%s) is not netavark, skipping IPv6 enabling on %s network"), backend, UyuniNetwork)
		} else {
			args = append(args, "--ipv6")
		}
	}
	args = append(args, UyuniNetwork)
	err := utils.RunCmd("podman", args...)
	if err != nil {
		return fmt.Errorf(L("failed to create %s network with IPv6 enabled: %s"), UyuniNetwork, err)
	}
	return nil
}

func isIpv6Enabled() bool {
	files := []string{
		"/sys/module/ipv6/parameters/disable",
		"/proc/sys/net/ipv6/conf/default/disable_ipv6",
		"/proc/sys/net/ipv6/conf/all/disable_ipv6",
	}

	for _, file := range files {
		// Mind that we are checking disable files, the semantic is inverted
		if utils.GetFileBoolean(file) {
			return false
		}
	}
	return true
}

// DeleteNetwork deletes the uyuni podman network.
// If dryRun is set to true, nothing will be done, only messages logged to explain what would happen.
func DeleteNetwork(dryRun bool) {
	err := utils.RunCmd("podman", "network", "exists", UyuniNetwork)
	if err != nil {
		log.Info().Msgf(L("Network %s already removed"), UyuniNetwork)
	} else {
		if dryRun {
			log.Info().Msgf(L("Would run %s"), "podman network rm "+UyuniNetwork)
		} else {
			err := utils.RunCmd("podman", "network", "rm", UyuniNetwork)
			if err != nil {
				log.Error().Msgf(L("Failed to remove network %s"), UyuniNetwork)
			} else {
				log.Info().Msg(L("Network removed"))
			}
		}
	}
}

// IsNetworkPresent returns whether a network is already present.
func IsNetworkPresent(network string) bool {
	cmd := exec.Command("podman", "network", "exists", network)
	if err := cmd.Run(); err != nil {
		return false
	}
	return cmd.ProcessState.ExitCode() == 0
}
0707010000012B000081B4000000000000000000000001662A752800001206000000000000000000000000000000000000002500000000uyuni-tools/shared/podman/systemd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path"

	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const servicesPath = "/etc/systemd/system/"

// Name of the systemd service for the server.
const ServerService = "uyuni-server"

// Name of the systemd service for the coco attestation container.
const ServerAttestationService = "uyuni-server-attestation"

// Name of the systemd service for the proxy.
const ProxyService = "uyuni-proxy-pod"

// HasService returns if a systemd service is installed.
// name is the name of the service without the '.service' part.
func HasService(name string) bool {
	err := utils.RunCmd("systemctl", "list-unit-files", name+".service")
	return err == nil
}

// GetServicePath return the path for a given service.
func GetServicePath(name string) string {
	return path.Join(servicesPath, name+".service")
}

// UninstallService stops and remove a systemd service.
// If dryRun is set to true, nothing happens but messages are logged to explain what would be done.
func UninstallService(name string, dryRun bool) {
	servicePath := GetServicePath(name)
	if !HasService(name) {
		log.Info().Msgf(L("Systemd has no %s.service unit"), name)
	} else {
		if dryRun {
			log.Info().Msgf(L("Would run %s"), "systemctl disable --now "+name)
			log.Info().Msgf(L("Would remove %s"), servicePath)
		} else {
			log.Info().Msgf(L("Disable %s service"), name)
			// disable server
			err := utils.RunCmd("systemctl", "disable", "--now", name)
			if err != nil {
				log.Error().Err(err).Msgf(L("Failed to disable %s service"), name)
			}

			// Remove the service unit
			log.Info().Msgf(L("Remove %s"), servicePath)
			if err := os.Remove(servicePath); err != nil {
				log.Error().Err(err).Msgf(L("Failed to remove %s.service file"), name)
			}
		}
	}
}

// ReloadDaemon resets the failed state of services and reload the systemd daemon.
// If dryRun is set to true, nothing happens but messages are logged to explain what would be done.
func ReloadDaemon(dryRun bool) error {
	if dryRun {
		log.Info().Msgf(L("Would run %s"), "systemctl reset-failed")
		log.Info().Msgf(L("Would run %s"), "systemctl daemon-reload")
	} else {
		err := utils.RunCmd("systemctl", "reset-failed")
		if err != nil {
			return errors.New(L("failed to reset-failed systemd"))
		}
		err = utils.RunCmd("systemctl", "daemon-reload")
		if err != nil {
			return errors.New(L("failed to reload systemd daemon"))
		}
	}
	return nil
}

// IsServiceRunning returns whether the systemd service is started or not.
func IsServiceRunning(service string) bool {
	cmd := exec.Command("systemctl", "is-active", "-q", service)
	if err := cmd.Run(); err != nil {
		return false
	}
	return cmd.ProcessState.ExitCode() == 0
}

// RestartService restarts the systemd service.
func RestartService(service string) error {
	if err := utils.RunCmd("systemctl", "restart", service); err != nil {
		return fmt.Errorf(L("failed to restart systemd %s.service: %s"), service, err)
	}
	return nil
}

// StartService starts the systemd service.
func StartService(service string) error {
	if err := utils.RunCmd("systemctl", "start", service); err != nil {
		return fmt.Errorf(L("failed to start systemd %s.service: %s"), service, err)
	}
	return nil
}

// StopService starts the systemd service.
func StopService(service string) error {
	if err := utils.RunCmd("systemctl", "stop", service); err != nil {
		return fmt.Errorf(L("failed to stop systemd %s.service: %s"), service, err)
	}
	return nil
}

// EnableService enables and starts a systemd service.
func EnableService(service string) error {
	if err := utils.RunCmd("systemctl", "enable", "--now", service); err != nil {
		return fmt.Errorf(L("failed to enable %s systemd service: %s"), service, err)
	}
	return nil
}

// Create new systemd service configuration file.
func GenerateSystemdConfFile(serviceName string, section string, body string) error {
	systemdFilePath := GetServicePath(serviceName)

	systemdConfFolder := systemdFilePath + ".d"
	if err := os.MkdirAll(systemdConfFolder, 0750); err != nil {
		return fmt.Errorf(L("failed to create %s folder: %s"), systemdConfFolder, err)
	}
	systemdConfFilePath := path.Join(systemdConfFolder, section+".conf")

	content := []byte("[" + section + "]" + "\n" + body + "\n")
	if err := os.WriteFile(systemdConfFilePath, content, 0644); err != nil {
		return fmt.Errorf(L("cannot write %s file: %s"), systemdConfFilePath, err)
	}

	return nil
}
0707010000012C000081B4000000000000000000000001662A752800001EDE000000000000000000000000000000000000002300000000uyuni-tools/shared/podman/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/utils"
)

const commonArgs = "--rm --cap-add NET_RAW --tmpfs /run -v cgroup:/sys/fs/cgroup:rw"

// ServerContainerName represents the server container name.
const ServerContainerName = "uyuni-server"

// ProxyContainerNames represents all the proxy container names.
var ProxyContainerNames = []string{
	"uyuni-proxy-httpd",
	"uyuni-proxy-salt-broker",
	"uyuni-proxy-squid",
	"uyuni-proxy-ssh",
	"uyuni-proxy-tftpd",
}

// PodmanFlags stores the podman arguments.
type PodmanFlags struct {
	Args   []string         `mapstructure:"arg"`
	Mounts PodmanMountFlags `mapstructure:"mount"`
}

// PodmanMountFlags stores the --podman-mount-* arguments.
type PodmanMountFlags struct {
	Cache      string
	Postgresql string
	Spacewalk  string
	Www        string
}

// GetCommonParams splits the common arguments.
func GetCommonParams() []string {
	return strings.Split(commonArgs, " ")
}

// AddPodmanArgFlag add the podman arguments to a command.
func AddPodmanArgFlag(cmd *cobra.Command) {
	cmd.Flags().StringSlice("podman-arg", []string{}, L("Extra arguments to pass to podman"))
}

// AddPodmanInstallFlag add the podman install arguments to a command.
func AddPodmanInstallFlag(cmd *cobra.Command) {
	AddPodmanArgFlag(cmd)
	cmd.Flags().String("podman-mount-cache", "", L("Path to custom /var/cache volume"))
	cmd.Flags().String("podman-mount-postgresql", "", L("Path to custom /var/lib/pgsql volume"))
	cmd.Flags().String("podman-mount-spacewalk", "", L("Path to custom /var/spacewalk volume"))
	cmd.Flags().String("podman-mount-www", "", L("Path to custom /srv/www/ volume"))

	_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "podman", Title: "Podman Flags"})
	_ = utils.AddFlagToHelpGroupID(cmd, "podman-arg", "podman")
	_ = utils.AddFlagToHelpGroupID(cmd, "podman-mount-cache", "podman")
	_ = utils.AddFlagToHelpGroupID(cmd, "podman-mount-postgresql", "podman")
	_ = utils.AddFlagToHelpGroupID(cmd, "podman-mount-spacewalk", "podman")
	_ = utils.AddFlagToHelpGroupID(cmd, "podman-mount-www", "podman")
}

// EnablePodmanSocket enables the podman socket.
func EnablePodmanSocket() error {
	err := utils.RunCmd("systemctl", "enable", "--now", "podman.socket")
	if err != nil {
		return fmt.Errorf(L("failed to enable podman.socket unit: %s"), err)
	}
	return err
}

// RunContainer execute a container.
func RunContainer(name string, image string, extraArgs []string, cmd []string) error {
	podmanArgs := append([]string{"run", "--name", name}, GetCommonParams()...)
	podmanArgs = append(podmanArgs, extraArgs...)
	for _, volume := range utils.ServerVolumeMounts {
		podmanArgs = append(podmanArgs, "-v", volume.Name+":"+volume.MountPath)
	}
	podmanArgs = append(podmanArgs, image)
	podmanArgs = append(podmanArgs, cmd...)

	err := utils.RunCmdStdMapping(zerolog.DebugLevel, "podman", podmanArgs...)
	if err != nil {
		return fmt.Errorf(L("failed to run %s container: %s"), name, err)
	}

	return nil
}

// DeleteContainer deletes a container based on its name.
// If dryRun is set to true, nothing will be done, only messages logged to explain what would happen.
func DeleteContainer(name string, dryRun bool) {
	if out, _ := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "ps", "-a", "-q", "-f", "name="+name); len(out) > 0 {
		if dryRun {
			log.Info().Msgf(L("Would run podman kill %s for container id: %s"), name, out)
			log.Info().Msgf(L("Would run podman remove %s for container id: %s"), name, out)
		} else {
			log.Info().Msgf(L("Run podman kill %s for container id: %s"), name, out)
			err := utils.RunCmd("podman", "kill", name)
			if err != nil {
				log.Error().Err(err).Msg(L("Failed to kill the server"))

				log.Info().Msgf(L("Run podman remove %s for container id: %s"), name, out)
				err = utils.RunCmd("podman", "rm", name)
				if err != nil {
					log.Error().Err(err).Msg(L("Error removing container"))
				}
			}
		}
	} else {
		log.Info().Msg(L("Container already removed"))
	}
}

// DeleteVolume deletes a podman volume based on its name.
// If dryRun is set to true, nothing will be done, only messages logged to explain what would happen.
func DeleteVolume(name string, dryRun bool) error {
	exists := isVolumePresent(name)
	if exists {
		if dryRun {
			log.Info().Msgf(L("Would run %s"), "podman volume rm "+name)
		} else {
			log.Info().Msgf(L("Run %s"), "podman volume rm "+name)
			err := utils.RunCmd("podman", "volume", "rm", name)
			if err != nil {
				log.Error().Err(err).Msgf(L("Failed to remove volume %s"), name)
			}
		}
	}
	return nil
}

func isVolumePresent(volume string) bool {
	cmd := exec.Command("podman", "volume", "exists", volume)
	if err := cmd.Run(); err != nil {
		return false
	}
	return cmd.ProcessState.ExitCode() == 0
}

// LinkVolumes adds the symlinks for the podman volumes if needed.
func LinkVolumes(mountFlags *PodmanMountFlags) error {
	graphRoot, err := getGraphRoot()
	if err != nil {
		return err
	}

	data := map[string]string{
		"var-cache":     mountFlags.Cache,
		"var-spacewalk": mountFlags.Spacewalk,
		"var-pgsql":     mountFlags.Postgresql,
		"srv-www":       mountFlags.Www,
	}
	for volume, value := range data {
		if value != "" {
			volumePath := path.Join(graphRoot, "volumes", volume)
			if utils.FileExists(volumePath) {
				return fmt.Errorf(L("volume folder (%s) already exists, cannot link it to %s"), volumePath, value)
			}
			baseFolder := path.Join(graphRoot, "volumes")
			if err := utils.RunCmd("mkdir", "-p", baseFolder); err != nil {
				return fmt.Errorf(L("failed to create volumes folder %s: %s"), baseFolder, err)
			}

			if err := utils.RunCmd("ln", "-s", value, volumePath); err != nil {
				return fmt.Errorf(L("failed to link volume folder %s to %s: %s"), value, volumePath, err)
			}
		}
	}
	return nil
}

func getGraphRoot() (string, error) {
	out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", "system", "info", "--format", "{{ .Store.GraphRoot }}")
	if err != nil {
		return "", fmt.Errorf(L("failed to get podman's volumes folder: %s"), err)
	}
	return strings.TrimSpace(string(out)), nil
}

// Inspect check values on a given image and deploy.
func Inspect(serverImage string, pullPolicy string) (map[string]string, error) {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("failed to create temporary directory %s"), err)
	}

	inspectedHostValues, err := utils.InspectHost()
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot inspect host values: %s"), err)
	}

	pullArgs := []string{}
	_, scc_user_exist := inspectedHostValues["host_scc_username"]
	_, scc_user_password := inspectedHostValues["host_scc_password"]
	if scc_user_exist && scc_user_password {
		pullArgs = append(pullArgs, "--creds", inspectedHostValues["host_scc_username"]+":"+inspectedHostValues["host_scc_password"])
	}

	preparedImage, err := PrepareImage(serverImage, pullPolicy, pullArgs...)
	if err != nil {
		return map[string]string{}, err
	}

	if err := utils.GenerateInspectContainerScript(scriptDir); err != nil {
		return map[string]string{}, err
	}

	podmanArgs := []string{
		"-v", scriptDir + ":" + utils.InspectOutputFile.Directory,
		"--security-opt", "label:disable",
	}

	err = RunContainer("uyuni-inspect", preparedImage, podmanArgs,
		[]string{utils.InspectOutputFile.Directory + "/" + utils.InspectScriptFilename})
	if err != nil {
		return map[string]string{}, err
	}

	inspectResult, err := utils.ReadInspectData(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot inspect data. %s"), err)
	}

	return inspectResult, err
}
0707010000012D000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001D00000000uyuni-tools/shared/templates0707010000012E000081B4000000000000000000000001662A7528000002D5000000000000000000000000000000000000003000000000uyuni-tools/shared/templates/inspectTemplate.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package templates

import (
	"io"
	"text/template"

	"github.com/uyuni-project/uyuni-tools/shared/types"
)

const inspectTemplate = `#!/bin/bash
# inspect.sh, generated by mgradm
{{- range .Param }}
echo "{{ .Variable }}=$({{ .CLI }})" >> {{ $.OutputFile }}
{{- end }}
exit 0
`

// InspectTemplateData represents information used to create inspect script.
type InspectTemplateData struct {
	Param      []types.InspectData
	OutputFile string
}

// Render will create inspect script.
func (data InspectTemplateData) Render(wr io.Writer) error {
	t := template.Must(template.New("inspect").Parse(inspectTemplate))
	return t.Execute(wr, data)
}
0707010000012F000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001900000000uyuni-tools/shared/types07070100000130000081B4000000000000000000000001662A7528000000FD000000000000000000000000000000000000002200000000uyuni-tools/shared/types/chart.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// ChartFlags represents the flags required by charts.
type ChartFlags struct {
	Namespace string
	Chart     string
	Version   string
	Values    string
}
07070100000131000081B4000000000000000000000001662A7528000007C5000000000000000000000000000000000000002700000000uyuni-tools/shared/types/deployment.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package types

// VolumeMount type used for mapping pod definition structure.
type VolumeMount struct {
	MountPath string `json:"mountPath,omitempty"`
	Name      string `json:"name,omitempty"`
}

// Container type used for mapping pod definition structure.
type Container struct {
	Name         string        `json:"name,omitempty"`
	Image        string        `json:"image,omitempty"`
	VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
}

// PersistentVolumeClaim type used for mapping Volume structure.
type PersistentVolumeClaim struct {
	ClaimName string `json:"claimName,omitempty"`
}

// HostPath type used for mapping Volume structure.
type HostPath struct {
	Path string `json:"path,omitempty"`
	Type string `json:"type,omitempty"`
}

// Secret Item for mapping Secret structure.
type SecretItem struct {
	Key  string `json:"key,omitempty"`
	Path string `json:"path,omitempty"`
}

// Secret type for mapping Volume structure.
type Secret struct {
	SecretName string       `json:"secretName,omitempty"`
	Items      []SecretItem `json:"items,omitempty"`
}

// Volume type for mapping Spec structure.
type Volume struct {
	Name                  string                 `json:"name,omitempty"`
	PersistentVolumeClaim *PersistentVolumeClaim `json:"persistentVolumeClaim,omitempty"`
	HostPath              *HostPath              `json:"hostPath,omitempty"`
	Secret                *Secret                `json:"secret,omitempty"`
}

// Spec type for mapping Deployment structure.
type Spec struct {
	NodeName      string      `json:"nodeName,omitempty"`
	RestartPolicy string      `json:"restartPolicy,omitempty"`
	Containers    []Container `json:"containers,omitempty"`
	Volumes       []Volume    `json:"volumes,omitempty"`
}

// Deployment type can store k8s deployment data.
type Deployment struct {
	APIVersion string `json:"apiVersion,omitempty"`
	Spec       *Spec  `json:"spec,omitempty"`
}
07070100000132000081B4000000000000000000000001662A7528000003B7000000000000000000000000000000000000002300000000uyuni-tools/shared/types/distro.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// Distribution contains information about the distribution.
type Distribution struct {
	TreeLabel    string
	BasePath     string
	ChannelLabel string
	InstallType  string
}

// DistributionDetails contains distro details passed from the command line.
type DistributionDetails struct {
	Name    string
	Version string
	Arch    Arch
}

// Arch type to store architecture.
type Arch string

// Constants for supported archhitectures.
const (
	UnknownArch Arch = "unknown"
	AMD64       Arch = "x86_64"
	AArch64     Arch = "aarch64"
	S390X       Arch = "s390x"
	PPC64LE     Arch = "ppc64le"
)

// Translates string representation of architecture to Arch type.
func GetArch(a string) Arch {
	switch a {
	case "x86_64":
		return AMD64
	case "aarch64":
		return AArch64
	case "s390x":
		return S390X
	case "ppc64le":
		return PPC64LE
	}
	return UnknownArch
}
07070100000133000081B4000000000000000000000001662A7528000000DF000000000000000000000000000000000000002300000000uyuni-tools/shared/types/global.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// GlobalFlags represents the flags used by all commands.
type GlobalFlags struct {
	ConfigPath string
	LogLevel   string
}
07070100000134000081B4000000000000000000000001662A75280000025D000000000000000000000000000000000000002300000000uyuni-tools/shared/types/images.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// ImageFlags represents the flags used by an image.
type ImageFlags struct {
	Name       string `mapstructure:"image"`
	Tag        string `mapstructure:"tag"`
	PullPolicy string `mapstructure:"pullPolicy"`
}

// ImageMetadata represents the image metadata of an RPM image.
type ImageMetadata struct {
	Name string   `json:"name"`
	Tags []string `json:"tags"`
	File string   `json:"file"`
}

// Metadata represents the metadata of an RPM image.
type Metadata struct {
	Image ImageMetadata `json:"image"`
}
07070100000135000081B4000000000000000000000001662A752800000290000000000000000000000000000000000000002400000000uyuni-tools/shared/types/inspect.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

/* InspectData represents CLI command to run in the container
* and the variable where the output is stored.
 */
type InspectData struct {
	Variable string
	CLI      string
}

/* InspectFile represent where the inspect file should be stored
* and the command to run in the container.
 */
type InspectFile struct {
	Directory string
	Basename  string
	Commands  []InspectData
}

// NewInspectData creates an InspectData instance.
func NewInspectData(variable string, cli string) InspectData {
	return InspectData{
		Variable: variable,
		CLI:      cli,
	}
}
07070100000136000081B4000000000000000000000001662A7528000000D6000000000000000000000000000000000000002500000000uyuni-tools/shared/types/networks.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package types

// PortMap describes a port.
type PortMap struct {
	Name     string
	Exposed  int
	Port     int
	Protocol string
}
07070100000137000041FD000000000000000000000002662A752800000000000000000000000000000000000000000000001900000000uyuni-tools/shared/utils07070100000138000081B4000000000000000000000001662A752800000B33000000000000000000000000000000000000002000000000uyuni-tools/shared/utils/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/types"
)

// Default path where to look for locale files.
//
// On SUSE distros this should be overridden with /usr/share/locale.
var LocaleRoot = "locale"

// DefaultNamespace represents the default name used for image.
var DefaultNamespace = "registry.opensuse.org/uyuni"

// DefaultTag represents the default tag used for image.
var DefaultTag = "latest"

// This variable needs to be set a build time using git tags.
var Version = "0.0.0"

// CommandFunc is a function to be executed by a Cobra command.
type CommandFunc[F interface{}] func(*types.GlobalFlags, *F, *cobra.Command, []string) error

// CommandHelper parses the configuration file into the flags and runs the fn function.
// This function should be passed to Command's RunE.
func CommandHelper[T interface{}](
	globalFlags *types.GlobalFlags,
	cmd *cobra.Command,
	args []string,
	flags *T,
	fn CommandFunc[T],
) error {
	viper, err := ReadConfig(globalFlags.ConfigPath, cmd)
	if err != nil {
		return err
	}
	if err := viper.Unmarshal(&flags); err != nil {
		log.Error().Err(err).Msg(L("failed to unmarshall configuration"))
		return fmt.Errorf(L("failed to unmarshall configuration")+": %s", err)
	}
	return fn(globalFlags, flags, cmd, args)
}

// AddBackendFlag add the flag for setting the backend ('podman', 'podman-remote', 'kubectl').
func AddBackendFlag(cmd *cobra.Command) {
	cmd.Flags().String("backend", "", L("tool to use to reach the container. Possible values: 'podman', 'podman-remote', 'kubectl'. Default guesses which to use."))
}

// AddPullPolicyFlag adds the --pullPolicy flag to a command.
//
// Since podman doesn't have such a concept of pull policy like kubernetes,
// the values need some explanations for it:
//   - Never: just check and fail if needed
//   - IfNotPresent: check and pull
//   - Always: pull without checking
//
// For kubernetes the value is simply passed to the helm charts.
func AddPullPolicyFlag(cmd *cobra.Command) {
	cmd.Flags().String("pullPolicy", "IfNotPresent",
		L("set whether to pull the images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"))
}

// AddPullPolicyFlag adds the --pullPolicy flag to an upgrade command.
func AddPullPolicyUpgradeFlag(cmd *cobra.Command) {
	cmd.Flags().String("pullPolicy", "Always",
		L("set whether to pull the images or not. The value can be one of 'Never', 'IfNotPresent' or 'Always'"))
}

// AddPTFFlag add PTF flag to a command.
func AddPTFFlag(cmd *cobra.Command) {
	cmd.Flags().String("ptf", "", L("PTF ID"))
	cmd.Flags().String("test", "", L("Test package ID"))
	cmd.Flags().String("user", "", L("SCC user"))
}
07070100000139000081B4000000000000000000000001662A752800001397000000000000000000000000000000000000002300000000uyuni-tools/shared/utils/config.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"
	"os"
	"path"
	"strings"
	"text/template"

	"github.com/rs/zerolog/log"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

const envPrefix = "UYUNI"
const appName = "uyuni-tools"
const configFilename = "config.yaml"

// ReadConfig parse configuration file and env variables a return parameters.
func ReadConfig(configPath string, cmd *cobra.Command) (*viper.Viper, error) {
	v := viper.New()

	v.SetConfigType("yaml")
	v.SetConfigName(configFilename)

	if configPath != "" {
		log.Info().Msgf(L("Using config file %s"), configPath)
		v.SetConfigFile(configPath)
	} else {
		xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
		if xdgConfigHome == "" {
			home, err := os.UserHomeDir()
			if err != nil {
				log.Err(err).Msg(L("Failed to find home directory"))
			} else {
				xdgConfigHome = path.Join(home, ".config")
			}
		}
		if xdgConfigHome != "" {
			v.AddConfigPath(path.Join(xdgConfigHome, appName))
		}
		v.AddConfigPath(".")
	}

	if err := bindFlags(cmd, v); err != nil {
		return nil, err
	}

	if err := v.ReadInConfig(); err != nil {
		// It's okay if there isn't a config file
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
			// TODO Provide help on the config file format
			return nil, fmt.Errorf(L("failed to parse configuration file %s: %s"), v.ConfigFileUsed(), err)
		}
	}

	v.SetEnvPrefix(envPrefix)

	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

	v.AutomaticEnv()

	return v, nil
}

// Bind each cobra flag to its associated viper configuration (config file and environment variable).
func bindFlags(cmd *cobra.Command, v *viper.Viper) error {
	var errors []error
	cmd.Flags().VisitAll(func(f *pflag.Flag) {
		configName := strings.ReplaceAll(f.Name, "-", ".")
		if err := v.BindPFlag(configName, f); err != nil {
			errors = append(errors, fmt.Errorf(L("failed to bind %s config to parameter %s: %s"), configName, f.Name, err))
		}
	})

	if len(errors) > 0 {
		return errors[0]
	}
	return nil
}

// GetLocalizedUsageTemplate provides the help template, but localized.
func GetLocalizedUsageTemplate() string {
	return L(`Usage:{{if .Runnable}}
  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}

Aliases:
  {{.NameAndAliases}}{{end}}{{if .HasExample}}

Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}

Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}

Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}

Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}

Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}

Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`)
}

// GetConfigHelpCommand provides a help command describing the config file and environment variables.
func GetConfigHelpCommand() *cobra.Command {
	var configTemplate = L(`
Configuration:

  All the non-global flags can alternatively be passed as configuration.
  
  The configuration file is a YAML file with entries matching the flag name.
  The name of a flag is the part after the '--' of the command line parameter.
  Every '_' character in the flag name means a nested property.
  
  For instance the '--tz CEST' and '--ssl-password secret' will be mapped to
  this YAML configuration:
  
    tz: CEST
    ssl:
      password: secret
  
  The configuration file will be searched in the following places and order:
  · $XDG_CONFIG_HOME/{{ .Name }}/{{ .ConfigFile }}
  · $HOME/.config/{{ .Name }}/{{ .ConfigFile }}
  · $PWD/{{ .ConfigFile }}
  · the value of the --config flag


Environment variables:

  All the non-global flags can also be passed as environment variables.
  
  The environment variable name is the flag name with '-' replaced by with '_'
  and the {{ .EnvPrefix }} prefix.
  
  For example the '--tz CEST' flag will be mapped to '{{ .EnvPrefix }}_TZ'
  and '--ssl-password' flags to '{{ .EnvPrefix }}_SSL_PASSWORD' 
`)

	cmd := &cobra.Command{
		Use:   "config",
		Short: L("Help on configuration file and environment variables"),
	}
	t := template.Must(template.New("help").Parse(configTemplate))
	var helpBuilder strings.Builder
	if err := t.Execute(&helpBuilder, configTemplateData{
		EnvPrefix:  envPrefix,
		Name:       appName,
		ConfigFile: configFilename,
	}); err != nil {
		log.Fatal().Err(err).Msg(L("failed to compute config help command"))
	}
	cmd.SetHelpTemplate(helpBuilder.String())
	return cmd
}

type configTemplateData struct {
	EnvPrefix  string
	ConfigFile string
	Name       string
}
0707010000013A000081B4000000000000000000000001662A752800000928000000000000000000000000000000000000002100000000uyuni-tools/shared/utils/exec.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/briandowns/spinner"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

// OutputLogWriter contains information output the logger and the loglevel.
type OutputLogWriter struct {
	Logger   zerolog.Logger
	LogLevel zerolog.Level
}

// Write writes a byte array to an OutputLogWriter.
func (l OutputLogWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	if n > 0 && p[n-1] == '\n' {
		// Trim CR added by stdlog.
		p = p[0 : n-1]
	}
	l.Logger.WithLevel(l.LogLevel).CallerSkipFrame(1).Msg(string(p))
	return
}

// RunCmd execute a shell command.
func RunCmd(command string, args ...string) error {
	s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) // Build our new spinner
	s.Suffix = fmt.Sprintf(" %s %s\n", command, strings.Join(args, " "))
	s.Start() // Start the spinner
	log.Debug().Msgf("Running: %s %s", command, strings.Join(args, " "))
	err := exec.Command(command, args...).Run()
	s.Stop()
	return err
}

// RunCmdStdMapping execute a shell command mapping the stdout and stderr.
func RunCmdStdMapping(logLevel zerolog.Level, command string, args ...string) error {
	localLogger := log.Level(logLevel)
	localLogger.Debug().Msgf("Running: %s %s", command, strings.Join(args, " "))

	runCmd := exec.Command(command, args...)
	runCmd.Stdout = os.Stdout
	runCmd.Stderr = os.Stderr
	err := runCmd.Run()
	return err
}

// RunCmdOutput execute a shell command and collects output.
func RunCmdOutput(logLevel zerolog.Level, command string, args ...string) ([]byte, error) {
	localLogger := log.Level(logLevel)
	s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) // Build our new spinner
	s.Suffix = fmt.Sprintf(" %s %s\n", command, strings.Join(args, " "))
	if logLevel != zerolog.Disabled {
		s.Start() // Start the spinner
	}
	localLogger.Debug().Msgf("Running: %s %s", command, strings.Join(args, " "))
	output, err := exec.Command(command, args...).Output()
	if logLevel != zerolog.Disabled {
		s.Stop()
	}
	localLogger.Trace().Msgf("Command output: %s, error: %s", output, err)
	return output, err
}

// IsInstalled checks if a tool is in the path.
func IsInstalled(tool string) bool {
	_, err := exec.LookPath("kubectl")
	return err == nil
}
0707010000013B000081B4000000000000000000000001662A75280000091E000000000000000000000000000000000000002700000000uyuni-tools/shared/utils/flaggroups.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"
	"regexp"

	"github.com/spf13/cobra"
	flag "github.com/spf13/pflag"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

// Group Structure to manage groups for commands.
type Group struct {
	ID    string
	Title string
}

// Annotation to store the flag group.
const FlagHelpGroupAnnotation = "cobra_annotation_flag_help_group"

var commandGroups = make(map[*cobra.Command][]Group)

func usageByFlagHelpGroupID(cmd *cobra.Command, groupID string) string {
	fs := &flag.FlagSet{}

	cmd.LocalFlags().VisitAll(func(f *flag.Flag) {
		if _, ok := f.Annotations[FlagHelpGroupAnnotation]; !ok {
			if groupID == "" {
				fs.AddFlag(f)
			}
			return
		}

		if id := f.Annotations[FlagHelpGroupAnnotation][0]; id == groupID {
			fs.AddFlag(f)
		}
	})

	return fs.FlagUsages()
}

func usageFunc(cmd *cobra.Command) error {
	flagsUsage := ""
	for _, group := range commandGroups[cmd] {
		flagsUsage += group.Title + ":\n"
		flagsUsage += usageByFlagHelpGroupID(cmd, group.ID)
		flagsUsage += "\n"
	}

	genericFlagsUsage := usageByFlagHelpGroupID(cmd, "")
	if len(genericFlagsUsage) > 0 {
		flagsUsage = L("Flags:\n") + genericFlagsUsage + "\n" + flagsUsage
	}

	template := cmd.UsageTemplate()
	re := regexp.MustCompile(`(?s)\{\{if \.HasAvailableLocalFlags\}\}.*?\{\{end\}\}`)
	template = re.ReplaceAllString(template, "\n\n"+flagsUsage)
	cmd.SetUsageTemplate(template)

	// call the original UsageFunc with the modified template
	cmd.SetUsageFunc(nil)
	origUsageFunc := cmd.UsageFunc()
	cmd.SetUsageFunc(usageFunc)

	return origUsageFunc(cmd)
}

// Add new flags group.
func AddFlagHelpGroup(cmd *cobra.Command, groups ...*Group) error {
	for _, group := range groups {
		commandGroups[cmd] = append(commandGroups[cmd], *group)
	}

	cmd.SetUsageFunc(usageFunc)
	return nil
}

// Add flag to a group.
func AddFlagToHelpGroupID(cmd *cobra.Command, flag, groupID string) error {
	lf := cmd.Flags()

	found := false
	for _, existing := range commandGroups[cmd] {
		if existing.ID == groupID {
			found = true
			break
		}
	}
	if !found {
		return fmt.Errorf(L("no such flag help group: %v"), groupID)
	}

	err := lf.SetAnnotation(flag, FlagHelpGroupAnnotation, []string{groupID})
	if err != nil {
		return err
	}

	return nil
}
0707010000013C000081B4000000000000000000000001662A752800000105000000000000000000000000000000000000002700000000uyuni-tools/shared/utils/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build !nok8s

package utils

// KubernetesBuilt is a flag for compiling kubernetes code. True when go:build !nok8s, False when go:build nok8s.
const KubernetesBuilt = true
0707010000013D000081B4000000000000000000000001662A752800000879000000000000000000000000000000000000002500000000uyuni-tools/shared/utils/logUtils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"io"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"golang.org/x/term"
	"gopkg.in/natefinch/lumberjack.v2"
)

// LogInit initialize logs.
func LogInit(logToConsole bool) {
	zerolog.CallerMarshalFunc = logCallerMarshalFunction
	zerolog.SetGlobalLevel(zerolog.InfoLevel)

	fileWriter := getFileWriter()
	writers := []io.Writer{fileWriter}
	if logToConsole {
		consoleWriter := zerolog.NewConsoleWriter()
		consoleWriter.NoColor = !term.IsTerminal(int(os.Stdout.Fd()))
		writers = append(writers, consoleWriter)
	}

	multi := zerolog.MultiLevelWriter(writers...)
	log.Logger = zerolog.New(multi).With().Timestamp().Stack().Logger()
}

func getFileWriter() *lumberjack.Logger {
	const globalLogPath = "/var/log/"
	logPath := globalLogPath

	if file, err := os.OpenFile(globalLogPath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600); err != nil {
		logPath, err = os.UserHomeDir()
		if err != nil {
			logPath = "./"
		}
	} else {
		file.Close()
	}

	fileLogger := &lumberjack.Logger{
		Filename:   path.Join(logPath, "uyuni-tools.log"),
		MaxSize:    5,
		MaxBackups: 5,
		MaxAge:     90,
		Compress:   true,
	}
	return fileLogger
}

// SetLogLevel sets the loglevel.
func SetLogLevel(logLevel string) {
	globalLevel := zerolog.InfoLevel

	level, err := zerolog.ParseLevel(logLevel)
	if logLevel != "" && err == nil {
		globalLevel = level
	}
	if globalLevel <= zerolog.DebugLevel {
		log.Logger = log.Logger.With().Caller().Logger()
	}
	zerolog.SetGlobalLevel(globalLevel)
}

func logCallerMarshalFunction(pc uintptr, file string, line int) string {
	paths := strings.Split(file, "/")
	callerFile := file
	foundSubDir := false
	if strings.HasSuffix(file, "/io/io.go") {
		return "Cmd output"
	}

	for _, currentPath := range paths {
		if foundSubDir {
			if callerFile != "" {
				callerFile = callerFile + "/"
			}
			callerFile = callerFile + currentPath
		} else {
			if strings.Contains(currentPath, "uyuni-tools") {
				foundSubDir = true
				callerFile = ""
			}
		}
	}
	return callerFile + ":" + strconv.Itoa(line)
}
0707010000013E000081B4000000000000000000000001662A752800000093000000000000000000000000000000000000002900000000uyuni-tools/shared/utils/nokubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

//go:build nok8s

package utils

const KubernetesBuilt = false
0707010000013F000081B4000000000000000000000001662A752800000708000000000000000000000000000000000000002200000000uyuni-tools/shared/utils/ports.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import "github.com/uyuni-project/uyuni-tools/shared/types"

// NewPortMap is a constructor for PortMap type.
func NewPortMap(name string, exposed int, port int) types.PortMap {
	return types.PortMap{
		Name:    name,
		Exposed: exposed,
		Port:    port,
	}
}

// TCP_PORTS are the tcp ports required by the server
// The port names should be less than 15 characters long and lowercased for traefik to eat them.
var TCP_PORTS = []types.PortMap{
	NewPortMap("postgres", 5432, 5432),
	NewPortMap("salt-publish", 4505, 4505),
	NewPortMap("salt-request", 4506, 4506),
	NewPortMap("cobbler", 25151, 25151),
	NewPortMap("psql-mtrx", 9187, 9187),
	NewPortMap("tasko-jmx-mtrx", 5556, 5556),
	NewPortMap("tomcat-jmx-mtrx", 5557, 5557),
	// TODO: Replace Node exporter with cAdvisor
	NewPortMap("node-exporter", 9100, 9100),
	NewPortMap("tasko-mtrx", 9800, 9800),
}

// DEBUG_PORTS are the port used by dev for debugging applications.
var DEBUG_PORTS = []types.PortMap{
	// We can't expose on port 8000 since traefik already uses it
	NewPortMap("tomcat-debug", 8003, 8003),
	NewPortMap("tasko-debug", 8001, 8001),
	NewPortMap("search-debug", 8002, 8002),
}

// UDP_PORTS are the udp ports required by the server.
var UDP_PORTS = []types.PortMap{
	{
		Name:     "tftp",
		Exposed:  69,
		Port:     69,
		Protocol: "udp",
	},
}

// PROXY_TCP_PORTS are the tcp ports required by the proxy.
var PROXY_TCP_PORTS = []types.PortMap{
	NewPortMap("ssh", 8022, 22),
	NewPortMap("salt-publish", 4505, 4505),
	NewPortMap("salt-request", 4506, 4506),
}

// PROXY_PODMAN_PORTS are the http/s ports required by the proxy.
var PROXY_PODMAN_PORTS = []types.PortMap{
	NewPortMap("https", 443, 443),
	NewPortMap("http", 80, 80),
}
07070100000140000081B4000000000000000000000001662A752800000136000000000000000000000000000000000000002300000000uyuni-tools/shared/utils/slices.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

// Contains returns true if a string is contained in a string slice.
func Contains(slice []string, needle string) bool {
	for _, item := range slice {
		if item == needle {
			return true
		}
	}
	return false
}
07070100000141000081B4000000000000000000000001662A752800000AAA000000000000000000000000000000000000002000000000uyuni-tools/shared/utils/tar.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/rs/zerolog/log"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

// Extracts a tar.gz file.
func ExtractTarGz(tarballPath string, dstPath string) error {
	reader, err := os.Open(tarballPath)
	if err != nil {
		return err
	}
	defer reader.Close()

	archive, err := gzip.NewReader(reader)
	if err != nil {
		return err
	}
	defer archive.Close()

	tarReader := tar.NewReader(archive)
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}

		path, err := filepath.Abs(filepath.Join(dstPath, header.Name))
		if err != nil {
			return err
		}
		if !strings.HasPrefix(path, dstPath) {
			log.Warn().Msgf(L("Skipping extraction of %s in %s file as it resolves outside the target path"),
				header.Name, tarballPath)
			continue
		}

		info := header.FileInfo()
		if info.IsDir() {
			log.Debug().Msgf("Creating folder %s", path)
			if err = os.MkdirAll(path, info.Mode()); err != nil {
				return err
			}
			continue
		}

		log.Debug().Msgf("Extracting file %s", path)
		file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
		if err != nil {
			return err
		}
		defer file.Close()
		if _, err = io.Copy(file, tarReader); err != nil {
			return err
		}
	}

	return nil
}

// Object holding a .tar.gz to write it to a file.
type TarGz struct {
	fileWriter *os.File
	tarWriter  *tar.Writer
	gzipWriter *gzip.Writer
}

// NewTarGz create a targz object with writers opened.
// A successful call should be followed with a close.
func NewTarGz(path string) (*TarGz, error) {
	var targz TarGz
	var err error
	targz.fileWriter, err = os.Create(path)
	if err != nil {
		return nil, fmt.Errorf(L("failed to write tar.gz to %s: %s"), path, err)
	}

	targz.gzipWriter = gzip.NewWriter(targz.fileWriter)
	targz.tarWriter = tar.NewWriter(targz.gzipWriter)
	return &targz, nil
}

// Close stops all the writers.
func (t *TarGz) Close() {
	t.tarWriter.Close()
	t.gzipWriter.Close()
	t.fileWriter.Close()
}

// AddFile adds the file at filepath to the archive as entrypath.
func (t *TarGz) AddFile(filepath string, entrypath string) error {
	file, err := os.Open(filepath)
	if err != nil {
		return err
	}
	defer file.Close()

	info, err := file.Stat()
	if err != nil {
		return err
	}

	header, err := tar.FileInfoHeader(info, info.Name())
	if err != nil {
		return err
	}

	header.Name = entrypath
	if err = t.tarWriter.WriteHeader(header); err != nil {
		return err
	}

	if _, err = io.Copy(t.tarWriter, file); err != nil {
		return err
	}
	return nil
}
07070100000142000081B4000000000000000000000001662A752800000E75000000000000000000000000000000000000002500000000uyuni-tools/shared/utils/tar_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"os"
	"os/exec"
	"path"
	"testing"
)

const dataDir = "data"
const outDir = "out"

const file1_content = "file1 content"

var filesData = map[string]string{
	"file1":     file1_content,
	"sub/file2": "file2 content",
}

// Prepare test files to include in the tarball.
func setup(t *testing.T) (string, func(t *testing.T)) {
	dir, err := os.MkdirTemp("", "uyuni-tools-test-")
	if err != nil {
		t.Fatalf("failed to create temporary directory for test: %s", err)
	}

	// Create sub directories for the data and the test
	for _, dirPath := range []string{dataDir, outDir} {
		subDir := path.Join(dir, dirPath)
		if err := os.Mkdir(subDir, 0700); err != nil {
			t.Fatalf("failed to create %s directory: %s", dirPath, err)
		}
	}

	// Add some content to the data directory
	for name, content := range filesData {
		filePath := path.Dir(name)
		if filePath != "." {
			absDir := path.Join(dir, dataDir, filePath)
			if err := os.MkdirAll(absDir, 0700); err != nil {
				t.Fatalf("failed to create subdirectory %s for test: %s", absDir, err)
			}
		}
		if err := os.WriteFile(path.Join(dir, dataDir, name), []byte(content), 0700); err != nil {
			t.Fatalf("failed to write test data file %s: %s", name, err)
		}
	}

	// Returns the teardown function.
	return dir, func(t *testing.T) {
		if err := os.RemoveAll(dir); err != nil {
			t.Logf("failed to clean test directory: %s", err)
		}
	}
}

func TestWriteTarGz(t *testing.T) {
	tmpDir, teardown := setup(t)
	defer teardown(t)

	// Create the tarball
	tarballPath := path.Join(tmpDir, "test.tar.gz")
	tarball, err := NewTarGz(tarballPath)
	if err != nil {
		t.Fatalf("failed to create tarball: %s", err)
	}
	if err := tarball.AddFile(path.Join(tmpDir, dataDir, "file1"), "otherfile1"); err != nil {
		t.Fatalf("failed to add file1 to tarball: %s", err)
	}
	if err := tarball.AddFile(path.Join(tmpDir, dataDir, "sub/file2"), "sub/file2"); err != nil {
		t.Fatalf("failed to add sub/file2 to tarball: %s", err)
	}
	tarball.Close()

	// Check the tarball using the tar utility
	testDir := path.Join(tmpDir, outDir)
	if out, err := exec.Command("tar", "xzf", tarballPath, "-C", testDir).CombinedOutput(); err != nil {
		t.Fatalf("failed to extract generated tarball: %s", string(out))
	}

	// Ensure we have all expected files
	for _, file := range []string{"otherfile1", "sub/file2"} {
		if !FileExists(path.Join(testDir, file)) {
			t.Errorf("Missing %s in archive", file)
		}
	}

	// Check the content of a file
	if out, err := os.ReadFile(path.Join(testDir, "otherfile1")); err != nil {
		t.Errorf("failed to read otherfile1: %s", err)
	} else if string(out) != file1_content {
		t.Errorf("expected otherfile1 content %s, but got %s", file1_content, string(out))
	}
}

func TestExtractTarGz(t *testing.T) {
	tmpDir, teardown := setup(t)
	defer teardown(t)

	// Create an archive using the tar tool
	tarballPath := path.Join(tmpDir, "test.tar.gz")
	dataPath := path.Join(tmpDir, dataDir)
	if out, err := exec.Command("tar", "czf", tarballPath, "-C", dataPath, ".").CombinedOutput(); err != nil {
		t.Fatalf("failed to create test tar.gz: %s", string(out))
	}

	// Extract the tarball
	testDir := path.Join(tmpDir, outDir)
	if err := ExtractTarGz(tarballPath, testDir); err != nil {
		t.Errorf("Failed to extract tar.gz: %s", err)
	}

	// Check the extracted content
	for name, content := range filesData {
		if out, err := os.ReadFile(path.Join(testDir, name)); err != nil {
			t.Errorf("failed to read %s: %s", name, err)
		} else if string(out) != content {
			t.Errorf("expected %s content %s, but got %s", name, content, string(out))
		}
	}
}
07070100000143000081B4000000000000000000000001662A75280000035A000000000000000000000000000000000000002500000000uyuni-tools/shared/utils/template.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"
	"io"
	"os"

	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
)

// Template is an interface for implementing Render function.
type Template interface {
	Render(wr io.Writer) error
}

// WriteTemplateToFile writes a template to a file.
func WriteTemplateToFile(template Template, path string, perm os.FileMode, overwrite bool) error {
	// Check if the file is existing
	if !overwrite {
		if FileExists(path) {
			return fmt.Errorf(L("%s file already present, not overwriting"), path)
		}
	}

	// Write the configuration
	file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
	if err != nil {
		return fmt.Errorf(L("failed to open %s for writing: %s"), path, err)
	}
	defer file.Close()

	return template.Render(file)
}
07070100000144000081B4000000000000000000000001662A752800002FE0000000000000000000000000000000000000002200000000uyuni-tools/shared/utils/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"unicode"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/viper"
	. "github.com/uyuni-project/uyuni-tools/shared/l10n"
	"github.com/uyuni-project/uyuni-tools/shared/templates"
	"github.com/uyuni-project/uyuni-tools/shared/types"
	"golang.org/x/term"
)

const prompt_end = ": "

var prodVersionArchRegex = regexp.MustCompile(`suse\/manager\/.*:`)
var imageValid = regexp.MustCompile("^((?:[^:/]+(?::[0-9]+)?/)?[^:]+)(?::([^:]+))?$")

// InspectScriptFilename is the inspect script basename.
var InspectScriptFilename = "inspect.sh"

var inspectValues = []types.InspectData{
	types.NewInspectData("uyuni_release", "cat /etc/*release | grep 'Uyuni release' | cut -d ' ' -f3 || true"),
	types.NewInspectData("suse_manager_release", "cat /etc/*release | grep 'SUSE Manager release' | cut -d ' ' -f4 || true"),
	types.NewInspectData("architecture", "lscpu | grep Architecture | awk '{print $2}' || true"),
	types.NewInspectData("fqdn", "cat /etc/rhn/rhn.conf 2>/dev/null | grep 'java.hostname' | cut -d' ' -f3 || true"),
	types.NewInspectData("image_pg_version", "rpm -qa --qf '%{VERSION}\\n' 'name=postgresql[0-8][0-9]-server'  | cut -d. -f1 | sort -n | tail -1 || true"),
	types.NewInspectData("current_pg_version", "(test -e /var/lib/pgsql/data/PG_VERSION && cat /var/lib/pgsql/data/PG_VERSION) || true"),
	types.NewInspectData("registration_info", "transactional-update --quiet register --status 2>/dev/null || true"),
	types.NewInspectData("scc_username", "cat /etc/zypp/credentials.d/SCCcredentials 2>&1 /dev/null | grep username | cut -d= -f2 || true"),
	types.NewInspectData("scc_password", "cat /etc/zypp/credentials.d/SCCcredentials 2>&1 /dev/null | grep password | cut -d= -f2 || true"),
}

// InspectOutputFile represents the directory and the basename where the inspect values are stored.
var InspectOutputFile = types.InspectFile{
	Directory: "/var/lib/uyuni-tools",
	Basename:  "data",
}

func checkValueSize(value string, min int, max int) bool {
	if min == 0 && max == 0 {
		return true
	}

	if len(value) < min {
		fmt.Printf(NL("Has to be more than %d character long", "Has to be more than %d characters long", min), min)
		return false
	}
	if len(value) > max {
		fmt.Printf(NL("Has to be less than %d character long", "Has to be less than %d characters long", max), max)
		return false
	}
	return true
}

// AskPasswordIfMissing asks for password if missing.
// Don't perform any check if min and max are set to 0.
func AskPasswordIfMissing(value *string, prompt string, min int, max int) {
	for *value == "" {
		fmt.Print(prompt + prompt_end)
		bytePassword, err := term.ReadPassword(int(syscall.Stdin))
		if err != nil {
			log.Fatal().Err(err).Msgf(L("Failed to read password"))
		}
		tmpValue := strings.TrimSpace(string(bytePassword))
		r := regexp.MustCompile(`^[^\t ]+$`)
		validChars := r.MatchString(tmpValue)
		if !validChars {
			fmt.Printf(L("Cannot contain spaces or tabs"))
		}

		if validChars && checkValueSize(tmpValue, min, max) {
			*value = tmpValue
		}
		fmt.Println()
		if *value == "" {
			fmt.Println("A value is required")
		}
	}
}

// AskIfMissing asks for a value if missing.
// Don't perform any check if min and max are set to 0.
func AskIfMissing(value *string, prompt string, min int, max int, checker func(string) bool) {
	reader := bufio.NewReader(os.Stdin)
	for *value == "" {
		fmt.Print(prompt + prompt_end)
		newValue, err := reader.ReadString('\n')
		if err != nil {
			log.Fatal().Err(err).Msgf(L("Failed to read input"))
		}
		tmpValue := strings.TrimSpace(newValue)
		if checkValueSize(tmpValue, min, max) && (checker == nil || checker(tmpValue)) {
			*value = tmpValue
		}
		fmt.Println()
		if *value == "" {
			fmt.Println(L("A value is required"))
		}
	}
}

// YesNo asks a question in CLI.
func YesNo(question string) (bool, error) {
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Printf("%s [y/N]?", question)

		response, err := reader.ReadString('\n')
		if err != nil {
			return false, err
		}

		response = strings.ToLower(strings.TrimSpace(response))

		if strings.ToLower(response) == "y" || strings.ToLower(response) == "yes" {
			return true, nil
		}
		return false, nil
	}
}

// ComputeImage assembles the container image from its name and tag.
func ComputeImage(name string, tag string, appendToName ...string) (string, error) {
	submatches := imageValid.FindStringSubmatch(name)
	if submatches == nil {
		return "", fmt.Errorf(L("invalid image name: %s"), name)
	}
	if submatches[2] == `` {
		if len(tag) <= 0 {
			return name, fmt.Errorf(L("tag missing on %s"), name)
		}
		if len(appendToName) > 0 {
			name = name + strings.Join(appendToName, ``)
		}
		// No tag provided in the URL name, append the one passed
		imageName := fmt.Sprintf("%s:%s", name, tag)
		imageName = strings.ToLower(imageName) // podman does not accept repo in upper case
		log.Debug().Msgf("Computed image name is %s", imageName)
		return imageName, nil
	}
	imageName := submatches[1] + strings.Join(appendToName, ``) + `:` + submatches[2]
	imageName = strings.ToLower(imageName) // podman does not accept repo in upper case
	log.Debug().Msgf("Computed image name is %s", imageName)
	return imageName, nil
}

// ComputePTF returns a PTF or Test image from registry.suse.com.
func ComputePTF(user string, ptfId string, fullImage string, suffix string) (string, error) {
	prefix := fmt.Sprintf("registry.suse.com/a/%s/%s/", user, ptfId)
	submatches := prodVersionArchRegex.FindStringSubmatch(fullImage)
	if submatches == nil || len(submatches) > 1 {
		return "", fmt.Errorf(L("invalid image name: %s"), fullImage)
	}
	tag := fmt.Sprintf("latest-%s-%s", suffix, ptfId)
	return prefix + submatches[0] + tag, nil
}

// Get the timezone set on the machine running the tool.
func GetLocalTimezone() string {
	out, err := RunCmdOutput(zerolog.DebugLevel, "timedatectl", "show", "--value", "-p", "Timezone")
	if err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to run %s"), "timedatectl show --value -p Timezone")
	}
	return string(out)
}

// Check if a given path exists.
func FileExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	} else if !os.IsNotExist(err) {
		log.Fatal().Err(err).Msgf(L("Failed to get %s file informations"), path)
	}
	return false
}

// Returns the content of a file and exit if there was an error.
func ReadFile(file string) []byte {
	out, err := os.ReadFile(file)
	if err != nil {
		log.Fatal().Err(err).Msgf(L("Failed to read file %s"), file)
	}
	return out
}

// Get the value of a file containing a boolean.
// This is handy for files from the kernel API.
func GetFileBoolean(file string) bool {
	return string(ReadFile(file)) != "0"
}

// Uninstalls a file.
func UninstallFile(path string, dryRun bool) {
	if FileExists(path) {
		if dryRun {
			log.Info().Msgf(L("Would remove file %s"), path)
		} else {
			log.Info().Msgf(L("Removing file %s"), path)
			if err := os.Remove(path); err != nil {
				log.Info().Err(err).Msgf(L("Failed to remove file %s"), path)
			}
		}
	}
}

// GetRandomBase64 generates random base64-encoded data.
func GetRandomBase64(size int) string {
	data := make([]byte, size)
	if _, err := rand.Read(data); err != nil {
		log.Fatal().Err(err).Msg(L("Failed to read random data"))
	}
	return base64.StdEncoding.EncodeToString(data)
}

// ContainsUpperCase check if string contains an uppercase character.
func ContainsUpperCase(str string) bool {
	for _, char := range str {
		if unicode.IsUpper(char) {
			return true
		}
	}
	return false
}

// GetURLBody provide the body content of an GET HTTP request.
func GetURLBody(URL string) ([]byte, error) {
	// Download the key from the URL
	log.Debug().Msgf("Downloading %s", URL)
	resp, err := http.Get(URL)
	if err != nil {
		return nil, fmt.Errorf(L("error downloading from %s: %s"), URL, err)
	}
	defer resp.Body.Close()

	// Check server response
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf(L("bad status: %s"), resp.Status)
	}

	var buf bytes.Buffer

	if _, err = io.Copy(&buf, resp.Body); err != nil {
		return nil, err
	}

	// Extract the byte slice from the buffer
	data := buf.Bytes()
	return data, nil
}

// DownloadFile downloads from a remote path to a local file.
func DownloadFile(filepath string, URL string) (err error) {
	data, err := GetURLBody(URL)
	if err != nil {
		return err
	}

	// Writer the body to file
	log.Debug().Msgf("Saving %s to %s", URL, filepath)
	if err := os.WriteFile(filepath, data, 0644); err != nil {
		return err
	}

	return nil
}

// ReadInspectData returns a map with the values inspected by an image and deploy.
func ReadInspectData(scriptDir string, prefix ...string) (map[string]string, error) {
	path := filepath.Join(scriptDir, "data")
	log.Debug().Msgf("Trying to read %s", path)
	data, err := os.ReadFile(path)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot parse file %s: %s"), path, err)
	}

	inspectResult := make(map[string]string)

	viper.SetConfigType("env")
	if err := viper.ReadConfig(bytes.NewBuffer(data)); err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot read config: %s"), err)
	}

	for _, v := range inspectValues {
		if len(viper.GetString(v.Variable)) > 0 {
			index := v.Variable
			/* Just the first value of prefix is used.
			 * This slice is just to allow an empty argument
			 */
			if len(prefix) >= 1 {
				index = prefix[0] + v.Variable
			}
			inspectResult[index] = viper.GetString(v.Variable)
		}
	}
	return inspectResult, nil
}

// InspectHost check values on a host machine.
func InspectHost() (map[string]string, error) {
	scriptDir, err := os.MkdirTemp("", "mgradm-*")
	defer os.RemoveAll(scriptDir)
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("failed to create temporary directory: %s"), err)
	}

	if err := GenerateInspectHostScript(scriptDir); err != nil {
		return map[string]string{}, err
	}

	if err := RunCmdStdMapping(zerolog.DebugLevel, scriptDir+"/inspect.sh"); err != nil {
		return map[string]string{}, fmt.Errorf(L("failed to run inspect script in host system: %s"), err)
	}

	inspectResult, err := ReadInspectData(scriptDir, "host_")
	if err != nil {
		return map[string]string{}, fmt.Errorf(L("cannot inspect host data: %s"), err)
	}

	return inspectResult, err
}

// GenerateInspectContainerScript create the host inspect script.
func GenerateInspectHostScript(scriptDir string) error {
	data := templates.InspectTemplateData{
		Param:      inspectValues,
		OutputFile: scriptDir + "/" + InspectOutputFile.Basename,
	}

	scriptPath := filepath.Join(scriptDir, InspectScriptFilename)
	if err := WriteTemplateToFile(data, scriptPath, 0555, true); err != nil {
		return fmt.Errorf(L("failed to generate inspect script: %s"), err)
	}
	return nil
}

// GenerateInspectContainerScript create the container inspect script.
func GenerateInspectContainerScript(scriptDir string) error {
	data := templates.InspectTemplateData{
		Param:      inspectValues,
		OutputFile: InspectOutputFile.Directory + "/" + InspectOutputFile.Basename,
	}

	scriptPath := filepath.Join(scriptDir, InspectScriptFilename)
	if err := WriteTemplateToFile(data, scriptPath, 0555, true); err != nil {
		return fmt.Errorf(L("failed to generate inspect script: %s"), err)
	}
	return nil
}

// CompareVersion compare the server image version and the server deployed  version.
func CompareVersion(imageVersion string, deployedVersion string) int {
	re := regexp.MustCompile(`\((.*?)\)`)
	imageVersionCleaned := strings.ReplaceAll(imageVersion, ".", "")
	imageVersionCleaned = strings.TrimSpace(imageVersionCleaned)
	imageVersionCleaned = re.ReplaceAllString(imageVersionCleaned, "")
	imageVersionInt, _ := strconv.Atoi(imageVersionCleaned)

	deployedVersionCleaned := strings.ReplaceAll(deployedVersion, ".", "")
	deployedVersionCleaned = strings.TrimSpace(deployedVersionCleaned)
	deployedVersionCleaned = re.ReplaceAllString(deployedVersionCleaned, "")
	deployedVersionInt, _ := strconv.Atoi(deployedVersionCleaned)
	return imageVersionInt - deployedVersionInt
}
07070100000145000081B4000000000000000000000001662A752800001BAE000000000000000000000000000000000000002700000000uyuni-tools/shared/utils/utils_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"syscall"
	"testing"

	expect "github.com/Netflix/go-expect"
	"github.com/chai2010/gettext-go"
	l10n_utils "github.com/uyuni-project/uyuni-tools/shared/l10n/utils"
)

type askTestData struct {
	value           string
	expectedMessage string
	min             int
	max             int
	checker         func(string) bool
}

func TestAskIfMissing(t *testing.T) {
	// Set english locale to not depend on the system one
	gettext.BindLocale(gettext.New("", "", l10n_utils.New("")))
	gettext.SetLanguage("en")

	c, err := expect.NewConsole(expect.WithStdout(os.Stdout))
	if err != nil {
		t.Errorf("Failed to create fake console")
	}
	defer c.Close()

	origStdin := os.Stdin
	origStdout := os.Stdout

	os.Stdin = c.Tty()
	os.Stdout = c.Tty()
	defer func() {
		os.Stdin = origStdin
		os.Stdout = origStdout
	}()

	fChecker := func(v string) bool {
		if !strings.Contains(v, "f") {
			fmt.Println("Has to contain an 'f'")
			return false
		}
		return true
	}

	data := []askTestData{
		{value: "\n", expectedMessage: "A value is required", min: 1, max: 5, checker: nil},
		{value: "superlong\n", expectedMessage: "Has to be less than 5 characters long", min: 1, max: 5, checker: nil},
		{value: "a\n", expectedMessage: "Has to be more than 2 characters long", min: 2, max: 5, checker: nil},
		{value: "booh\n", expectedMessage: "Has to contain an 'f'", min: 0, max: 0, checker: fChecker},
	}

	for i, testCase := range data {
		go func() {
			if _, err := c.ExpectString("Prompted value: "); err != nil {
				t.Errorf("Testcase %d: Expected prompt error: %s", i, err)
			}
			if _, err := c.Send(testCase.value); err != nil {
				t.Errorf("Testcase %d: Failed to send value to fake console: %s", i, err)
			}
			if _, err := c.Expect(expect.Regexp(regexp.MustCompile(testCase.expectedMessage))); err != nil {
				t.Errorf("Testcase %d: Expected '%s' message: %s", i, testCase.expectedMessage, err)
			}
			if _, err := c.ExpectString("Prompted value: "); err != nil {
				t.Errorf("Testcase %d: Expected prompt error: %s", i, err)
			}
			if _, err := c.Send("foo\n"); err != nil {
				t.Errorf("Testcase %d: Failed to send value to fake console: %s", i, err)
			}
		}()

		var value string
		AskIfMissing(&value, "Prompted value", testCase.min, testCase.max, testCase.checker)
		if value != "foo" {
			t.Errorf("Testcase %d: Expected 'foo', got '%s' value", i, value)
		}
	}
}

func TestAskPasswordIfMissing(t *testing.T) {
	// Set english locale to not depend on the system one
	gettext.BindLocale(gettext.New("", "", l10n_utils.New("")))
	gettext.SetLanguage("en")

	c, err := expect.NewConsole(expect.WithStdout(os.Stdout))
	if err != nil {
		t.Errorf("Failed to create fake console")
	}
	defer c.Close()

	origStdin := syscall.Stdin
	origStdout := os.Stdout

	syscall.Stdin = int(c.Tty().Fd())
	os.Stdout = c.Tty()
	defer func() {
		syscall.Stdin = origStdin
		os.Stdout = origStdout
	}()

	data := []askTestData{
		{value: "\n", expectedMessage: "A value is required", min: 1, max: 5, checker: nil},
		{value: "superlong\n", expectedMessage: "Has to be less than 5 characters long", min: 1, max: 5, checker: nil},
		{value: "a\n", expectedMessage: "Has to be more than 2 characters long", min: 2, max: 5, checker: nil},
	}

	for i, testCase := range data {
		go func() {
			if _, err := c.ExpectString("Prompted password: "); err != nil {
				t.Errorf("Testcase %d: Expected prompt error: %s", i, err)
			}
			if _, err := c.Send(testCase.value); err != nil {
				t.Errorf("Testcase %d: Failed to send value to fake console: %s", i, err)
			}
			if _, err := c.Expect(expect.Regexp(regexp.MustCompile(testCase.expectedMessage))); err != nil {
				t.Errorf("Testcase %d: Expected '%s' message: %s", i, testCase.expectedMessage, err)
			}
			if _, err := c.ExpectString("Prompted password: "); err != nil {
				t.Errorf("Testcase %d: Expected prompt error: %s", i, err)
			}
			if _, err := c.Send("foo\n"); err != nil {
				t.Errorf("Testcase %d: Failed to send value to fake console: %s", i, err)
			}
		}()

		var value string
		AskPasswordIfMissing(&value, "Prompted password", testCase.min, testCase.max)
		if value != "foo" {
			t.Errorf("Expected 'foo', got '%s' value", value)
		}
	}
}

func TestComputePTF(t *testing.T) {
	data := [][]string{
		{"registry.suse.com/a/a196136/27977/suse/manager/5.0/x86_64/proxy-helm:latest-ptf-27977", "a196136", "27977", "registry.suse.com/suse/manager/5.0/x86_64/proxy-helm:latest", "ptf"},
		//{"registry.suse.com/a/a196136/26859/suse/manager/5.0/x86_64/server:latest-test-26859", "a196136", "26859", "registry.suse.com/suse/manager/5.0/x64_64/server:latest", "test"},
	}

	for i, testCase := range data {
		result := testCase[0]
		user := testCase[1]
		ptfId := testCase[2]
		fullImage := testCase[3]
		suffix := testCase[4]

		actual, err := ComputePTF(user, ptfId, fullImage, suffix)

		if err != nil {
			t.Errorf("Testcase %d: Unexpected error while computing image with %s, %s, %s, %s: %s", i, user, ptfId, fullImage, suffix, err)
		}
		if actual != result {
			t.Errorf("Testcase %d: Expected %s got %s when computing image with %s, %s, %s, %s", i, result, actual, user, ptfId, fullImage, suffix)
		}
	}
}

func TestComputeImage(t *testing.T) {
	data := [][]string{
		{"registry:5000/path/to/image:foo", "registry:5000/path/to/image:foo", "bar"},
		{"registry:5000/path/to/image:foo", "REGISTRY:5000/path/to/image:foo", "bar"},
		{"registry:5000/path/to/image:foo", "REGISTRY:5000/path/to/image:foo", "BAR"},
		{"registry:5000/path/to/image:bar", "registry:5000/path/to/image", "bar"},
		{"registry/path/to/image:foo", "registry/path/to/image:foo", "bar"},
		{"registry/path/to/image:bar", "registry/path/to/image", "bar"},
		{"registry:5000/path/to/image-migration-14-16:foo", "registry:5000/path/to/image:foo", "bar", "-migration-14-16"},
		{"registry:5000/path/to/image-migration-14-16:bar", "registry:5000/path/to/image", "bar", "-migration-14-16"},
		{"registry/path/to/image-migration-14-16:foo", "registry/path/to/image:foo", "bar", "-migration-14-16"},
		{"registry/path/to/image-migration-14-16:bar", "registry/path/to/image", "bar", "-migration-14-16"},
	}

	for i, testCase := range data {
		result := testCase[0]
		image := testCase[1]
		tag := testCase[2]
		appendToImage := testCase[3:]

		actual, err := ComputeImage(image, tag, appendToImage...)

		if err != nil {
			t.Errorf("Testcase %d: Unexpected error while computing image with %s, %s, %s: %s", i, image, tag, appendToImage, err)
		}
		if actual != result {
			t.Errorf("Testcase %d: Expected %s got %s when computing image with %s, %s, %s", i, result, actual, image, tag, appendToImage)
		}
	}
}

func TestComputeImageError(t *testing.T) {
	data := [][]string{
		{"registry:path/to/image:tag:tag", "bar"},
	}

	for _, testCase := range data {
		image := testCase[0]
		tag := testCase[1]

		_, err := ComputeImage(image, tag)
		if err == nil {
			t.Errorf("Expected error for %s with tag %s, got none", image, tag)
		}
	}
}
07070100000146000081B4000000000000000000000001662A7528000017CC000000000000000000000000000000000000002400000000uyuni-tools/shared/utils/volumes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package utils

import "github.com/uyuni-project/uyuni-tools/shared/types"

// PgsqlRequiredVolumeMounts represents volumes mount used by PostgreSQL.
var PgsqlRequiredVolumeMounts = []types.VolumeMount{
	{MountPath: "/etc/pki/tls", Name: "etc-tls"},
	{MountPath: "/var/lib/pgsql", Name: "var-pgsql"},
	{MountPath: "/etc/rhn", Name: "etc-rhn"},
	{MountPath: "/etc/pki/spacewalk-tls", Name: "tls-key"},
}

// PgsqlRequiredVolumes represents volumes used by PostgreSQL.
var PgsqlRequiredVolumes = []types.Volume{
	{Name: "etc-tls", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-tls"}},
	{Name: "var-pgsql", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "var-pgsql"}},
	{Name: "etc-rhn", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-rhn"}},
	{Name: "tls-key",
		Secret: &types.Secret{
			SecretName: "uyuni-cert", Items: []types.SecretItem{
				{Key: "tls.crt", Path: "spacewalk.crt"},
				{Key: "tls.key", Path: "spacewalk.key"},
			},
		},
	},
}

// EtcServerVolumeMounts represents volumes mounted in /etc folder.
var EtcServerVolumeMounts = []types.VolumeMount{
	{MountPath: "/etc/apache2", Name: "etc-apache2"},
	{MountPath: "/etc/systemd/system/multi-user.target.wants", Name: "etc-systemd-multi"},
	{MountPath: "/etc/systemd/system/sockets.target.wants", Name: "etc-systemd-sockets"},
	{MountPath: "/etc/salt", Name: "etc-salt"},
	{MountPath: "/etc/rhn", Name: "etc-rhn"},
	{MountPath: "/etc/tomcat", Name: "etc-tomcat"},
	{MountPath: "/etc/cobbler", Name: "etc-cobbler"},
	{MountPath: "/etc/sysconfig", Name: "etc-sysconfig"},
	{MountPath: "/etc/postfix", Name: "etc-postfix"},
	{MountPath: "/etc/sssd", Name: "etc-sssd"},
}

// EtcServerVolumes represents volumes used for configuration.
var EtcServerVolumes = []types.Volume{
	{Name: "etc-apache2", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-apache2"}},
	{Name: "etc-systemd-multi", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-systemd-multi"}},
	{Name: "etc-systemd-sockets", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-systemd-sockets"}},
	{Name: "etc-salt", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-salt"}},
	{Name: "etc-tomcat", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-tomcat"}},
	{Name: "etc-cobbler", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-cobbler"}},
	{Name: "etc-sysconfig", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-sysconfig"}},
	{Name: "etc-postfix", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-postfix"}},
	{Name: "etc-rhn", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-rhn"}},
	{Name: "etc-sssd", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "etc-sssd"}},
}

var etcAndPgsqlVolumeMounts = append(PgsqlRequiredVolumeMounts, EtcServerVolumeMounts[:]...)
var etcAndPgsqlVolumes = append(PgsqlRequiredVolumes, EtcServerVolumes[:]...)

// ServerVolumeMounts should match the volumes mapping from the container definition in both
// the helm chart and the systemctl services definitions.
var ServerVolumeMounts = append([]types.VolumeMount{
	{MountPath: "/var/lib/cobbler", Name: "var-cobbler"},
	{MountPath: "/var/lib/salt", Name: "var-salt"},
	{MountPath: "/var/cache", Name: "var-cache"},
	{MountPath: "/var/spacewalk", Name: "var-spacewalk"},
	{MountPath: "/var/log", Name: "var-log"},
	{MountPath: "/srv/salt", Name: "srv-salt"},
	{MountPath: "/srv/www/", Name: "srv-www"},
	{MountPath: "/srv/tftpboot", Name: "srv-tftpboot"},
	{MountPath: "/srv/formula_metadata", Name: "srv-formulametadata"},
	{MountPath: "/srv/pillar", Name: "srv-pillar"},
	{MountPath: "/srv/susemanager", Name: "srv-susemanager"},
	{MountPath: "/srv/spacewalk", Name: "srv-spacewalk"},
	{MountPath: "/root", Name: "root"},
	{MountPath: "/etc/pki/trust/anchors", Name: "ca-cert"},
}, etcAndPgsqlVolumeMounts[:]...)

// ServerVolumes match the volume with Persistent Volume Claim.
var ServerVolumes = append([]types.Volume{
	{Name: "var-cobbler", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "var-cobbler"}},
	{Name: "var-salt", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "var-salt"}},
	{Name: "var-cache", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "var-cache"}},
	{Name: "var-spacewalk", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "var-spacewalk"}},
	{Name: "var-log", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "var-log"}},
	{Name: "srv-salt", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-salt"}},
	{Name: "srv-www", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-www"}},
	{Name: "srv-tftpboot", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-tftpboot"}},
	{Name: "srv-formulametadata", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-formulametadata"}},
	{Name: "srv-pillar", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-pillar"}},
	{Name: "srv-susemanager", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-susemanager"}},
	{Name: "srv-spacewalk", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "srv-spacewalk"}},
	{Name: "root", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "root"}},
	{Name: "ca-cert", PersistentVolumeClaim: &types.PersistentVolumeClaim{ClaimName: "ca-cert"}},
}, etcAndPgsqlVolumes[:]...)

// PROXY_HTTPD_VOLUMES volumes used by HTTPD in proxy.
var PROXY_HTTPD_VOLUMES = map[string]string{
	"uyuni-proxy-rhn-cache": "/var/cache/rhn",
	"uyuni-proxy-tftpboot":  "/srv/tftpboot",
}

// PROXY_HTTPD_VOLUMES volumes used by Squid in  proxy.
var PROXY_SQUID_VOLUMES = map[string]string{
	"uyuni-proxy-squid-cache": "/var/cache/squid",
}

// PROXY_TFTPD_VOLUMES volumes used by TFTP in proxy.
var PROXY_TFTPD_VOLUMES = map[string]string{
	"uyuni-proxy-tftpboot": "/srv/tftpboot:ro",
}
07070100000147000081B4000000000000000000000001662A752800001080000000000000000000000000000000000000002000000000uyuni-tools/uyuni-tools.changes-------------------------------------------------------------------
Tue Apr 16 13:33:34 CEST 2024 - marina.latini@suse.com

- version 0.1.7-0
  * Fix wrong cobbler spacewalk_authentication_endpoint property
    after upgrade or migration
  * Fix migration script using awk missing in migration image

-------------------------------------------------------------------
Mon Apr 08 17:40:38 CEST 2024 - marina.latini@suse.com

- version 0.1.6-0
  * Pull image from authenticated registry
  * Port 80 should be published to the port 80 of the containers.
    8080 is squid
  * Autogenerate the database password
  * Add mgrctl term command
  * Fix --version flag
  * Deny uyuni to suma upgrade and viceversa
  * Refactor upgrade to clarify script end adding post upgrade
    script (bsc#1219887)
  * Add mgradm install podman arguments to define big volumes storage
  * k8s migration use same functions as upgrade
  * Allow to use images from RPM if present
  * Schedule a system list refresh after migrate if not runned before
  * Ignore error on optional flag
  * Fix migration of multiple autoinstallable distributions
  * Obsolete uyuni-proxy-systemd-service package by mgrpxy
  * Add GitHub workflow for checking changelog
  * Allow installation using --image image:tag
  * Add command to register Peripheral server to Hub
  * Add Node exporter (9100) and Taskomatic (9800) ports to the list
    of open TCP ports
  * Fix minimal administrator password length
  * Do not assume the current host is a cluster node when getting
    kubelet version
  * Add mgrpxy start, stop and restart commands
  * Remove shm size constraints on the server
  * Add mgrpxy and mgradm status commands
  * Use uninstall commands dry run by default to avoid unintended
    removals
  * Make first user mandatory at install time
  * Add inspect and upgrade command
  * Improve error handling when exec.Command is used
  * Start/Stop/Restart command with kubernetes

-------------------------------------------------------------------
Tue Feb 27 14:50:42 CET 2024 - marina.latini@suse.com

- version 0.1.5-0
  * Install aardvark-dns if netavark is installed (bsc#1220371)

-------------------------------------------------------------------
Tue Feb 13 18:45:11 CET 2024 - marina.latini@suse.com

- version 0.1.4-1
  * Add mgradm start stop and restart commands
  * Do not build fish shell completion on Red Hat Enterprise Linux
    and clones
  * Stop services and database in podman server gracefully
  * tomcat and taskomatic should listen on all interfaces also in podman case

-------------------------------------------------------------------
Wed Jan 31 14:56:34 CET 2024 - rosuna@suse.com

- version 0.1.3-1
  * Add configuration help
  * Add a warning message for interactive shell
  * Accept image URLs with the tag already appended
  * Add mgradm supportconfig command
  * Verify if podman, kubectl or helm are installed before using them
  * Add migration of config files
  * Disable SELinux relabeling by Podman for migration container.
    Fixes SELinux access problems for SSH agent socket.
  * FQDN optional in command install for Podman

-------------------------------------------------------------------
Mon Jan 15 11:08:45 CET 2024 - marina.latini@suse.com

- version 0.1.2-1
  * Adapt the build tags also in the spec file

-------------------------------------------------------------------
Thu Jan 11 16:49:18 CET 2024 - marina.latini@suse.com

- version 0.1.1-1
  * Use tito for releasing
  * Use the latest git tag as version instead of hardcoding it
  * Comply to reuse.software rules for license documentation
  * Add shell autocompletions
  * Rename the tools to mgradm and mgrctl
  * Add postgres migration
  * Add migration of autoinstallable distributions
  * Add mgrpxy tool with install and uninstall subcommands
  * Merge /srv/www/ volumes and add one for /var/lib/salt
  * Build uyuniadm also for Tumbleweed and ALP

-------------------------------------------------------------------
Tue Oct 24 13:24:46 UTC 2023 - Michele Bussolotto <michele.bussolotto@suse.com>

- Initial packaging of uyuni-tools 0.0.3
  * Create uyuniadm and uyunictl packages
  * Make it possible to build uyuniadm only on specific distro
07070100000148000081B4000000000000000000000001662A75280000001B000000000000000000000000000000000000002C00000000uyuni-tools/uyuni-tools.changes.cbosdo.l10n- Add localization support
07070100000149000081B4000000000000000000000001662A752800000074000000000000000000000000000000000000002C00000000uyuni-tools/uyuni-tools.changes.cbosdo.main- Require podman 4.5.0 for its --shm-size-systemd parameter
- Add --podman-mount-www flag for install and migration
0707010000014A000081B4000000000000000000000001662A752800000033000000000000000000000000000000000000003000000000uyuni-tools/uyuni-tools.changes.cbosdo.no-color- Only colorize output if outputting to a terminal
0707010000014B000081B4000000000000000000000001662A752800000031000000000000000000000000000000000000003300000000uyuni-tools/uyuni-tools.changes.cbosdo.spinner-fix- Fix output missing newlines due to the spinner
0707010000014C000081B4000000000000000000000001662A75280000003A000000000000000000000000000000000000003300000000uyuni-tools/uyuni-tools.changes.cbosdo.version-fix- Add product version and commit id to the version output
0707010000014D000081B4000000000000000000000001662A752800000053000000000000000000000000000000000000002D00000000uyuni-tools/uyuni-tools.changes.kwalter.coco- Add initial installation support for confidental computing attestation container
0707010000014E000081B4000000000000000000000001662A75280000001C000000000000000000000000000000000000003000000000uyuni-tools/uyuni-tools.changes.mbussolotto.gpg- add gpg command to mgradm
0707010000014F000081B4000000000000000000000001662A752800000013000000000000000000000000000000000000003500000000uyuni-tools/uyuni-tools.changes.mbussolotto.gpg_help- improve gpg help
07070100000150000081B4000000000000000000000001662A752800000044000000000000000000000000000000000000003500000000uyuni-tools/uyuni-tools.changes.mbussolotto.hide_scc- Hide message in stdout if SCCcredentials is missing (bsc#1222277)
07070100000151000081B4000000000000000000000001662A75280000002F000000000000000000000000000000000000003000000000uyuni-tools/uyuni-tools.changes.mbussolotto.pam- Allow PAM and LDAP authentication using SSSD
07070100000152000081B4000000000000000000000001662A75280000004E000000000000000000000000000000000000003D00000000uyuni-tools/uyuni-tools.changes.mbussolotto.path_volume_flag- podman-mount* flag should be used only on server installation and migration
07070100000153000081B4000000000000000000000001662A75280000003E000000000000000000000000000000000000003000000000uyuni-tools/uyuni-tools.changes.mbussolotto.ptf- create support ptf commands
- create mgrpxy upgrade command
07070100000154000081B4000000000000000000000001662A752800000036000000000000000000000000000000000000004000000000uyuni-tools/uyuni-tools.changes.mbussolotto.pull_policy_upgrade- change pull policy default to Always during upgrade
07070100000155000081B4000000000000000000000001662A75280000002C000000000000000000000000000000000000004300000000uyuni-tools/uyuni-tools.changes.mc.add-domain-for-internal-network- add domain for internal container network
07070100000156000081B4000000000000000000000001662A75280000001F000000000000000000000000000000000000003500000000uyuni-tools/uyuni-tools.changes.nadvornik.flaggroups- Add flag groups in help text
07070100000157000081B4000000000000000000000001662A752800000030000000000000000000000000000000000000002F00000000uyuni-tools/uyuni-tools.changes.nadvornik.stop- Stop the server if first user creation failed
07070100000158000081B4000000000000000000000001662A752800000036000000000000000000000000000000000000002F00000000uyuni-tools/uyuni-tools.changes.nadvornik.sudo- Allow migration with non-root user on source server
07070100000159000081B4000000000000000000000001662A75280000004B000000000000000000000000000000000000003900000000uyuni-tools/uyuni-tools.changes.oholecek.distromap_fixes- Rework distribution mapping to work with distros without .treeinfo data.
0707010000015A000081B4000000000000000000000001662A752800000029000000000000000000000000000000000000004500000000uyuni-tools/uyuni-tools.changes.oholecek.setup_proxy_pod_correct_net- Attach proxy pod to our podman network
0707010000015B000081B4000000000000000000000001662A752800000023000000000000000000000000000000000000003D00000000uyuni-tools/uyuni-tools.changes.oholecek.support_sql_command- Add 'mgradm support sql' command
0707010000015C000081B4000000000000000000000001662A7528000036F4000000000000000000000000000000000000001D00000000uyuni-tools/uyuni-tools.spec#
# spec file for package uyuni-tools
#
# Copyright (c) 2024 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via https://bugs.opensuse.org/
#


%global provider        github
%global provider_tld    com
%global org             uyuni-project
%global project         uyuni-tools
%global provider_prefix %{provider}.%{provider_tld}/%{org}/%{project}
%global productname     Uyuni

%global namespace       registry.opensuse.org/uyuni

%if 0%{?suse_version} >= 1600 || 0%{?sle_version} >= 150400 || 0%{?rhel} >= 8 || 0%{?fedora} >= 37 || 0%{?debian} >= 12 || 0%{?ubuntu} >= 2004
%define adm_build    1
%else
%define adm_build    0
%endif

%define name_adm mgradm
%define name_ctl mgrctl
%define name_pxy mgrpxy

# Completion files
%if 0%{?debian} || 0%{?ubuntu}
%define _zshdir %{_datarootdir}/zsh/vendor-completions
%else
%define _zshdir %{_datarootdir}/zsh/site-functions
%endif
# 0%{?debian} || 0%{?ubuntu}

Name:           %{project}
Version:        0.1.7
Release:        0
Summary:        Tools for managing %{productname} container
License:        Apache-2.0
Group:          System/Management
URL:            https://%{provider_prefix}
Source0:        %{name}-%{version}.tar.gz
Source1:        vendor.tar.gz
BuildRequires:  bash-completion
BuildRequires:  coreutils
%if 0%{?debian} || 0%{?ubuntu}
BuildRequires:  gettext
%endif
# 0%{?debian} || 0%{?ubuntu}

%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
BuildRequires:  fish
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

BuildRequires:  zsh
# Get the proper Go version on different distros
%if 0%{?suse_version}
BuildRequires:  golang(API) >= 1.20
%endif
# 0%{?suse_version}

%if 0%{?ubuntu}
%define go_version      1.20
BuildRequires:  golang-%{go_version}
%endif
# 0%{?ubuntu}

%if 0%{?debian}
BuildRequires:  golang >= 1.20
%endif
# 0%{?debian}


%if 0%{?fedora} || 0%{?rhel}
BuildRequires:  golang >= 1.19
%endif
# 0%{?fedora} || 0%{?rhel}


%description
Tools for managing uyuni container.


%if %{adm_build}

%package -n %{name_adm}
Summary:        Command line tool to install and update %{productname}
%if 0%{?suse_version}
Requires:       (aardvark-dns if netavark)
Requires:       (podman >= 4.5.0 if podman)
%endif
# 0%{?suse_version}

%description -n %{name_adm}
%{name_adm} is a convenient tool to install and update %{productname} components as containers running
either on Podman or a Kubernetes cluster.

%package -n %{name_pxy}
Summary:        Command line tool to install and update %{productname} proxy
Obsoletes:      uyuni-proxy-systemd-services
%if 0%{?suse_version}
Requires:       (aardvark-dns if netavark)
%endif
# 0%{?suse_version}

%description -n %{name_pxy}
%{name_pxy} is a convenient tool to install and update %{productname} proxy components as containers
running either on Podman or a Kubernetes cluster.

%package -n %{name_adm}-bash-completion
Summary:        Bash Completion for %{name_adm}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_adm} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_adm} and bash-completion)
%else
Supplements:    bash-completion
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_adm}-bash-completion
Bash command line completion support for %{name_adm}.

%package -n %{name_adm}-zsh-completion
Summary:        Zsh Completion for %{name_adm}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_adm} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_adm} and zsh)
%else
Supplements:    zsh
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_adm}-zsh-completion
Zsh command line completion support for %{name_adm}.

%package -n %{name_pxy}-bash-completion
Summary:        Bash Completion for %{name_pxy}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_pxy} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_pxy} and bash-completion)
%else
Supplements:    bash-completion
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_pxy}-bash-completion
Bash command line completion support for %{name_pxy}.

%package -n %{name_pxy}-zsh-completion
Summary:        Zsh Completion for %{name_pxy}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_pxy} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_pxy} and zsh)
%else
Supplements:    zsh
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_pxy}-zsh-completion
Zsh command line completion support for %{name_pxy}.


%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
%package -n %{name_adm}-fish-completion
Summary:        Fish Completion for %{name_adm}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_adm} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_adm} and fish)
%else
Supplements:    fish
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_adm}-fish-completion
Fish command line completion support for %{name_adm}.

%package -n %{name_pxy}-fish-completion
Summary:        Fish Completion for %{name_pxy}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_pxy} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_pxy} and fish)
%else
Supplements:    fish
%endif
# 0%{?suse_version} >= 150000


%description -n %{name_pxy}-fish-completion
Fish command line completion support for %{name_pxy}.

%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

%endif
# %{adm_build}

%package -n %{name_ctl}
Summary:        Command line tool to perform day-to-day operations on %{productname}

%description -n %{name_ctl}
%{name_ctl} is a tool helping with dayly tasks on %{productname} components running as containers
either on Podman or a Kubernetes cluster.

%package -n %{name_ctl}-bash-completion
Summary:        Bash Completion for %{name_ctl}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_ctl} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_ctl} and bash-completion)
%else
Supplements:    bash-completion
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_ctl}-bash-completion
Bash command line completion support for %{name_ctl}.

%package -n %{name_ctl}-zsh-completion
Summary:        Zsh Completion for %{name_ctl}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_ctl} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_ctl} and zsh)
%else
Supplements:    zsh
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_ctl}-zsh-completion
Zsh command line completion support for %{name_ctl}.


%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
%package -n %{name_ctl}-fish-completion
Summary:        Fish Completion for %{name_ctl}
Group:          System/Shells
BuildArch:      noarch
Requires:       %{name_ctl} = %{version}
%if 0%{?suse_version} >= 150000
Supplements:    (%{name_ctl} and fish)
%else
Supplements:    fish
%endif
# 0%{?suse_version} >= 150000

%description -n %{name_ctl}-fish-completion
Fish command line completion support for %{name_ctl}.
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

# Only SUSE distros have a -lang packages, for the others they
# will all be in the correspdonding tool package.
%if 0%{?suse_version} || 0%{?sle_version}
%lang_package -n %{name_ctl}
%lang_package -n %{name_pxy}

%if %{adm_build}
%lang_package -n %{name_adm}
%endif
# %{adm_build}

%endif
# 0%{?suse_version} || 0%{?sle_version}

%prep
%autosetup
tar -zxf %{SOURCE1}

%build
export GOFLAGS=-mod=vendor
mkdir -p bin
UTILS_PATH="%{provider_prefix}/shared/utils"

tag=%{!?_default_tag:latest}
%if "%{?_default_tag}" != ""
    tag='%{_default_tag}'
%endif
# "%{?_default_tag}" != ""

image=%{namespace}
%if "%{?_default_namespace}" != ""
  namespace='%{_default_namespace}'
%endif
# "%{?_default_namespace}" != ""

go_tags=""
%if "%{?_uyuni_tools_tags}" != ""
  go_tags="-tags %{_uyuni_tools_tags}"
%endif
# "%{?_uyuni_tools_tags}" != ""

go_path=
%if 0%{?ubuntu}
  go_path=/usr/lib/go-%{go_version}/bin/
%else
  %if "%{?_go_bin}" != ""
    go_path='%{_go_bin}/'
  %endif
# "%{?_go_bin}" != ""

%endif
# 0%{?ubuntu}

GOLD_FLAGS="-X \"${UTILS_PATH}.Version=%{version} (%{version_details})\" -X ${UTILS_PATH}.LocaleRoot=%{_datadir}/locale"
if test -n "${namespace}"; then
    GOLD_FLAGS="${GOLD_FLAGS} -X ${UTILS_PATH}.DefaultNamespace=${namespace} -X ${UTILS_PATH}.DefaultTag=${tag}"
fi

if test -n "${tag}"; then
    GOLD_FLAGS="${GOLD_FLAGS} -X ${UTILS_PATH}.DefaultTag=${tag}"
fi

# Workaround for rpm on Fedora and EL clones not able to handle go's compressed debug symbols
# Found compressed .debug_aranges section, not attempting dwz compression
%if 0%{?rhel} >= 8 || 0%{?fedora} >= 38
GOLD_FLAGS="-compressdwarf=false ${GOLD_FLAGS}"
%endif
# 0%{?rhel} >= 8 || 0%{?fedora} >= 38

# Workaround for missing build-id on Fedora
# error: Missing build-id in [...]
%if 0%{?fedora} >= 38
GOLD_FLAGS="-B 0x$(head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') ${GOLD_FLAGS}"
%endif
# 0%{?fedora} >= 38

${go_path}go build ${go_tags} -ldflags "${GOLD_FLAGS}" -o ./bin ./...

%if ! %{adm_build}
rm ./bin/%{name_adm}
rm ./bin/%{name_pxy}
%endif
# ! %{adm_build}

%install
install -m 0755 -vd %{buildroot}%{_bindir}
install -m 0755 -vp ./bin/* %{buildroot}%{_bindir}/

# Generate the machine object files for localizations
./locale/build.sh %{buildroot}%{_datadir}/locale/

%find_lang %{name_ctl}
%if %{adm_build}
%find_lang %{name_adm}
%find_lang %{name_pxy}
%else
rm %{buildroot}%{_datadir}/locale/*/LC_MESSAGES/%{name_adm}.mo
rm %{buildroot}%{_datadir}/locale/*/LC_MESSAGES/%{name_pxy}.mo
%endif
# %{adm_build}

# Completion files
mkdir -p %{buildroot}%{_datarootdir}/bash-completion/completions/
mkdir -p %{buildroot}%{_zshdir}

%{buildroot}/%{_bindir}/%{name_ctl} completion bash > %{buildroot}%{_datarootdir}/bash-completion/completions/%{name_ctl}
%{buildroot}/%{_bindir}/%{name_ctl} completion zsh > %{buildroot}%{_zshdir}/_%{name_ctl}

%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
mkdir -p %{buildroot}%{_datarootdir}/fish/vendor_completions.d/
%{buildroot}/%{_bindir}/%{name_ctl} completion fish > %{buildroot}%{_datarootdir}/fish/vendor_completions.d/%{name_ctl}.fish
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

%if %{adm_build}

%{buildroot}/%{_bindir}/%{name_adm} completion bash > %{buildroot}%{_datarootdir}/bash-completion/completions/%{name_adm}
%{buildroot}/%{_bindir}/%{name_adm} completion zsh > %{buildroot}%{_zshdir}/_%{name_adm}

%{buildroot}/%{_bindir}/%{name_pxy} completion bash > %{buildroot}%{_datarootdir}/bash-completion/completions/%{name_pxy}
%{buildroot}/%{_bindir}/%{name_pxy} completion zsh > %{buildroot}%{_zshdir}/_%{name_pxy}

%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
%{buildroot}/%{_bindir}/%{name_adm} completion fish > %{buildroot}%{_datarootdir}/fish/vendor_completions.d/%{name_adm}.fish
%{buildroot}/%{_bindir}/%{name_pxy} completion fish > %{buildroot}%{_datarootdir}/fish/vendor_completions.d/%{name_pxy}.fish
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

%endif
# %{adm_build}

%if %{adm_build}

# mgradm packages files

# Only SUSE distros have a -lang package
%if 0%{?suse_version} || 0%{?sle_version}
%files -n %{name_adm}-lang -f %{name_adm}.lang

%files -n %{name_adm}
%else
%files -n %{name_adm} -f %{name_adm}.lang
%endif
# 0%{?suse_version} || 0%{?sle_version}

%defattr(-,root,root)
%doc README.md
%license LICENSE
%{_bindir}/%{name_adm}

%files -n %{name_adm}-bash-completion
%{_datarootdir}/bash-completion/completions/%{name_adm}

%files -n %{name_adm}-zsh-completion
%{_zshdir}/_%{name_adm}

%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
%files -n %{name_adm}-fish-completion
%{_datarootdir}/fish/vendor_completions.d/%{name_adm}.fish
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}


# mgrpxy packages files

# Only SUSE distros have a -lang package
%if 0%{?suse_version} || 0%{?sle_version}
%files -n %{name_pxy}-lang -f %{name_pxy}.lang

%files -n %{name_pxy}
%else
%files -n %{name_pxy} -f %{name_pxy}.lang
%endif
# 0%{?suse_version} || 0%{?sle_version}

%defattr(-,root,root)
%doc README.md
%license LICENSE
%{_bindir}/%{name_pxy}

%files -n %{name_pxy}-bash-completion
%{_datarootdir}/bash-completion/completions/%{name_pxy}

%files -n %{name_pxy}-zsh-completion
%{_zshdir}/_%{name_pxy}

%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
%files -n %{name_pxy}-fish-completion
%{_datarootdir}/fish/vendor_completions.d/%{name_pxy}.fish
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

%endif
# %{adm_build}

# mgrctl packages files

# Only SUSE distros have a -lang package
%if 0%{?suse_version} || 0%{?sle_version}
%files -n %{name_ctl}-lang -f %{name_ctl}.lang

%files -n %{name_ctl}
%else
%files -n %{name_ctl} -f %{name_ctl}.lang
%endif
# 0%{?suse_version} || 0%{?sle_version}

%defattr(-,root,root)
%doc README.md
%license LICENSE
%{_bindir}/%{name_ctl}

%files -n %{name_ctl}-bash-completion
%{_datarootdir}/bash-completion/completions/%{name_ctl}

%files -n %{name_ctl}-zsh-completion
%{_zshdir}/_%{name_ctl}

%if 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}
%files -n %{name_ctl}-fish-completion
%{_datarootdir}/fish/vendor_completions.d/%{name_ctl}.fish
%endif
# 0%{?is_opensuse} || 0%{?fedora} || 0%{?debian} || 0%{?ubuntu}

%changelog
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!
openSUSE Build Service is sponsored by