Skip to content

Commit cd1cf47

Browse files
committed
Implement Init plugin subcommand
1 parent 262d394 commit cd1cf47

File tree

8 files changed

+5609
-21
lines changed

8 files changed

+5609
-21
lines changed

cmd/plugin/cmd/init.go

Lines changed: 413 additions & 20 deletions
Large diffs are not rendered by default.

cmd/plugin/cmd/init_test.go

Lines changed: 530 additions & 0 deletions
Large diffs are not rendered by default.

cmd/plugin/cmd/root.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,26 @@ import (
2222
"os"
2323
"strings"
2424

25+
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
2528
"github.com/MakeNowJust/heredoc"
2629
goerrors "github.com/go-errors/errors"
30+
"github.com/go-logr/logr"
2731
"github.com/spf13/cobra"
2832
)
2933

3034
const (
3135
groupDebug = "group-debug"
3236
groupManagement = "group-management"
3337
groupOther = "group-other"
38+
latestVersion = "latest"
3439
)
3540

3641
var verbosity *int
3742

43+
var log logr.Logger
44+
3845
// RootCmd is capioperator root CLI command.
3946
var RootCmd = &cobra.Command{
4047
Use: "capioperator",
@@ -67,6 +74,10 @@ func init() {
6774

6875
verbosity = flag.CommandLine.Int("v", 0, "Set the log level verbosity. This overrides the CAPIOPERATOR_LOG_LEVEL environment variable.")
6976

77+
log = logf.NewLogger(logf.WithThreshold(verbosity))
78+
logf.SetLogger(log)
79+
ctrl.SetLogger(log)
80+
7081
RootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
7182

7283
RootCmd.AddGroup(

cmd/plugin/cmd/suite_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2023 The Kubernetes 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 cmd
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"testing"
23+
"time"
24+
25+
"sigs.k8s.io/cluster-api-operator/internal/envtest"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
)
28+
29+
const (
30+
waitShort = time.Second * 5
31+
waitLong = time.Second * 20
32+
)
33+
34+
var (
35+
env *envtest.Environment
36+
ctx = ctrl.SetupSignalHandler()
37+
)
38+
39+
func TestMain(m *testing.M) {
40+
fmt.Println("Creating new test environment")
41+
42+
env = envtest.New()
43+
44+
go func() {
45+
if err := env.Start(ctx); err != nil {
46+
panic(fmt.Sprintf("Failed to start the envtest manager: %v", err))
47+
}
48+
}()
49+
<-env.Manager.Elected()
50+
51+
// Run tests
52+
code := m.Run()
53+
// Tearing down the test environment
54+
if err := env.Stop(); err != nil {
55+
panic(fmt.Sprintf("Failed to stop the envtest: %v", err))
56+
}
57+
58+
// Report exit code
59+
os.Exit(code)
60+
}

cmd/plugin/cmd/utils.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,32 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20+
"context"
21+
"errors"
2022
"fmt"
23+
"os"
2124

25+
appsv1 "k8s.io/api/apps/v1"
26+
corev1 "k8s.io/api/core/v1"
27+
apierrors "k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2229
"k8s.io/apimachinery/pkg/runtime"
2330
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2431
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2532
"k8s.io/client-go/tools/clientcmd"
33+
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
2634
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
2735

2836
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
2937
)
3038

39+
var capiOperatorLabels = map[string]string{
40+
"clusterctl.cluster.x-k8s.io/core": "capi-operator",
41+
"control-plane": "controller-manager",
42+
}
43+
44+
var ErrNotFound = fmt.Errorf("resource was not found")
45+
3146
// CreateKubeClient creates a kubernetes client from provided kubeconfig and kubecontext.
3247
func CreateKubeClient(kubeconfigPath, kubeconfigContext string) (ctrlclient.Client, error) {
3348
// Use specified kubeconfig path and context
@@ -51,3 +66,98 @@ func CreateKubeClient(kubeconfigPath, kubeconfigContext string) (ctrlclient.Clie
5166

5267
return client, nil
5368
}
69+
70+
func EnsureNamespaceExists(ctx context.Context, client ctrlclient.Client, namespace string) error {
71+
// Check if the namespace exists
72+
ns := &corev1.Namespace{}
73+
74+
err := client.Get(ctx, ctrlclient.ObjectKey{Name: namespace}, ns)
75+
if err == nil {
76+
return nil
77+
}
78+
79+
if !apierrors.IsNotFound(err) {
80+
return fmt.Errorf("unexpected error during namespace checking: %w", err)
81+
}
82+
83+
// Create the namespace if it doesn't exist
84+
newNamespace := &corev1.Namespace{
85+
ObjectMeta: metav1.ObjectMeta{
86+
Name: namespace,
87+
},
88+
}
89+
90+
if err := client.Create(ctx, newNamespace); err != nil {
91+
return fmt.Errorf("unable to create namespace %s: %w", namespace, err)
92+
}
93+
94+
return nil
95+
}
96+
97+
// GetDeploymentByLabels fetches deployment based on the provided labels.
98+
func GetDeploymentByLabels(ctx context.Context, client ctrlclient.Client, labels map[string]string) (*appsv1.Deployment, error) {
99+
var deploymentList appsv1.DeploymentList
100+
101+
// Search deployments with desired labels in all namespaces.
102+
if err := client.List(ctx, &deploymentList, ctrlclient.MatchingLabels(labels)); err != nil {
103+
return nil, fmt.Errorf("cannot get a list of deployments from the server: %w", err)
104+
}
105+
106+
if len(deploymentList.Items) > 1 {
107+
return nil, fmt.Errorf("more than one deployment found for given labels %v", labels)
108+
}
109+
110+
if len(deploymentList.Items) == 0 {
111+
return nil, ErrNotFound
112+
}
113+
114+
return &deploymentList.Items[0], nil
115+
}
116+
117+
// CheckDeploymentAvailability checks if the deployment with given labels is available.
118+
func CheckDeploymentAvailability(ctx context.Context, client ctrlclient.Client, labels map[string]string) (bool, error) {
119+
deployment, err := GetDeploymentByLabels(ctx, client, labels)
120+
if err != nil {
121+
if errors.Is(err, ErrNotFound) {
122+
return false, nil
123+
}
124+
125+
return false, err
126+
}
127+
128+
for _, cond := range deployment.Status.Conditions {
129+
if cond.Type == appsv1.DeploymentAvailable && cond.Status == corev1.ConditionTrue {
130+
return true, nil
131+
}
132+
}
133+
134+
return false, nil
135+
}
136+
137+
// GetKubeconfigLocation will read the environment variable $KUBECONFIG otherwise set it to ~/.kube/config.
138+
func GetKubeconfigLocation() string {
139+
if kubeconfig := os.Getenv("KUBECONFIG"); kubeconfig != "" {
140+
return kubeconfig
141+
}
142+
143+
return clientcmd.RecommendedHomeFile
144+
}
145+
146+
func NewGenericProvider(providerType clusterctlv1.ProviderType) operatorv1.GenericProvider {
147+
switch providerType {
148+
case clusterctlv1.CoreProviderType:
149+
return &operatorv1.CoreProvider{}
150+
case clusterctlv1.BootstrapProviderType:
151+
return &operatorv1.BootstrapProvider{}
152+
case clusterctlv1.ControlPlaneProviderType:
153+
return &operatorv1.ControlPlaneProvider{}
154+
case clusterctlv1.InfrastructureProviderType:
155+
return &operatorv1.InfrastructureProvider{}
156+
case clusterctlv1.AddonProviderType:
157+
return &operatorv1.AddonProvider{}
158+
case clusterctlv1.IPAMProviderType, clusterctlv1.RuntimeExtensionProviderType, clusterctlv1.ProviderTypeUnknown:
159+
panic(fmt.Sprintf("unsupported provider type %s", providerType))
160+
default:
161+
panic(fmt.Sprintf("unknown provider type %s", providerType))
162+
}
163+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/MakeNowJust/heredoc v1.0.0
99
github.com/evanphx/json-patch/v5 v5.7.0
1010
github.com/go-errors/errors v1.5.1
11+
github.com/go-logr/logr v1.3.0
1112
github.com/google/go-cmp v0.6.0
1213
github.com/google/go-github/v52 v52.0.0
1314
github.com/google/gofuzz v1.2.0
@@ -51,7 +52,6 @@ require (
5152
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
5253
github.com/felixge/httpsnoop v1.0.4 // indirect
5354
github.com/fsnotify/fsnotify v1.6.0 // indirect
54-
github.com/go-logr/logr v1.3.0 // indirect
5555
github.com/go-logr/stdr v1.2.2 // indirect
5656
github.com/go-openapi/jsonpointer v0.19.6 // indirect
5757
github.com/go-openapi/jsonreference v0.20.2 // indirect

internal/envtest/environment.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ func New(uncachedObjs ...client.Object) *Environment {
103103
root := path.Join(path.Dir(filename), "..", "..")
104104
crdPaths := []string{
105105
filepath.Join(root, "config", "crd", "bases"),
106+
// cert-manager CRDs are stored there.
107+
filepath.Join(root, "test", "testdata"),
106108
}
107109

108110
if capiPath := getFilePathToClusterctlCRDs(root); capiPath != "" {

0 commit comments

Comments
 (0)