feat: add cli

This commit is contained in:
unteem 2023-04-14 15:53:41 +02:00
parent d74037e6db
commit b9532d240b
61 changed files with 5690 additions and 175 deletions

287
LICENSE Normal file
View file

@ -0,0 +1,287 @@
EUROPEAN UNION PUBLIC LICENCE v. 1.2
EUPL © the European Union 2007, 2016
This European Union Public Licence (the EUPL) applies to the Work (as defined
below) which is provided under the terms of this Licence. Any use of the Work,
other than as authorised under this Licence is prohibited (to the extent such
use is covered by a right of the copyright holder of the Work).
The Work is provided under the terms of this Licence when the Licensor (as
defined below) has placed the following notice immediately following the
copyright notice for the Work:
Licensed under the EUPL
or has expressed by any other means his willingness to license under the EUPL.
1. Definitions
In this Licence, the following terms have the following meaning:
- The Licence: this Licence.
- The Original Work: the work or software distributed or communicated by the
Licensor under this Licence, available as Source Code and also as Executable
Code as the case may be.
- Derivative Works: the works or software that could be created by the
Licensee, based upon the Original Work or modifications thereof. This Licence
does not define the extent of modification or dependence on the Original Work
required in order to classify a work as a Derivative Work; this extent is
determined by copyright law applicable in the country mentioned in Article 15.
- The Work: the Original Work or its Derivative Works.
- The Source Code: the human-readable form of the Work which is the most
convenient for people to study and modify.
- The Executable Code: any code which has generally been compiled and which is
meant to be interpreted by a computer as a program.
- The Licensor: the natural or legal person that distributes or communicates
the Work under the Licence.
- Contributor(s): any natural or legal person who modifies the Work under the
Licence, or otherwise contributes to the creation of a Derivative Work.
- The Licensee or You: any natural or legal person who makes any usage of
the Work under the terms of the Licence.
- Distribution or Communication: any act of selling, giving, lending,
renting, distributing, communicating, transmitting, or otherwise making
available, online or offline, copies of the Work or providing access to its
essential functionalities at the disposal of any other natural or legal
person.
2. Scope of the rights granted by the Licence
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
sublicensable licence to do the following, for the duration of copyright vested
in the Original Work:
- use the Work in any circumstance and for all usage,
- reproduce the Work,
- modify the Work, and make Derivative Works based upon the Work,
- communicate to the public, including the right to make available or display
the Work or copies thereof to the public and perform publicly, as the case may
be, the Work,
- distribute the Work or copies thereof,
- lend and rent the Work or copies thereof,
- sublicense rights in the Work or copies thereof.
Those rights can be exercised on any media, supports and formats, whether now
known or later invented, as far as the applicable law permits so.
In the countries where moral rights apply, the Licensor waives his right to
exercise his moral right to the extent allowed by law in order to make effective
the licence of the economic rights here above listed.
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
any patents held by the Licensor, to the extent necessary to make use of the
rights granted on the Work under this Licence.
3. Communication of the Source Code
The Licensor may provide the Work either in its Source Code form, or as
Executable Code. If the Work is provided as Executable Code, the Licensor
provides in addition a machine-readable copy of the Source Code of the Work
along with each copy of the Work that the Licensor distributes or indicates, in
a notice following the copyright notice attached to the Work, a repository where
the Source Code is easily and freely accessible for as long as the Licensor
continues to distribute or communicate the Work.
4. Limitations on copyright
Nothing in this Licence is intended to deprive the Licensee of the benefits from
any exception or limitation to the exclusive rights of the rights owners in the
Work, of the exhaustion of those rights or of other applicable limitations
thereto.
5. Obligations of the Licensee
The grant of the rights mentioned above is subject to some restrictions and
obligations imposed on the Licensee. Those obligations are the following:
Attribution right: The Licensee shall keep intact all copyright, patent or
trademarks notices and all notices that refer to the Licence and to the
disclaimer of warranties. The Licensee must include a copy of such notices and a
copy of the Licence with every copy of the Work he/she distributes or
communicates. The Licensee must cause any Derivative Work to carry prominent
notices stating that the Work has been modified and the date of modification.
Copyleft clause: If the Licensee distributes or communicates copies of the
Original Works or Derivative Works, this Distribution or Communication will be
done under the terms of this Licence or of a later version of this Licence
unless the Original Work is expressly distributed only under this version of the
Licence — for example by communicating EUPL v. 1.2 only. The Licensee
(becoming Licensor) cannot offer or impose any additional terms or conditions on
the Work or Derivative Work that alter or restrict the terms of the Licence.
Compatibility clause: If the Licensee Distributes or Communicates Derivative
Works or copies thereof based upon both the Work and another work licensed under
a Compatible Licence, this Distribution or Communication can be done under the
terms of this Compatible Licence. For the sake of this clause, Compatible
Licence refers to the licences listed in the appendix attached to this Licence.
Should the Licensee's obligations under the Compatible Licence conflict with
his/her obligations under this Licence, the obligations of the Compatible
Licence shall prevail.
Provision of Source Code: When distributing or communicating copies of the Work,
the Licensee will provide a machine-readable copy of the Source Code or indicate
a repository where this Source will be easily and freely available for as long
as the Licensee continues to distribute or communicate the Work.
Legal Protection: This Licence does not grant permission to use the trade names,
trademarks, service marks, or 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 copyright notice.
6. Chain of Authorship
The original Licensor warrants that the copyright in the Original Work granted
hereunder is owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each Contributor warrants that the copyright in the modifications he/she brings
to the Work are owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each time You accept the Licence, the original Licensor and subsequent
Contributors grant You a licence to their contributions to the Work, under the
terms of this Licence.
7. Disclaimer of Warranty
The Work is a work in progress, which is continuously improved by numerous
Contributors. It is not a finished work and may therefore contain defects or
bugs inherent to this type of development.
For the above reason, the Work is provided under the Licence on an as is basis
and without warranties of any kind concerning the Work, including without
limitation merchantability, fitness for a particular purpose, absence of defects
or errors, accuracy, non-infringement of intellectual property rights other than
copyright as stated in Article 6 of this Licence.
This disclaimer of warranty is an essential part of the Licence and a condition
for the grant of any rights to the Work.
8. Disclaimer of Liability
Except in the cases of wilful misconduct or damages directly caused to natural
persons, the Licensor will in no event be liable for any direct or indirect,
material or moral, damages of any kind, arising out of the Licence or of the use
of the Work, including without limitation, damages for loss of goodwill, work
stoppage, computer failure or malfunction, loss of data or any commercial
damage, even if the Licensor has been advised of the possibility of such damage.
However, the Licensor will be liable under statutory product liability laws as
far such laws apply to the Work.
9. Additional agreements
While distributing the Work, You may choose to conclude an additional agreement,
defining obligations or services consistent with this Licence. However, if
accepting obligations, You may act only on your own behalf and on your sole
responsibility, not on behalf of the original Licensor or 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
the fact You have accepted any warranty or additional liability.
10. Acceptance of the Licence
The provisions of this Licence can be accepted by clicking on an icon I agree
placed under the bottom of a window displaying the text of this Licence or by
affirming consent in any other similar way, in accordance with the rules of
applicable law. Clicking on that icon indicates your clear and irrevocable
acceptance of this Licence and all of its terms and conditions.
Similarly, you irrevocably accept this Licence and all of its terms and
conditions by exercising any rights granted to You by Article 2 of this Licence,
such as the use of the Work, the creation by You of a Derivative Work or the
Distribution or Communication by You of the Work or copies thereof.
11. Information to the public
In case of any Distribution or Communication of the Work by means of electronic
communication by You (for example, by offering to download the Work from a
remote location) the distribution channel or media (for example, a website) must
at least provide to the public the information requested by the applicable law
regarding the Licensor, the Licence and the way it may be accessible, concluded,
stored and reproduced by the Licensee.
12. Termination of the Licence
The Licence and the rights granted hereunder will terminate automatically upon
any breach by the Licensee of the terms of the Licence.
Such a termination will not terminate the licences of any person who has
received the Work from the Licensee under the Licence, provided such persons
remain in full compliance with the Licence.
13. Miscellaneous
Without prejudice of Article 9 above, the Licence represents the complete
agreement between the Parties as to the Work.
If any provision of the Licence is invalid or unenforceable under applicable
law, this will not affect the validity or enforceability of the Licence as a
whole. Such provision will be construed or reformed so as necessary to make it
valid and enforceable.
The European Commission may publish other linguistic versions or new versions of
this Licence or updated versions of the Appendix, so far this is required and
reasonable, without reducing the scope of the rights granted by the Licence. New
versions of the Licence will be published with a unique version number.
All linguistic versions of this Licence, approved by the European Commission,
have identical value. Parties can take advantage of the linguistic version of
their choice.
14. Jurisdiction
Without prejudice to specific agreement between parties,
- any litigation resulting from the interpretation of this License, arising
between the European Union institutions, bodies, offices or agencies, as a
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
of Justice of the European Union, as laid down in article 272 of the Treaty on
the Functioning of the European Union,
- any litigation arising between other parties and resulting from the
interpretation of this License, will be subject to the exclusive jurisdiction
of the competent court where the Licensor resides or conducts its primary
business.
15. Applicable Law
Without prejudice to specific agreement between parties,
- this Licence shall be governed by the law of the European Union Member State
where the Licensor has his seat, resides or has his registered office,
- this licence shall be governed by Belgian law if the Licensor has no seat,
residence or registered office inside a European Union Member State.
Appendix
Compatible Licences according to Article 5 EUPL are:
- GNU General Public License (GPL) v. 2, v. 3
- GNU Affero General Public License (AGPL) v. 3
- Open Software License (OSL) v. 2.1, v. 3.0
- Eclipse Public License (EPL) v. 1.0
- CeCILL v. 2.0, v. 2.1
- Mozilla Public Licence (MPL) v. 2
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
works other than software
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
Reciprocity (LiLiQ-R+).
The European Commission may update this Appendix to later versions of the above
licences without producing a new version of the EUPL, as long as they provide
the rights granted in Article 2 of this Licence and protect the covered Source
Code from exclusive appropriation.
All other changes or additions to this Appendix require the production of a new
EUPL version.

7
cm.yaml Normal file
View file

@ -0,0 +1,7 @@
apiVersion: v1
data:
CLUSTER_DOMAIN: dev.local
kind: ConfigMap
metadata:
name: cluster-settings
namespace: flux-system

357
cmd/init.go Normal file
View file

