diff --git a/api/types/template_types.go b/api/types/template_types.go index 2bf137626..166d00c2c 100644 --- a/api/types/template_types.go +++ b/api/types/template_types.go @@ -44,7 +44,19 @@ type Template struct { AppliesTo []string `json:"appliesTo,omitempty"` // Plugin Source - Source *Source `json:"source,omitempty"` + Source *Source `json:"source,omitempty"` + Install *Installation `json:"install,omitempty"` +} + +type Chart struct { + Repo string `json:"repo"` + URl string `json:"url"` + Name string `json:"name"` + Version string `json:"version"` +} + +type Installation struct { + Helm []Chart `json:"helm"` } type DefinitionType string diff --git a/cmd/vela/main.go b/cmd/vela/main.go index 884e3a4bb..10bc9c766 100644 --- a/cmd/vela/main.go +++ b/cmd/vela/main.go @@ -94,19 +94,15 @@ func newCommand() *cobra.Command { Schema: scheme, } - if err := system.InitApplicationDir(); err != nil { - fmt.Println("InitApplicationDir err", err) - os.Exit(1) - } - if err := system.InitDefinitionDir(); err != nil { - fmt.Println("InitDefinitionDir err", err) + if err := system.InitDirs(); err != nil { + fmt.Println("InitDir err", err) os.Exit(1) } // Getting Start cmd.EnvCommandGroup(cmds, commandArgs, ioStream) // Others - cmd.AddonCommandGroup(cmds, ioStream) + cmd.AddonCommandGroup(cmds, commandArgs, ioStream) // System cmd.SystemCommandGroup(cmds, commandArgs, ioStream) diff --git a/config/samples/traits/route.yaml b/config/samples/traits/route.yaml new file mode 100644 index 000000000..85908f066 --- /dev/null +++ b/config/samples/traits/route.yaml @@ -0,0 +1,41 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: ingresses.networking.k8s.io + annotations: + "oam.appengine.info/apiVersion": "networking.k8s.io/v1beta1" + "oam.appengine.info/kind": "Ingress" +spec: + revisionEnabled: true + appliesToWorkloads: + - core.oam.dev/v1alpha2.ContainerizedWorkload + - deployments.apps + definitionRef: + name: ingresses.networking.k8s.io + extension: + install: + helm: + - repo: stable + name: nginx-ingress + version: 1.41.2 + template: | + #Template: { + apiVersion: "networking.k8s.io/v1beta1" + kind: "Ingress" + spec: { + rules: [{ + host: route.domain + http: paths: [{ + backend: { + serviceName: route.service + servicePort: route.port + }}] + }] + } + } + route: { + domain: string + port: *80 | int + service: string + } + diff --git a/pkg/cmd/addon.go b/pkg/cmd/addon.go index ce2ac004a..0d88637f6 100644 --- a/pkg/cmd/addon.go +++ b/pkg/cmd/addon.go @@ -6,8 +6,14 @@ import ( "fmt" "io/ioutil" "path/filepath" + "strings" + + "github.com/ghodss/yaml" + + "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" "github.com/gosuri/uitable" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/cloud-native-application/rudrx/pkg/utils/system" @@ -19,11 +25,12 @@ import ( "github.com/spf13/cobra" ) -func AddonCommandGroup(parentCmd *cobra.Command, ioStream cmdutil.IOStreams) { +func AddonCommandGroup(parentCmd *cobra.Command, c types.Args, ioStream cmdutil.IOStreams) { parentCmd.AddCommand( NewAddonConfigCommand(ioStream), NewAddonListCommand(ioStream), NewAddonUpdateCommand(ioStream), + NewAddonInstallCommand(c, ioStream), ) } @@ -72,6 +79,37 @@ func NewAddonConfigCommand(ioStreams cmdutil.IOStreams) *cobra.Command { return cmd } +func NewAddonInstallCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "addon:install /", + Short: "Install addon plugin into cluster", + Long: "Install addon plugin into cluster", + Example: `vela addon:install myhub/route`, + RunE: func(cmd *cobra.Command, args []string) error { + argsLength := len(args) + if argsLength < 1 { + return errors.New("you must specify / for addon plugin you want to install") + } + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + ss := strings.Split(args[0], "/") + if len(ss) < 2 { + return errors.New("invalid format for " + args[0] + ", please follow format /") + } + repoName := ss[0] + name := ss[1] + return InstallAddonPlugin(newClient, repoName, name, ioStreams) + }, + Annotations: map[string]string{ + types.TagCommandType: types.TypeOthers, + }, + } + cmd.PersistentFlags().StringP("token", "t", "", "Github Repo token") + return cmd +} + func NewAddonUpdateCommand(ioStreams cmdutil.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "addon:update ", @@ -87,6 +125,9 @@ func NewAddonUpdateCommand(ioStreams cmdutil.IOStreams) *cobra.Command { if len(args) > 0 { specified = args[0] } + if len(repos) == 0 { + return fmt.Errorf("no addon repo configured") + } find := false if specified != "" { for idx, r := range repos { @@ -135,12 +176,17 @@ func NewAddonListCommand(ioStreams cmdutil.IOStreams) *cobra.Command { table := uitable.New() table.AddRow("NAME", "TYPE", "DEFINITION", "STATUS", "APPLIES-TO") if repoName != "" { - return ListRepoAddons(table, filepath.Join(dir, repoName), ioStreams) + if err = ListRepoAddons(table, filepath.Join(dir, repoName), ioStreams); err != nil { + return err + } + ioStreams.Info(table.String()) + return nil } dirs, err := ioutil.ReadDir(dir) if err != nil { return err } + for _, dd := range dirs { if !dd.IsDir() { continue @@ -149,6 +195,7 @@ func NewAddonListCommand(ioStreams cmdutil.IOStreams) *cobra.Command { return err } } + ioStreams.Info(table.String()) return nil }, Annotations: map[string]string{ @@ -158,6 +205,76 @@ func NewAddonListCommand(ioStreams cmdutil.IOStreams) *cobra.Command { return cmd } +func InstallAddonPlugin(client client.Client, repoName, addonName string, ioStreams cmdutil.IOStreams) error { + dir, _ := system.GetRepoDir() + repoDir := filepath.Join(dir, repoName) + tp, err := GetTemplate(repoName, addonName) + if err != nil { + return err + } + switch tp.Type { + case types.TypeWorkload: + var wd v1alpha2.WorkloadDefinition + workloadData, err := ioutil.ReadFile(filepath.Join(repoDir, tp.CrdName+".yaml")) + if err != nil { + return nil + } + if err = yaml.Unmarshal(workloadData, &wd); err != nil { + return err + } + wd.Namespace = types.DefaultOAMNS + ioStreams.Info("Installing workload plugin " + wd.Name) + if tp.Install != nil { + if err = InstallHelmChart(ioStreams, tp.Install.Helm); err != nil { + return err + } + } + return client.Create(context.Background(), &wd) + case types.TypeTrait: + var td v1alpha2.TraitDefinition + traitdata, err := ioutil.ReadFile(filepath.Join(repoDir, tp.CrdName+".yaml")) + if err != nil { + return nil + } + if err = yaml.Unmarshal(traitdata, &td); err != nil { + return err + } + td.Namespace = types.DefaultOAMNS + ioStreams.Info("Installing trait plugin " + td.Name) + if tp.Install != nil { + if err = InstallHelmChart(ioStreams, tp.Install.Helm); err != nil { + return err + } + } + return client.Create(context.Background(), &td) + } + return nil +} + +func InstallHelmChart(ioStreams cmdutil.IOStreams, charts []types.Chart) error { + for _, c := range charts { + if err := HelmInstall(ioStreams, c.Repo, c.URl, c.Repo+"/"+c.Name, c.Version, c.Name); err != nil { + return err + } + } + return nil +} + +func GetTemplate(repoName, addonName string) (types.Template, error) { + dir, _ := system.GetRepoDir() + repoDir := filepath.Join(dir, repoName) + templates, err := plugins.LoadPluginsFromLocal(repoDir) + if err != nil { + return types.Template{}, err + } + for _, t := range templates { + if t.Name == addonName { + return t, nil + } + } + return types.Template{}, fmt.Errorf("%s/%s not exist, try vela addon:update %s to sync from remote", repoName, addonName, repoName) +} + func ListRepoAddons(table *uitable.Table, repoDir string, ioStreams cmdutil.IOStreams) error { templates, err := plugins.LoadPluginsFromLocal(repoDir) if err != nil { @@ -171,15 +288,21 @@ func ListRepoAddons(table *uitable.Table, repoDir string, ioStreams cmdutil.IOSt status := CheckInstalled(baseDir, p) table.AddRow(baseDir+"/"+p.Name, p.Type, p.Type, status, p.AppliesTo) } - ioStreams.Info(table.String()) return nil } func CheckInstalled(repoName string, tmp types.Template) string { var status = "uninstalled" dir, _ := system.GetDefinitionDir() + switch tmp.Type { + case types.TypeTrait: + dir = filepath.Join(dir, "traits") + case types.TypeWorkload: + dir = filepath.Join(dir, "workloads") + } installed, _ := plugins.LoadTempFromLocal(dir) for _, i := range installed { + //TODO handle source on install if i.Source != nil && i.Source.RepoName == repoName && i.Name == tmp.Name && i.CrdName == tmp.CrdName { return "installed" } diff --git a/pkg/cmd/system.go b/pkg/cmd/system.go index 61149ca25..b499d11ea 100644 --- a/pkg/cmd/system.go +++ b/pkg/cmd/system.go @@ -185,35 +185,35 @@ func (i *initCmd) IsOamRuntimeExist() bool { } func InstallOamRuntime(ioStreams cmdutil.IOStreams, version string) error { + return HelmInstall(ioStreams, types.DefaultOAMRepoName, types.DefaultOAMRepoUrl, types.DefaultOAMChartName, version, types.DefaultOAMReleaseName) +} - if !IsHelmRepositoryExist(types.DefaultOAMRepoName, types.DefaultOAMRepoUrl) { - err := AddHelmRepository(types.DefaultOAMRepoName, types.DefaultOAMRepoUrl, +func HelmInstall(ioStreams cmdutil.IOStreams, repoName, repoUrl, chartName, version, releaseName string) error { + if !IsHelmRepositoryExist(repoName, repoUrl) { + err := AddHelmRepository(repoName, repoUrl, "", "", "", "", "", false, ioStreams.Out) if err != nil { return err } } - chartClient, err := NewHelmInstall(version, ioStreams) + chartClient, err := NewHelmInstall(version, releaseName, ioStreams) if err != nil { return err } - - chartRequested, err := GetChart(chartClient, types.DefaultOAMChartName) + chartRequested, err := GetChart(chartClient, chartName) if err != nil { return err } - release, err := chartClient.Run(chartRequested, nil) if err != nil { return err } - - fmt.Println("Successfully installed oam-kubernetes-runtime release: ", release.Name) + ioStreams.Infof("Successfully installed %s as release name %s\n", chartName, release.Name) return nil } -func NewHelmInstall(version string, ioStreams cmdutil.IOStreams) (*action.Install, error) { +func NewHelmInstall(version, releaseName string, ioStreams cmdutil.IOStreams) (*action.Install, error) { actionConfig := new(action.Configuration) if err := actionConfig.Init( @@ -227,12 +227,8 @@ func NewHelmInstall(version string, ioStreams cmdutil.IOStreams) (*action.Instal client := action.NewInstall(actionConfig) client.Namespace = types.DefaultOAMNS - client.ReleaseName = types.DefaultOAMReleaseName - if len(version) > 0 { - client.Version = version - return client, nil - } - client.Version = types.DefaultOAMVersion + client.ReleaseName = releaseName + client.Version = version return client, nil } diff --git a/pkg/cue/testdata/traits/route.cue b/pkg/cue/testdata/traits/route.cue index 0e40bd5bc..44678366c 100644 --- a/pkg/cue/testdata/traits/route.cue +++ b/pkg/cue/testdata/traits/route.cue @@ -1,11 +1,19 @@ #Template: { - apiVersion: "apps/v1" - kind: "Route" + apiVersion: "networking.k8s.io/v1beta1" + kind: "Ingress" spec: { - domain: route.domain + rules: [{ + host: route.domain + http: paths: [{ + backend: { + serviceName: route.service + servicePort: route.port + }}] + }] } } - route: { - domain: string + domain: string + port: *80 | int + service: string } diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index ed27739af..281c815ce 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "path/filepath" + "github.com/cloud-native-application/rudrx/pkg/utils/system" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -91,6 +93,7 @@ func HandleTemplate(in *runtime.RawExtension, name, syncDir string) (types.Templ if tmp.Template == "" { return types.Template{}, errors.New("template not exist in definition") } + system.StatAndCreate(syncDir) filePath := filepath.Join(syncDir, name+".cue") err = ioutil.WriteFile(filePath, []byte(tmp.Template), 0644) if err != nil { diff --git a/pkg/plugins/local.go b/pkg/plugins/local.go index b6fcd5541..e28be6e47 100644 --- a/pkg/plugins/local.go +++ b/pkg/plugins/local.go @@ -66,7 +66,7 @@ func LoadPluginsFromLocal(dir string) ([]types.Template, error) { fmt.Printf("read file %s err %v\n", f.Name(), err) continue } - tmp, err := GetDefinitionFromURL(data, filepath.Join(dir, ".tmp")) + tmp, err := ParseAndSyncDefinition(data, filepath.Join(dir, ".tmp")) if err != nil { fmt.Printf("get definition of %s err %v\n", f.Name(), err) continue diff --git a/pkg/plugins/repository.go b/pkg/plugins/repository.go index 9acd6fbba..e64d3d9ff 100644 --- a/pkg/plugins/repository.go +++ b/pkg/plugins/repository.go @@ -159,7 +159,7 @@ func StoreRepos(repos []RepoConfig) error { return ioutil.WriteFile(config, data, 0644) } -func GetDefinitionFromURL(data []byte, syncDir string) (types.Template, error) { +func ParseAndSyncDefinition(data []byte, syncDir string) (types.Template, error) { var obj = unstructured.Unstructured{Object: make(map[string]interface{})} err := yaml.Unmarshal(data, &obj.Object) if err != nil { @@ -235,9 +235,14 @@ func (g *GithubAddon) SyncRemoteAddons() error { return fmt.Errorf("decode github content %s err %v", *fileContent.Path, err) } } - err = ioutil.WriteFile(filepath.Join(repoDir, *fileContent.Name), data, 0644) + tmp, err := ParseAndSyncDefinition(data, filepath.Join(dir, ".tmp")) if err != nil { - fmt.Printf("write definition %s to %s err %v\n", *fileContent.Name, repoDir, err) + fmt.Printf("parse definition of %s err %v\n", *fileContent.Name, err) + continue + } + err = ioutil.WriteFile(filepath.Join(repoDir, tmp.CrdName+".yaml"), data, 0644) + if err != nil { + fmt.Printf("write definition %s to %s err %v\n", tmp.CrdName+".yaml", repoDir, err) continue } success++ diff --git a/pkg/utils/system/system.go b/pkg/utils/system/system.go index 4d10b1707..ecc135e86 100644 --- a/pkg/utils/system/system.go +++ b/pkg/utils/system/system.go @@ -28,7 +28,7 @@ func GetRepoDir() (string, error) { if err != nil { return "", err } - return filepath.Join(home, ".repo"), nil + return filepath.Join(home, "repositories"), nil } func GetRepoConfig() (string, error) { @@ -36,7 +36,6 @@ func GetRepoConfig() (string, error) { if err != nil { return "", err } - StatAndCreate(home) return filepath.Join(home, "config.yaml"), nil } @@ -72,12 +71,33 @@ func GetCurrentEnvPath() (string, error) { return filepath.Join(homedir, "curenv"), nil } +func InitDirs() error { + if err := InitDefinitionDir(); err != nil { + return err + } + if err := InitApplicationDir(); err != nil { + return err + } + if err := InitRepositoryDir(); err != nil { + return err + } + return nil +} + +func InitRepositoryDir() error { + home, err := GetRepoDir() + if err != nil { + return err + } + return StatAndCreate(filepath.Join(home, ".tmp")) +} + func InitDefinitionDir() error { dir, err := GetDefinitionDir() if err != nil { return err } - return os.MkdirAll(dir, 0755) + return StatAndCreate(dir) } func InitApplicationDir() error { @@ -85,7 +105,7 @@ func InitApplicationDir() error { if err != nil { return err } - return os.MkdirAll(dir, 0755) + return StatAndCreate(dir) } func InitDefaultEnv() error { @@ -108,8 +128,9 @@ func InitDefaultEnv() error { return nil } -func StatAndCreate(dir string) { +func StatAndCreate(dir string) error { if _, err := os.Stat(dir); os.IsNotExist(err) { - os.MkdirAll(dir, 0755) + return os.MkdirAll(dir, 0755) } + return nil }