mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Make odo work if optional metadata.name field is missing in Devfile (#6015)
* Move 'starter_project.go' from 'pkg/component' to 'pkg/registry'
Functions in this file are only called from the 'registry' package,
so it makes sense for this file to belong to this package.
Furthermore, this paves the way to calling 'alizer.DetectName' from 'component.go',
thus avoiding a cyclic dependency between packages.
* Introduce central logic for determining component names in 'pkg/component/component.go'
This rewrites the 'component#GatherName' function that was already there,
but not used, to meet the expectations, i.e.:
- use 'metadata.name' field (after sanitizing it) if it is defined in the Devfile
- otherwise, use Alizer to detect the name. Under the hood, this leverages the 'alizer#DetectName' introduced in 83ad3ee, which means that:
-- use Alizer to detect the name automatically
-- otherwise, use the name of the Devfile base directory after sanitizing it
* Compute and store the component name in the CLI context, and pass it as needed
As commented out in [1], the context should ideally be built
and passed down to the business clients structs.
[1] https://github.com/redhat-developer/odo/pull/6015#discussion_r957005479
* Enrich relevant integration test cases
For the sake of both performance and readability,
only the tests that break in the absence of a 'metadata.name'
field in their Devfiles have been updated (to test this specific case).
* Add test case for 'odo dev' when a project with no source code is used with no 'metadata.name' in the Devfile
The rationale behind this is to purposely make
the Alizer library unable to detect the project.
Per the requirements, this would force us to use the project
directory name as component name.
This highlights an interesting behavior if the project
directory name is all-numeric (as is the case in our tests);
our sanitization logic automatically prepends an "x" prefix
to the directory name, so it can be used as a valid name
for the component.
This commit is contained in:
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/devfile/registry-support/registry-library/library"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"github.com/redhat-developer/odo/pkg/component"
|
||||
"github.com/redhat-developer/odo/pkg/devfile"
|
||||
"github.com/redhat-developer/odo/pkg/devfile/location"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
@@ -51,7 +50,7 @@ func (o RegistryClient) DownloadFileInMemory(params dfutil.HTTPRequestParams) ([
|
||||
// DownloadStarterProject downloads a starter project referenced in devfile
|
||||
// This will first remove the content of the contextDir
|
||||
func (o RegistryClient) DownloadStarterProject(starterProject *devfilev1.StarterProject, decryptedToken string, contextDir string, verbose bool) error {
|
||||
return component.DownloadStarterProject(starterProject, decryptedToken, contextDir, verbose)
|
||||
return DownloadStarterProject(starterProject, decryptedToken, contextDir, verbose)
|
||||
}
|
||||
|
||||
// GetDevfileRegistries gets devfile registries from preference file,
|
||||
|
||||
188
pkg/registry/starter_project.go
Normal file
188
pkg/registry/starter_project.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/devfile/location"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
"github.com/redhat-developer/odo/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
RegistryUser = "default"
|
||||
)
|
||||
|
||||
func checkoutProject(subDir, zipURL, path, starterToken string) error {
|
||||
|
||||
if subDir == "" {
|
||||
subDir = "/"
|
||||
}
|
||||
err := util.GetAndExtractZip(zipURL, path, subDir, starterToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download and extract project zip folder: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadStarterProject downloads a starter project referenced in devfile
|
||||
// This will first remove the content of the contextDir
|
||||
func DownloadStarterProject(starterProject *devfilev1.StarterProject, decryptedToken string, contextDir string, verbose bool) error {
|
||||
var path string
|
||||
var err error
|
||||
// Retrieve the working directory in order to clone correctly
|
||||
if contextDir == "" {
|
||||
path, err = os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get the current working directory: %w", err)
|
||||
}
|
||||
} else {
|
||||
path = contextDir
|
||||
}
|
||||
|
||||
// We will check to see if the project has a valid directory
|
||||
err = util.IsValidProjectDir(path, location.DevfileLocation(""))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if verbose {
|
||||
log.Info("\nStarter Project")
|
||||
}
|
||||
|
||||
if starterProject.Git != nil {
|
||||
err := downloadGitProject(starterProject, decryptedToken, path, verbose)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if starterProject.Zip != nil {
|
||||
url := starterProject.Zip.Location
|
||||
sparseDir := starterProject.SubDir
|
||||
var downloadSpinner *log.Status
|
||||
if verbose {
|
||||
downloadSpinner = log.Spinnerf("Downloading starter project %s from %s", starterProject.Name, url)
|
||||
}
|
||||
err := checkoutProject(sparseDir, url, path, decryptedToken)
|
||||
if err != nil {
|
||||
if verbose {
|
||||
downloadSpinner.End(false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if verbose {
|
||||
downloadSpinner.End(true)
|
||||
}
|
||||
} else {
|
||||
return errors.New("project type not supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadGitProject downloads the git starter projects from devfile.yaml
|
||||
func downloadGitProject(starterProject *devfilev1.StarterProject, starterToken, path string, verbose bool) error {
|
||||
remoteName, remoteUrl, revision, err := parsercommon.GetDefaultSource(starterProject.Git.GitLikeProjectSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get default project source for starter project %s: %w", starterProject.Name, err)
|
||||
}
|
||||
|
||||
// convert revision to referenceName type, ref name could be a branch or tag
|
||||
// if revision is not specified it would be the default branch of the project
|
||||
refName := plumbing.ReferenceName(revision)
|
||||
|
||||
if plumbing.IsHash(revision) {
|
||||
// Specifying commit in the reference name is not supported by the go-git library
|
||||
// while doing git.PlainClone()
|
||||
log.Warning("Specifying commit in 'revision' is not yet supported in odo.")
|
||||
// overriding revision to empty as we do not support this
|
||||
revision = ""
|
||||
}
|
||||
|
||||
if revision != "" {
|
||||
// lets consider revision to be a branch name first
|
||||
refName = plumbing.NewBranchReferenceName(revision)
|
||||
}
|
||||
|
||||
var downloadSpinner *log.Status
|
||||
if verbose {
|
||||
downloadSpinner = log.Spinnerf("Downloading starter project %s from %s", starterProject.Name, remoteUrl)
|
||||
defer downloadSpinner.End(false)
|
||||
}
|
||||
|
||||
cloneOptions := &git.CloneOptions{
|
||||
URL: remoteUrl,
|
||||
RemoteName: remoteName,
|
||||
ReferenceName: refName,
|
||||
SingleBranch: true,
|
||||
// we don't need history for starter projects
|
||||
Depth: 1,
|
||||
}
|
||||
|
||||
if starterToken != "" {
|
||||
cloneOptions.Auth = &http.BasicAuth{
|
||||
Username: RegistryUser,
|
||||
Password: starterToken,
|
||||
}
|
||||
}
|
||||
|
||||
originalPath := ""
|
||||
if starterProject.SubDir != "" {
|
||||
originalPath = path
|
||||
path, err = ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = git.PlainClone(path, false, cloneOptions)
|
||||
|
||||
if err != nil {
|
||||
|
||||
// it returns the following error if no matching ref found
|
||||
// if we get this error, we are trying again considering revision as tag, only if revision is specified.
|
||||
if _, ok := err.(git.NoMatchingRefSpecError); !ok || revision == "" {
|
||||
return err
|
||||
}
|
||||
|
||||
// try again to consider revision as tag name
|
||||
cloneOptions.ReferenceName = plumbing.NewTagReferenceName(revision)
|
||||
// remove if any .git folder downloaded in above try
|
||||
_ = os.RemoveAll(filepath.Join(path, ".git"))
|
||||
_, err = git.PlainClone(path, false, cloneOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// we don't want to download project be a git repo
|
||||
err = os.RemoveAll(filepath.Join(path, ".git"))
|
||||
if err != nil {
|
||||
// we don't need to return (fail) if this happens
|
||||
log.Warning("Unable to delete .git from cloned starter project")
|
||||
}
|
||||
|
||||
if starterProject.SubDir != "" {
|
||||
err = util.GitSubDir(path, originalPath,
|
||||
starterProject.SubDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
downloadSpinner.End(true)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user