@ -0,0 +1,357 @@
package cmd
import (
"context"
"fmt"
"log"
"os"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
fluxKSv1beta2 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/ssa"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"libre.sh/cli/pkg/component"
"libre.sh/cli/pkg/component/crds"
"libre.sh/cli/pkg/component/databases"
"libre.sh/cli/pkg/component/general"
"libre.sh/cli/pkg/component/minio"
"libre.sh/cli/pkg/component/networking"
"libre.sh/cli/pkg/component/observability"
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var (
Scheme = runtime.NewScheme()
ActiveDot = "•"
InactiveDot = "○"
)
type Client struct {
KubeClient client.Client
}
func NewClientForConfig(cfg *rest.Config) (*Client, error) {
rm, err := apiutil.NewDynamicRESTMapper(cfg)
if err != nil {
return nil, fmt.Errorf("failed to create dynamic rest mapper: %v", err)
}
cl, err := client.New(cfg, client.Options{
Scheme: Scheme,
Mapper: rm,
})
if err != nil {
return nil, fmt.Errorf("failed to create client: %v", err)
}
c := &Client{
KubeClient: cl,
}
return c, nil
}
var initCmd = &cobra.Command{
Use: "init",
Short: "",
Hidden: true,
Run: initStage,
}
type LibreshModel /* [T any] */ struct {
index int
manager *ssa.ResourceManager
Stages []component.Model
Scopes []component.Scope
width int
height int
}
func NewLibreshModel(manager *ssa.ResourceManager) LibreshModel {
scopes := []component.Scope{
&general.Model{
ScopeModel: &component.ScopeModel{},
},
&crds.Model{
ScopeModel: &component.ScopeModel{},
},
/* &csi.Model{
ScopeModel: &component.ScopeModel{},
}, */
&networking.Model{
ScopeModel: &component.ScopeModel{},
},
&minio.Model{
ScopeModel: &component.ScopeModel{},
},
&databases.Model{
ScopeModel: &component.ScopeModel{},
},
/* &auth.Model{
ScopeModel: &component.ScopeModel{},
}, */
&observability.Model{
ScopeModel: &component.ScopeModel{},
},
}
model := LibreshModel{
manager: manager,
// Scopes: []component.Scope{},
}
for _, scope := range scopes {
scope.SetManager(manager)
scope.Init()
stage, err := scope.Stage()
if err != nil {
log.Fatal(err)
}
model.Stages = append(model.Stages, stage)
}
return model
}
func initStage(cmd *cobra.Command, args []string) {
// ctx := context.Background()
utilruntime.Must(clientgoscheme.AddToScheme(Scheme))
utilruntime.Must(fluxKSv1beta2.AddToScheme(Scheme))
utilruntime.Must(sourcev1.AddToScheme(Scheme))
client, err := NewClientForConfig(config.GetConfigOrDie())
if err != nil {
log.Fatal(err)
}
pollingOpts := polling.Options{}
poller := polling.NewStatusPoller(client.KubeClient, client.KubeClient.RESTMapper(), pollingOpts)
// Create the server-side apply manager.
manager := ssa.NewResourceManager(client.KubeClient, poller, ssa.Owner{
Field: "rutime-manager",
Group: "libre.sh",
})
ns := &corev1.Namespace{}
ns.SetName("libresh-system")
_, err = ctrl.CreateOrUpdate(context.TODO(), client.KubeClient, ns, func() error {
return nil
})
if err != nil {
// TODO manager error
log.Fatal(err)
}
gitRepo := &sourcev1.GitRepository{}
gitRepo.SetName("libresh-cluster")
gitRepo.SetNamespace("libresh-system")
_, err = ctrl.CreateOrUpdate(context.TODO(), client.KubeClient, gitRepo, func() error {
ignore := `
# exclude all
/*
# include kubernetes directory
!/cluster
`
gitRepo.Spec = sourcev1.GitRepositorySpec{
URL: "https://forge.liiib.re/libre.sh/libre.sh",
Reference: &sourcev1.GitRepositoryRef{
// TODO fix when we are prod ready
Branch: "runtime",
},
Ignore: &ignore,
Interval: metav1.Duration{
Duration: 10 * time.Minute,
},
Timeout: &metav1.Duration{
Duration: 1 * time.Minute,
},
}
return nil
})
if err != nil {
// TODO manager error
log.Fatal(err)
}
libreshModel := NewLibreshModel(manager)
// fmt.Println(minioStage.View())
if _, err := tea.NewProgram(libreshModel, tea.WithAltScreen()).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
func init() {
rootCmd.AddCommand(initCmd)
}
func (s LibreshModel) Init() tea.Cmd {
return nil
}
func (m LibreshModel) View() string {
b := strings.Builder{}
logo := `
`
/* logoImg := `
||||||||
||||||||||||||||
||||||||||||||||||||||||
||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||
|||||||||||||||| || ||||||||||||||||
||||||||||||||| |||||| |||||||||||||||
||||||||||||||| |||||| |||||||||||||||
|||||||||||||||||| || ||||||||||||||||||
|||||||||||||||||||| ||||||||||||||||||||
|||||||||| |||||||||| |||||||||| ||||||||||
|||||||| |||||||| |||||||| ||||||||
||||||||||| ||||||||| ||||||||| |||||||||||
|||||||||||| ||||||||| ||||||||| ||||||||||||
|||||||||||| |||||||| |||||||| ||||||||||||
|||||||||||||| |||||| |||||| ||||||||||||||
|||||||||||||| | |||| | ||||||||||||||
||||||||||||||| |||||| |||||||||||||||
|||||||||||||| |||| ||||||||||||||
||||||||||||||| |||||||||||||||
||||||||||||||||||||||||||||||
||||||||||||||||||||||||||
||||||||||||||||||||||||
`
*/
// totalStage := len(s.Stages)
// b.WriteString("\n \n")
/* buttonStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFF7DB")).
Background(lipgloss.Color("#888B7E")).
Padding(0, 1).
MarginTop(1)
// Border(lipgloss.RoundedBorder())
activeButtonStyle := buttonStyle.Copy().
Foreground(lipgloss.Color("#FFF7DB")).
Background(lipgloss.Color("#F25D94")).
// MarginRight(2).
Underline(true)
continueButton := activeButtonStyle.Render("✔") */
// b.WriteString(continueButton + "\n \n")
var s string
for i := 0; i < len(m.Stages); i++ {
if i == m.index {
// m += m.ActiveDot
s += " ✔ " + "━━━"
continue
}
s += "━━━" + " " + InactiveDot + " " + "━━━"
}
header := lipgloss.PlaceHorizontal(
/* 50, 3, */
m.width-10, /* 1, */
lipgloss.Center, /* lipgloss.Center, */
lipgloss.JoinVertical(
lipgloss.Top,
logo,
s,
/* b.progress.ViewAs(instance.builder.Progress()/100),
FeintStyle.Render(instance.builder.ProgressText()),
output, */
),
)
b.WriteString(header + "\n \n")
// b.WriteString(s + "\n \n")
for _, stage := range m.Stages {
isStageCompleted, _ := stage.Complete()
if !isStageCompleted {
b.WriteString(stage.View())
return b.String()
}
}
// TODO
return b.String()
}
func (s LibreshModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
s.width = msg.Width
s.height = msg.Height
// f.browser.UpdateSize(f.width, f.height-f.eventBrowserOffset()-2)
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyCtrlBackslash:
return s, tea.Quit
}
}
s.index = 0
for _, stage := range s.Stages {
isStageCompleted, _ := stage.Complete()
if !isStageCompleted {
_, cmd = stage.Update(msg)
cmds = append(cmds, cmd)
// break
return s, tea.Batch(cmds...)
}
s.index++
}
return s, tea.Batch(cmds...)
}

62
cmd/root.go Normal file
View file

