Skip to content

Commit

Permalink
Merge pull request #334 from Fedosin/plugin_init
Browse files Browse the repository at this point in the history
✨ Implement Init plugin subcommand
  • Loading branch information
k8s-ci-robot committed Jan 24, 2024
2 parents 4ac4c28 + cd1cf47 commit e3f4480
Show file tree
Hide file tree
Showing 13 changed files with 5,668 additions and 67 deletions.
433 changes: 413 additions & 20 deletions cmd/plugin/cmd/init.go

Large diffs are not rendered by default.

530 changes: 530 additions & 0 deletions cmd/plugin/cmd/init_test.go

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions cmd/plugin/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,26 @@ import (
"os"
"strings"

logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/MakeNowJust/heredoc"
goerrors "github.com/go-errors/errors"
"github.com/go-logr/logr"
"github.com/spf13/cobra"
)

const (
groupDebug = "group-debug"
groupManagement = "group-management"
groupOther = "group-other"
latestVersion = "latest"
)

var verbosity *int

var log logr.Logger

// RootCmd is capioperator root CLI command.
var RootCmd = &cobra.Command{
Use: "capioperator",
Expand Down Expand Up @@ -67,6 +74,10 @@ func init() {

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

log = logf.NewLogger(logf.WithThreshold(verbosity))
logf.SetLogger(log)
ctrl.SetLogger(log)

RootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)

RootCmd.AddGroup(
Expand Down
60 changes: 60 additions & 0 deletions cmd/plugin/cmd/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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 (
"fmt"
"os"
"testing"
"time"

"sigs.k8s.io/cluster-api-operator/internal/envtest"
ctrl "sigs.k8s.io/controller-runtime"
)

const (
waitShort = time.Second * 5
waitLong = time.Second * 20
)

var (
env *envtest.Environment
ctx = ctrl.SetupSignalHandler()
)

func TestMain(m *testing.M) {
fmt.Println("Creating new test environment")

env = envtest.New()

go func() {
if err := env.Start(ctx); err != nil {
panic(fmt.Sprintf("Failed to start the envtest manager: %v", err))
}
}()
<-env.Manager.Elected()

// Run tests
code := m.Run()
// Tearing down the test environment
if err := env.Stop(); err != nil {
panic(fmt.Sprintf("Failed to stop the envtest: %v", err))
}

// Report exit code
os.Exit(code)
}
110 changes: 110 additions & 0 deletions cmd/plugin/cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,32 @@ limitations under the License.
package cmd

import (
"context"
"errors"
"fmt"
"os"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
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/tools/clientcmd"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"

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

var capiOperatorLabels = map[string]string{
"clusterctl.cluster.x-k8s.io/core": "capi-operator",
"control-plane": "controller-manager",
}

var ErrNotFound = fmt.Errorf("resource was not found")

// CreateKubeClient creates a kubernetes client from provided kubeconfig and kubecontext.
func CreateKubeClient(kubeconfigPath, kubeconfigContext string) (ctrlclient.Client, error) {
// Use specified kubeconfig path and context
Expand All @@ -51,3 +66,98 @@ func CreateKubeClient(kubeconfigPath, kubeconfigContext string) (ctrlclient.Clie

return client, nil
}

func EnsureNamespaceExists(ctx context.Context, client ctrlclient.Client, namespace string) error {
// Check if the namespace exists
ns := &corev1.Namespace{}

err := client.Get(ctx, ctrlclient.ObjectKey{Name: namespace}, ns)
if err == nil {
return nil
}

if !apierrors.IsNotFound(err) {
return fmt.Errorf("unexpected error during namespace checking: %w", err)
}

// Create the namespace if it doesn't exist
newNamespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}

if err := client.Create(ctx, newNamespace); err != nil {
return fmt.Errorf("unable to create namespace %s: %w", namespace, err)
}

return nil
}

// GetDeploymentByLabels fetches deployment based on the provided labels.
func GetDeploymentByLabels(ctx context.Context, client ctrlclient.Client, labels map[string]string) (*appsv1.Deployment, error) {
var deploymentList appsv1.DeploymentList

// Search deployments with desired labels in all namespaces.
if err := client.List(ctx, &deploymentList, ctrlclient.MatchingLabels(labels)); err != nil {
return nil, fmt.Errorf("cannot get a list of deployments from the server: %w", err)
}

if len(deploymentList.Items) > 1 {
return nil, fmt.Errorf("more than one deployment found for given labels %v", labels)
}

if len(deploymentList.Items) == 0 {
return nil, ErrNotFound
}

return &deploymentList.Items[0], nil
}

// CheckDeploymentAvailability checks if the deployment with given labels is available.
func CheckDeploymentAvailability(ctx context.Context, client ctrlclient.Client, labels map[string]string) (bool, error) {
deployment, err := GetDeploymentByLabels(ctx, client, labels)
if err != nil {
if errors.Is(err, ErrNotFound) {
return false, nil
}

return false, err
}

for _, cond := range deployment.Status.Conditions {
if cond.Type == appsv1.DeploymentAvailable && cond.Status == corev1.ConditionTrue {
return true, nil
}
}

return false, nil
}

// GetKubeconfigLocation will read the environment variable $KUBECONFIG otherwise set it to ~/.kube/config.
func GetKubeconfigLocation() string {
if kubeconfig := os.Getenv("KUBECONFIG"); kubeconfig != "" {
return kubeconfig
}

return clientcmd.RecommendedHomeFile
}

func NewGenericProvider(providerType clusterctlv1.ProviderType) operatorv1.GenericProvider {
switch providerType {
case clusterctlv1.CoreProviderType:
return &operatorv1.CoreProvider{}
case clusterctlv1.BootstrapProviderType:
return &operatorv1.BootstrapProvider{}
case clusterctlv1.ControlPlaneProviderType:
return &operatorv1.ControlPlaneProvider{}
case clusterctlv1.InfrastructureProviderType:
return &operatorv1.InfrastructureProvider{}
case clusterctlv1.AddonProviderType:
return &operatorv1.AddonProvider{}
case clusterctlv1.IPAMProviderType, clusterctlv1.RuntimeExtensionProviderType, clusterctlv1.ProviderTypeUnknown:
panic(fmt.Sprintf("unsupported provider type %s", providerType))
default:
panic(fmt.Sprintf("unknown provider type %s", providerType))
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/MakeNowJust/heredoc v1.0.0
github.com/evanphx/json-patch/v5 v5.7.0
github.com/go-errors/errors v1.5.1
github.com/go-logr/logr v1.3.0
github.com/google/go-cmp v0.6.0
github.com/google/go-github/v52 v52.0.0
github.com/google/gofuzz v1.2.0
Expand Down Expand Up @@ -51,7 +52,6 @@ require (
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
Expand Down
7 changes: 2 additions & 5 deletions internal/controller/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ const (
// if some preflight check has failed.
preflightFailedRequeueAfter = 30 * time.Second

httpsScheme = "https"
githubDomain = "github.com"
gitlabHostPrefix = "gitlab."
gitlabPackagesAPIPrefix = "/api/v4/projects/"
configPath = "/config/clusterctl.yaml"
// configPath is the path to the clusterctl config file.
configPath = "/config/clusterctl.yaml"
)
7 changes: 5 additions & 2 deletions internal/controller/manifests_downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"

ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
"sigs.k8s.io/cluster-api-operator/util"
)

const (
Expand Down Expand Up @@ -76,7 +79,7 @@ func (p *phaseReconciler) downloadManifests(ctx context.Context) (reconcile.Resu

log.Info("Downloading provider manifests")

repo, err := repositoryFactory(ctx, p.providerConfig, p.configClient.Variables())
repo, err := util.RepositoryFactory(ctx, p.providerConfig, p.configClient.Variables())
if err != nil {
err = fmt.Errorf("failed to create repo from provider url for provider %q: %w", p.provider.GetName(), err)

Expand Down
38 changes: 0 additions & 38 deletions internal/controller/phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ import (
"context"
"fmt"
"io"
"net/url"
"os"
"strings"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -554,42 +552,6 @@ func (p *phaseReconciler) newClusterClient() cluster.Client {
}))
}

// repositoryFactory returns the repository implementation corresponding to the provider URL.
// inspired by https://github.com/kubernetes-sigs/cluster-api/blob/124d9be7035e492f027cdc7a701b6b179451190a/cmd/clusterctl/client/repository/client.go#L170
func repositoryFactory(ctx context.Context, providerConfig configclient.Provider, configVariablesClient configclient.VariablesClient) (repository.Repository, error) {
// parse the repository url
rURL, err := url.Parse(providerConfig.URL())
if err != nil {
return nil, fmt.Errorf("failed to parse repository url %q", providerConfig.URL())
}

if rURL.Scheme != httpsScheme {
return nil, fmt.Errorf("invalid provider url. there are no provider implementation for %q schema", rURL.Scheme)
}

// if the url is a GitHub repository
if rURL.Host == githubDomain {
repo, err := repository.NewGitHubRepository(ctx, providerConfig, configVariablesClient)
if err != nil {
return nil, fmt.Errorf("error creating the GitHub repository client: %w", err)
}

return repo, err
}

// if the url is a GitLab repository
if strings.HasPrefix(rURL.Host, gitlabHostPrefix) && strings.HasPrefix(rURL.Path, gitlabPackagesAPIPrefix) {
repo, err := repository.NewGitLabRepository(providerConfig, configVariablesClient)
if err != nil {
return nil, fmt.Errorf("error creating the GitLab repository client: %w", err)
}

return repo, err
}

return nil, fmt.Errorf("invalid provider url. Only GitHub and GitLab are supported for %q schema", rURL.Scheme)
}

func getLatestVersion(repoVersions []string) (string, error) {
if len(repoVersions) == 0 {
err := fmt.Errorf("no versions available")
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/phases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"

operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
"sigs.k8s.io/cluster-api-operator/util"
)

func TestSecretReader(t *testing.T) {
Expand Down Expand Up @@ -475,7 +476,7 @@ func TestRepositoryFactory(t *testing.T) {
providerConfig, err := configClient.Providers().Get(providerName, providerType)
g.Expect(err).ToNot(HaveOccurred())

repo, err := repositoryFactory(ctx, providerConfig, configClient.Variables())
repo, err := util.RepositoryFactory(ctx, providerConfig, configClient.Variables())
if tc.expectedError {
g.Expect(err).To(HaveOccurred())

Expand Down
2 changes: 2 additions & 0 deletions internal/envtest/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ func New(uncachedObjs ...client.Object) *Environment {
root := path.Join(path.Dir(filename), "..", "..")
crdPaths := []string{
filepath.Join(root, "config", "crd", "bases"),
// cert-manager CRDs are stored there.
filepath.Join(root, "test", "testdata"),
}

if capiPath := getFilePathToClusterctlCRDs(root); capiPath != "" {
Expand Down
Loading

0 comments on commit e3f4480

Please sign in to comment.