Skip to content

Commit

Permalink
Hub resolver: add version constraints
Browse files Browse the repository at this point in the history
This let the user specify a version constraint to choose from.

Version constraint looks like this:

```yaml
version: ">= 0.5.0"
```

This will only choose the tasks that are greater than 0.5.0

Additional constraint operators are available, for example:

```yaml
version: ">= 0.5.0, < 2.0.0"
```

This will only choose the tasks that are greater than 0.5.0 and less
than 2.0.0

Signed-off-by: Chmouel Boudjnah <[email protected]>
  • Loading branch information
chmouel authored and tekton-robot committed Oct 24, 2023
1 parent b958eb5 commit f0c7ed5
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 17 deletions.
14 changes: 5 additions & 9 deletions cmd/resolvers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import (

func main() {
ctx := filteredinformerfactory.WithSelectors(signals.NewContext(), v1alpha1.ManagedByLabelKey)
tektonHubURL := buildHubURL(os.Getenv("TEKTON_HUB_API"), "", hub.TektonHubYamlEndpoint)
artifactHubURL := buildHubURL(os.Getenv("ARTIFACT_HUB_API"), hub.DefaultArtifactHubURL, hub.ArtifactHubYamlEndpoint)
tektonHubURL := buildHubURL(os.Getenv("TEKTON_HUB_API"), "")
artifactHubURL := buildHubURL(os.Getenv("ARTIFACT_HUB_API"), hub.DefaultArtifactHubURL)

sharedmain.MainWithContext(ctx, "controller",
framework.NewController(ctx, &git.Resolver{}),
Expand All @@ -43,16 +43,12 @@ func main() {
framework.NewController(ctx, &cluster.Resolver{}))
}

func buildHubURL(configAPI, defaultURL, yamlEndpoint string) string {
func buildHubURL(configAPI, defaultURL string) string {
var hubURL string
if configAPI == "" {
hubURL = defaultURL
} else {
if !strings.HasSuffix(configAPI, "/") {
configAPI += "/"
}
hubURL = configAPI + yamlEndpoint
hubURL = configAPI
}

return hubURL
return strings.TrimSuffix(hubURL, "/")
}
40 changes: 40 additions & 0 deletions cmd/resolvers/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import "testing"

func TestBuildHubURL(t *testing.T) {
testCases := []struct {
name string
configAPI string
defaultURL string
expected string
}{
{
name: "configAPI empty",
configAPI: "",
defaultURL: "https://tekton.dev",
expected: "https://tekton.dev",
},
{
name: "configAPI not empty",
configAPI: "https://myhub.com",
defaultURL: "https://foo.com",
expected: "https://myhub.com",
},
{
name: "defaultURL ends with slash",
configAPI: "",
defaultURL: "https://bar.com/",
expected: "https://bar.com",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := buildHubURL(tc.configAPI, tc.defaultURL)
if actual != tc.expected {
t.Errorf("expected %s, but got %s", tc.expected, actual)
}
})
}
}
34 changes: 33 additions & 1 deletion docs/hub-resolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Use resolver type `hub`.
| `type` | The type of Hub from where to pull the resource (Optional). Either `artifact` or `tekton` | Default: `artifact` |
| `kind` | Either `task` or `pipeline` (Optional) | Default: `task` |
| `name` | The name of the task or pipeline to fetch from the hub | `golang-build` |
| `version` | Version of task or pipeline to pull in from hub. Wrap the number in quotes! | `"0.5.0"` |
| `version` | Version or a Constraint (see [below](#version-constraint) of a task or a pipeline to pull in from. Wrap the number in quotes! | `"0.5.0"`, `">= 0.5.0"` |

The Catalogs in the Artifact Hub follows the semVer (i.e.` <major-version>.<minor-version>.0`) and the Catalogs in the Tekton Hub follows the simplified semVer (i.e. `<major-version>.<minor-version>`). Both full and simplified semantic versioning will be accepted by the `version` parameter. The Hub Resolver will map the version to the format expected by the target Hub `type`.

Expand Down Expand Up @@ -129,6 +129,38 @@ spec:
# overall will not succeed without those parameters.
```

### Version constraint

Instead of a version you can specify a constraint to choose from. The constraint is a string as documented in the [go-version](https://github.com/hashicorp/go-version) library.

Some examples:

```yaml
params:
- name: name
value: git-clone
- name: version
value: ">=0.7.0"
```

Will only choose the git-clone task that is greater than version `0.7.0`

```yaml
params:
- name: name
value: git-clone
- name: version
value: ">=0.7.0, < 2.0.0"
```

Will select the **latest** git-clone task that is greater than version `0.7.0` and
less than version `2.0.0`, so if the latest task is the version `0.9.0` it will
be selected.

Other operators for selection are available for comparisons, see the
[go-version](https://github.com/hashicorp/go-version/blob/644291d14038339745c2d883a1a114488e30b702/constraint.go#L40C2-L48)
source code.

---

Except as otherwise noted, the content of this page is licensed under the
Expand Down
8 changes: 7 additions & 1 deletion pkg/resolution/resolver/hub/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ limitations under the License.
package hub

// DefaultArtifactHubURL is the default url for the Artifact hub api
const DefaultArtifactHubURL = "https://artifacthub.io/api/v1/packages/tekton-%s/%s/%s/%s"
const DefaultArtifactHubURL = "https://artifacthub.io"

// TektonHubYamlEndpoint is the suffix for a private custom Tekton hub instance
const TektonHubYamlEndpoint = "v1/resource/%s/%s/%s/%s/yaml"

// DefaultTektonHubListTasksEndpoint
const TektonHubListTasksEndpoint = "v1/resource/%s/%s/%s"

// ArtifactHubYamlEndpoint is the suffix for a private custom Artifact hub instance
const ArtifactHubYamlEndpoint = "api/v1/packages/tekton-%s/%s/%s/%s"

// ArtifactHubListTasksEndpoint
const ArtifactHubListTasksEndpoint = "api/v1/packages/tekton-%s/%s/%s"

// ParamName is the parameter defining what the layer name in the bundle
// image is.
const ParamName = "name"
Expand Down
98 changes: 95 additions & 3 deletions pkg/resolution/resolver/hub/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"net/http"
"strings"

goversion "github.com/hashicorp/go-version"
resolverconfig "github.com/tektoncd/pipeline/pkg/apis/config/resolver"
pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"github.com/tektoncd/pipeline/pkg/resolution/common"
Expand Down Expand Up @@ -121,6 +122,14 @@ func (r *Resolver) Resolve(ctx context.Context, params []pipelinev1.Param) (fram
return nil, fmt.Errorf("failed to validate params: %w", err)
}

if constraint, err := goversion.NewConstraint(paramsMap[ParamVersion]); err == nil {
chosen, err := r.resolveVersionConstraint(ctx, paramsMap, constraint)
if err != nil {
return nil, err
}
paramsMap[ParamVersion] = chosen.String()
}

resVer, err := resolveVersion(paramsMap[ParamVersion], paramsMap[ParamType])
if err != nil {
return nil, err
Expand All @@ -130,7 +139,8 @@ func (r *Resolver) Resolve(ctx context.Context, params []pipelinev1.Param) (fram
// call hub API
switch paramsMap[ParamType] {
case ArtifactHubType:
url := fmt.Sprintf(r.ArtifactHubURL, paramsMap[ParamKind], paramsMap[ParamCatalog], paramsMap[ParamName], paramsMap[ParamVersion])
url := fmt.Sprintf(fmt.Sprintf("%s/%s", r.ArtifactHubURL, ArtifactHubYamlEndpoint),
paramsMap[ParamKind], paramsMap[ParamCatalog], paramsMap[ParamName], paramsMap[ParamVersion])
resp := artifactHubResponse{}
if err := fetchHubResource(ctx, url, &resp); err != nil {
return nil, fmt.Errorf("fail to fetch Artifact Hub resource: %w", err)
Expand All @@ -140,7 +150,8 @@ func (r *Resolver) Resolve(ctx context.Context, params []pipelinev1.Param) (fram
Content: []byte(resp.Data.YAML),
}, nil
case TektonHubType:
url := fmt.Sprintf(r.TektonHubURL, paramsMap[ParamCatalog], paramsMap[ParamKind], paramsMap[ParamName], paramsMap[ParamVersion])
url := fmt.Sprintf(fmt.Sprintf("%s/%s", r.TektonHubURL, TektonHubYamlEndpoint),
paramsMap[ParamCatalog], paramsMap[ParamKind], paramsMap[ParamName], paramsMap[ParamVersion])
resp := tektonHubResponse{}
if err := fetchHubResource(ctx, url, &resp); err != nil {
return nil, fmt.Errorf("fail to fetch Tekton Hub resource: %w", err)
Expand Down Expand Up @@ -218,7 +229,6 @@ func fetchHubResource(ctx context.Context, apiEndpoint string, v interface{}) er
if err != nil {
return fmt.Errorf("error unmarshalling json response: %w", err)
}

return nil
}

Expand Down Expand Up @@ -256,6 +266,88 @@ func resolveCatalogName(paramsMap, conf map[string]string) (string, error) {
return paramsMap[ParamCatalog], nil
}

type artifactHubavailableVersionsResults struct {
Version string `json:"version"`
Prerelease bool `json:"prerelease"`
}

type artifactHubListResult struct {
AvailableVersions []artifactHubavailableVersionsResults `json:"available_versions"`
Version string `json:"version"`
}

type tektonHubListResultVersion struct {
Version string `json:"version"`
}

type tektonHubListDataResult struct {
Versions []tektonHubListResultVersion `json:"versions"`
}

type tektonHubListResult struct {
Data tektonHubListDataResult `json:"data"`
}

func (r *Resolver) resolveVersionConstraint(ctx context.Context, paramsMap map[string]string, constraint goversion.Constraints) (*goversion.Version, error) {
var ret *goversion.Version
if paramsMap[ParamType] == ArtifactHubType {
allVersionsURL := fmt.Sprintf("%s/%s", r.ArtifactHubURL, fmt.Sprintf(
ArtifactHubListTasksEndpoint,
paramsMap[ParamKind], paramsMap[ParamCatalog], paramsMap[ParamName]))
resp := artifactHubListResult{}
if err := fetchHubResource(ctx, allVersionsURL, &resp); err != nil {
return nil, fmt.Errorf("fail to fetch Artifact Hub resource: %w", err)
}
for _, vers := range resp.AvailableVersions {
if vers.Prerelease {
continue
}
checkV, err := goversion.NewVersion(vers.Version)
if err != nil {
return nil, fmt.Errorf("fail to parse version %s from %s: %w", ArtifactHubType, vers.Version, err)
}
if checkV == nil {
continue
}
if constraint.Check(checkV) {
if ret != nil && ret.GreaterThan(checkV) {
continue
}
// TODO(chmouel): log constraint result in controller
ret = checkV
}
}
} else if paramsMap[ParamType] == TektonHubType {
allVersionsURL := fmt.Sprintf("%s/%s", r.TektonHubURL,
fmt.Sprintf(TektonHubListTasksEndpoint,
paramsMap[ParamCatalog], paramsMap[ParamKind], paramsMap[ParamName]))
resp := tektonHubListResult{}
if err := fetchHubResource(ctx, allVersionsURL, &resp); err != nil {
return nil, fmt.Errorf("fail to fetch Tekton Hub resource: %w", err)
}
for _, vers := range resp.Data.Versions {
checkV, err := goversion.NewVersion(vers.Version)
if err != nil {
return nil, fmt.Errorf("fail to parse version %s from %s: %w", TektonHubType, vers, err)
}
if checkV == nil {
continue
}
if constraint.Check(checkV) {
if ret != nil && ret.GreaterThan(checkV) {
continue
}
// TODO(chmouel): log constraint result in controller
ret = checkV
}
}
}
if ret == nil {
return nil, fmt.Errorf("no version found for constraint %s", paramsMap[ParamVersion])
}
return ret, nil
}

// the Artifact Hub follows the semVer (i.e. <major-version>.<minor-version>.0)
// the Tekton Hub follows the simplified semVer (i.e. <major-version>.<minor-version>)
// for resolution request with "artifact" type, we append ".0" suffix if the input version is simplified semVer
Expand Down
Loading

0 comments on commit f0c7ed5

Please sign in to comment.