Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
[comment]: # ( Copyright Contributors to the Open Cluster Management project )**Table of Contents**

- [Contributing guidelines](#contributing-guidelines)
- [Terms](#terms)
- [Certificate of Origin](#certificate-of-origin)
- [Contributing a patch](#contributing-a-patch)
- [Issue and pull request management](#issue-and-pull-request-management)
- [Requirements](#requirements)
- [Develop new commands](#Develop-new-commands)
- [Terms](#terms)
- [Certificate of Origin](#certificate-of-origin)
- [Contributing a patch](#contributing-a-patch)
- [Issue and pull request management](#issue-and-pull-request-management)
- [Requirements](#requirements)
- [Develop new commands](#develop-new-commands)
- [Resources](#resources)
- [Client](#client)
- [Unit tests](#unit-tests)
- [E2E tests](#e2e-tests)

# Contributing guidelines

Expand All @@ -27,6 +31,7 @@ By contributing to this project, you agree to the Developer Certificate of Origi
## Issue and pull request management

Anyone can comment on issues and submit reviews for pull requests. In order to be assigned an issue or pull request, you can leave a `/assign <your Github ID>` comment on the issue or pull request.

# Requirements

- Go 1.17
Expand All @@ -43,18 +48,17 @@ clusteradm <cmd> [subcmd] [flags]
- Each command must support the flag `--dry-run`.
- The command uses [klog V2](https://github.com/kubernetes/klog) as logging package. All messages must be using `klog.V(x)`, in rare exception `klog.Error` and `klog.Warning` can be used.


## Resources

- Some commands needs resources files, in the project uses the `Go 1.17` `go:embed` functionality to store the resources files.
- Each command package contains its own resources in the scenario package. The scenario package contains one go file which provides the `go:embed` `embed.FS` files.
- Each command package contains its own resources in the scenario package. The scenario package contains one go file which provides the `go:embed` `embed.FS` files.

## Client

- The [main](cmd/clusteradm.go) provides a cmdutil.Factory which can be leveraged to get different clients and also the *rest.Config. The factory can be passed to the cobra.Command and then save in the Options.

```Go
kubeClient, err := o.factory.KubernetesClientSet()
kubeClient, err := o.factory.KubernetesClientSet()
```

```Go
Expand Down
1 change: 1 addition & 0 deletions cmd/clusteradm/clusteradm.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func main() {

clusteradmFlags := genericclioptionsclusteradm.NewClusteradmFlags(f)
clusteradmFlags.AddFlags(flags)
clusteradmFlags.SetContext(kubeConfigFlags.Context)

// From this point and forward we get warnings on flags that contain "_" separators

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
k8s.io/component-base v0.23.5
k8s.io/klog/v2 v2.30.0
k8s.io/kubectl v0.23.5
open-cluster-management.io/api v0.7.1-0.20220630123726-101917f3a51a
open-cluster-management.io/api v0.8.0
open-cluster-management.io/cluster-proxy v0.1.2
open-cluster-management.io/managed-serviceaccount v0.2.1-0.20220427065210-de6a7b7b5be8
sigs.k8s.io/apiserver-network-proxy v0.0.30
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1802,10 +1802,8 @@ open-cluster-management.io/addon-framework v0.1.1-0.20211223101009-d6b1a7adae93/
open-cluster-management.io/addon-framework v0.2.1-0.20220317063747-100a0230a883/go.mod h1:8QG1fHwPCEdyRrzY2Jji+qhMmJl+/HzTpuSDVmeG+7s=
open-cluster-management.io/api v0.5.0/go.mod h1:9qiA5h/8kvPQnJEOlAPHVjRO9a1jCmDhGzvgMBvXEaE=
open-cluster-management.io/api v0.5.1-0.20220112073018-2d280a97a052/go.mod h1:0IUTh8J+p4pv1THh1r9oO0luX9Z1FLDEAmvzW09qC0o=
open-cluster-management.io/api v0.7.0 h1:Xt1tRCwt+wrhtCOEQ6g+7sFvIkMjffWnn5PSUSoKJcc=
open-cluster-management.io/api v0.7.0/go.mod h1:Wg7YOcVNxsNDj2G8ViWTD/utCfb9cZc9MpNb4fKlXSs=
open-cluster-management.io/api v0.7.1-0.20220630123726-101917f3a51a h1:FzGON866W5oidZEQwUoBaZZqWfcePIJSDTU5fsKPUVU=
open-cluster-management.io/api v0.7.1-0.20220630123726-101917f3a51a/go.mod h1:+OEARSAl2jIhuLItUcS30UgLA3khmA9ihygLVxzEn+U=
open-cluster-management.io/api v0.8.0 h1:hQLNyvvdx0G0iNxq80RWp93epNsUtsqJdLmGbXiYG5o=
open-cluster-management.io/api v0.8.0/go.mod h1:+OEARSAl2jIhuLItUcS30UgLA3khmA9ihygLVxzEn+U=
open-cluster-management.io/cluster-proxy v0.1.2 h1:0WYhPEZT6Wlt0dyE35GCC7zcqPo2poaALzdmVbQp9Dg=
open-cluster-management.io/cluster-proxy v0.1.2/go.mod h1:f4HVfparQqOJsPrh4fTA4uyWx5KJ7MG2AcuSRypedws=
open-cluster-management.io/managed-serviceaccount v0.2.1-0.20220427065210-de6a7b7b5be8 h1:foP5RkSec9DkUA8sJ5FLna5We8VxJyu0179id5zNXgA=
Expand Down
24 changes: 24 additions & 0 deletions pkg/cmd/init/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"open-cluster-management.io/clusteradm/pkg/cmd/init/preflight"
"open-cluster-management.io/clusteradm/pkg/cmd/init/scenario"
"open-cluster-management.io/clusteradm/pkg/helpers"
"open-cluster-management.io/clusteradm/pkg/helpers/apply"
Expand Down Expand Up @@ -51,6 +53,28 @@ func (o *Options) validate() error {
if o.force {
return nil
}
// preflight check
f := o.ClusteradmFlags.KubectlFactory
kubeClient, _, _, err := helpers.GetClients(f)
if err != nil {
return err
}
if err := preflight.RunChecks(
[]preflight.Checker{
preflight.HubApiServerCheck{
ClusterCtx: o.ClusteradmFlags.Context,
ConfigPath: "", // TODO(@Promacanthus): user custom kubeconfig path from command line arguments.
},
preflight.ClusterInfoCheck{
Namespace: metav1.NamespacePublic,
ResourceName: preflight.BootstrapConfigMap,
ClusterCtx: o.ClusteradmFlags.Context,
ConfigPath: "", // TODO(@Promacanthus): user custom kubeconfig path from command line arguments.
Client: kubeClient,
},
}, os.Stderr); err != nil {
return err
}
restConfig, err := o.ClusteradmFlags.KubectlFactory.ToRESTConfig()
if err != nil {
return err
Expand Down
183 changes: 183 additions & 0 deletions pkg/cmd/init/preflight/checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright Contributors to the Open Cluster Management project
package preflight

import (
"bytes"
"context"
"fmt"
"io"
"net"
"net/url"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

var BootstrapConfigMap = "cluster-info"

type Error struct {
Msg string
}

func (e Error) Error() string {
return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s", e.Msg)
}

func (e *Error) Preflight() bool {
return true
}

// Checker validates the state of the cluster to ensure
// clusteradm will be successfully as often as possible.
type Checker interface {
Check() (warnings, errorList []error)
Name() string
}

type HubApiServerCheck struct {
ClusterCtx string // current-context in kubeconfig
ConfigPath string // kubeconfig file path
}

func (c HubApiServerCheck) Check() (warnings []error, errorList []error) {
cluster, err := loadCurrentCluster(c.ClusterCtx, c.ConfigPath)
if err != nil {
return nil, []error{err}
}
u, err := url.Parse(cluster.Server)
if err != nil {
return nil, []error{err}
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
return nil, []error{err}
}
if net.ParseIP(host) == nil {
return []error{errors.New("Hub Api Server is a domain name, maybe you should set HostAlias in klusterlet")}, nil
}
return nil, nil
}

func (c HubApiServerCheck) Name() string {
return "HubApiServer check"
}

// ClusterInfoCheck checks whether the target kubernetes resource exist in the cluster.
type ClusterInfoCheck struct {
Namespace string
ResourceName string
ClusterCtx string // current-context in kubeconfig
ConfigPath string // kubeconfig file path
Client kubernetes.Interface
}

func (c ClusterInfoCheck) Check() (warnings []error, errorList []error) {
cm, err := c.Client.CoreV1().ConfigMaps(c.Namespace).Get(context.Background(), c.ResourceName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
resourceNotFound := errors.New("no ConfigMap named cluster-info in the kube-public namespace, clusteradm will creates it")
cluster, err := loadCurrentCluster(c.ClusterCtx, c.ConfigPath)
if err != nil {
return []error{resourceNotFound}, []error{err}
}
if err := createClusterInfo(c.Client, cluster); err != nil {
return []error{resourceNotFound}, []error{err}
}
return []error{resourceNotFound}, nil
}
return nil, []error{err}
}
if len(cm.Data["kubeconfig"]) == 0 {
return nil, []error{errors.New("empty kubeconfig data in cluster-info")}
}
return nil, nil
}

func (c ClusterInfoCheck) Name() string {
return "cluster-info check"
}

// loadCurrentCluster will load kubeconfig from file and return the current cluster.
// The default file path is ~/.kube/config.
func loadCurrentCluster(context string, kubeConfigFilePath string) (*api.Cluster, error) {
var (
currentConfig *clientcmdapi.Config
err error
)

if len(kubeConfigFilePath) == 0 {
currentConfig, err = clientcmd.NewDefaultClientConfigLoadingRules().Load()
if err != nil {
return nil, err
}
} else {
currentConfig, err = clientcmd.LoadFromFile(kubeConfigFilePath)
if err != nil {
return nil, errors.Wrapf(err, "failed to load kubeconfig file %s that already exists on disk", kubeConfigFilePath)
}
}
// load kubeconfig from file
// get the hub cluster context
if len(context) == 0 {
// use the current context from the kubeconfig
context = currentConfig.CurrentContext
}
currentCtx, exists := currentConfig.Contexts[context]
if !exists {
return nil, errors.Errorf("failed to find the given Current Context in Contexts of the kubeconfig file %s", kubeConfigFilePath)
}
currentCluster, exists := currentConfig.Clusters[currentCtx.Cluster]
if !exists {
return nil, errors.Errorf("failed to find the given CurrentContext Cluster in Clusters of the kubeconfig file %s", kubeConfigFilePath)
}
return currentCluster, nil
}

// createClusterInfo will create a ConfigMap named cluster-info in the kube-public namespace.
func createClusterInfo(client kubernetes.Interface, cluster *clientcmdapi.Cluster) error {
kubeconfig := &clientcmdapi.Config{Clusters: map[string]*clientcmdapi.Cluster{"": cluster}}
if err := clientcmdapi.FlattenConfig(kubeconfig); err != nil {
return err
}
kubeconfigBytes, err := clientcmd.Write(*kubeconfig)
if err != nil {
return err
}
clusterInfo := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: BootstrapConfigMap,
Namespace: metav1.NamespacePublic,
},
Immutable: BoolPointer(true),
Data: map[string]string{
"kubeconfig": string(kubeconfigBytes),
},
}
return CreateOrUpdateConfigMap(client, clusterInfo)
}

// RunChecks runs each check, display it's warning/errors,
// and once all are processed will exist if any errors occured.
func RunChecks(checks []Checker, ww io.Writer) error {
var errsBuffer bytes.Buffer
for _, check := range checks {
name := check.Name()
warnings, errs := check.Check()
for _, warning := range warnings {
_, _ = io.WriteString(ww, fmt.Sprintf("\t[WARNING %s]: %v\n", name, warning))
}
for _, err := range errs {
_, _ = errsBuffer.WriteString(fmt.Sprintf("\t[ERROR %s]: %v\n", name, err.Error()))
}
}
if errsBuffer.Len() > 0 {
return &Error{Msg: errsBuffer.String()}
}
return nil
}
Loading