@ -0,0 +1,62 @@
/*
Copyright © 2023 Libresh <contact@libre.sh>
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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.
*/
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var (
version = "0.1.0"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "cli",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

130
go.mod Normal file
View file

@ -0,0 +1,130 @@
module libre.sh/cli
go 1.19
require (
github.com/brittonhayes/glitter v0.2.0
github.com/charmbracelet/bubbles v0.15.0
github.com/charmbracelet/bubbletea v0.23.2
github.com/charmbracelet/lipgloss v0.7.1
github.com/fluxcd/kustomize-controller/api v0.35.0
github.com/fluxcd/pkg/apis/meta v0.19.1
github.com/fluxcd/pkg/ssa v0.24.1
github.com/fluxcd/source-controller/api v0.36.1
github.com/minio/minio-go v6.0.14+incompatible
github.com/minio/minio-go/v7 v7.0.47
github.com/spf13/cobra v1.6.1
golang.org/x/term v0.5.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.26.2
k8s.io/apimachinery v0.26.2
k8s.io/client-go v0.26.2
libre.sh/object-storage-operator v0.0.0-20230320164638-257f27e80c27
sigs.k8s.io/cli-utils v0.34.0
sigs.k8s.io/controller-runtime v0.14.5
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v0.8.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.40.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.26.2 // indirect
k8s.io/cli-runtime v0.26.2 // indirect
k8s.io/component-base v0.26.2 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230227204213-929b88f6cb43 // indirect
k8s.io/kubectl v0.25.4 // indirect
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

513
go.sum Normal file
View file

@ -0,0 +1,513 @@
cloud.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=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/brittonhayes/glitter v0.2.0 h1:p3p+WAgFvkPwFtN5rzjT9fx1c1UYiWYmRgl5op1/gtQ=
github.com/brittonhayes/glitter v0.2.0/go.mod h1:wFAjLUmCA/0EqSReefEwQ46eJGEtmJmLiuimGN0K33c=
github.com/brittonhayes/pkg v0.5.1/go.mod h1:Nl7GzQY5iqM9wwztYdOpqpGpbjJhrYOXEExdFS0MFKE=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+AI16Ch0KPZPeDs=
github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI=
github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74=
github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
github.com/charmbracelet/bubbletea v0.14.0/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps=
github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg=
github.com/charmbracelet/lipgloss v0.2.1/go.mod h1:uiZUfrHLQN14I0lJ5591WtcHyY1X76pOIPSaRKPY6dE=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/fluxcd/kustomize-controller/api v0.35.0 h1:G3Bk56GNGKM6R97tx3tJfwYk4N/ZS3Kv/Qqscyv0hd0=
github.com/fluxcd/kustomize-controller/api v0.35.0/go.mod h1:hrxVOUss0om4mg+ykMYtH4CgLuM2RReSPf0hG9e0b18=
github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q=
github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8=
github.com/fluxcd/pkg/apis/kustomize v0.8.1 h1:uRH9xVDJfSBGIiL6PIhkguHvf2Nme6uTWX+RX1iZznc=
github.com/fluxcd/pkg/apis/kustomize v0.8.1/go.mod h1:TBem+2mHp6Ib7XD1fmzDkoUnBzx07wSzIYo6BVx3XAc=
github.com/fluxcd/pkg/apis/meta v0.19.1 h1:fCI5CnTXpAqr67UlaI9q0H+OztMKB5kDTr6xV6vlAo0=
github.com/fluxcd/pkg/apis/meta v0.19.1/go.mod h1:ZPPMYrPnWwPQYNEGM/Uc0N4SurUPS3xNI3IIpCQEfuM=
github.com/fluxcd/pkg/ssa v0.24.1 h1:0dn5FqyYdGa+VuDp5EJrkLbPq5xhhSAAkMgGUeMpOM0=
github.com/fluxcd/pkg/ssa v0.24.1/go.mod h1:nEOUOwGotBlNZkTkO6GHPlI0U0BmHTavFd1Jk+TzsGw=
github.com/fluxcd/source-controller/api v0.36.1 h1:/ul69kJNEwrFG1Cwk2P/GwgraIxOETCL+tP+zMtxTu8=
github.com/fluxcd/source-controller/api v0.36.1/go.mod h1:GktZmd5Dfxo84vPFBdLDl0bBtiJRODfd47uugK0romU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
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/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
github.com/minio/minio-go/v7 v7.0.47 h1:sLiuCKGSIcn/MI6lREmTzX91DX/oRau4ia0j6e6eOSs=
github.com/minio/minio-go/v7 v7.0.47/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c h1:RC8WMpjonrBfyAh6VN/POIPtYD5tRAq0qMqCRjQNK+g=
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c/go.mod h1:9OcmHNQQUTbk4XCffrLgN1NEKc2mh5u++biHVrvHsSU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a h1:jlDOeO5TU0pYlbc/y6PFguab5IjANI0Knrpg3u/ton4=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0=
github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q=
github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk=
github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 h1:5/KzhcSqd4UgY51l17r7C5g/JiE6DRw1Vq7VJfQHuMc=
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-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-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/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
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.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=
k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU=
k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko=
k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8=
k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
k8s.io/cli-runtime v0.26.2 h1:6XcIQOYW1RGNwFgRwejvyUyAojhToPmJLGr0JBMC5jw=
k8s.io/cli-runtime v0.26.2/go.mod h1:U7sIXX7n6ZB+MmYQsyJratzPeJwgITqrSlpr1a5wM5I=
k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=
k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI=
k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs=
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230227204213-929b88f6cb43 h1:HY8L2rAW+bsRTWnhVVkcnyqmLt34nQUGe99AGh/NMbE=
k8s.io/kube-openapi v0.0.0-20230227204213-929b88f6cb43/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY=
k8s.io/kubectl v0.25.4 h1:O3OA1z4V1ZyvxCvScjq0pxAP7ABgznr8UvnVObgI6Dc=
k8s.io/kubectl v0.25.4/go.mod h1:CKMrQ67Bn2YCP26tZStPQGq62zr9pvzEf65A0navm8k=
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
libre.sh/object-storage-operator v0.0.0-20230320164638-257f27e80c27 h1:pD/xpdsB2QoKDkUZe3BJMDk3jwUFVGHwpnuZn32fwls=
libre.sh/object-storage-operator v0.0.0-20230320164638-257f27e80c27/go.mod h1:BEOzzPZw+Y7WOQG2f1uPCB6ZO3IilYb56g5rZJ+D0/0=
sigs.k8s.io/cli-utils v0.34.0 h1:zCUitt54f0/MYj/ajVFnG6XSXMhpZ72O/3RewIchW8w=
sigs.k8s.io/cli-utils v0.34.0/go.mod h1:EXyMwPMu9OL+LRnj0JEMsGG/fRvbgFadcVlSnE8RhFs=
sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s=
sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM=
sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s=
sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk=
sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

9
keycloak-secret.yaml Normal file
View file

@ -0,0 +1,9 @@
apiVersion: v1
stringData:
password: password
username: admin
kind: Secret
metadata:
name: keycloak-admin-password
namespace: libresh-system
type: Opaque

23
keycloak.yaml Normal file
View file

@ -0,0 +1,23 @@
ingress:
enabled: true
ingressClassName: nginx
console:
enabled: true
serviceMonitor:
enabled: true
namespace: keycloak
prometheusRule:
enabled: true
namespace: keycloak
autoscaling:
enabled: true
dbChecker:
enabled: true
extraEnv: |
- name: KEYCLOAK_ADMIN
value: admin
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-admin-password
key: password

26
logo.txt Normal file
View file

@ -0,0 +1,26 @@
||||||||
||||||||||||||||
||||||||||||||||||||||||
||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||
|||||||||||||||| || ||||||||||||||||
||||||||||||||| |||||| |||||||||||||||
||||||||||||||| |||||| |||||||||||||||
|||||||||||||||||| || ||||||||||||||||||
|||||||||||||||||||| ||||||||||||||||||||
|||||||||| |||||||||| |||||||||| ||||||||||
|||||||| |||||||| |||||||| ||||||||
||||||||||| ||||||||| ||||||||| |||||||||||
|||||||||||| ||||||||| ||||||||| ||||||||||||
|||||||||||| |||||||| |||||||| ||||||||||||
|||||||||||||| |||||| |||||| ||||||||||||||
|||||||||||||| | |||| | ||||||||||||||
||||||||||||||| |||||| |||||||||||||||
|||||||||||||| |||| ||||||||||||||
||||||||||||||| |||||||||||||||
||||||||||||||||||||||||||||||
||||||||||||||||||||||||||
||||||||||||||||||||||||

24
main.go Normal file
View file

@ -0,0 +1,24 @@
/*
Copyright © 2023 Libresh <contact@libre.sh>
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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.
*/
package main
import (
"libre.sh/cli/cmd"
)
func main() {
cmd.Execute()
}

397
pkg/bubble/installer.go Normal file
View file

@ -0,0 +1,397 @@
package bubble
import (
"context"
"fmt"
"strings"
"time"
"github.com/brittonhayes/glitter/glitter"
gstyle "github.com/brittonhayes/glitter/style"
"github.com/brittonhayes/glitter/theme"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/fluxcd/pkg/ssa"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"libre.sh/cli/pkg/component"
"sigs.k8s.io/cli-utils/pkg/object"
)
type InstallerOpts[T any] struct {
TaskOpts[T]
Prompt string
/* // OffsetY represents the list offset from the Y position.
PaddingY int
// ItemGetter returns all available items to the list
// items on each render of the list.
//
// This is required.
ItemGetter ItemGetter */
Components []component.Component
Manager *ssa.ResourceManager
}
func NewInstaller[T any](id string, opts InstallerOpts[T]) Task[T] {
p := progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
progress.WithoutPercentage(),
)
s := spinner.New()
s.Spinner = spinner.Monkey
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
return &Installer[T]{
id: id,
opts: opts,
spinner: s,
progress: p,
changeSet: ssa.NewChangeSet(),
foo: ssa.NewChangeSet(),
bar: ssa.NewChangeSet(),
}
}
type Installer[T any] struct {
id string
opts InstallerOpts[T]
spinner spinner.Model
progress progress.Model
index int
width int
height int
done bool
init bool
changeSet *ssa.ChangeSet
foo *ssa.ChangeSet
bar *ssa.ChangeSet
}
func (i Installer[T]) ID() string {
return i.id
}
func (i Installer[T]) Initialized() bool {
return i.init
}
func (i Installer[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the question.
func (i Installer[T]) Render(model T) string {
b := &strings.Builder{}
n := len(i.opts.Components)
w := lipgloss.Width(fmt.Sprintf("%d", n))
pkgsDone := len(i.foo.Entries)
pkgCount := fmt.Sprintf(" %*d/%*d", w, pkgsDone, w, n)
spin := i.spinner.View() + " "
prog := i.progress.View()
cellsAvail := max(0, i.width-lipgloss.Width(spin+prog+pkgCount))
intro := "Installing components "
cellsRemaining := max(0, i.width-lipgloss.Width(spin+intro+prog+pkgCount))
gap := strings.Repeat(" ", cellsRemaining)
b.WriteString(intro + gap + prog + pkgCount + "\n")
if i.changeSet != nil && i.changeSet.Entries != nil {
for _, set := range i.changeSet.Entries {
info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render(set.String())
b.WriteString(spin + " " + info + "\n")
}
}
if i.foo != nil && i.foo.Entries != nil {
for _, set := range i.foo.Entries {
info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render(set.String())
b.WriteString(checkMark.String() + " " + info + "\n")
}
}
if i.done {
ui := glitter.NewUI(theme.Nord)
b.WriteString("\n" + ui.Button("Continue", gstyle.Info).Render())
}
return b.String()
}
type CreatedEntry struct {
Entry *ssa.ChangeSetEntry
// ResourceStatus event.ResourceStatus
}
type ReadyEntry struct {
Entry *ssa.ChangeSetEntry
}
// UpdateTea updates the model T via Bubbletea UI
func (i *Installer[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
i.width, i.height = msg.Width, msg.Height
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc", "q":
return i, tea.Quit
case "enter":
if i.done {
_ = i.UpdateResult(model, i.done)
}
}
case errMsg:
// TODO
fmt.Println(msg.err.Error())
case ReadyEntry:
if i.foo == nil {
i.foo = ssa.NewChangeSet()
}
SetChangeSetEntry(*msg.Entry, i.foo)
newChangeset := RemoveChangeSetEntry(*msg.Entry, i.changeSet)
i.changeSet = newChangeset
entriesReady := len(i.foo.Entries)
progressCmd := i.progress.SetPercent(float64(entriesReady) / float64(len(i.opts.Components)))
cmds = append(cmds, progressCmd)
if entriesReady >= len(i.opts.Components) {
// Everything's been installed. We're done!
i.done = true
progressCmd := i.progress.SetPercent(1)
return i, progressCmd
}
return i, tea.Batch(cmds...)
case CreatedEntry:
i.init = true
SetChangeSetEntry(*msg.Entry, i.changeSet)
cmds = append(cmds, WaitCmd(i, *msg.Entry))
i.index++
if i.index <= len(i.opts.Components)-1 {
cmds = append(cmds, InstallCmd(i))
}
return i, tea.Batch(cmds...)
case spinner.TickMsg:
i.spinner, cmd = i.spinner.Update(msg)
return i, cmd
case progress.FrameMsg:
newModel, cmd := i.progress.Update(msg)
if newModel, ok := newModel.(progress.Model); ok {
i.progress = newModel
}
return i, cmd
}
if i.index == 0 && !i.init {
i.init = true
return i, tea.Batch(
i.spinner.Tick,
InstallCmd(i),
)
}
return i, cmd
}
func (i Installer[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next question in the chain
func (i Installer[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
func (i Installer[T]) Init() tea.Cmd {
if !i.init {
i.init = true
return tea.Batch(i.spinner.Tick)
}
return nil
}
func InstallCmd[T any](i *Installer[T]) tea.Cmd {
ctx := context.TODO()
return func() tea.Msg {
// objects := []*unstructured.Unstructured{}
// changeSet := &ssa.ChangeSet{}
entry := &ssa.ChangeSetEntry{}
// fmt.Println(i.index)
c := i.opts.Components[i.index]
resources := c.Resources(i.opts.Manager.Client().Scheme())
for _, resource := range resources {
obj, err := ToUnstructured(resource)
if err != nil {
return errMsg{err}
}
// objects = append(objects, obj)
err = i.opts.Manager.Client().Create(ctx, obj)
if err != nil {
if apierrors.IsAlreadyExists(err) {
// changeSet.Add(*ChangeSetEntry(i.opts.Manager, obj, ssa.SkippedAction))
entry = ChangeSetEntry(i.opts.Manager, obj, ssa.SkippedAction)
continue
} else {
return errMsg{err}
}
}
// changeSet.Add(*ChangeSetEntry(i.opts.Manager, obj, ssa.CreatedAction))
entry = ChangeSetEntry(i.opts.Manager, obj, ssa.CreatedAction)
}
return CreatedEntry{
Entry: entry,
}
// return changeSet
}
}
func WaitCmd[T any](i *Installer[T], changeSetEntry ssa.ChangeSetEntry) tea.Cmd {
return func() tea.Msg {
if err := i.opts.Manager.WaitForSet(ChangeSetEntryToObjMetadataSet(changeSetEntry), ssa.WaitOptions{
Interval: 2 * time.Second,
// TODO ?
// Timeout: component.KSTimeout,
Timeout: 3 * time.Minute,
}); err != nil {
return errMsg{err}
}
changeSet := ssa.NewChangeSet()
changeSet.Add(changeSetEntry)
return ReadyEntry{
Entry: &changeSetEntry,
}
}
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
var (
currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
doneStyle = lipgloss.NewStyle().Margin(1, 2)
checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓")
failedMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✖")
)
type errMsg struct{ err error }
func ToUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) {
// If the incoming object is already unstructured, perform a deep copy first
// otherwise DefaultUnstructuredConverter ends up returning the inner map without
// making a copy.
if _, ok := obj.(runtime.Unstructured); ok {
obj = obj.DeepCopyObject()
}
rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: rawMap}, nil
}
func ChangeSetEntry(m *ssa.ResourceManager, o *unstructured.Unstructured, action ssa.Action) *ssa.ChangeSetEntry {
return &ssa.ChangeSetEntry{
ObjMetadata: object.UnstructuredToObjMetadata(o),
GroupVersion: o.GroupVersionKind().Version,
Subject: ssa.FmtUnstructured(o),
Action: action,
}
}
func SetChangeSetEntry(entry ssa.ChangeSetEntry, changeSet *ssa.ChangeSet) {
for _, e := range changeSet.Entries {
if entry.ObjMetadata == e.ObjMetadata {
e = entry
return
}
}
changeSet.Entries = append(changeSet.Entries, entry)
}
func ChangeSetEntryToObjMetadataSet(entry ssa.ChangeSetEntry) object.ObjMetadataSet {
var res []object.ObjMetadata
res = append(res, entry.ObjMetadata)
return res
}
func EntriesReady(changeSet *ssa.ChangeSet) int {
ready := 0
for _, entry := range changeSet.Entries {
if entry.Action == ssa.CreatedAction {
ready++
}
}
return ready
}
func RemoveChangeSetEntry(entry ssa.ChangeSetEntry, changeSet *ssa.ChangeSet) *ssa.ChangeSet {
newChangeSet := ssa.NewChangeSet()
for _, e := range changeSet.Entries {
if e.ObjMetadata != entry.ObjMetadata {
newChangeSet.Entries = append(newChangeSet.Entries, e)
}
}
changeSet = newChangeSet
return newChangeSet
}

View file

@ -0,0 +1,106 @@
package bubble
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type InstructionOpts[T any] struct {
TaskOpts[T]
Text string
// TODO use check function or it happens in updateResult ?
// Check func(T) error
}
func NewInstruction[T any](id string, opts InstructionOpts[T]) Task[T] {
return &instruction[T]{
id: id,
opts: opts,
}
}
type instruction[T any] struct {
id string
opts InstructionOpts[T]
err error
}
func (i instruction[T]) ID() string {
return i.id
}
func (i instruction[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the question.
func (i instruction[T]) Render(model T) string {
doc := &strings.Builder{}
// TODO important this should not be hardcoded !!!
width := 96
var instruction string
continueButton := activeButtonStyle.Render("Continue")
// cancelButton := buttonStyle.Render("Maybe")
if i.err != nil {
instruction = lipgloss.NewStyle().Width(50).Align(lipgloss.Center).Render(i.err.Error())
} else {
instruction = lipgloss.NewStyle().Width(50).Align(lipgloss.Center).Render(i.opts.Text)
}
buttons := lipgloss.JoinHorizontal(lipgloss.Top, continueButton /* , cancelButton */)
ui := lipgloss.JoinVertical(lipgloss.Center, instruction, buttons)
dialog := lipgloss.Place(width, 9,
lipgloss.Center, lipgloss.Center,
dialogBoxStyle.Render(ui),
lipgloss.WithWhitespaceChars("猫咪"),
lipgloss.WithWhitespaceForeground(subtle),
)
doc.WriteString(dialog + "\n\n")
return doc.String()
}
// UpdateTea updates the model T via Bubbletea UI
func (i instruction[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
if key, ok := msg.(tea.KeyMsg); ok && key.Type == tea.KeyEnter {
if i.err != nil {
i.err = nil
return i, nil
}
err := i.UpdateResult(model, true)
if err != nil {
i.err = err
}
}
return i, nil
}
func (i instruction[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next question in the chain
func (i instruction[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
// Next returns the next question in the chain
func (i instruction[T]) Init() tea.Cmd {
return nil
}

102
pkg/bubble/program.go Normal file
View file

@ -0,0 +1,102 @@
package bubble
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
)
type Program[T any] interface {
Intro(T) string
Root() Stage[T]
// Task returns a task by ID
Stage(id string) Stage[T]
Complete() (bool, error)
}
type program[T any] struct {
stages []*Stage[T]
// Store the current task.
stage *Stage[T]
}
func (t program[T]) Init() tea.Cmd {
return nil
}
func (t program[T]) View() string {
s := &strings.Builder{}
if t.stage.Intro != nil {
s.WriteString(t.stage.Intro(t.stage.Model) + "\n")
}
q := t.stage.Task(t.stage.RootID)
for q != nil {
if _, err := q.Result(t.stage.Model); err == nil {
s.WriteString(q.Render(t.stage.Model))
}
q = t.stage.Task(q.Next(t.stage.Model))
}
if t.stage != nil {
// s.WriteString(t.stage.Render(t.stage.Model))
}
return s.String()
}
func (t *program[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
// With no task, quit rendering.
if t.stage == nil {
return t, tea.Quit
}
switch msg := msg.(type) {
case tea.WindowSizeMsg:
_, _ = msg.Width, msg.Height
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyCtrlBackslash:
return t, tea.Quit
}
}
// Ensure we update the current task. The UpdateTea method here is a
// pointer, so this should
if t.stage != nil {
// t.stage, cmd = t.stage.UpdateTea(t.stage.Model, msg)
cmds = append(cmds, cmd)
}
// Ensure we don't re-ask any answered tasks.
for {
if t.stage == nil {
break
}
if _, err := t.stage.Result(t.stage.Model); err != nil {
// This isn't answered, so we can skip going to the next
// task.
break
}
// For each task that's answered, skip to the next task.
// t.stage = t.stage.Task(t.task.Next(t.stage.Model))
}
if t.stage == nil {
// Ensure we handle nil tasks when progressing to the end
// of the stage.
return t, tea.Quit
}
return t, tea.Batch(cmds...)
}

View file

@ -0,0 +1,156 @@
package bubble
import (
"fmt"
"os"
"strings"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"golang.org/x/term"
)
type ItemGetter interface {
Items() []list.Item
}
type BasicItem struct {
Name string
Desc string
}
type BasicItemList struct {
List []BasicItem
}
func (i BasicItemList) Items() []list.Item {
items := []list.Item{}
for _, item := range i.List {
items = append(items, item)
}
return items
}
func (i BasicItem) Title() string { return i.Name }
func (i BasicItem) Description() string { return i.Desc }
func (i BasicItem) FilterValue() string { return i.Name }
type ChoiceQuestionOpts[T any] struct {
TaskOpts[T]
Prompt string
// OffsetY represents the list offset from the Y position.
PaddingY int
// ItemGetter returns all available items to the list
// items on each render of the list.
//
// This is required.
ItemGetter ItemGetter
// Items []list.Item
// TeaState allows overriding the list.Model used to render the list, if desired.
//
// This is an optional field. It is a pointer, and can be mutated outside
// of the Tea framework.
TeaState *list.Model
// Render optionally handles rendering the text list manually.
Render func(model T, textinput list.Model) string
}
func NewChoiceQuestion[T any](id string, opts ChoiceQuestionOpts[T]) Task[T] {
var state list.Model
if opts.TeaState != nil {
state = *opts.TeaState
} else {
width, height, _ := term.GetSize(int(os.Stdout.Fd()))
d := list.NewDefaultDelegate()
// TODO check how to configure it when creating the question, defaul should be disabled ??
// d.ShowDescription = false
state = list.New(opts.ItemGetter.Items(), d, width, height-opts.PaddingY)
state.SetShowFilter(false)
state.SetShowHelp(false)
state.SetShowStatusBar(false)
state.SetShowTitle(false)
}
return &ChoiceQuestion[T]{
id: id,
opts: opts,
state: &state,
}
}
type ChoiceQuestion[T any] struct {
id string
opts ChoiceQuestionOpts[T]
state *list.Model
}
func (i ChoiceQuestion[T]) ID() string {
return i.id
}
func (i ChoiceQuestion[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the question.
func (i ChoiceQuestion[T]) Render(model T) string {
if i.opts.Render != nil {
// Overriding render, so just use that function.
return i.opts.Render(model, *i.state)
}
a, err := i.Result(model)
if err == nil {
return fmt.Sprintf("%s: %s\n", i.opts.Prompt, BoldStyle.Render(a))
}
b := &strings.Builder{}
b.WriteString(BoldStyle.Render(i.opts.Prompt+":") + "\n\n")
b.WriteString(i.state.View())
return b.String()
}
// UpdateTea updates the model T via Bubbletea UI
func (i ChoiceQuestion[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
state, cmd := i.state.Update(msg)
i.state = &state
switch msg := msg.(type) {
case tea.WindowSizeMsg:
i.state.SetWidth(msg.Width)
i.state.SetHeight(msg.Height - i.opts.PaddingY)
}
if key, ok := msg.(tea.KeyMsg); ok && key.Type == tea.KeyEnter {
_ = i.UpdateResult(model, i.state.SelectedItem())
}
return i, cmd
}
func (i ChoiceQuestion[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next question in the chain
func (i ChoiceQuestion[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
// Next returns the next question in the chain
func (i ChoiceQuestion[T]) Init() tea.Cmd {
return nil
}

View file

@ -0,0 +1,107 @@
package bubble
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
)
type InputQuestionOpts[T any] struct {
TaskOpts[T]
Prompt string
Placeholder string
// Render optionally handles rendering the text input manually.
Render func(model T, textinput textinput.Model) string
// TeaState allows overriding the textinput.Model used to render the
// text input.
//
// This is an optional field.
TeaState *textinput.Model
}
func NewInputQuestion[T any](id string, opts InputQuestionOpts[T]) Task[T] {
var state textinput.Model
if opts.TeaState != nil {
state = *opts.TeaState
} else {
state = textinput.New()
state.Placeholder = opts.Placeholder
// Focus triggers updating this placeholder when rendering.
state.Focus()
state.Prompt = "→ "
}
return &inputQuestion[T]{
id: id,
opts: opts,
state: &state,
}
}
type inputQuestion[T any] struct {
id string
opts InputQuestionOpts[T]
state *textinput.Model
}
func (i inputQuestion[T]) ID() string {
return i.id
}
func (i inputQuestion[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the question.
func (i inputQuestion[T]) Render(model T) string {
if i.opts.Render != nil {
// Overriding render, so just use that function.
return i.opts.Render(model, *i.state)
}
a, err := i.Result(model)
if err == nil {
return fmt.Sprintf("%s: %s\n", i.opts.Prompt, BoldStyle.Render(a))
}
b := &strings.Builder{}
b.WriteString(BoldStyle.Render(i.opts.Prompt+":") + "\n")
b.WriteString(i.state.View())
return b.String()
}
// UpdateTea updates the model T via Bubbletea UI
func (i inputQuestion[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
state, cmd := i.state.Update(msg)
i.state = &state
if key, ok := msg.(tea.KeyMsg); ok && key.Type == tea.KeyEnter {
_ = i.UpdateResult(model, i.state.Value())
}
return i, cmd
}
func (i inputQuestion[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next question in the chain
func (i inputQuestion[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
// Next returns the next question in the chain
func (i inputQuestion[T]) Init() tea.Cmd {
return nil
}

View file

@ -0,0 +1,203 @@
package bubble
import (
"fmt"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/brittonhayes/glitter/glitter"
gstyle "github.com/brittonhayes/glitter/style"
"github.com/brittonhayes/glitter/theme"
)
type SelectItemGetter interface {
Items() []SelectItem
}
type SelectItem struct {
Name string
Desc string
Chosen bool
}
func (i SelectItem) Title() string { return i.Name }
func (i SelectItem) Description() string { return i.Desc }
func (i SelectItem) FilterValue() string { return i.Name }
type ItemList struct {
List []SelectItem
}
func (i ItemList) Items() []SelectItem {
return i.List
}
type SelectQuestionOpts[T any] struct {
TaskOpts[T]
Cursor int
Prompt string
MultiSelect bool
// OffsetY represents the list offset from the Y position.
PaddingY int
// ItemGetter returns all available items to the list
// items on each render of the list.
//
// This is required.
ItemGetter SelectItemGetter
// TeaState allows overriding the list.Model used to render the list, if desired.
//
// This is an optional field. It is a pointer, and can be mutated outside
// of the Tea framework.
TeaState *list.Model
// Render optionally handles rendering the text list manually.
Render func(model T, textinput list.Model) string
}
func NewSelectQuestion[T any](id string, opts SelectQuestionOpts[T]) Task[T] {
/* var state list.Model
if opts.TeaState != nil {
state = *opts.TeaState
} else {
width, height, _ := term.GetSize(int(os.Stdout.Fd()))
d := list.NewDefaultDelegate()
d.ShowDescription = false
state = list.New(opts.ItemGetter.Items(), d, width, height-opts.PaddingY)
state.SetShowFilter(false)
state.SetShowHelp(false)
state.SetShowStatusBar(false)
state.SetShowTitle(false)
}
*/
return &SelectQuestion[T]{
id: id,
opts: opts,
// state: &state,
}
}
type SelectQuestion[T any] struct {
id string
opts SelectQuestionOpts[T]
state *list.Model
}
func (i SelectQuestion[T]) ID() string {
return i.id
}
func (i SelectQuestion[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the question.
func (i SelectQuestion[T]) Render(model T) string {
a, err := i.Result(model)
if err == nil {
return fmt.Sprintf("%s: %s\n", i.opts.Prompt, BoldStyle.Render(a))
}
currentChoice := i.opts.Cursor
header := i.opts.Prompt
out := "~ " + header + ":\n"
items := i.opts.ItemGetter.Items()
for i, item := range items {
sel := " "
if i == currentChoice {
sel = ">"
}
check := " "
if items[i].Chosen {
check = "✓"
}
out += fmt.Sprintf("%s [%s] %s\n", sel, check, item.Name)
}
// Create a new glitter UI and select a theme
ui := glitter.NewUI(theme.Nord)
btn := ui.Button("Confirm", gstyle.Success)
if currentChoice == -1 || currentChoice >= len(items) {
btn = ui.Button("Confirm", gstyle.Info)
}
out += btn.Render()
return out
}
/* var style = lipgloss.NewStyle().
Bold(true).
Border().
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
// PaddingTop(2).
PaddingLeft(4).
Width(12)
*/
// UpdateTea updates the model T via Bubbletea UI
func (i SelectQuestion[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
items := i.opts.ItemGetter.Items()
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "j", "down":
i.opts.Cursor++
if i.opts.Cursor >= len(items) {
i.opts.Cursor = -1
}
case "k", "up":
i.opts.Cursor--
if i.opts.Cursor < 0 {
i.opts.Cursor = len(items)
}
case "enter":
if i.opts.Cursor == -1 || i.opts.Cursor >= len(items) {
// TODO manage errors
_ = i.UpdateResult(model, items)
return i, nil
}
items[i.opts.Cursor].Chosen = !items[i.opts.Cursor].Chosen
return i, cmd
}
}
return i, nil
}
func (i SelectQuestion[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next question in the chain
func (i SelectQuestion[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
// Next returns the next question in the chain
func (i SelectQuestion[T]) Init() tea.Cmd {
return nil
}

View file

@ -0,0 +1,107 @@
package bubble
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/textarea"
tea "github.com/charmbracelet/bubbletea"
)
type TextAreaQuestionOpts[T any] struct {
TaskOpts[T]
Prompt string
Placeholder string
// Render optionally handles rendering the text textArea manually.
Render func(model T, textArea textarea.Model) string
// TeaState allows overriding the textArea.Model used to render the
// text input.
//
// This is an optional field.
TeaState *textarea.Model
}
func NewTextAreaQuestion[T any](id string, opts TextAreaQuestionOpts[T]) Task[T] {
var state textarea.Model
if opts.TeaState != nil {
state = *opts.TeaState
} else {
state = textarea.New()
state.Placeholder = opts.Placeholder
// Focus triggers updating this placeholder when rendering.
state.Focus()
// state.Prompt = "→ "
}
return &textAreaQuestion[T]{
id: id,
opts: opts,
state: &state,
}
}
type textAreaQuestion[T any] struct {
id string
opts TextAreaQuestionOpts[T]
state *textarea.Model
}
func (i textAreaQuestion[T]) ID() string {
return i.id
}
func (i textAreaQuestion[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the question.
func (i textAreaQuestion[T]) Render(model T) string {
if i.opts.Render != nil {
// Overriding render, so just use that function.
return i.opts.Render(model, *i.state)
}
a, err := i.Result(model)
if err == nil {
return fmt.Sprintf("%s: %s\n", i.opts.Prompt, BoldStyle.Render(a))
}
b := &strings.Builder{}
b.WriteString(BoldStyle.Render(i.opts.Prompt+":") + "\n")
b.WriteString(i.state.View())
return b.String()
}
// UpdateTea updates the model T via Bubbletea UI
func (i textAreaQuestion[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
state, cmd := i.state.Update(msg)
i.state = &state
if key, ok := msg.(tea.KeyMsg); ok && key.Type == tea.KeyEnter {
_ = i.UpdateResult(model, i.state.Value())
}
return i, cmd
}
func (i textAreaQuestion[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next question in the chain
func (i textAreaQuestion[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
// Next returns the next question in the chain
func (i textAreaQuestion[T]) Init() tea.Cmd {
return nil
}

216
pkg/bubble/stage.go Normal file
View file

@ -0,0 +1,216 @@
package bubble
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
type Foo[T any] interface {
}
type StageOpts[T any] struct {
Tasks []Task[T]
RootID string
// Intro renders an intro to the stage.
Intro func(T) string
ID string
// Spinner spinner.Model
TaskOpts[T]
}
func NewStage[T any](model T, opts StageOpts[T]) (*Stage[T], error) {
stage := &Stage[T]{
Model: model,
StageOpts: opts,
}
if stage.Root() == nil {
return stage, fmt.Errorf("root task not found: %s", opts.RootID)
}
stage.task = stage.Root()
/* s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63")) */
// stage.spinner = s
return stage, nil
}
type Stage[T any] struct {
StageOpts[T]
// TaskOpts TaskOpts[T]
// Model represents the model that this stage updates.
Model T
// is it needed ? Should we store current step though ?
root Task[T]
// Current task.
task Task[T]
// previousTask string
complete bool
}
func (s *Stage[T]) ID() string {
return s.StageOpts.ID
}
func (s *Stage[T]) Root() Task[T] {
if s.root == nil {
s.root = s.Task(s.RootID)
}
return s.root
}
// Task returns a step by ID
func (s Stage[T]) Task(id string) Task[T] {
for _, q := range s.Tasks {
if q.ID() == id {
return q
}
}
return nil
}
func (s *Stage[T]) Complete() (bool, error) {
if s.complete {
return true, nil
}
queue := []Task[T]{s.Root()}
for len(queue) > 0 {
item := queue[0]
queue = queue[1:]
if item == nil {
continue
}
_, err := item.Result(s.Model)
if err == ErrNotCompleted {
return false, nil
}
if err != nil {
return false, err
}
next := item.Next(s.Model)
queue = append(queue, s.Task(next))
}
return true, nil
}
func (s *Stage[T]) Init() tea.Cmd {
if ok, err := s.Complete(); ok && err == nil {
return nil
}
s.task = s.Task(s.RootID)
// return tea.Batch(s.StageOpts.Spinner.Tick)
return nil
}
func (s Stage[T]) View() string {
b := &strings.Builder{}
if s.Intro != nil {
b.WriteString(s.Intro(s.Model) + "\n")
}
q := s.Task(s.RootID)
for q != nil {
// TODO keep it if we want to see the result
/*
if _, err := q.Result(s.Model); err == nil {
b.WriteString(q.Render(s.Model))
}
*/
q = s.Task(q.Next(s.Model))
}
if s.task != nil {
b.WriteString(s.task.Render(s.Model))
}
return b.String()
}
func (s *Stage[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
// With no task, quit rendering.
if s.task == nil {
s.complete = true
return s, nil
// return s, tea.Quit
}
switch msg := msg.(type) {
case tea.WindowSizeMsg:
_, _ = msg.Width, msg.Height
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyCtrlBackslash:
return s, tea.Quit
}
}
// Ensure we update the current task. The UpdateTea method here is a
// pointer, so this should
if s.task != nil {
//cmd = s.task.Init()
// cmds = append(cmds, cmd)
s.task, cmd = s.task.UpdateTea(s.Model, msg)
// return s, cmd
cmds = append(cmds, cmd)
// return s, tea.Batch(cmds...)
}
// Ensure we don't re-ask any answered tasks.
for {
if s.task == nil {
break
}
if _, err := s.task.Result(s.Model); err != nil {
// This isn't answered, so we can skip going to the next
// task.
// s.task, cmd = s.task.UpdateTea(s.Model, msg)
// return s, cmd
// cmds = append(cmds, cmd)
// return s, tea.Batch(cmds...)
break
}
// For each task that's answered, skip to the next task.
s.task = s.Task(s.task.Next(s.Model))
// fmt.Println("bbbbbbbbbbbbbbb")
}
if s.task == nil {
// Ensure we handle nil tasks when progressing to the end
// of the stage.
s.complete = true
// return s, tea.Quit
return s, nil
}
return s, tea.Batch(cmds...)
}

68
pkg/bubble/step.go Normal file
View file

@ -0,0 +1,68 @@
package bubble
import (
tea "github.com/charmbracelet/bubbletea"
)
// NewTask returns a task that custom-renders everything with no base.
func NewTask[T any](id string, opts TaskOpts[T], render func(T) string) Task[T] {
return &task[T]{
id: id,
opts: opts,
render: render,
}
}
type task[T any] struct {
id string
prev string
opts TaskOpts[T]
render func(T) string
}
func (i task[T]) ID() string {
return i.id
}
func (i task[T]) Initialized() bool {
return true
}
func (i task[T]) Init() tea.Cmd {
return nil
}
func (i task[T]) Result(model T) (string, error) {
return i.opts.Result(model)
}
// Render renders the task.
func (i task[T]) Render(model T) string {
return i.render(model)
}
// UpdateTea updates the model T via Bubbletea UI
func (i task[T]) UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd) {
return i, nil
}
func (i task[T]) UpdateResult(model T, value interface{}) error {
return i.opts.UpdateResult(model, value)
}
// Next returns the next task in the chain
func (i task[T]) Next(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}
// TODO
// Prev returns the next task in the chain
func (i task[T]) Prev(model T) string {
if i.opts.Next == nil {
return ""
}
return i.opts.Next(model)
}

140
pkg/bubble/styles.go Normal file
View file

@ -0,0 +1,140 @@
package bubble
import "github.com/charmbracelet/lipgloss"
var (
subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"}
Feint = lipgloss.AdaptiveColor{Light: "#333333", Dark: "#888888"}
Color = lipgloss.AdaptiveColor{Light: "#111222", Dark: "#FAFAFA"}
TextStyle = lipgloss.NewStyle().Foreground(Color)
FeintStyle = TextStyle.Copy().Foreground(Feint)
BoldStyle = TextStyle.Copy().Bold(true)
// Dialog.
dialogBoxStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#874BFD")).
Padding(1, 0).
BorderTop(true).
BorderLeft(true).
BorderRight(true).
BorderBottom(true)
buttonStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFF7DB")).
Background(lipgloss.Color("#888B7E")).
Padding(0, 3).
MarginTop(1)
activeButtonStyle = buttonStyle.Copy().
Foreground(lipgloss.Color("#FFF7DB")).
Background(lipgloss.Color("#F25D94")).
MarginRight(2).
Underline(true)
)
/*
var (
Color = lipgloss.AdaptiveColor{Light: "#111222", Dark: "#FAFAFA"}
Primary = lipgloss.Color("#4636f5")
Green = lipgloss.Color("#9dcc3a")
Red = lipgloss.Color("#ff0000")
White = lipgloss.Color("#ffffff")
Black = lipgloss.Color("#000000")
Orange = lipgloss.Color("#D3A347")
Feint = lipgloss.AdaptiveColor{Light: "#333333", Dark: "#888888"}
Iris = lipgloss.Color("#5D5FEF")
Fuschia = lipgloss.Color("#EF5DA8")
TextStyle = lipgloss.NewStyle().Foreground(Color)
FeintStyle = TextStyle.Copy().Foreground(Feint)
BoldStyle = TextStyle.Copy().Bold(true)
)
// RenderError returns a formatted error string.
func RenderError(msg string) string {
// Error applies styles to an error message
err := lipgloss.NewStyle().Background(Red).Foreground(White).Bold(true).Padding(0, 1).Render("Error")
content := lipgloss.NewStyle().Bold(true).Padding(0, 1).Render(msg)
return err + content
}
// RenderWarning returns a formatted warning string.
func RenderWarning(msg string) string {
// Error applies styles to an error message
err := lipgloss.NewStyle().Foreground(Orange).Bold(true).Render("Warning: ")
content := lipgloss.NewStyle().Bold(true).Foreground(Orange).Padding(0, 1).Render(msg)
return err + content
}
// Utils
const (
progressBarWidth = 71
progressFullChar = "█"
progressEmptyChar = "░"
)
// General stuff for styling the view
var (
Term = termenv.EnvColorProfile()
Keyword = MakeFgStyle("211")
Subtle = MakeFgStyle("241")
ProgressEmpty = Subtle(progressEmptyChar)
Dot = ColorFg(" • ", "236")
// Gradient colors we'll use for the progress bar
Ramp = MakeRamp("#B14FFF", "#00FFA3", progressBarWidth)
)
// Color a string's foreground with the given value.
func ColorFg(val, color string) string {
return termenv.String(val).Foreground(Term.Color(color)).String()
}
// Return a function that will colorize the foreground of a given string.
func MakeFgStyle(color string) func(string) string {
return termenv.Style{}.Foreground(Term.Color(color)).Styled
}
// Color a string's foreground and background with the given value.
func MakeFgBgStyle(fg, bg string) func(string) string {
return termenv.Style{}.
Foreground(Term.Color(fg)).
Background(Term.Color(bg)).
Styled
}
// Generate a blend of colors.
func MakeRamp(colorA, colorB string, steps float64) (s []string) {
cA, _ := colorful.Hex(colorA)
cB, _ := colorful.Hex(colorB)
for i := 0.0; i < steps; i++ {
c := cA.BlendLuv(cB, i/steps)
s = append(s, ColorToHex(c))
}
return
}
// Convert a colorful.Color to a hexadecimal format compatible with termenv.
func ColorToHex(c colorful.Color) string {
return fmt.Sprintf("#%s%s%s", ColorFloatToHex(c.R), ColorFloatToHex(c.G), ColorFloatToHex(c.B))
}
// Helper function for converting colors to hex. Assumes a value between 0 and
// 1.
func ColorFloatToHex(f float64) (s string) {
s = strconv.FormatInt(int64(f*255), 16)
if len(s) == 1 {
s = "0" + s
}
return
}
*/

67
pkg/bubble/task.go Normal file
View file

@ -0,0 +1,67 @@
package bubble
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
)
var (
ErrNotCompleted = fmt.Errorf("this tasks is not completed")
)
type Task[T any] interface {
// ID allows us to reference tasks, eg. for setting the root
ID() string
// Init initialize the model
// Init(model T) tea.Cmd
// Result returns the result as a string. If the tasks is
// not completed, this must return the error ErrNotCompleted.
Result(T) (string, error)
// Render renders the tasks.
Render(T) string
// UpdateTea updates the model T via Bubbletea UI, returning any
// commands necessary to re-render the state.
UpdateTea(model T, msg tea.Msg) (Task[T], tea.Cmd)
// UpdateResult is called when the qestion is completed.
UpdateResult(model T, answer interface{}) error
// Next returns the next tasks in the chain
Next(T) string
// Initialized() bool
Init() tea.Cmd
// Prev returns the previous tasks in the chain
// Prev(T) string
}
type TaskOpts[T any] struct {
// Result returns the current result as a string for display, or
// ErrNotCompleted if this tasks has no result.
Result func(T) (string, error)
// UpdateResult is called with the answer from the tasks to
// update the model.
//
// This must return a custom error if the result is invalid,
// or ErrNotCompleted if there's no result.
UpdateResult func(T, interface{}) error
// Next returns the ID of the next tasks in the chain, if applicable.
Next func(T) string
// Next returns the ID of the previous tasks in the chain, if applicable.
// Prev func(T) string
// UpdateTea is called when receing a tea.Msg to update any internal
// tea models.
UpdateTea func(model T, msg tea.Msg) (Task[T], tea.Cmd)
// Init func() tea.Cmd
}

103
pkg/bubble/tea.go Normal file
View file

@ -0,0 +1,103 @@
package bubble
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
type teaProgram[T any] struct {
stage *Stage[T]
// Store the current task.
task Task[T]
}
func NewProgram[T any](model T, opts StageOpts[T]) (*Stage[T], error) {
stage := &Stage[T]{
Model: model,
StageOpts: opts,
}
if stage.Root() == nil {
return nil, fmt.Errorf("root stage not found: %s", opts.RootID)
}
return stage, nil
}
func (t teaProgram[T]) Init() tea.Cmd {
return nil
}
func (t teaProgram[T]) View() string {
s := &strings.Builder{}
if t.stage.Intro != nil {
s.WriteString(t.stage.Intro(t.stage.Model) + "\n")
}
q := t.stage.Task(t.stage.RootID)
for q != nil {
if _, err := q.Result(t.stage.Model); err == nil {
s.WriteString(q.Render(t.stage.Model))
}
q = t.stage.Task(q.Next(t.stage.Model))
}
if t.task != nil {
s.WriteString(t.task.Render(t.stage.Model))
}
return s.String()
}
func (t *teaProgram[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
// With no task, quit rendering.
if t.task == nil {
return t, tea.Quit
}
switch msg := msg.(type) {
case tea.WindowSizeMsg:
_, _ = msg.Width, msg.Height
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyCtrlBackslash:
return t, tea.Quit
}
}
// Ensure we update the current task. The UpdateTea method here is a
// pointer, so this should
if t.task != nil {
t.task, cmd = t.task.UpdateTea(t.stage.Model, msg)
cmds = append(cmds, cmd)
}
// Ensure we don't re-ask any answered tasks.
for {
if t.task == nil {
break
}
if _, err := t.task.Result(t.stage.Model); err != nil {
// This isn't answered, so we can skip going to the next
// task.
break
}
// For each task that's answered, skip to the next task.
t.task = t.stage.Task(t.task.Next(t.stage.Model))
}
if t.task == nil {
// Ensure we handle nil tasks when progressing to the end
// of the stage.
return t, tea.Quit
}
return t, tea.Batch(cmds...)
}

View file

@ -0,0 +1,23 @@
package auth
import (
"github.com/fluxcd/pkg/apis/meta"
"libre.sh/cli/pkg/component"
)
var (
Keycloak = component.BasicComponent{
Name: "keycloak",
Type: "Kustomize",
Scope: "Auth",
DependsOn: []meta.NamespacedObjectReference{
{
Name: "postgres-zalando",
},
{
Name: "postgres-libresh",
},
},
AdminCreds: true,
}
)

View file

@ -0,0 +1,36 @@
package auth
import (
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (c Model) Components() []component.Component {
return []component.Component{
&Keycloak,
}
}
func (c Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range c.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
type Model struct {
*component.ScopeModel
/*
components []string
done *bool
Manager *ssa.ResourceManager
*/
}
func (c *Model) Init() error {
return nil
}

View file

@ -0,0 +1,87 @@
package auth
import (
"fmt"
"strconv"
"strings"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (m *Model) Stage() (component.Model, error) {
components := Model{}.Items()
stepStage, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Modeluring Auth components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewSelectQuestion(
"components",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the components that should be deployed on your cluster",
ItemGetter: components,
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
// TODO
if len(model.ScopeModel.Components) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.ScopeModel.Components), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
fmt.Printf("%#v", answer)
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
model.ScopeModel.Components = append(model.ScopeModel.Components, item.Name)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "installer"
},
},
},
),
bubble.NewInstaller(
"installer",
bubble.InstallerOpts[*Model]{
Components: Model{}.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
},
},
),
},
})
return stepStage, err
}

371
pkg/component/component.go Normal file
View file

@ -0,0 +1,371 @@
package component
import (
"context"
"fmt"
"log"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
fluxKSv1beta2 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/ssa"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/rand"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
const (
LibreshSystemNamespace = "libresh-system"
FluxNamespace = "flux-system"
KSClusterPath = "./cluster"
KSComponentsSubPath = "components"
KSInterval = 30 * time.Minute
KSRetryInterval = 1 * time.Minute
KSTimeout = 3 * time.Minute
)
type Scope interface {
Stage() (Model, error)
SetManager(m *ssa.ResourceManager)
Init() error
// Manager() *ssa.ResourceManager
}
type Model interface {
Complete() (bool, error)
tea.Model
}
/*
func ComponentPath(component string) string {
return fmt.Sprintf("%s/%s/%s", KSClusterPath, KSComponentsSubPath, component)
}
*/
type ScopeModel struct {
Components []string
Done *bool
Manager *ssa.ResourceManager
}
func NewScopeModel(m *ssa.ResourceManager) *ScopeModel {
return &ScopeModel{
Manager: m,
}
}
/*
func (s *ScopeModel) Manager() *ssa.ResourceManager {
return s.Manager
}
*/
func (s *ScopeModel) SetManager(m *ssa.ResourceManager) {
s.Manager = m
}
type ClusterSettings struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
Email string `json:"email,omitempty"`
Environment string `json:"environment,omitempty"`
ClusterIssuer string `json:"clusterIssuer,omitempty"`
}
type Component interface {
Check() error
GetName() string
Reconcile(ctx context.Context, manager *ssa.ResourceManager) (*ssa.ChangeSet, error)
Resources(scheme *runtime.Scheme) []runtime.Object
}
type BasicComponent struct {
Name string `json:"name,omitempty"`
Source string `json:"source,omitempty"`
Type string `json:"type,omitempty"`
DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"`
Scope string `json:"scope,omitempty"`
Required bool `json:"required,omitempty"`
AdminCreds bool `json:"adminCreds,omitempty"`
}
func (c *BasicComponent) Check() error {
return nil
}
func (c *BasicComponent) GetName() string {
return c.Name
}
func (c *BasicComponent) KSPath() string {
// TODO find a better way
if c.Scope == "crds" {
return fmt.Sprintf("%s/crds/%s", KSClusterPath, c.Name)
}
if c.Scope == "Repositories" {
return fmt.Sprintf("%s/repositories", KSClusterPath)
}
return fmt.Sprintf("%s/%s/%s/%s", KSClusterPath, KSComponentsSubPath, strings.ToLower(c.Scope), c.Name)
}
func (c *BasicComponent) Resources(scheme *runtime.Scheme) []runtime.Object {
resources := []runtime.Object{}
if c.AdminCreds {
// TODO move it to installCMD ?
secret := &corev1.Secret{}
gvk, err := apiutil.GVKForObject(secret, scheme)
if err != nil {
// TODO manage error
log.Fatal(err)
}
secret.GetObjectKind().SetGroupVersionKind(gvk)
secret.SetName(fmt.Sprintf("%s.admin.creds", c.Name))
secret.SetNamespace("libresh-system")
secret.Data = map[string][]byte{
"username": []byte("admin"),
"password": []byte(rand.String(32)),
}
resources = append(resources, secret)
}
ks := &fluxKSv1beta2.Kustomization{}
ks.SetName(c.Name)
// TODO
ks.SetNamespace("libresh-system")
gvk, err := apiutil.GVKForObject(ks, scheme)
if err != nil {
// TODO manage error
log.Fatal(err)
}
ks.GetObjectKind().SetGroupVersionKind(gvk)
ks.Spec = fluxKSv1beta2.KustomizationSpec{
Path: c.KSPath(),
Prune: true,
SourceRef: fluxKSv1beta2.CrossNamespaceSourceReference{
Kind: "GitRepository",
Name: "libresh-cluster",
},
Wait: true,
TargetNamespace: LibreshSystemNamespace,
PostBuild: &fluxKSv1beta2.PostBuild{
SubstituteFrom: []fluxKSv1beta2.SubstituteReference{
{
Kind: "Secret",
Name: "cluster-settings",
},
},
},
DependsOn: c.DependsOn,
}
ks.Spec.Interval.Duration = KSInterval
ks.Spec.RetryInterval = &metav1.Duration{
Duration: KSRetryInterval,
}
ks.Spec.Timeout = &metav1.Duration{
Duration: KSTimeout,
}
resources = append(resources, ks)
return resources
}
func WaitForComponent(Component) error {
return nil
}
func ConfigureComponent() {
// Return questions
}
func ChangeSetEntry(m *ssa.ResourceManager, o *unstructured.Unstructured, action ssa.Action) *ssa.ChangeSetEntry {
return &ssa.ChangeSetEntry{
ObjMetadata: object.UnstructuredToObjMetadata(o),
GroupVersion: o.GroupVersionKind().Version,
Subject: ssa.FmtUnstructured(o),
Action: action,
}
}
func (c *BasicComponent) Reconcile(ctx context.Context, manager *ssa.ResourceManager) (*ssa.ChangeSet, error) {
objects := []*unstructured.Unstructured{}
changeSet := &ssa.ChangeSet{}
resources := c.Resources(manager.Client().Scheme())
for _, resource := range resources {
obj, err := ToUnstructured(resource)
if err != nil {
return changeSet, err
}
objects = append(objects, obj)
}
resultSet := ssa.NewChangeSet()
// TODO
applyOpts := ssa.ApplyOptions{}
changeSet, err := manager.ApplyAll(ctx, objects, applyOpts)
if err != nil {
return changeSet, err
}
// TODO manage if changeset.Entries == nil ? risk of nil poiner receiver ?
resultSet.Append(changeSet.Entries)
if changeSet != nil && len(changeSet.Entries) > 0 {
// log.Info("server-side apply for cluster class types completed", "output", changeSet.ToMap())
for _, change := range changeSet.Entries {
if change.Action != ssa.UnchangedAction {
// changeSetLog.WriteString(change.String() + "\n")
}
}
if err := manager.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{
Interval: 2 * time.Second,
//Timeout: obj.GetTimeout(),
// TODO ?
Timeout: KSTimeout,
}); err != nil {
return changeSet, err
}
}
return changeSet, nil
}
func (c *BasicComponent) Create(ctx context.Context, manager *ssa.ResourceManager) (*ssa.ChangeSet, error) {
// objects := []*unstructured.Unstructured{}
changeSet := &ssa.ChangeSet{}
return changeSet, nil
}
func (c *BasicComponent) Apply(ctx context.Context, manager *ssa.ResourceManager) error {
objects := []*unstructured.Unstructured{}
resources := c.Resources(manager.Client().Scheme())
for _, resource := range resources {
obj, err := ToUnstructured(resource)
if err != nil {
return err
}
objects = append(objects, obj)
}
resultSet := ssa.NewChangeSet()
// TODO
applyOpts := ssa.ApplyOptions{}
changeSet, err := manager.ApplyAll(ctx, objects, applyOpts)
if err != nil {
return err
}
// TODO manage if changeset.Entries == nil ? risk of nil poiner receiver ?
resultSet.Append(changeSet.Entries)
if changeSet != nil && len(changeSet.Entries) > 0 {
// log.Info("server-side apply for cluster class types completed", "output", changeSet.ToMap())
for _, change := range changeSet.Entries {
if change.Action != ssa.UnchangedAction {
// changeSetLog.WriteString(change.String() + "\n")
}
}
if err := manager.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{
Interval: 2 * time.Second,
//Timeout: obj.GetTimeout(),
// TODO ?
Timeout: KSTimeout,
}); err != nil {
return err
}
}
return nil
}
func (c *BasicComponent) Wait(ctx context.Context, manager *ssa.ResourceManager) error {
objects := []*unstructured.Unstructured{}
resources := c.Resources(manager.Client().Scheme())
for _, resource := range resources {
obj, err := ToUnstructured(resource)
if err != nil {
return err
}
objects = append(objects, obj)
}
resultSet := ssa.NewChangeSet()
// TODO
applyOpts := ssa.ApplyOptions{}
changeSet, err := manager.ApplyAll(ctx, objects, applyOpts)
if err != nil {
return err
}
// TODO manage if changeset.Entries == nil ? risk of nil poiner receiver ?
resultSet.Append(changeSet.Entries)
if changeSet != nil && len(changeSet.Entries) > 0 {
// log.Info("server-side apply for cluster class types completed", "output", changeSet.ToMap())
for _, change := range changeSet.Entries {
if change.Action != ssa.UnchangedAction {
// changeSetLog.WriteString(change.String() + "\n")
}
}
if err := manager.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{
Interval: 2 * time.Second,
//Timeout: obj.GetTimeout(),
// TODO ?
Timeout: KSTimeout,
}); err != nil {
return err
}
}
return nil
}
func ToUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) {
// If the incoming object is already unstructured, perform a deep copy first
// otherwise DefaultUnstructuredConverter ends up returning the inner map without
// making a copy.
if _, ok := obj.(runtime.Unstructured); ok {
obj = obj.DeepCopyObject()
}
rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: rawMap}, nil
}

View file

@ -0,0 +1,19 @@
package crds
import (
"libre.sh/cli/pkg/component"
)
var (
Libresh = component.BasicComponent{
Name: "libresh-api",
Type: "Kustomize",
Scope: "crds",
}
Prometheus = component.BasicComponent{
Name: "prometheus-crds",
Type: "Kustomize",
Scope: "crds",
}
)

View file

@ -0,0 +1,35 @@
package crds
import (
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (c Model) Components() []component.Component {
return []component.Component{
&Libresh,
&Prometheus,
}
}
func (c Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range c.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
type Model struct {
/* components []string
done *bool
manager *ssa.ResourceManager */
*component.ScopeModel
}
func (c *Model) Init() error {
return nil
}

View file

@ -0,0 +1,85 @@
package crds
import (
"fmt"
"strconv"
"strings"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (m *Model) Stage() (component.Model, error) {
stepStage, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring CRDS components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewSelectQuestion(
"components",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the components that should be deployed on your cluster",
ItemGetter: m.Items(),
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
// TODO
if len(model.ScopeModel.Components) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.ScopeModel.Components), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
fmt.Printf("%#v", answer)
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
model.ScopeModel.Components = append(model.ScopeModel.Components, item.Name)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "installer"
},
},
},
),
bubble.NewInstaller(
"installer",
bubble.InstallerOpts[*Model]{
Components: Model{}.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
},
},
),
},
})
return stepStage, err
}

View file

@ -0,0 +1,14 @@
package csi
import (
"libre.sh/cli/pkg/component"
)
var (
OpenEBS = component.BasicComponent{
Name: "openebs",
Source: "openebs",
Type: "HelmRelease",
Scope: "csi",
}
)

View file

@ -0,0 +1,34 @@
package csi
import (
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (c Model) Components() []component.Component {
return []component.Component{
&OpenEBS,
}
}
func (c Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range c.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
type Model struct {
/* components []string
done *bool
manager *ssa.ResourceManager */
*component.ScopeModel
}
func (c *Model) Init() error {
return nil
}

View file

@ -0,0 +1,85 @@
package csi
import (
"fmt"
"strconv"
"strings"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (m *Model) Stage() (component.Model, error) {
stepStage, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring CSI components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewSelectQuestion(
"components",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the components that should be deployed on your cluster",
ItemGetter: m.Items(),
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
// TODO
if len(model.ScopeModel.Components) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.ScopeModel.Components), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
fmt.Printf("%#v", answer)
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
model.ScopeModel.Components = append(model.ScopeModel.Components, item.Name)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "installer"
},
},
},
),
bubble.NewInstaller(
"installer",
bubble.InstallerOpts[*Model]{
Components: m.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
},
},
),
},
})
return stepStage, err
}

View file

@ -0,0 +1,51 @@
package databases
import (
"github.com/fluxcd/pkg/apis/meta"
"libre.sh/cli/pkg/component"
)
var (
PostgresZalando = component.BasicComponent{
Name: "postgres-zalando",
Source: "postgres-zalando",
Type: "HelmRelease",
Scope: "Databases",
DependsOn: []meta.NamespacedObjectReference{
/* {
Name: "openebs",
}, */
{
Name: "object-storage-operator",
},
},
}
PostgresLibresh = component.BasicComponent{
Name: "postgres-libresh",
Type: "Kustomize",
Scope: "Databases",
DependsOn: []meta.NamespacedObjectReference{
{
Name: "postgres-zalando",
},
{
Name: "libresh-api",
},
{
Name: "object-storage-operator",
},
},
}
KeyDB = component.BasicComponent{
Name: "keydb",
Type: "Kustomize",
Scope: "Databases",
/* DependsOn: []meta.NamespacedObjectReference{
{
Name: "openebs",
},
}, */
}
)

View file

@ -0,0 +1,36 @@
package databases
import (
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (c Model) Components() []component.Component {
return []component.Component{
&PostgresZalando,
&PostgresLibresh,
&KeyDB,
}
}
func (c Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range c.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
type Model struct {
/* components []string
done *bool
manager *ssa.ResourceManager */
*component.ScopeModel
}
func (c *Model) Init() error {
return nil
}

View file

@ -0,0 +1,85 @@
package databases
import (
"fmt"
"strconv"
"strings"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (m *Model) Stage() (component.Model, error) {
stepStage, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring Databases components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewSelectQuestion(
"components",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the components that should be deployed on your cluster",
ItemGetter: m.Items(),
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
// TODO
if len(model.ScopeModel.Components) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.ScopeModel.Components), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
fmt.Printf("%#v", answer)
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
model.ScopeModel.Components = append(model.ScopeModel.Components, item.Name)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "installer"
},
},
},
),
bubble.NewInstaller(
"installer",
bubble.InstallerOpts[*Model]{
Components: m.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
},
},
),
},
})
return stepStage, err
}

View file

@ -0,0 +1,13 @@
package general
import (
"libre.sh/cli/pkg/component"
)
var (
Repos = component.BasicComponent{
Name: "repositories",
Type: "Kustomize",
Scope: "Repositories",
}
)

View file

@ -0,0 +1,301 @@
package general
import (
"context"
"log"
"strconv"
"strings"
corev1 "k8s.io/api/core/v1"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Model struct {
*component.ScopeModel
/* components []string
done *bool
Manager *ssa.ResourceManager */
name string
domain string
email string
envMode string
}
func (c *Model) Init() error {
secret := &corev1.Secret{}
err := c.Manager.Client().Get(context.TODO(), client.ObjectKey{Name: "cluster-settings", Namespace: "libresh-system"}, secret)
if err != nil {
return client.IgnoreNotFound(err)
}
if secret.Data != nil {
c.name = string(secret.Data["CLUSTER_NAME"])
c.domain = string(secret.Data["CLUSTER_DOMAIN"])
c.email = string(secret.Data["CLUSTER_EMAIL"])
// TODO find a better way
if string(secret.Data["DEFAULT_CLUSTERISSUER"]) == "letsencrypt" {
c.envMode = "production"
}
if string(secret.Data["DEFAULT_CLUSTERISSUER"]) == "selfsigned" {
c.envMode = "dev"
}
}
return nil
}
func (c *Model) Components() []component.Component {
return []component.Component{
&Repos,
}
}
func (m *Model) Stage() (component.Model, error) {
envModes := &bubble.BasicItemList{
List: []bubble.BasicItem{
{
Name: "production",
},
{
Name: "development",
},
},
}
stepStage, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "name",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring General Cluster Settings"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewInputQuestion(
"name",
bubble.InputQuestionOpts[*Model]{
Prompt: "Enter your cluster name",
Placeholder: "eg. adalovelace",
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.name == "" {
return "", bubble.ErrNotCompleted
}
return model.name, nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case string:
if v != "" {
model.name = v
secret := &corev1.Secret{}
secret.SetName("cluster-settings")
secret.SetNamespace("libresh-system")
_, err := ctrl.CreateOrUpdate(context.TODO(), model.Manager.Client(), secret, func() error {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
secret.Data["CLUSTER_NAME"] = []byte(v)
return nil
})
if err != nil {
// TODO manager error
log.Fatal(err)
return err
}
return nil
}
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "domain"
},
},
},
),
bubble.NewInputQuestion(
"domain",
bubble.InputQuestionOpts[*Model]{
Prompt: "Enter your cluster domain",
Placeholder: "eg. adalovelace.libre.sh",
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.domain == "" {
return "", bubble.ErrNotCompleted
}
return model.domain, nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case string:
if v != "" {
model.domain = v
secret := &corev1.Secret{}
secret.SetName("cluster-settings")
secret.SetNamespace("libresh-system")
_, err := ctrl.CreateOrUpdate(context.TODO(), model.Manager.Client(), secret, func() error {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
secret.Data["CLUSTER_DOMAIN"] = []byte(v)
return nil
})
if err != nil {
// TODO manager error
log.Fatal(err)
}
return nil
}
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "email"
},
},
},
),
bubble.NewInputQuestion(
"email",
bubble.InputQuestionOpts[*Model]{
Prompt: "Enter the cluster maintainer email",
Placeholder: "eg. contact@libre.sh",
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.email == "" {
return "", bubble.ErrNotCompleted
}
return model.email, nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case string:
if v != "" {
model.email = v
secret := &corev1.Secret{}
secret.SetName("cluster-settings")
secret.SetNamespace("libresh-system")
_, err := ctrl.CreateOrUpdate(context.TODO(), model.Manager.Client(), secret, func() error {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
secret.Data["CLUSTER_EMAIL"] = []byte(v)
return nil
})
if err != nil {
// TODO manager error
log.Fatal(err)
}
return nil
}
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "envMode"
},
},
},
),
bubble.NewChoiceQuestion(
"envMode",
bubble.ChoiceQuestionOpts[*Model]{
Prompt: "Environment Mode",
ItemGetter: envModes,
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.envMode == "" {
return "", bubble.ErrNotCompleted
}
return model.envMode, nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bubble.BasicItem:
model.envMode = v.Name
secret := &corev1.Secret{}
secret.SetName("cluster-settings")
secret.SetNamespace("libresh-system")
_, err := ctrl.CreateOrUpdate(context.TODO(), model.Manager.Client(), secret, func() error {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
if v.Name == "production" {
secret.Data["DEFAULT_CLUSTERISSUER"] = []byte("letsencrypt")
} else {
secret.Data["DEFAULT_CLUSTERISSUER"] = []byte("selfsigned")
}
return nil
})
if err != nil {
// TODO manager error
log.Fatal(err)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "installer"
},
},
},
),
bubble.NewInstaller(
"installer",
bubble.InstallerOpts[*Model]{
Components: m.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
},
},
),
},
})
return stepStage, err
}

