Skip to content

Commit

Permalink
Add create addon cmd (#370)
Browse files Browse the repository at this point in the history
* Add craete addon cmd

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Add addon e2e test

Signed-off-by: Jian Qiu <jqiu@redhat.com>

---------

Signed-off-by: Jian Qiu <jqiu@redhat.com>
  • Loading branch information
qiujian16 authored Sep 19, 2023
1 parent 7caa618 commit a8e7827
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 5 deletions.
2 changes: 2 additions & 0 deletions pkg/cmd/addon/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package addon
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"open-cluster-management.io/clusteradm/pkg/cmd/addon/create"
"open-cluster-management.io/clusteradm/pkg/cmd/addon/disable"
"open-cluster-management.io/clusteradm/pkg/cmd/addon/enable"
genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
Expand All @@ -19,6 +20,7 @@ func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, stream

cmd.AddCommand(enable.NewCmd(clusteradmFlags, streams))
cmd.AddCommand(disable.NewCmd(clusteradmFlags, streams))
cmd.AddCommand(create.NewCmd(clusteradmFlags, streams))

return cmd
}
57 changes: 57 additions & 0 deletions pkg/cmd/addon/create/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright Contributors to the Open Cluster Management project
package create

import (
"fmt"

genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
clusteradmhelpers "open-cluster-management.io/clusteradm/pkg/helpers"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

var example = `
Create an addon from manifests by using AddonTemplate
%[1]s addon create helloworld -f deployment.yaml
`

// NewCmd creates a cammand to create an addon
func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, streams genericclioptions.IOStreams) *cobra.Command {
o := NewOptions(clusteradmFlags, streams)

cmd := &cobra.Command{
Use: "create",
Short: "create a specified addon",
Long: "create a specific add-on(s) on the hub",
Example: fmt.Sprintf(example, clusteradmhelpers.GetExampleHeader()),
SilenceUsage: true,
PreRunE: func(c *cobra.Command, args []string) error {
clusteradmhelpers.DryRunMessage(clusteradmFlags.DryRun)

return nil
},
RunE: func(c *cobra.Command, args []string) error {
if err := o.complete(c, args); err != nil {
return err
}
if err := o.Validate(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
}

return nil
},
}

cmd.Flags().StringVar(&o.Version, "version", "0.0.1", "Specified version of the addon")
cmd.Flags().BoolVar(&o.Overwrite, "overwrite", false, "Overwrite the existing addon if it exists already")
cmd.Flags().BoolVar(&o.EnableHubRegistration, "hub-registration", false, "Enable the agent to register to the hub cluster")
cmd.Flags().StringVar(&o.ClusterRoleBindingRef, "cluster-role-bind", "", "The rolebinding to the clusterrole in "+
"the cluster namespace for the addon agent")
o.FileNameFlags.AddFlags(cmd.Flags())

return cmd
}
229 changes: 229 additions & 0 deletions pkg/cmd/addon/create/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Copyright Contributors to the Open Cluster Management project
package create

import (
"context"
"fmt"

"github.com/spf13/cobra"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource"
addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
addonclientset "open-cluster-management.io/api/client/addon/clientset/versioned"
workapiv1 "open-cluster-management.io/api/work/v1"
)

func newAddonTemplate(o *Options) (*addonv1alpha1.AddOnTemplate, error) {
manifests, err := o.readManifests()
if err != nil {
return nil, err
}
addon := &addonv1alpha1.AddOnTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: o.templateName(),
},
Spec: addonv1alpha1.AddOnTemplateSpec{
AddonName: o.Name,
AgentSpec: workapiv1.ManifestWorkSpec{
Workload: workapiv1.ManifestsTemplate{
Manifests: manifests,
},
},
Registration: []addonv1alpha1.RegistrationSpec{},
},
}

if o.EnableHubRegistration {
addon.Spec.Registration = []addonv1alpha1.RegistrationSpec{
{
Type: addonv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonv1alpha1.HubPermissionConfig{},
},
},
}

if o.ClusterRoleBindingRef != "" {
addon.Spec.Registration[0].KubeClient.HubPermissions = append(addon.Spec.Registration[0].KubeClient.HubPermissions,
addonv1alpha1.HubPermissionConfig{
Type: addonv1alpha1.HubPermissionsBindingCurrentCluster,
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: o.ClusterRoleBindingRef,
},
})
}
}

return addon, nil
}

func newClusterManagementAddon(o *Options) *addonv1alpha1.ClusterManagementAddOn {
cma := &addonv1alpha1.ClusterManagementAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
Annotations: map[string]string{
"addon.open-cluster-management.io/lifecycle": "addon-manager",
},
},
Spec: addonv1alpha1.ClusterManagementAddOnSpec{
SupportedConfigs: []addonv1alpha1.ConfigMeta{
{
ConfigGroupResource: addonv1alpha1.ConfigGroupResource{
Group: addonv1alpha1.GroupVersion.Group,
Resource: "addontemplates",
},
DefaultConfig: &addonv1alpha1.ConfigReferent{
Name: o.templateName(),
},
},
},
InstallStrategy: addonv1alpha1.InstallStrategy{
Type: addonv1alpha1.AddonInstallStrategyManual,
},
},
}

return cma
}

