Files
odo/pkg/odo/cli/init/init.go
Philippe Martin ff03b8e49a Select and pull a devfile using Alizer (#5464)
* Add alizer library and test functionality

<!--
Thank you for opening a PR! Here are some things you need to know before submitting:

1. Please read our developer guideline: https://github.com/redhat-developer/odo/wiki/Developer-Guidelines
2. Label this PR accordingly with the '/kind' line
3. Ensure you have written and ran the appropriate tests: https://github.com/redhat-developer/odo/wiki/Writing-and-running-tests
4. Read how we approve and LGTM each PR: https://github.com/redhat-developer/odo/wiki/PR-Review

Documentation:

If you are pushing a change to documentation, please read: https://github.com/redhat-developer/odo/wiki/Contributing-to-Docs
-->

**What type of PR is this:**

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind tests
/kind documentation

Feel free to use other [labels](https://github.com/redhat-developer/odo/labels) as needed. However one of the above labels must be present or the PR will not be reviewed. This instruction is for reviewers as well.
-->

/kind feature

**What does this PR do / why we need it:**

Adds the alizer library from
https://github.com/redhat-developer/alizer/tree/main/go as part of our
implementaion of `odo dev` and `odo init`.

This builds upon @feloy 's PR located here: https://github.com/redhat-developer/odo/pull/5434

**Which issue(s) this PR fixes:**
<!--
Specifying the issue will automatically close it when this PR is merged
-->

Fixes #

**PR acceptance criteria:**

- [X] Unit test

- [X] Integration test

- [X] Documentation

**How to test changes / Special notes to the reviewer:**

N/A. Only function implementation

* New alizer version

* Use alizer for odo init

* Add integration tests

* Add Alizer to odo deploy

* review

* Ask component name for odo deploy

* Fix unit test

Co-authored-by: Charlie Drage <charlie@charliedrage.com>
2022-02-23 01:52:51 -05:00

208 lines
6.9 KiB
Go

package init
import (
"context"
"errors"
"fmt"
"github.com/spf13/cobra"
"github.com/devfile/library/pkg/devfile"
"github.com/devfile/library/pkg/devfile/parser"
"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/init/backend"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
scontext "github.com/redhat-developer/odo/pkg/segment/context"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/utils/pointer"
)
// RecommendedCommandName is the recommended command name
const RecommendedCommandName = "init"
var initExample = templates.Examples(`
# Boostrap a new component in interactive mode
%[1]s
# Bootstrap a new component with a specific devfile from registry
%[1]s --name my-app --devfile nodejs
# Bootstrap a new component with a specific devfile from a specific registry
%[1]s --name my-app --devfile nodejs --devfile-registry MyRegistry
# Bootstrap a new component with a specific devfile from the local filesystem
%[1]s --name my-app --devfile-path $HOME/devfiles/nodejs/devfile.yaml
# Bootstrap a new component with a specific devfile from the web
%[1]s --name my-app --devfile-path https://devfiles.example.com/nodejs/devfile.yaml
# Bootstrap a new component and download a starter project
%[1]s --name my-app --devfile nodejs --starter nodejs-starter
`)
type InitOptions struct {
// CMD context
ctx context.Context
// Clients
clientset *clientset.Clientset
// Flags passed to the command
flags map[string]string
// devfileLocation is the information needed to pull a devfile
devfileLocation *backend.DevfileLocation
// Destination directory
contextDir string
}
// NewInitOptions creates a new InitOptions instance
func NewInitOptions() *InitOptions {
return &InitOptions{}
}
func (o *InitOptions) SetClientset(clientset *clientset.Clientset) {
o.clientset = clientset
}
// Complete will build the parameters for init, using different backends based on the flags set,
// either by using flags or interactively if no flag is passed
// Complete will return an error immediately if the current working directory is not empty
func (o *InitOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.ctx = cmdline.Context()
o.contextDir, err = o.clientset.FS.Getwd()
if err != nil {
return err
}
o.flags = cmdline.GetFlags()
return nil
}
// Validate validates the InitOptions based on completed values
func (o *InitOptions) Validate() error {
devfilePresent, err := location.DirectoryContainsDevfile(o.clientset.FS, o.contextDir)
if err != nil {
return err
}
if devfilePresent {
return errors.New("a devfile already exists in the current directory")
}
err = o.clientset.InitClient.Validate(o.flags, o.clientset.FS, o.contextDir)
if err != nil {
return err
}
return nil
}
// Run contains the logic for the odo command
func (o *InitOptions) Run() (err error) {
var starterDownloaded bool
defer func() {
if err == nil {
return
}
if starterDownloaded {
err = fmt.Errorf("%w\nthe command failed after downloading the starter project. By security, the directory is not cleaned up", err)
} else {
_ = o.clientset.FS.Remove("devfile.yaml")
err = fmt.Errorf("%w\nthe command failed, the devfile has been removed from current directory", err)
}
}()
o.devfileLocation, err = o.clientset.InitClient.SelectDevfile(o.flags, o.clientset.FS, o.contextDir)
if err != nil {
return err
}
devfilePath, err := o.clientset.InitClient.DownloadDevfile(o.devfileLocation, o.contextDir)
if err != nil {
return fmt.Errorf("unable to download devfile: %w", err)
}
devfileObj, _, err := devfile.ParseDevfileAndValidate(parser.ParserArgs{Path: devfilePath, FlattenedDevfile: pointer.BoolPtr(false)})
if err != nil {
return err
}
scontext.SetComponentType(o.ctx, component.GetComponentTypeFromDevfileMetadata(devfileObj.Data.GetMetadata()))
starterInfo, err := o.clientset.InitClient.SelectStarterProject(devfileObj, o.flags, o.clientset.FS, o.contextDir)
if err != nil {
return err
}
if starterInfo != nil {
// WARNING: this will remove all the content of the destination directory, ie the devfile.yaml file
err = o.clientset.InitClient.DownloadStarterProject(starterInfo, o.contextDir)
if err != nil {
return fmt.Errorf("unable to download starter project %q: %w", starterInfo.Name, err)
}
starterDownloaded = true
// in case the starter project contains a devfile, read it again
if _, err = o.clientset.FS.Stat(devfilePath); err == nil {
devfileObj, _, err = devfile.ParseDevfileAndValidate(parser.ParserArgs{Path: devfilePath, FlattenedDevfile: pointer.BoolPtr(false)})
if err != nil {
return err
}
}
}
// Set the name in the devfile *AND* writes the devfile back to the disk in case
// it has been removed and not replaced by the starter project
err = o.clientset.InitClient.PersonalizeName(devfileObj, o.flags)
if err != nil {
return fmt.Errorf("Failed to update the devfile's name: %w", err)
}
log.Italicf(`
Your new component %q is ready in the current directory.
To start editing your component, use "odo dev" and open this folder in your favorite IDE.
Changes will be directly reflected on the cluster.
To deploy your component to a cluster use "odo deploy".`, devfileObj.Data.GetMetadata().Name)
return nil
}
// NewCmdInit implements the odo command
func NewCmdInit(name, fullName string) *cobra.Command {
o := NewInitOptions()
initCmd := &cobra.Command{
Use: name,
Short: "Init bootstraps a new project",
Long: "Bootstraps a new project",
Example: fmt.Sprintf(initExample, fullName),
Args: cobra.MaximumNArgs(0),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
clientset.Add(initCmd, clientset.PREFERENCE, clientset.FILESYSTEM, clientset.REGISTRY, clientset.INIT)
initCmd.Flags().String(backend.FLAG_NAME, "", "name of the component to create")
initCmd.Flags().String(backend.FLAG_DEVFILE, "", "name of the devfile in devfile registry")
initCmd.Flags().String(backend.FLAG_DEVFILE_REGISTRY, "", "name of the devfile registry (as configured in \"odo registry list\"). It can be used in combination with --devfile, but not with --devfile-path")
initCmd.Flags().String(backend.FLAG_STARTER, "", "name of the starter project. Available starter projects can be found with \"odo catalog describe component <devfile>\"")
initCmd.Flags().String(backend.FLAG_DEVFILE_PATH, "", "path to a devfile. This is an alternative to using devfile from Devfile registry. It can be local filesystem path or http(s) URL")
// Add a defined annotation in order to appear in the help menu
initCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
return initCmd
}