View file

@ -0,0 +1,101 @@
package minio
import (
"github.com/fluxcd/pkg/apis/meta"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
lshv1alpha1 "libre.sh/object-storage-operator/api/v1alpha1"
)
var (
MinioOperator = component.BasicComponent{
Name: "minio",
Source: "minio",
Type: "HelmRelease",
Scope: "Objectstore",
DependsOn: []meta.NamespacedObjectReference{
/* {
Name: "openebs",
}, */
{
Name: "cert-manager",
},
{
Name: "ingress-nginx",
},
},
}
ObjectStorageOperator = component.BasicComponent{
Name: "object-storage-operator",
// TODO
// Source: "",
Type: "Kustomization",
Scope: "Objectstore",
Required: true,
DependsOn: []meta.NamespacedObjectReference{
/* {
Name: "openebs",
}, */
{
Name: "cert-manager",
},
{
Name: "ingress-nginx",
},
},
}
)
func (o Model) Components() []component.Component {
return []component.Component{
&MinioOperator,
}
}
func (o Model) ObjectStoreComponents() []component.Component {
return []component.Component{
&ObjectStorageOperator,
}
}
func (n Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range n.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
type Model struct {
/*
MinioOperator ComponentConfig
ObjectStorageOperator ComponentConfig
*/
*component.ScopeModel
// components []string
MinioProviders []string
CurrentData string
CurrentProvider string
Providers []lshv1alpha1.MinIOProvider
DataProvider []lshv1alpha1.MinIOProvider
PITRProvider []lshv1alpha1.MinIOProvider
MetricsProvider []lshv1alpha1.MinIOProvider
Mapping map[string]string
MinioOperator *bool
MinioOperatorDone *bool
ObjectStoreOperatorConfigDone *bool
ObjectStoreOperatorDone *bool
// manager *ssa.ResourceManager
MinioReady *bool
}

View file

@ -0,0 +1,233 @@
package minio
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/lipgloss"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
lshobs "libre.sh/object-storage-operator/api/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func (c *Model) Init() error {
return nil
}
func (m *Model) Stage() (component.Model, error) {
foo := bubble.BasicItemList{
List: []bubble.BasicItem{
{
Name: "yes",
},
{
Name: "no",
},
},
}
s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
stepForm, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring Networking components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Answer these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
// Spinner: s,
Tasks: []bubble.Task[*Model]{
bubble.NewChoiceQuestion(
"components",
bubble.ChoiceQuestionOpts[*Model]{
Prompt: "Do you want to deploy a minio tenant in your cluster",
ItemGetter: foo,
PaddingY: 18,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.MinioOperator == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.MinioOperator), nil
// return "", nil
},
UpdateResult: func(model *Model, answer interface{}) error {
// fmt.Printf("%#v", answer)
switch v := answer.(type) {
case bubble.BasicItem:
if v.Name == "yes" {
pbool := true
model.MinioOperator = &pbool
} else {
pbool := false
model.MinioOperator = &pbool
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
if model.MinioOperator != nil && *model.MinioOperator {
return "minio-operator-installer"
}
return "minio-tenant-install"
},
},
},
),
bubble.NewInstaller(
"minio-operator-installer",
bubble.InstallerOpts[*Model]{
Components: m.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.MinioOperatorDone == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.MinioOperatorDone), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.MinioOperatorDone = &v
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "minio-tenant-install"
},
},
},
),
bubble.NewInstruction(
"minio-tenant-install",
bubble.InstructionOpts[*Model]{
Text: `
You now need to deploy minio tenant(s).
Once done you will need to map your tenants configuration with providers for the object storage operator.
Store this configuration in a secret called object-storage-config
Tap any key when you are done.
`,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.ObjectStoreOperatorConfigDone == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.ObjectStoreOperatorConfigDone), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
secret := &corev1.Secret{}
err := model.Manager.Client().Get(context.TODO(), client.ObjectKey{Name: "object-storage-config", Namespace: "libresh-system"}, secret)
if err != nil {
return err
}
ctrlConfig := lshobs.ObjectStorageConfig{}
if secret.Data["config.yaml"] == nil {
return fmt.Errorf("config file is empty")
}
err = yaml.Unmarshal(secret.Data["config.yaml"], &ctrlConfig)
if err != nil {
return err
}
if len(ctrlConfig.Providers) == 0 {
return fmt.Errorf("no providers are defined in config file")
}
for _, provider := range ctrlConfig.Providers {
s3Client, err := minio.New(provider.Host, &minio.Options{
Creds: credentials.NewStaticV4(provider.AccessKey, provider.SecretKey, ""),
Secure: !provider.Insecure,
})
if err != nil {
return err
}
ready, err := MinioReady(s3Client)
if err != nil {
return err
}
if !ready {
return fmt.Errorf("tenant is not ready")
}
}
done := true
model.ObjectStoreOperatorConfigDone = &done
return nil
},
Next: func(model *Model) string {
return "objectstorage-operator-installer"
},
},
},
),
bubble.NewInstaller(
"objectstorage-operator-installer",
bubble.InstallerOpts[*Model]{
Components: m.ObjectStoreComponents(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.ObjectStoreOperatorDone == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.ObjectStoreOperatorDone), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.ObjectStoreOperatorDone = &v
return nil
}
return bubble.ErrNotCompleted
},
/*
Next: func(model *Model) string {
return ""
},
*/
},
},
),
},
})
return stepForm, err
}
func MinioReady(c *minio.Client /* , hcDuration time.Duration */) (bool, error) {
probeBucketName := "probe-health"
gctx, gcancel := context.WithTimeout(context.Background(), 3*time.Second)
_, err := c.GetBucketLocation(gctx, probeBucketName)
gcancel()
if err != nil {
return false, err
}
return true, err
}