func (o *Options) complete(cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 {
return fmt.Errorf("addon name must be specified")
}

if len(args) > 1 {
return fmt.Errorf("only one adon name can be specified")
}

o.Name = args[0]

return nil
}

func (o *Options) Validate() (err error) {
err = o.ClusteradmFlags.ValidateHub()
if err != nil {
return err
}

if len(o.Version) == 0 {
return fmt.Errorf("addon version must be specified")
}

if len(*o.FileNameFlags.Filenames) == 0 {
return fmt.Errorf("manifest files must be specified")
}

return nil
}

func (o *Options) Run() error {
restConfig, err := o.ClusteradmFlags.KubectlFactory.ToRESTConfig()
if err != nil {
return err
}

addonClient, err := addonclientset.NewForConfig(restConfig)
if err != nil {
return err
}

if err := o.applyCMA(addonClient); err != nil {
return err
}

return o.applyTemplate(addonClient)
}

func (o *Options) templateName() string {
return o.Name + "-" + o.Version
}

func (o *Options) applyCMA(addonClient addonclientset.Interface) error {
cma := newClusterManagementAddon(o)

// apply cma at first
originalCMA, err := addonClient.AddonV1alpha1().ClusterManagementAddOns().Get(context.TODO(), o.Name, metav1.GetOptions{})
if errors.IsNotFound(err) {
_, err := addonClient.AddonV1alpha1().ClusterManagementAddOns().Create(context.TODO(), cma, metav1.CreateOptions{})
fmt.Fprintf(o.Streams.Out, "ClusterManagementAddon %s is created\n", o.Name)
return err
}
if err != nil {
return err
}

if !o.Overwrite {
fmt.Fprintf(o.Streams.Out, "ClusterManagementAddon %s is not updated when overwrite is disabled\n", o.Name)
return nil
}

cma.ResourceVersion = originalCMA.ResourceVersion
if _, err = addonClient.AddonV1alpha1().ClusterManagementAddOns().Update(context.TODO(), cma, metav1.UpdateOptions{}); err != nil {
return err
}

fmt.Fprintf(o.Streams.Out, "ClusterManagementAddon %s is updated\n", o.Name)
return nil
}

func (o *Options) applyTemplate(addonClient addonclientset.Interface) error {
addon, err := newAddonTemplate(o)
if err != nil {
return err
}

originalAddon, err := addonClient.AddonV1alpha1().AddOnTemplates().Get(context.TODO(), o.templateName(), metav1.GetOptions{})
if errors.IsNotFound(err) {
_, err := addonClient.AddonV1alpha1().AddOnTemplates().Create(context.TODO(), addon, metav1.CreateOptions{})
fmt.Fprintf(o.Streams.Out, "AddonTemplate %s is created\n", addon.Name)
return err
}
if err != nil {
return err
}

if !o.Overwrite {
fmt.Fprintf(o.Streams.Out, "AddonTemplate %s is not updated when overwrite is disabled\n", addon.Name)
return nil
}

addon.ResourceVersion = originalAddon.ResourceVersion
if _, err = addonClient.AddonV1alpha1().AddOnTemplates().Update(context.TODO(), addon, metav1.UpdateOptions{}); err != nil {
return err
}

fmt.Fprintf(o.Streams.Out, "AddonTemplate %s is updated\n", addon.Name)
return nil
}

func (o *Options) readManifests() ([]workapiv1.Manifest, error) {
opt := o.FileNameFlags.ToOptions()
builder := resource.NewLocalBuilder().
Unstructured().
FilenameParam(false, &opt).
Flatten().
ContinueOnError()
result := builder.Do()

if err := result.Err(); err != nil {
return nil, err
}

manifests := []workapiv1.Manifest{}

items, err := result.Infos()
if err != nil {
return nil, err
}
for _, item := range items {
manifests = append(manifests, workapiv1.Manifest{RawExtension: runtime.RawExtension{Object: item.Object}})
}

return manifests, nil
}
40 changes: 40 additions & 0 deletions pkg/cmd/addon/create/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright Contributors to the Open Cluster Management project
package create

import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/utils/pointer"
genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
)

type Options struct {
//ClusteradmFlags: The generic options from the clusteradm cli-runtime.
ClusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags
//Name is the addon name
Name string

// version is the version of the addon
Version string

Overwrite bool

EnableHubRegistration bool

// registration only supports clusterRoleBinding with cluster namespace
ClusterRoleBindingRef string

FileNameFlags genericclioptions.FileNameFlags
//
Streams genericclioptions.IOStreams
}

func NewOptions(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, streams genericclioptions.IOStreams) *Options {
return &Options{
ClusteradmFlags: clusteradmFlags,
Streams: streams,
FileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{},
Recursive: pointer.Bool(true),
},
}
}
7 changes: 2 additions & 5 deletions pkg/cmd/create/work/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package work

import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/utils/pointer"
genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions"
)

Expand Down Expand Up @@ -32,11 +33,7 @@ func newOptions(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, st
ClusterOption: genericclioptionsclusteradm.NewClusterOption().AllowUnset(),
FileNameFlags: genericclioptions.FileNameFlags{
Filenames: &[]string{},
Recursive: boolPtr(true),
Recursive: pointer.Bool(true),
},
}
}

func boolPtr(val bool) *bool {
return &val
}
Loading

0 comments on commit a8e7827

Please sign in to comment.