mirror of
https://forge.liiib.re/indiehost/libre.sh/libre.sh.git
synced 2024-12-26 13:46:41 +00:00
feat: add cli
This commit is contained in:
parent
d74037e6db
commit
b9532d240b
61 changed files with 5690 additions and 175 deletions
287
LICENSE
Normal file
287
LICENSE
Normal 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
7
cm.yaml
Normal 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
357
cmd/init.go
Normal 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
62
cmd/root.go
Normal 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
130
go.mod
Normal 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
513
go.sum
Normal 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
9
keycloak-secret.yaml
Normal 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
23
keycloak.yaml
Normal 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
26
logo.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
||||||||
|
||||
||||||||||||||||
|
||||
||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||||||||||||||
|
||||
|||||||||||||||| || ||||||||||||||||
|
||||
||||||||||||||| |||||| |||||||||||||||
|
||||
||||||||||||||| |||||| |||||||||||||||
|
||||
|||||||||||||||||| || ||||||||||||||||||
|
||||
|||||||||||||||||||| ||||||||||||||||||||
|
||||
|||||||||| |||||||||| |||||||||| ||||||||||
|
||||
|||||||| |||||||| |||||||| ||||||||
|
||||
||||||||||| ||||||||| ||||||||| |||||||||||
|
||||
|||||||||||| ||||||||| ||||||||| ||||||||||||
|
||||
|||||||||||| |||||||| |||||||| ||||||||||||
|
||||
|||||||||||||| |||||| |||||| ||||||||||||||
|
||||
|||||||||||||| | |||| | ||||||||||||||
|
||||
||||||||||||||| |||||| |||||||||||||||
|
||||
|||||||||||||| |||| ||||||||||||||
|
||||
||||||||||||||| |||||||||||||||
|
||||
||||||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||||
|
||||
||||||||||||||||||||||||
|
||||
|
||||
|
24
main.go
Normal file
24
main.go
Normal 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
397
pkg/bubble/installer.go
Normal 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
|
||||
}
|
106
pkg/bubble/instruction_task.go
Normal file
106
pkg/bubble/instruction_task.go
Normal 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
102
pkg/bubble/program.go
Normal 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...)
|
||||
}
|
156
pkg/bubble/question_choice.go
Normal file
156
pkg/bubble/question_choice.go
Normal 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
|
||||
}
|
107
pkg/bubble/question_input.go
Normal file
107
pkg/bubble/question_input.go
Normal 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
|
||||
}
|
203
pkg/bubble/question_select.go
Normal file
203
pkg/bubble/question_select.go
Normal 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
|
||||
}
|
107
pkg/bubble/question_textArea.go
Normal file
107
pkg/bubble/question_textArea.go
Normal 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
216
pkg/bubble/stage.go
Normal 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
68
pkg/bubble/step.go
Normal 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
140
pkg/bubble/styles.go
Normal 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
67
pkg/bubble/task.go
Normal 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
103
pkg/bubble/tea.go
Normal 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...)
|
||||
}
|
23
pkg/component/auth/components.go
Normal file
23
pkg/component/auth/components.go
Normal 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,
|
||||
}
|
||||
)
|
36
pkg/component/auth/config.go
Normal file
36
pkg/component/auth/config.go
Normal 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
|
||||
}
|
87
pkg/component/auth/tasks.go
Normal file
87
pkg/component/auth/tasks.go
Normal 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
371
pkg/component/component.go
Normal 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
|
||||
}
|
19
pkg/component/crds/components.go
Normal file
19
pkg/component/crds/components.go
Normal 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",
|
||||
}
|
||||
)
|
35
pkg/component/crds/config.go
Normal file
35
pkg/component/crds/config.go
Normal 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
|
||||
}
|
85
pkg/component/crds/tasks.go
Normal file
85
pkg/component/crds/tasks.go
Normal 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
|
||||
}
|
14
pkg/component/csi/components.go
Normal file
14
pkg/component/csi/components.go
Normal 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",
|
||||
}
|
||||
)
|
34
pkg/component/csi/config.go
Normal file
34
pkg/component/csi/config.go
Normal 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
|
||||
}
|
85
pkg/component/csi/tasks.go
Normal file
85
pkg/component/csi/tasks.go
Normal 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
|
||||
}
|
51
pkg/component/databases/components.go
Normal file
51
pkg/component/databases/components.go
Normal 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",
|
||||
},
|
||||
}, */
|
||||
}
|
||||
)
|
36
pkg/component/databases/config.go
Normal file
36
pkg/component/databases/config.go
Normal 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
|
||||
}
|
85
pkg/component/databases/tasks.go
Normal file
85
pkg/component/databases/tasks.go
Normal 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
|
||||
}
|
13
pkg/component/general/components.go
Normal file
13
pkg/component/general/components.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package general
|
||||
|
||||
import (
|
||||
"libre.sh/cli/pkg/component"
|
||||
)
|
||||
|
||||
var (
|
||||
Repos = component.BasicComponent{
|
||||
Name: "repositories",
|
||||
Type: "Kustomize",
|
||||
Scope: "Repositories",
|
||||
}
|
||||
)
|
301
pkg/component/general/tasks.go
Normal file
301
pkg/component/general/tasks.go
Normal 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
|
||||
}
|
101
pkg/component/minio/components.go
Normal file
101
pkg/component/minio/components.go
Normal 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
|
||||
}
|
233
pkg/component/minio/tasks.go
Normal file
233
pkg/component/minio/tasks.go
Normal 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
|
||||
}
|
255
pkg/component/networking/networking.go
Normal file
255
pkg/component/networking/networking.go
Normal 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",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
88
pkg/component/observability/components.go
Normal file
88
pkg/component/observability/components.go
Normal 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",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
38
pkg/component/observability/config.go
Normal file
38
pkg/component/observability/config.go
Normal 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
|
||||
}
|
91
pkg/component/observability/tasks.go
Normal file
91
pkg/component/observability/tasks.go
Normal 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
41
sample-app.yaml
Normal 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
11
secrets.yaml
Normal 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
|
|
@ -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
|
41
testing/kustomization/monitoring.yaml
Normal file
41
testing/kustomization/monitoring.yaml
Normal 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
|
35
testing/kustomization/networking.yaml
Normal file
35
testing/kustomization/networking.yaml
Normal 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
|
13
testing/kustomization/repositories.yaml
Normal file
13
testing/kustomization/repositories.yaml
Normal 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
|
36
testing/kustomization/system.yaml
Normal file
36
testing/kustomization/system.yaml
Normal 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
|
9
testing/loki-auth copy.yaml
Normal file
9
testing/loki-auth copy.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
password: czc5YjY0bHF6bjRqZzljdG41djdicHg1OXR4NnRwbTU=
|
||||
username: bG9raQ==
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: loki-auth
|
||||
namespace: monitoring
|
||||
type: Opaque
|
|
@ -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
|
|
@ -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: /
|
||||
|
@ -79,7 +79,7 @@ metadata:
|
|||
cert-manager.io/cluster-issuer: selfsigned-issuer
|
||||
## 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/backend-protocol: "HTTPS"
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
|
|
|
@ -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
|
||||
type: Opaque
|
||||
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
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
accesskey: "bWluaW8K"
|
||||
secretkey: "bWluaW8xMjMK"
|
||||
immutable: true
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: hot-secret
|
||||
namespace: minio
|
||||
type: Opaque
|
|
@ -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
|
||||
- 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
|
||||
|
|
|
@ -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
|
|
@ -1,8 +0,0 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
CONSOLE_ACCESS_KEY: Y29uc29sZQ==
|
||||
CONSOLE_SECRET_KEY: Y29uc29sZTEyMw==
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: hot-user-0
|
||||
type: Opaque
|
18
testing/thanos-config.yaml
Normal file
18
testing/thanos-config.yaml
Normal 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
10
values-ingress.yaml
Normal 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
10
values-thanos.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
values.yaml: |-
|
||||
objstoreConfig:
|
||||
config:
|
||||
insecure: true
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: thanos-custom-values
|
||||
namespace: libresh-system
|
Loading…
Reference in a new issue