View file

@ -0,0 +1,255 @@
package networking
import (
"context"
"fmt"
"log"
"strconv"
"strings"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/lipgloss"
"github.com/fluxcd/pkg/apis/meta"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Model struct {
*component.ScopeModel
spinner spinner.Model
progress progress.Model
// done *bool
// manager *ssa.ResourceManager
IngressNodes []string
// components []string
}
func (c *Model) Init() error {
return nil
}
func (n Model) Components() []component.Component {
return []component.Component{
&CertManager,
&CertManagerIssuers,
&Ingress,
}
}
func (n Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range n.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
func (n Model) ComponentsName() []string {
components := []string{}
for _, component := range n.Components() {
components = append(components, component.GetName())
}
return components
}
func (m *Model) Stage() (component.Model, error) {
// TODO should be done somewhere else, like an init function ?
m.progress = progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
progress.WithoutPercentage(),
)
m.spinner = spinner.New()
m.spinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
nodeList := &corev1.NodeList{}
ctx := context.TODO()
items := &bubble.ItemList{}
if err := m.Manager.Client().List(ctx, nodeList); err != nil {
log.Fatal(err)
}
for _, node := range nodeList.Items {
var item bubble.SelectItem
item.Name = node.Name
if node.Labels["ingress"] == "true" {
item.Chosen = true
}
items.List = append(items.List, item)
}
components := m.Items()
stepForm, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring Networking components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewSelectQuestion(
"components",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the components that should be deployed on your cluster",
ItemGetter: components,
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
// TODO manage when no components are selected
if len(model.ScopeModel.Components) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.ScopeModel.Components), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
model.ScopeModel.Components = append(model.ScopeModel.Components, item.Name)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
for _, component := range model.ScopeModel.Components {
if component == "ingress-nginx" {
return "ingressNodes"
}
}
return ""
},
},
},
),
bubble.NewSelectQuestion(
"ingressNodes",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the nodes on which ingress should be deployed",
ItemGetter: items,
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if len(model.IngressNodes) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.IngressNodes), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
if item.Chosen {
model.IngressNodes = append(model.IngressNodes, item.Name)
patch := []byte(`{"metadata":{"labels":{"ingress": "true"}}}`)
// TODO manage error
_ = m.Manager.Client().Patch(context.Background(), &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: item.Name,
},
}, client.RawPatch(types.StrategicMergePatchType, patch))
}
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "networking-installer"
},
},
},
),
bubble.NewInstaller(
"networking-installer",
bubble.InstallerOpts[*Model]{
Components: m.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return ""
},
},
},
),
},
})
return stepForm, err
}
var (
Ingress = component.BasicComponent{
Name: "ingress-nginx",
Source: "ingress-nginx",
Type: "HelmRelease",
Scope: "Networking",
DependsOn: []meta.NamespacedObjectReference{
{
Name: "cert-manager",
},
},
}
CertManager = component.BasicComponent{
Name: "cert-manager",
Source: "jetstack",
Type: "HelmRelease",
Scope: "Networking",
}
CertManagerIssuers = component.BasicComponent{
Name: "cert-manager-issuers",
// Source: "jetstack",
Type: "Kustomization",
Scope: "Networking",
DependsOn: []meta.NamespacedObjectReference{
{
Name: "cert-manager",
},
},
}
)

