Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding support for public and private git providers #160

Merged
merged 17 commits into from
May 2, 2023
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
62 changes: 53 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@ The Devfile Parser library is a Golang module that:
2. writes to the devfile.yaml with the updated data.
3. generates Kubernetes objects for the various devfile resources.
4. defines util functions for the devfile.
5. downloads resources from a parent devfile if specified in the devfile.yaml

## Private repository support

Tokens are required to be set in the following cases:
1. parsing a devfile from a private repository
2. parsing a devfile containing a parent devfile from a private repository [1]
3. parsing a devfile from a private repository containing a parent devfile from a public repository [2]

Set the token for the repository:
```go
parser.ParserArgs{
...
kim-tsao marked this conversation as resolved.
Show resolved Hide resolved
// URL must point to a devfile.yaml
URL: <url-to-devfile-on-supported-git-provider-repo>/devfile.yaml
Token: <repo-personal-access-token>
...
}
```
Note: The url must also be set with a supported git provider repo url.

Minimum token scope required:
1. GitHub: Read access to code
2. GitLab: Read repository
3. Bitbucket: Read repository

Note: To select token scopes for GitHub, a fine-grained token is required.

For more information about personal access tokens:
1. [GitHub docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
2. [GitLab docs](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token)
3. [Bitbucket docs](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens/)

[1] Currently, this works under the assumption that the token can authenticate the devfile and the parent devfile; both devfiles are in the same repository.

[2] In this scenario, the token will be used to authenticate the main devfile.

mike-hoang marked this conversation as resolved.
Show resolved Hide resolved
## Usage

Expand All @@ -35,7 +71,6 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
devfile, variableWarning, err := devfilePkg.ParseDevfileAndValidate(parserArgs)
```


2. To override the HTTP request and response timeouts for a devfile with a parent reference from a registry URL, specify the HTTPTimeout value in the parser arguments
```go
// specify the timeout in seconds
Expand All @@ -45,7 +80,6 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
}
```


3. To get specific content from devfile
```go
// To get all the components from the devfile
Expand Down Expand Up @@ -77,7 +111,7 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
},
})
```

4. To get the Kubernetes objects from the devfile, visit [generators.go source file](pkg/devfile/generator/generators.go)
```go
// To get a slice of Kubernetes containers of type corev1.Container from the devfile component containers
Expand All @@ -94,7 +128,7 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
}
deployment := generator.GetDeployment(deployParams)
```

5. To update devfile content
```go
// To update an existing component in devfile object
Expand Down Expand Up @@ -131,20 +165,19 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
```go
// If the devfile object has been created with devfile path already set, can simply call WriteYamlDevfile to writes the devfile
err := devfile.WriteYamlDevfile()



// To write to a devfile from scratch
// create a new DevfileData with a specific devfile version
devfileData, err := data.NewDevfileData(devfileVersion)

// set schema version
devfileData.SetSchemaVersion(devfileVersion)

// add devfile content use library APIs
devfileData.AddComponents([]v1.Component{...})
devfileData.AddCommands([]v1.Commands{...})
......

// create a new DevfileCtx
ctx := devfileCtx.NewDevfileCtx(devfilePath)
err = ctx.SetAbsPath()
Expand All @@ -154,10 +187,11 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
Ctx: ctx,
Data: devfileData,
}

// write to the devfile on disk
err = devfile.WriteYamlDevfile()
```

7. To parse the outerloop Kubernetes/OpenShift component's uri or inline content, call the read and parse functions
```go
// Read the YAML content
Expand All @@ -166,6 +200,7 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
// Get the Kubernetes resources
resources, err := ParseKubernetesYaml(values)
```

8. By default, the parser will set all unset boolean properties to their spec defined default values. Clients can override this behaviour by specifiying the parser argument `SetBooleanDefaults` to false
```go
setDefaults := false
Expand All @@ -174,6 +209,15 @@ The function documentation can be accessed via [pkg.go.dev](https://pkg.go.dev/g
}
```

9. When parsing a devfile that contains a parent reference, if the parent uri is a supported git provider repo url with the correct personal access token, all resources from the parent git repo excluding the parent devfile.yaml will be downloaded to the location of the devfile being parsed. **Note: The URL must point to a devfile.yaml**
```yaml
schemaVersion: 2.2.0
...
parent:
uri: <uri-to-parent-devfile>/devfile.yaml
...
```

