Skip to content

Commit 0eba966

Browse files
committed
prevent flux install from overriding bootrapped cluster
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
1 parent 7949135 commit 0eba966

File tree

4 files changed

+657
-0
lines changed

4 files changed

+657
-0
lines changed

cmd/flux/cluster_info.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2023 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
24+
"k8s.io/apimachinery/pkg/api/errors"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
28+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
29+
sourcev1 "github.com/fluxcd/source-controller/api/v1"
30+
)
31+
32+
// bootstrapLabels are labels put on a resource by kustomize-controller. These labels on the CRD indicates
33+
// that flux has been bootstrapped.
34+
var bootstrapLabels = []string{
35+
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group),
36+
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group),
37+
}
38+
39+
// fluxClusterInfo contains information about an existing flux installation on a cluster.
40+
type fluxClusterInfo struct {
41+
// bootstrapped indicates that Flux was installed using the `flux bootstrap` command.
42+
bootstrapped bool
43+
// managedBy is the name of the tool being used to manage the installation of Flux.
44+
managedBy string
45+
// version is the Flux version number in semver format.
46+
version string
47+
}
48+
49+
// getFluxClusterInfo returns information on the Flux installation running on the cluster.
50+
// If the information cannot be retrieved, the boolean return value will be false.
51+
// If an error occurred, the returned error will be non-nil.
52+
//
53+
// This function retrieves the GitRepository CRD from the cluster and checks it
54+
// for a set of labels used to determine the Flux version and how Flux was installed.
55+
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, bool, error) {
56+
var info fluxClusterInfo
57+
crdMetadata := &metav1.PartialObjectMetadata{
58+
TypeMeta: metav1.TypeMeta{
59+
APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
60+
Kind: "CustomResourceDefinition",
61+
},
62+
ObjectMeta: metav1.ObjectMeta{
63+
Name: fmt.Sprintf("gitrepositories.%s", sourcev1.GroupVersion.Group),
64+
},
65+
}
66+
if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil {
67+
if errors.IsNotFound(err) {
68+
return info, false, nil
69+
}
70+
return info, false, err
71+
}
72+
73+
info.version = crdMetadata.Labels["app.kubernetes.io/version"]
74+
75+
var present bool
76+
for _, l := range bootstrapLabels {
77+
_, present = crdMetadata.Labels[l]
78+
}
79+
if present {
80+
info.bootstrapped = true
81+
}
82+
83+
if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok {
84+
info.managedBy = manager
85+
}
86+
return info, true, nil
87+
}

cmd/flux/cluster_info_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
Copyright 2023 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"testing"
24+
25+
. "github.com/onsi/gomega"
26+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
29+
30+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
31+
"github.com/fluxcd/pkg/ssa"
32+
)
33+
34+
func Test_getFluxClusterInfo(t *testing.T) {
35+
g := NewWithT(t)
36+
f, err := os.Open("./testdata/cluster_info/gitrepositories.yaml")
37+
g.Expect(err).To(BeNil())
38+
39+
objs, err := ssa.ReadObjects(f)
40+
g.Expect(err).To(Not(HaveOccurred()))
41+
gitrepo := objs[0]
42+
43+
tests := []struct {
44+
name string
45+
labels map[string]string
46+
wantErr bool
47+
wantBool bool
48+
wantInfo fluxClusterInfo
49+
}{
50+
{
51+
name: "no git repository CRD present",
52+
wantBool: false,
53+
},
54+
{
55+
name: "CRD with kustomize-controller labels",
56+
labels: map[string]string{
57+
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system",
58+
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
59+
"app.kubernetes.io/version": "v2.1.0",
60+
},
61+
wantBool: true,
62+
wantInfo: fluxClusterInfo{
63+
version: "v2.1.0",
64+
bootstrapped: true,
65+
},
66+
},
67+
{
68+
name: "CRD with kustomize-controller labels and managed-by label",
69+
labels: map[string]string{
70+
fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group): "flux-system",
71+
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
72+
"app.kubernetes.io/version": "v2.1.0",
73+
"app.kubernetes.io/managed-by": "flux",
74+
},
75+
wantBool: true,
76+
wantInfo: fluxClusterInfo{
77+
version: "v2.1.0",
78+
bootstrapped: true,
79+
managedBy: "flux",
80+
},
81+
},
82+
{
83+
name: "CRD with only managed-by label",
84+
labels: map[string]string{
85+
"app.kubernetes.io/version": "v2.1.0",
86+
"app.kubernetes.io/managed-by": "helm",
87+
},
88+
wantBool: true,
89+
wantInfo: fluxClusterInfo{
90+
version: "v2.1.0",
91+
managedBy: "helm",
92+
},
93+
},
94+
{
95+
name: "CRD with no labels",
96+
labels: map[string]string{},
97+
wantBool: true,
98+
},
99+
{
100+
name: "CRD with only version label",
101+
labels: map[string]string{
102+
"app.kubernetes.io/version": "v2.1.0",
103+
},
104+
wantBool: true,
105+
wantInfo: fluxClusterInfo{
106+
version: "v2.1.0",
107+
},
108+
},
109+
}
110+
111+
for _, tt := range tests {
112+
t.Run(tt.name, func(t *testing.T) {
113+
g := NewWithT(t)
114+
newscheme := runtime.NewScheme()
115+
apiextensionsv1.AddToScheme(newscheme)
116+
builder := fake.NewClientBuilder().WithScheme(newscheme)
117+
if tt.labels != nil {
118+
gitrepo.SetLabels(tt.labels)
119+
builder = builder.WithRuntimeObjects(gitrepo)
120+
}
121+
122+
client := builder.Build()
123+
info, present, err := getFluxClusterInfo(context.Background(), client)
124+
if tt.wantErr {
125+
g.Expect(err).To(HaveOccurred())
126+
}
127+
128+
g.Expect(present).To(Equal(tt.wantBool))
129+
g.Expect(info).To(BeEquivalentTo(tt.wantInfo))
130+
})
131+
}
132+
}

cmd/flux/install.go

+14
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
183183
logger.Successf("manifests build completed")
184184
logger.Actionf("installing components in %s namespace", *kubeconfigArgs.Namespace)
185185

186+
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
187+
if err != nil {
188+
return err
189+
}
190+
191+
info, installed, err := getFluxClusterInfo(ctx, kubeClient)
192+
if err != nil {
193+
return fmt.Errorf("cluster info unavailable: %w", err)
194+
}
195+
196+
if installed && info.bootstrapped {
197+
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade", info.version)
198+
}
199+
186200
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
187201
if err != nil {
188202
return fmt.Errorf("install failed: %w", err)

0 commit comments

Comments
 (0)