View file

@ -0,0 +1,88 @@
package observability
import (
"github.com/fluxcd/pkg/apis/meta"
"libre.sh/cli/pkg/component"
)
var (
Grafana = component.BasicComponent{
Name: "grafana",
Source: "grafana",
Type: "HelmRelease",
Scope: "Observability",
DependsOn: []meta.NamespacedObjectReference{
{
Name: "postgres-zalando",
},
{
Name: "postgres-libresh",
},
},
AdminCreds: true,
}
Loki = component.BasicComponent{
Name: "loki",
Source: "grafana",
Type: "HelmRelease",
Scope: "Observability",
DependsOn: []meta.NamespacedObjectReference{
/* {
Name: "openebs",
}, */
{
Name: "object-storage-operator",
},
/* {
Name: "keycloak",
}, */
},
AdminCreds: true,
}
Promtail = component.BasicComponent{
Name: "promtail",
Source: "grafana",
Type: "HelmRelease",
Scope: "Observability",
DependsOn: []meta.NamespacedObjectReference{
{
Name: "loki",
},
},
}
PrometheusStack = component.BasicComponent{
Name: "prometheus-stack",
Source: "prometheus-community",
Type: "HelmRelease",
Scope: "Observability",
DependsOn: []meta.NamespacedObjectReference{
/* {
Name: "openebs",
}, */
{
Name: "object-storage-operator",
},
{
Name: "thanos",
},
},
}
Thanos = component.BasicComponent{
Name: "thanos",
Source: "bitnami",
Type: "HelmRelease",
Scope: "Observability",
DependsOn: []meta.NamespacedObjectReference{
/* {
Name: "openebs",
}, */
{
Name: "object-storage-operator",
},
},
}
)

