Skip to content

Commit c574e5f

Browse files
waleedhammamSamra10enekofb
authored
Add bootstrap command for gitops cli to bootstrap WGE (#3371)
* init gitops bootstrap command for wge * add checks for entitlement and flux * Add wge version choise list * add create admin username and password secret * add install wge * fix adding admin password * fix linting * run go mod tidy * enable ingress * add domain type selector * add option to bootstrap flux * adjust bootstraping flux and domain * add install extra controllers * fix lint * rename package checks to commands * refactor packages * early exit * refactor errors and add utils for git repos * refactor wge installation to use files * refactor extra controllers * remove extra unneeded values * Add OIDC * update portforward * add admin password revert * refactor error handeling * fix unhandeled errors * refactor creating helmrepos and helmreleases * refactor styling * go mod tidy * split utils * Add OIDC * refactor creating extra controllers * move install extra controller to commands * add oidc * Add OIDC * install capi controller * add terraform controller * refactor OIDC cli * fix linting * fix lintting * fix lint * fix lint * update OIDC CLI * handle error msgs * move check mark to method * reformat error * cleanup * update CLI OIDC * fix confirm input * type * Add localhost portforward info * add gitopssets controller * enable pipelines controller and cluster-controller and gitopssets by default * remove gitops sets * handle existing secrets * Update onboarding CLI messages * cleanup utils * cleanup utils * cleanup domain * cleanup utils * cleanup input * cleanup input * edit messages and variableNames * update cli messages * cleanup variables * cleanup constants * cleanup constants * add unit test for k8s utils and refactor accordingly * add unit tests for flux * add unit test for admin password * Add unit -test for adding capi & policy-agent * move controllers under gitops add * add unit test for wge version * test oidc get issuer * Move controllers under gitops add * Move controllers under gitops add * pause add controllers tests * restore install controller function after setup * fix controllers * pass opts to controllers * refactor git utilits * refactor git utilities * prepare bootstrap bommand for release1 * address messages and languague * clean extra variables * remove oidc flow to another branch * Update cmd/gitops/app/bootstrap/cmd.go Co-authored-by: Eneko Fernández <[email protected]> * Update cmd/gitops/app/bootstrap/cmd.go Co-authored-by: Eneko Fernández <[email protected]> * refactor git utility & add unit-test * remove out of scope componenets * apply code review comments to refactor file names and error messages * move bootstrap package under pkg * refactor using the k8s client and add silent mode refactor git utils * fix admin password * verify username and password * remove unused methods * fix spelling * remove aws related stuff * use git library add check for previous installation * fix lint * imporve error messages to stage failures * adjust gitopssets values and installation checks * clean silent mode * add checks for kubeconfig * adjust admin password * refactor commands to use config interface struct * improve error messages * remove unused variable * wip adding cli design doc * wip adding cli design doc * add error guidance * rename config to bootstrapper to be more clear * refactor commands to use steps pattern * continue on refactor commands to use steps pattern * fix ssh authenticate by explictly asking for private key file in case the key is not loaded in ssh agent. this usually happens on macos * added integration test, refactored configuration and design (#3458) * Changes after the review to enhance the following aspects Testing: - Added integration test so we could test the functionality e2e: it uses some local configuration that we need to test but already provides the acceptance layer that we were missing: Design: - Bootstrap workflow moved to the domain layer within `pkg` so it could be presented in different forms. - Integrated configuration chain of responsibility into a single a builder pattern, so we have configurability in this layer. As a result: - we dont need to pass the flags to the steps - we config the stepsbefore the workflow is executed which seems the right moment. Other refactors: - Moved steps to package `steps` from `command` for consistency * integrated ssh key management * add events and error messages and fix domain bug * add current context * fix lint * add entitlement expiration message * fix entitlement expiration * fix entitlement expiration * seperate entitlement secret validations * add messages for flux * edit messages to small letter and update success messages * add validation for password and wge version * add test cases for admin password create creds * add test cases for domain type * update entitlement test * add validation on password input * remove aws related message * panic in case of casting error to give more context about the error * handle portforward and error messages * fix external dns spacing * add validation on domain type * Cli eneko review (#3474) * reviewed documentation * updated docs with waleed input * reviewed TBD * latest set of changes * removing commented * remove debugging * removed stale documentation * removed withe space * review * removed unused --------- Co-authored-by: Ahmad Samir <[email protected]> Co-authored-by: Eneko Fernández <[email protected]> Co-authored-by: Eneko Fernandez <[email protected]>
1 parent 6f30d56 commit c574e5f

30 files changed

+3119
-10
lines changed

cmd/gitops/app/bootstrap/cmd.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package bootstrap
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
. "github.com/weaveworks/weave-gitops-enterprise/pkg/bootstrap"
9+
"github.com/weaveworks/weave-gitops-enterprise/pkg/bootstrap/steps"
10+
"github.com/weaveworks/weave-gitops/cmd/gitops/config"
11+
"github.com/weaveworks/weave-gitops/pkg/logger"
12+
)
13+
14+
const (
15+
cmdName = "bootstrap"
16+
cmdShortDescription = `gitops bootstrap installs Weave GitOps Enterprise in simple steps:
17+
- Entitlements: check that you have valid entitlements.
18+
- Flux: check or bootstrap Flux.
19+
- Weave Gitops: check or install a supported Weave GitOps version with default configuration.
20+
- Authentication: check or setup cluster user authentication to access the dashboard.
21+
`
22+
cmdExamples = `
23+
# Start WGE installation from the current kubeconfig
24+
gitops bootstrap
25+
26+
# Start WGE installation from a specific kubeconfig
27+
gitops bootstrap --kubeconfig <your-kubeconfig-location>
28+
29+
# Start WGE installation with given 'username' and 'password'
30+
gitops bootstrap --username wego-admin --password=hell0!
31+
`
32+
)
33+
34+
type bootstrapFlags struct {
35+
username string
36+
password string
37+
version string
38+
domainType string
39+
domain string
40+
privateKeyPath string
41+
privateKeyPassword string
42+
}
43+
44+
var flags bootstrapFlags
45+
46+
func Command(opts *config.Options) *cobra.Command {
47+
cmd := &cobra.Command{
48+
Use: cmdName,
49+
Short: cmdShortDescription,
50+
Example: cmdExamples,
51+
RunE: getBootstrapCmdRun(opts),
52+
}
53+
54+
cmd.Flags().StringVarP(&flags.username, "username", "u", "", "dashboard admin username")
55+
cmd.Flags().StringVarP(&flags.password, "password", "p", "", "dashboard admin password")
56+
cmd.Flags().StringVarP(&flags.version, "version", "v", "", "version of Weave GitOps Enterprise (should be from the latest 3 versions)")
57+
cmd.Flags().StringVarP(&flags.domainType, "domain-type", "t", "", "dashboard domain type: could be 'localhost' or 'externaldns'")
58+
cmd.Flags().StringVarP(&flags.domain, "domain", "d", "", "indicate the domain to use in case of using `externaldns`")
59+
cmd.Flags().StringVarP(&flags.privateKeyPath, "private-key", "k", "", "private key path. This key will be used to push the Weave GitOps Enterprise's resources to the default cluster repository")
60+
cmd.Flags().StringVarP(&flags.privateKeyPassword, "private-key-password", "c", "", "private key password. If the private key is encrypted using password")
61+
return cmd
62+
}
63+
64+
func getBootstrapCmdRun(opts *config.Options) func(*cobra.Command, []string) error {
65+
return func(cmd *cobra.Command, args []string) error {
66+
67+
cliLogger := logger.NewCLILogger(os.Stdout)
68+
69+
// create config from flags
70+
c, err := steps.NewConfigBuilder().
71+
WithLogWriter(cliLogger).
72+
WithKubeconfig(opts.Kubeconfig).
73+
WithUsername(flags.username).
74+
WithPassword(flags.password).
75+
WithVersion(flags.version).
76+
WithDomainType(flags.domainType).
77+
WithDomain(flags.domain).
78+
WithPrivateKey(flags.privateKeyPath, flags.privateKeyPassword).
79+
Build()
80+
81+
if err != nil {
82+
return fmt.Errorf("cannot config bootstrap: %v", err)
83+
}
84+
85+
err = Bootstrap(c)
86+
if err != nil {
87+
return fmt.Errorf("cannot execute bootstrap: %v", err)
88+
}
89+
return nil
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package bootstrap_test
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os"
10+
"sync"
11+
"testing"
12+
"time"
13+
14+
"github.com/go-logr/logr"
15+
"github.com/go-logr/logr/testr"
16+
"github.com/stretchr/testify/require"
17+
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/root"
18+
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/pkg/adapters"
19+
"github.com/weaveworks/weave-gitops/pkg/runner"
20+
corev1 "k8s.io/api/core/v1"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
24+
. "github.com/onsi/gomega"
25+
)
26+
27+
const (
28+
defaultTimeout = time.Second * 5
29+
defaultInterval = time.Second
30+
)
31+
32+
var fluxSystemNamespace = corev1.Namespace{
33+
TypeMeta: metav1.TypeMeta{
34+
Kind: "Namespace",
35+
APIVersion: "v1",
36+
},
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: "flux-system",
39+
},
40+
}
41+
42+
func createEntitlementSecretFromEnv(t *testing.T) corev1.Secret {
43+
44+
username := os.Getenv("WGE_ENTITLEMENT_USERNAME")
45+
require.NotEmpty(t, username)
46+
password := os.Getenv("WGE_ENTITLEMENT_PASSWORD")
47+
require.NotEmpty(t, password)
48+
entitlement := os.Getenv("WGE_ENTITLEMENT_ENTITLEMENT")
49+
require.NotEmpty(t, entitlement)
50+
51+
return corev1.Secret{
52+
TypeMeta: metav1.TypeMeta{
53+
Kind: "Secret",
54+
APIVersion: "v1",
55+
},
56+
ObjectMeta: metav1.ObjectMeta{
57+
Name: "weave-gitops-enterprise-credentials",
58+
Namespace: "flux-system",
59+
},
60+
Type: corev1.SecretTypeOpaque,
61+
Data: map[string][]byte{
62+
"username": []byte(username),
63+
"password": []byte(password),
64+
"entitlement": []byte(entitlement),
65+
},
66+
}
67+
}
68+
69+
// TestBootstrapCmd is an integration test for bootstrapping command.
70+
// It uses envtest to simulate a cluster.
71+
func TestBootstrapCmd(t *testing.T) {
72+
g := NewGomegaWithT(t)
73+
g.SetDefaultEventuallyTimeout(defaultTimeout)
74+
g.SetDefaultEventuallyPollingInterval(defaultInterval)
75+
testLog := testr.New(t)
76+
77+
lock := sync.Mutex{}
78+
79+
privateKeyFile := os.Getenv("GIT_PRIVATEKEY_PATH")
80+
g.Expect(privateKeyFile).NotTo(BeEmpty())
81+
privateKeyArg := fmt.Sprintf("--private-key=%s", privateKeyFile)
82+
83+
// ensure flux-system ns exists
84+
_ = k8sClient.Create(context.Background(), &fluxSystemNamespace)
85+
86+
tests := []struct {
87+
name string
88+
flags []string
89+
expectedErrorStr string
90+
setup func(t *testing.T)
91+
reset func(t *testing.T)
92+
}{
93+
{
94+
name: "should fail without flux bootstrapped",
95+
flags: []string{},
96+
expectedErrorStr: "please bootstrap Flux in `flux-system` namespace: more info https://fluxcd.io/flux/installation",
97+
},
98+
{
99+
name: "should fail without entitlements",
100+
flags: []string{},
101+
setup: func(t *testing.T) {
102+
bootstrapFluxSsh(g)
103+
},
104+
reset: func(t *testing.T) {
105+
uninstallFlux(g)
106+
},
107+
108+
expectedErrorStr: "entitlement file is not found",
109+
},
110+
{
111+
name: "should fail without private key",
112+
flags: []string{},
113+
setup: func(t *testing.T) {
114+
bootstrapFluxSsh(g)
115+
createEntitlements(t, testLog)
116+
},
117+
reset: func(t *testing.T) {
118+
deleteEntitlements(t, testLog)
119+
uninstallFlux(g)
120+
},
121+
expectedErrorStr: "cannot process input 'private key path and password",
122+
},
123+
{
124+
name: "should fail without selected wge version",
125+
flags: []string{
126+
privateKeyArg,
127+
"--private-key-password=\"\"",
128+
},
129+
setup: func(t *testing.T) {
130+
bootstrapFluxSsh(g)
131+
createEntitlements(t, testLog)
132+
},
133+
reset: func(t *testing.T) {
134+
deleteEntitlements(t, testLog)
135+
uninstallFlux(g)
136+
},
137+
expectedErrorStr: "cannot process input 'select WGE version'",
138+
},
139+
{
140+
name: "should fail without user authentication",
141+
flags: []string{"--version=0.33.0",
142+
privateKeyArg,
143+
"--private-key-password=\"\"",
144+
},
145+
setup: func(t *testing.T) {
146+
bootstrapFluxSsh(g)
147+
createEntitlements(t, testLog)
148+
},
149+
reset: func(t *testing.T) {
150+
deleteEntitlements(t, testLog)
151+
uninstallFlux(g)
152+
},
153+
expectedErrorStr: "cannot process input 'user authentication'",
154+
},
155+
{
156+
name: "should fail without dashboard access",
157+
flags: []string{"--version=0.33.0",
158+
privateKeyArg,
159+
"--private-key-password=\"\"",
160+
"--username=admin",
161+
"--password=admin123"},
162+
setup: func(t *testing.T) {
163+
bootstrapFluxSsh(g)
164+
createEntitlements(t, testLog)
165+
},
166+
reset: func(t *testing.T) {
167+
deleteClusterUser(t, testLog)
168+
deleteEntitlements(t, testLog)
169+
uninstallFlux(g)
170+
},
171+
expectedErrorStr: "cannot process input 'dashboard access'",
172+
},
173+
}
174+
for _, tt := range tests {
175+
lock.Lock()
176+
t.Run(tt.name, func(t *testing.T) {
177+
178+
defer lock.Unlock()
179+
180+
if tt.setup != nil {
181+
tt.setup(t)
182+
}
183+
184+
if tt.reset != nil {
185+
defer tt.reset(t)
186+
}
187+
188+
client := adapters.NewHTTPClient()
189+
cmd := root.Command(client)
190+
bootstrapCmdArgs := []string{"bootstrap"}
191+
bootstrapCmdArgs = append(bootstrapCmdArgs, tt.flags...)
192+
cmd.SetArgs(bootstrapCmdArgs)
193+
194+
err := cmd.Execute()
195+
if tt.expectedErrorStr != "" {
196+
g.Expect(err.Error()).To(ContainSubstring(tt.expectedErrorStr))
197+
return
198+
}
199+
g.Expect(err).To(BeNil())
200+
201+
})
202+
}
203+
}
204+
205+
func bootstrapFluxSsh(g *WithT) {
206+
var runner runner.CLIRunner
207+
208+
repoUrl := os.Getenv("GIT_URL_SSH")
209+
g.Expect(repoUrl).NotTo(BeEmpty())
210+
fmt.Println(repoUrl)
211+
212+
privateKeyFile := os.Getenv("GIT_PRIVATEKEY_PATH")
213+
g.Expect(privateKeyFile).NotTo(BeEmpty())
214+
fmt.Println(privateKeyFile)
215+
216+
args := []string{"bootstrap", "git", "-s", fmt.Sprintf("--url=%s", repoUrl), fmt.Sprintf("--private-key-file=%s", privateKeyFile), "--path=clusters/management"}
217+
fmt.Println(args)
218+
219+
s, err := runner.Run("flux", args...)
220+
fmt.Println(string(s))
221+
g.Expect(err).To(BeNil())
222+
223+
}
224+
225+
func uninstallFlux(g *WithT) {
226+
var runner runner.CLIRunner
227+
args := []string{"uninstall", "-s", "--keep-namespace"}
228+
_, err := runner.Run("flux", args...)
229+
g.Expect(err).To(BeNil())
230+
}
231+
232+
func createEntitlements(t *testing.T, testLog logr.Logger) {
233+
secret := createEntitlementSecretFromEnv(t)
234+
objects := []client.Object{
235+
&secret,
236+
}
237+
createResources(testLog, t, k8sClient, objects...)
238+
}
239+
240+
func deleteEntitlements(t *testing.T, testLog logr.Logger) {
241+
secret := createEntitlementSecretFromEnv(t)
242+
objects := []client.Object{
243+
&secret,
244+
}
245+
deleteResources(testLog, t, k8sClient, objects...)
246+
}
247+
248+
func deleteClusterUser(t *testing.T, testLog logr.Logger) {
249+
secret := corev1.Secret{
250+
TypeMeta: metav1.TypeMeta{
251+
Kind: "Secret",
252+
APIVersion: "v1",
253+
},
254+
Type: corev1.SecretTypeOpaque,
255+
ObjectMeta: metav1.ObjectMeta{
256+
Name: "cluster-user-auth",
257+
Namespace: "flux-system",
258+
},
259+
Data: map[string][]byte{},
260+
}
261+
262+
objects := []client.Object{
263+
&secret,
264+
}
265+
deleteResources(testLog, t, k8sClient, objects...)
266+
}
267+
268+
func createResources(log logr.Logger, t *testing.T, k client.Client, objects ...client.Object) {
269+
ctx := context.Background()
270+
t.Helper()
271+
for _, o := range objects {
272+
err := k.Create(ctx, o)
273+
if err != nil {
274+
t.Errorf("failed to create object: %s", err)
275+
}
276+
log.Info("created object", "name", o.GetName(), "ns", o.GetNamespace(), "kind", o.GetObjectKind().GroupVersionKind().Kind)
277+
}
278+
}
279+
280+
func deleteResources(log logr.Logger, t *testing.T, k client.Client, objects ...client.Object) {
281+
ctx := context.Background()
282+
t.Helper()
283+
for _, o := range objects {
284+
err := k.Delete(ctx, o)
285+
if err != nil {
286+
t.Logf("failed to cleanup object: %s", err)
287+
}
288+
log.Info("deleted object", "name", o.GetName(), "ns", o.GetNamespace(), "kind", o.GetObjectKind().GroupVersionKind().Kind)
289+
290+
}
291+
}

0 commit comments

Comments
 (0)