## Projects using devfile/library

The following projects are consuming this library as a Golang dependency
Expand Down
5 changes: 4 additions & 1 deletion pkg/devfile/parser/context/content.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright 2022-2023 Red Hat, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -69,6 +69,9 @@
if d.url != "" {
// set the client identifier for telemetry
params := util.HTTPRequestParams{URL: d.url, TelemetryClientName: util.TelemetryClientName}
if d.token != "" {
params.Token = d.token
}

Check warning on line 74 in pkg/devfile/parser/context/content.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/context/content.go#L73-L74

Added lines #L73 - L74 were not covered by tests
data, err = util.DownloadInMemory(params)
if err != nil {
return errors.Wrap(err, "error getting devfile info from url")
Expand Down
19 changes: 16 additions & 3 deletions pkg/devfile/parser/context/context.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright 2022-2023 Red Hat, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,13 +45,16 @@ type DevfileCtx struct {
// devfile json schema
jsonSchema string

//url path of the devfile
// url path of the devfile
url string

// token is a personal access token used with a private git repo URL
token string

// filesystem for devfile
fs filesystem.Filesystem

// devfile kubernetes components has been coverted from uri to inlined in memory
// devfile kubernetes components has been converted from uri to inlined in memory
convertUriToInlined bool
}

Expand Down Expand Up @@ -150,6 +153,16 @@ func (d *DevfileCtx) GetURL() string {
return d.url
}

// GetToken func returns current devfile token
func (d *DevfileCtx) GetToken() string {
return d.token
}

// SetToken sets the token for the devfile
func (d *DevfileCtx) SetToken(token string) {
d.token = token
}

// SetAbsPath sets absolute file path for devfile
func (d *DevfileCtx) SetAbsPath() (err error) {
// Set devfile absolute path
Expand Down
16 changes: 15 additions & 1 deletion pkg/devfile/parser/context/context_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright 2022-2023 Red Hat, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -83,6 +83,20 @@ func TestPopulateFromInvalidURL(t *testing.T) {
})
}

func TestNewURLDevfileCtx(t *testing.T) {
var (
token = "fake-token"
url = "https://github.com/devfile/registry/blob/main/stacks/go/2.0.0/devfile.yaml"
)
{
d := NewURLDevfileCtx(url)
assert.Equal(t, "https://github.com/devfile/registry/blob/main/stacks/go/2.0.0/devfile.yaml", d.GetURL())
assert.Equal(t, "", d.GetToken())
d.SetToken(token)
assert.Equal(t, "fake-token", d.GetToken())
}
}

func invalidJsonRawContent200() []byte {
return []byte(InvalidDevfileContent)
}
102 changes: 71 additions & 31 deletions pkg/devfile/parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"context"
"encoding/json"
"fmt"
"github.com/devfile/library/v2/pkg/git"
"github.com/hashicorp/go-multierror"
"io/ioutil"
"net/url"
"os"
Expand Down Expand Up @@ -46,6 +48,57 @@
"github.com/pkg/errors"
)

// downloadGitRepoResources is exposed as a global variable for the purpose of running mock tests
var downloadGitRepoResources = func(url string, destDir string, httpTimeout *int, token string) error {
var returnedErr error

gitUrl, err := git.NewGitUrlWithURL(url)
if err != nil {
return err
}

Check warning on line 58 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L52-L58

Added lines #L52 - L58 were not covered by tests

if gitUrl.IsGitProviderRepo() {
if !gitUrl.IsFile || gitUrl.Revision == "" || !strings.Contains(gitUrl.Path, OutputDevfileYamlPath) {
return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url)
}

Check warning on line 63 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L60-L63

Added lines #L60 - L63 were not covered by tests

stackDir, err := os.MkdirTemp("", fmt.Sprintf("git-resources"))
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}

Check warning on line 68 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L65-L68

Added lines #L65 - L68 were not covered by tests

defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
}

Check warning on line 74 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L70-L74

Added lines #L70 - L74 were not covered by tests
}(stackDir)

if !gitUrl.IsPublic(httpTimeout) {
err = gitUrl.SetToken(token, httpTimeout)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}

Check warning on line 82 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L77-L82