View file

@ -0,0 +1,38 @@
package observability
import (
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (o Model) Components() []component.Component {
return []component.Component{
&Thanos,
&PrometheusStack,
&Loki,
&Promtail,
&Grafana,
}
}
func (n Model) Items() bubble.ItemList {
items := bubble.ItemList{}
for _, component := range n.Components() {
items.List = append(items.List, bubble.SelectItem{
Name: component.GetName(),
Chosen: true,
})
}
return items
}
type Model struct {
/* components []string
manager *ssa.ResourceManager
done *bool */
*component.ScopeModel
}
func (c *Model) Init() error {
return nil
}

View file

@ -0,0 +1,91 @@
package observability
import (
"fmt"
"strconv"
"strings"
"libre.sh/cli/pkg/bubble"
"libre.sh/cli/pkg/component"
)
func (m *Model) Stage() (component.Model, error) {
components := m.Items()
stepStage, err := bubble.NewStage(m, bubble.StageOpts[*Model]{
RootID: "components",
Intro: func(model *Model) string {
b := &strings.Builder{}
b.WriteString("\n")
b.WriteString(bubble.BoldStyle.Render("Configuring Observability components"))
b.WriteString("\n")
b.WriteString(bubble.TextStyle.Copy().Foreground(bubble.Feint).Render("Result these questions to configure your cluster."))
b.WriteString("\n")
return b.String()
},
Tasks: []bubble.Task[*Model]{
bubble.NewSelectQuestion(
"components",
bubble.SelectQuestionOpts[*Model]{
Prompt: "Select the components that should be deployed on your cluster",
ItemGetter: components,
PaddingY: 8,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
// TODO
if len(model.ScopeModel.Components) == 0 {
return "", bubble.ErrNotCompleted
}
return fmt.Sprintf("%#v", model.ScopeModel.Components), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
fmt.Printf("%#v", answer)
switch v := answer.(type) {
case []bubble.SelectItem:
for _, item := range v {
model.ScopeModel.Components = append(model.ScopeModel.Components, item.Name)
}
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return "installer"
},
},
},
),
bubble.NewInstaller(
"installer",
bubble.InstallerOpts[*Model]{
Components: m.Components(),
Manager: m.Manager,
TaskOpts: bubble.TaskOpts[*Model]{
Result: func(model *Model) (string, error) {
if model.Done == nil {
return "", bubble.ErrNotCompleted
}
return strconv.FormatBool(*model.Done), nil
},
UpdateResult: func(model *Model, answer interface{}) error {
switch v := answer.(type) {
case bool:
model.Done = &v
return nil
}
return bubble.ErrNotCompleted
},
Next: func(model *Model) string {
return ""
},
},
},
),
},
})
return stepStage, err
}

41
sample-app.yaml Normal file
View file

@ -0,0 +1,41 @@
kind: Pod
apiVersion: v1
metadata:
name: apple-app
labels:
app: apple
spec:
containers:
- name: apple-app
image: hashicorp/http-echo
args:
- "-text=apple"
---
kind: Service
apiVersion: v1
metadata:
name: apple-service
spec:
selector:
app: apple
ports:
- port: 5678 # Default port for image
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: apple-app
namespace: default
spec:
ingressClassName: nginx
rules:
- host: app.dev.local
http:
paths:
- backend:
service:
name: apple-service
port:
number: 5678
path: /
pathType: Prefix

11
secrets.yaml Normal file
View file

@ -0,0 +1,11 @@
apiVersion: v1
stringData:
CLUSTER_NAME: louisemichel
CLUSTER_DOMAIN: dev.local
CLUSTER_EMAIL: support@libre.sh
DEFAULT_CLUSTERISSUER: selfsigned
kind: Secret
metadata:
name: cluster-config
namespace: flux-system
type: Opaque

View file

@ -21,3 +21,6 @@ nodes:
image: kindest/node:v1.24.6
- role: worker
image: kindest/node:v1.24.6
# TODO when ready to install Cilium
#networking:
# disableDefaultCNI: true

View file

@ -0,0 +1,41 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: monitoring
namespace: flux-system
spec:
interval: 10m
retryInterval: 1m
timeout: 3m
path: ./cluster/components/monitoring
prune: true
dependsOn:
# - name: core
# - name: repositories
sourceRef:
kind: GitRepository
name: flux-cluster
postBuild:
substituteFrom:
# - kind: ConfigMap
# name: cluster-settings
- kind: Secret
name: cluster-config
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: loki
namespace: monitoring
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: promtail
namespace: monitoring
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: thanos
namespace: monitoring
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: thanos
namespace: kube-prometheus-stack

View file

@ -0,0 +1,35 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: networking
namespace: flux-system
spec:
interval: 10m
retryInterval: 1m
timeout: 3m
path: ./cluster/components/networking
prune: true
dependsOn:
# - name: core
- name: repositories
# we need prometheusRule CRD => should we deploy CRDS separately ??
- name: monitoring
sourceRef:
kind: GitRepository
name: flux-cluster
postBuild:
substituteFrom:
# - kind: ConfigMap
# name: cluster-settings
- kind: Secret
name: cluster-config
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: ingress-nginx
namespace: networking
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: cert-manager
namespace: networking

View file

@ -0,0 +1,13 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: repositories
namespace: flux-system
spec:
interval: 10m
path: ./cluster/repositories/helm
prune: true
sourceRef:
kind: GitRepository
name: flux-cluster

View file

@ -0,0 +1,36 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: system
namespace: flux-system
spec:
interval: 10m
retryInterval: 1m
timeout: 3m
path: ./cluster/components/libresh
prune: true
dependsOn:
# - name: core
# TODO rename to charts
# - name: repositories
# - name: monitoring
# - name: networking
sourceRef:
kind: GitRepository
name: flux-cluster
postBuild:
substituteFrom:
# - kind: ConfigMap
# name: cluster-settings
- kind: Secret
name: cluster-config
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: postgres-zalando-operator
namespace: libresh-system
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: minio-operator
namespace: networking

View file

@ -0,0 +1,9 @@
apiVersion: v1
data:
password: czc5YjY0bHF6bjRqZzljdG41djdicHg1OXR4NnRwbTU=
username: bG9raQ==
kind: Secret
metadata:
name: loki-auth
namespace: monitoring
type: Opaque

View file

@ -1,34 +1,19 @@
apiVersion: v1
data:
CONSOLE_ACCESS_KEY: Y29uc29sZQ==
CONSOLE_SECRET_KEY: Y29uc29sZTEyMw==
kind: Secret
metadata:
name: hot-user-0
namespace: minio
type: Opaque
---
apiVersion: v1
data:
## Tenant credentials, base64 encoded (cat config.env | base64)
## export MINIO_ROOT_USER="minio"
## export MINIO_ROOT_PASSWORD="minio123"
## export MINIO_STORAGE_CLASS_STANDARD="EC:2"
## export MINIO_BROWSER="on"
config.env: ZXhwb3J0IE1JTklPX1JPT1RfVVNFUj0ibWluaW8iCmV4cG9ydCBNSU5JT19ST09UX1BBU1NXT1JEPSJtaW5pbzEyMyIKZXhwb3J0IE1JTklPX1NUT1JBR0VfQ0xBU1NfU1RBTkRBUkQ9IkVDOjIiCmV4cG9ydCBNSU5JT19CUk9XU0VSPSJvbiI=
kind: Secret
metadata:
name: hot-storage-configuration
namespace: minio
type: Opaque
---
apiVersion: v1
data:
accesskey: "bWluaW8K"
secretkey: "bWluaW8xMjMK"
immutable: true
kind: Secret
metadata:
name: hot-secret
namespace: minio
type: Opaque
name: object-storage-config
namespace: libresh-system
stringData:
config.yaml: |-
apiVersion: objectstorage.libre.sh/v1alpha1
kind: ObjectStorageConfig
mapping:
pitr: minio-tenant
data: minio-tenant
metrics: minio-tenant
providers:
- name: minio-tenant
host: s3.dev.local
insecure: true
accessKey: minio
secretKey: minio123

View file

@ -55,10 +55,10 @@ spec:
ingressClassName: nginx
tls:
- hosts:
- console.s3.dev.local
- tenant.dev.local
secretName: hot-minio-liib-re-tls
rules:
- host: console.s3.dev.local
- host: tenant.dev.local
http:
paths:
- path: /

View file

@ -1,4 +1,19 @@
apiVersion: v1
kind: Namespace
metadata:
name: minio-tenant
---
apiVersion: v1
data:
CONSOLE_ACCESS_KEY: Y29uc29sZQ==
CONSOLE_SECRET_KEY: Y29uc29sZTEyMw==
kind: Secret
metadata:
name: hot-user-0
namespace: minio-tenant
type: Opaque
---
apiVersion: v1
data:
## Tenant credentials, base64 encoded (cat config.env | base64)
## export MINIO_ROOT_USER="minio"
@ -9,5 +24,16 @@ data:
kind: Secret
metadata:
name: hot-storage-configuration
namespace: minio
namespace: minio-tenant
type: Opaque
---
apiVersion: v1
data:
accesskey: "bWluaW8K"
secretkey: "bWluaW8xMjMK"
immutable: true
kind: Secret
metadata:
name: hot-secret
namespace: minio-tenant
type: Opaque

View file

@ -1,10 +0,0 @@
apiVersion: v1
data:
accesskey: "bWluaW8K"
secretkey: "bWluaW8xMjMK"
immutable: true
kind: Secret
metadata:
name: hot-secret
namespace: minio
type: Opaque

View file

@ -1,13 +1,65 @@
apiVersion: v1
kind: Namespace
metadata:
name: minio-tenant
---
#apiVersion: v1
#data:
# CONSOLE_ACCESS_KEY: Y29uc29sZQ==
# CONSOLE_SECRET_KEY: Y29uc29sZTEyMw==
#kind: Secret
#metadata:
# name: hot-user-0
# namespace: minio-tenant
#type: Opaque
#---
apiVersion: v1
data:
## Tenant credentials, base64 encoded (cat config.env | base64)
## export MINIO_ROOT_USER="minio"
## export MINIO_ROOT_PASSWORD="minio123"
## export MINIO_STORAGE_CLASS_STANDARD="EC:2"
## export MINIO_BROWSER="on"
config.env: ZXhwb3J0IE1JTklPX1JPT1RfVVNFUj0ibWluaW8iCmV4cG9ydCBNSU5JT19ST09UX1BBU1NXT1JEPSJtaW5pbzEyMyIKZXhwb3J0IE1JTklPX1NUT1JBR0VfQ0xBU1NfU1RBTkRBUkQ9IkVDOjIiCmV4cG9ydCBNSU5JT19CUk9XU0VSPSJvbiI=
kind: Secret
metadata:
name: hot-storage-configuration
namespace: minio-tenant
type: Opaque
---
apiVersion: v1
data:
accesskey: "bWluaW8K"
secretkey: "bWluaW8xMjMK"
immutable: true
kind: Secret
metadata:
name: hot-secret
namespace: minio-tenant
type: Opaque
---
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: hot
namespace: minio
namespace: minio-tenant
spec:
configuration:
name: hot-storage-configuration
credsSecret:
name: hot-secret
requestAutoCert: true
features:
bucketDNS: true
domains:
console: http://tenant.dev.local/
# minio:
# - http://s3.dev.local
env:
- name: MINIO_DOMAIN
value: "minio-tenant.svc.cluster.local"
# - name: MINIO_CONSOLE_TLS_ENABLE
# value: "off"
pools:
- servers: 2
name: pool-0
@ -20,10 +72,80 @@ spec:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storage: 2Gi
containerSecurityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
users:
- name: hot-user-0
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hot-console
namespace: minio-tenant
annotations:
# cert-manager.io/cluster-issuer: self-signed
#kubernetes.io/tls-acme: "true"
## Remove if using CA signed certificate
nginx.ingress.kubernetes.io/proxy-ssl-verify: "off"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/server-snippet: |
client_max_body_size 0;
nginx.ingress.kubernetes.io/configuration-snippet: |
chunked_transfer_encoding off;
spec:
ingressClassName: nginx
# tls:
# - hosts:
# - tenant.dev.local
# secretName: hot-minio-liib-re-tls
rules:
- host: tenant.dev.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hot-console
port:
number: 9443
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hot-buckets
namespace: minio-tenant
annotations:
# cert-manager.io/cluster-issuer: self-signed
# kubernetes.io/tls-acme: "true"
## Remove if using CA signed certificate
nginx.ingress.kubernetes.io/proxy-ssl-verify: "off"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/server-snippet: |
client_max_body_size 0;
nginx.ingress.kubernetes.io/configuration-snippet: |
chunked_transfer_encoding off;
spec:
ingressClassName: nginx
# tls:
# - hosts:
# - s3.dev.local
# secretName: s3-dev-local-tls
rules:
- host: s3.dev.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minio
port:
number: 443

View file

@ -1,118 +0,0 @@
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: storage
namespace: minio-tenant
spec:
## Specification for MinIO Pool(s) in this Tenant.
pools:
## Servers specifies the number of MinIO Tenant Pods / Servers in this pool.
## For standalone mode, supply 1. For distributed mode, supply 4 or more.
## Note that the operator does not support upgrading from standalone to distributed mode.
- servers: 4
## custom pool name
name: pool-0
## volumesPerServer specifies the number of volumes attached per MinIO Tenant Pod / Server.
volumesPerServer: 2
## This VolumeClaimTemplate is used across all the volumes provisioned for MinIO Tenant in this Pool.
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
containerSecurityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: hot
namespace: minio-hot
spec:
configuration:
name: hot-env-configuration
credsSecret:
name: hot-secret
features:
bucketDNS: true
domains:
console: https://hot.minio.liiib.re/
minio:
- https://hot-objects.liiib.re
log:
audit:
diskCapacityGB: 5
db:
securityContext:
fsGroup: 999
runAsGroup: 999
runAsNonRoot: true
runAsUser: 999
volumeClaimTemplate:
metadata:
name: hot-log
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "5368709120"
storageClassName: openebs-lvm
securityContext:
fsGroup: 1000
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
mountPath: /export
pools:
- affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: v1.min.io/tenant
operator: In
values:
- hot
- key: v1.min.io/pool
operator: In
values:
- pool-0
topologyKey: kubernetes.io/hostname
name: pool-0
resources:
limits:
memory: 32Gi
requests:
memory: 2Gi
servers: 5
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "429496729600"
storageClassName: openebs-device-ssd
volumesPerServer: 1
prometheus:
diskCapacityGB: 5
resources: {}
securityContext:
fsGroup: 1000
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
storageClassName: openebs-hostpath
requestAutoCert: true
users:
- name: hot-user-0

View file

@ -1,8 +0,0 @@
apiVersion: v1
data:
CONSOLE_ACCESS_KEY: Y29uc29sZQ==
CONSOLE_SECRET_KEY: Y29uc29sZTEyMw==
kind: Secret
metadata:
name: hot-user-0
type: Opaque

View file

@ -0,0 +1,18 @@
apiVersion: v1
stringData:
objstore.yml: |-
type: S3
config:
bucket: ""
endpoint: ""
region: ""
access_key: ""
insecure: false
secret_key: ""
http_config:
insecure_skip_verify: false
kind: Secret
metadata:
name: thanos-config
namespace: monitoring
type: Opaque

10
values-ingress.yaml Normal file
View file

@ -0,0 +1,10 @@
apiVersion: v1
data:
values.yaml: |-
controller
config:
use-proxy-protocol: "false"
kind: ConfigMap
metadata:
name: ingress-custom-values
namespace: libresh-system

10
values-thanos.yaml Normal file
View file

@ -0,0 +1,10 @@
apiVersion: v1
data:
values.yaml: |-
objstoreConfig:
config:
insecure: true
kind: ConfigMap
metadata:
name: thanos-custom-values
namespace: libresh-system