Added lines #L77 - L82 were not covered by tests
}

err = gitUrl.CloneGitRepo(stackDir)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}

Check warning on line 89 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L85-L89

Added lines #L85 - L89 were not covered by tests

dir := path.Dir(path.Join(stackDir, gitUrl.Path))
err = git.CopyAllDirFiles(dir, destDir)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}

Check warning on line 96 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L91-L96

Added lines #L91 - L96 were not covered by tests
}

return nil

Check warning on line 99 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L99

Added line #L99 was not covered by tests
}

// ParseDevfile func validates the devfile integrity.
// Creates devfile context and runtime objects
func parseDevfile(d DevfileObj, resolveCtx *resolutionContextTree, tool resolverTools, flattenedDevfile bool) (DevfileObj, error) {
Expand Down Expand Up @@ -97,6 +150,8 @@
// RegistryURLs is a list of registry hosts which parser should pull parent devfile from.
// If registryUrl is defined in devfile, this list will be ignored.
RegistryURLs []string
// Token is a GitHub, GitLab, or Bitbucket personal access token used with a private git repo URL
Token string
// DefaultNamespace is the default namespace to use
// If namespace is defined under devfile's parent kubernetes object, this namespace will be ignored.
DefaultNamespace string
Expand Down Expand Up @@ -129,6 +184,10 @@
return d, errors.Wrap(err, "the devfile source is not provided")
}

if args.Token != "" {
d.Ctx.SetToken(args.Token)
}

Check warning on line 189 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L188-L189

Added lines #L188 - L189 were not covered by tests

tool := resolverTools{
defaultNamespace: args.DefaultNamespace,
registryURLs: args.RegistryURLs,
Expand Down Expand Up @@ -431,17 +490,16 @@
return DevfileObj{}, fmt.Errorf("failed to resolve parent uri, devfile context is missing absolute url and path to devfile. %s", resolveImportReference(importReference))
}

token := curDevfileCtx.GetToken()
d.Ctx = devfileCtx.NewURLDevfileCtx(newUri)
mike-hoang marked this conversation as resolved.
Show resolved Hide resolved
if strings.Contains(newUri, "raw.githubusercontent.com") {
urlComponents, err := util.GetGitUrlComponentsFromRaw(newUri)
if err != nil {
return DevfileObj{}, err
}
destDir := path.Dir(curDevfileCtx.GetAbsPath())
err = getResourcesFromGit(urlComponents, destDir)
if err != nil {
return DevfileObj{}, err
}
if token != "" {
d.Ctx.SetToken(token)
}

Check warning on line 497 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L496-L497

Added lines #L496 - L497 were not covered by tests

destDir := path.Dir(curDevfileCtx.GetAbsPath())
err = downloadGitRepoResources(newUri, destDir, tool.httpTimeout, token)
if err != nil {
return DevfileObj{}, err
}
}
importReference.Uri = newUri
Expand All @@ -450,27 +508,6 @@
return populateAndParseDevfile(d, newResolveCtx, tool, true)
}

func getResourcesFromGit(gitUrlComponents map[string]string, destDir string) error {
stackDir, err := ioutil.TempDir(os.TempDir(), fmt.Sprintf("git-resources"))
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
defer os.RemoveAll(stackDir)

err = util.CloneGitRepo(gitUrlComponents, stackDir)
if err != nil {
return err
}

dir := path.Dir(path.Join(stackDir, gitUrlComponents["file"]))
err = util.CopyAllDirFiles(dir, destDir)
if err != nil {
return err
}

return nil
}

func parseFromRegistry(importReference v1.ImportReference, resolveCtx *resolutionContextTree, tool resolverTools) (d DevfileObj, err error) {
id := importReference.Id
registryURL := importReference.RegistryUrl
Expand Down Expand Up @@ -839,6 +876,9 @@
newUri = uri
}
params := util.HTTPRequestParams{URL: newUri}
if d.GetToken() != "" {
params.Token = d.GetToken()
}

Check warning on line 881 in pkg/devfile/parser/parse.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/parse.go#L880-L881

Added lines #L880 - L881 were not covered by tests
data, err = util.DownloadInMemory(params)
if err != nil {
return nil, errors.Wrapf(err, "error getting kubernetes resources definition information")
Expand Down
Loading
Loading