diff --git a/api/pkg/cli/cmd/install/install.go b/api/pkg/cli/cmd/install/install.go index 0578dd39db..16114c7025 100644 --- a/api/pkg/cli/cmd/install/install.go +++ b/api/pkg/cli/cmd/install/install.go @@ -171,8 +171,14 @@ func (opts *options) run() error { resourceInstaller := installer.New(opts.cs) + org, err := opts.hubRes.Org() + if err != nil { + return err + } + hubType := opts.cli.Hub().GetType() + var errors []error - opts.resource, errors = resourceInstaller.Install([]byte(manifest), opts.from, opts.cs.Namespace()) + opts.resource, errors = resourceInstaller.Install([]byte(manifest), hubType, org, opts.from, opts.cs.Namespace()) if len(errors) != 0 { resourcePipelineMinVersion, err := opts.hubRes.MinPipelinesVersion() diff --git a/api/pkg/cli/hub/get_catalog.go b/api/pkg/cli/hub/get_catalog.go index dc4999e0f6..1b6f000e51 100644 --- a/api/pkg/cli/hub/get_catalog.go +++ b/api/pkg/cli/hub/get_catalog.go @@ -34,7 +34,7 @@ type artifactHubCatalogResponse struct { Name string `json:"name,omitempty"` } -func (c *tektonHubclient) GetAllCatalogs() CatalogResult { +func (c *tektonHubClient) GetAllCatalogs() CatalogResult { data, status, err := c.Get(tektonHubCatEndpoint) if status == http.StatusNotFound { err = nil @@ -76,7 +76,7 @@ func (a *artifactHubClient) GetCatalogsList() ([]string, error) { return cat, nil } -func (t *tektonHubclient) GetCatalogsList() ([]string, error) { +func (t *tektonHubClient) GetCatalogsList() ([]string, error) { // Get all catalogs c := t.GetAllCatalogs() diff --git a/api/pkg/cli/hub/get_resource.go b/api/pkg/cli/hub/get_resource.go index 3703d31a0e..8e94da0c1a 100644 --- a/api/pkg/cli/hub/get_resource.go +++ b/api/pkg/cli/hub/get_resource.go @@ -42,6 +42,7 @@ type ResourceResult interface { ResourceYaml() (string, error) ResourceVersion() (string, error) MinPipelinesVersion() (string, error) + Org() (string, error) UnmarshalData() error } @@ -96,6 +97,11 @@ type ArtifactHubPkgResponse struct { Name string `json:"name,omitempty"` Data ArtifactHubPkgData `json:"data,omitempty"` AvailableVersions []ArtifactHubVersion `json:"available_versions,omitempty"` + Repository ArtifactHubRepo `json:"repository,omitempty"` +} + +type ArtifactHubRepo struct { + Org string `json:"organization_name,omitempty"` } type ArtifactHubPkgData struct { @@ -114,7 +120,7 @@ func (a *artifactHubClient) GetResource(opt ResourceOption) ResourceResult { } // GetResource queries the data using Tekton Hub Endpoint -func (t *tektonHubclient) GetResource(opt ResourceOption) ResourceResult { +func (t *tektonHubClient) GetResource(opt ResourceOption) ResourceResult { data, status, err := t.Get(opt.Endpoint()) return &TektonHubResourceResult{ @@ -140,7 +146,7 @@ func (a *artifactHubClient) GetResourceYaml(opt ResourceOption) ResourceResult { } // GetResourceYaml queries the resource yaml using Tekton Hub Endpoint -func (t *tektonHubclient) GetResourceYaml(opt ResourceOption) ResourceResult { +func (t *tektonHubClient) GetResourceYaml(opt ResourceOption) ResourceResult { yaml, yamlStatus, yamlErr := t.Get(fmt.Sprintf("/v1/resource/%s/%s/%s/%s/yaml", opt.Catalog, opt.Kind, opt.Name, opt.Version)) data, status, err := t.Get(opt.Endpoint()) @@ -163,7 +169,7 @@ func (a *artifactHubClient) GetResourcesList(so SearchOption) ([]string, error) } // GetResourcesList queries the resources in the catalog using Tekton Hub Endpoint -func (t *tektonHubclient) GetResourcesList(so SearchOption) ([]string, error) { +func (t *tektonHubClient) GetResourcesList(so SearchOption) ([]string, error) { // Get all resources result := t.Search(SearchOption{ Kinds: so.Kinds, @@ -206,7 +212,7 @@ func (a *artifactHubClient) GetResourceVersionslist(r ResourceOption) ([]string, } // GetResourceVersionslist queries the versions of a resource using Tekton Hub Endpoint -func (t *tektonHubclient) GetResourceVersionslist(r ResourceOption) ([]string, error) { +func (t *tektonHubClient) GetResourceVersionslist(r ResourceOption) ([]string, error) { opts := &ResourceVersionOptions{} // Get the resource versions opts.hubResVersionsRes = t.GetResourceVersions(ResourceOption{ @@ -433,6 +439,24 @@ func (rr *ArtifactHubResourceResult) MinPipelinesVersion() (string, error) { return resp.Data.PipelineMinVer, nil } +// Org returns the organization of the catalog from Artifact Hub +func (rr *ArtifactHubResourceResult) Org() (string, error) { + if err := rr.validateData(); err != nil { + return "", err + } + res := ArtifactHubPkgResponse{} + if err := json.Unmarshal(rr.data, &res); err != nil { + return "", err + } + return res.Repository.Org, nil +} + +// Org returns the organization of the catalog from Tekton Hub +func (rr *TektonHubResourceResult) Org() (string, error) { + // TODO: implement org() for Tekton Hub + return "", nil +} + func (rr *ArtifactHubResourceResult) validateData() error { if rr.err != nil { return rr.err diff --git a/api/pkg/cli/hub/get_resource_version.go b/api/pkg/cli/hub/get_resource_version.go index 05eaa25374..720ea3b85c 100644 --- a/api/pkg/cli/hub/get_resource_version.go +++ b/api/pkg/cli/hub/get_resource_version.go @@ -50,7 +50,7 @@ func (a *artifactHubClient) GetResourceVersions(opt ResourceOption) ResourceVers } // GetResourceVersions queries the data using Tekton Hub Endpoint -func (t *tektonHubclient) GetResourceVersions(opt ResourceOption) ResourceVersionResult { +func (t *tektonHubClient) GetResourceVersions(opt ResourceOption) ResourceVersionResult { rvr := TektonHubResourceVersionResult{set: false} diff --git a/api/pkg/cli/hub/hub.go b/api/pkg/cli/hub/hub.go index e1a427fbd3..10df8cc4cd 100644 --- a/api/pkg/cli/hub/hub.go +++ b/api/pkg/cli/hub/hub.go @@ -54,7 +54,7 @@ type Client interface { GetResourceVersionslist(opt ResourceOption) ([]string, error) } -type tektonHubclient struct { +type tektonHubClient struct { apiURL string } @@ -62,11 +62,11 @@ type artifactHubClient struct { apiURL string } -var _ Client = (*tektonHubclient)(nil) +var _ Client = (*tektonHubClient)(nil) var _ Client = (*artifactHubClient)(nil) -func NewTektonHubClient() *tektonHubclient { - return &tektonHubclient{apiURL: tektonHubURL} +func NewTektonHubClient() *tektonHubClient { + return &tektonHubClient{apiURL: tektonHubURL} } func NewArtifactHubClient() *artifactHubClient { @@ -84,7 +84,7 @@ func (a *artifactHubClient) GetType() string { } // GetType returns the type of the Hub Client -func (t *tektonHubclient) GetType() string { +func (t *tektonHubClient) GetType() string { return TektonHubType } @@ -104,7 +104,7 @@ func (a *artifactHubClient) SetURL(apiURL string) error { // SetURL validates and sets the Tekton Hub apiURL server URL // URL passed through flag will take precedence over the Tekton Hub API URL // in config file and default URL -func (t *tektonHubclient) SetURL(apiURL string) error { +func (t *tektonHubClient) SetURL(apiURL string) error { resUrl, err := resolveUrl(apiURL, "TEKTON_HUB_API_SERVER", tektonHubURL) if err != nil { return err @@ -120,7 +120,7 @@ func (a *artifactHubClient) Get(endpoint string) ([]byte, int, error) { } // Get gets data from Tekton Hub -func (t *tektonHubclient) Get(endpoint string) ([]byte, int, error) { +func (t *tektonHubClient) Get(endpoint string) ([]byte, int, error) { return get(t.apiURL + endpoint) } diff --git a/api/pkg/cli/hub/hub_test.go b/api/pkg/cli/hub/hub_test.go index 6a9536f1b8..22137a5f0e 100644 --- a/api/pkg/cli/hub/hub_test.go +++ b/api/pkg/cli/hub/hub_test.go @@ -7,7 +7,7 @@ import ( ) func TestSetURL_TektonHub(t *testing.T) { - tHub := &tektonHubclient{} + tHub := &tektonHubClient{} err := tHub.SetURL("http://localhost:80000") assert.NoError(t, err) @@ -42,7 +42,7 @@ func TestSetURL_ArtifactHub(t *testing.T) { func TestSetURL_InvalidCase(t *testing.T) { - hub := &tektonHubclient{} + hub := &tektonHubClient{} err := hub.SetURL("abc") assert.Error(t, err) assert.EqualError(t, err, "parse \"abc\": invalid URI for request") diff --git a/api/pkg/cli/hub/search.go b/api/pkg/cli/hub/search.go index 0431a70d71..0824e6a154 100644 --- a/api/pkg/cli/hub/search.go +++ b/api/pkg/cli/hub/search.go @@ -56,7 +56,7 @@ func (a *artifactHubClient) Search(so SearchOption) SearchResult { } // Search queries the data using TektonHub Endpoint -func (t *tektonHubclient) Search(so SearchOption) SearchResult { +func (t *tektonHubClient) Search(so SearchOption) SearchResult { data, status, err := t.Get(so.Endpoint()) if status == http.StatusNotFound { err = nil diff --git a/api/pkg/cli/installer/action.go b/api/pkg/cli/installer/action.go index eaf8a409e3..9f06c0aad7 100644 --- a/api/pkg/cli/installer/action.go +++ b/api/pkg/cli/installer/action.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" + "github.com/tektoncd/hub/api/pkg/cli/hub" tknVer "github.com/tektoncd/hub/api/pkg/cli/version" kErr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,8 +28,14 @@ import ( ) const ( - catalogLabel = "hub.tekton.dev/catalog" - versionLabel = "app.kubernetes.io/version" + versionLabel = "app.kubernetes.io/version" + tektonHubCatalogLabel = "hub.tekton.dev/catalog" + artifactHubCatalogLabel = "artifacthub.io/catalog" + artifactHubOrgLabel = "artifacthub.io/org" + supportTierLabel = "artifacthub.io/support-tier" + communitySupportTier = "Community" + verifiedSupportTier = "Verified" + verifiedCatOrg = "tektoncd" ) // Errors @@ -82,7 +89,7 @@ func (i *Installer) checkVersion(resPipMinVersion string) error { } // Install a resource -func (i *Installer) Install(data []byte, catalog, namespace string) (*unstructured.Unstructured, []error) { +func (i *Installer) Install(data []byte, hubType, org, catalog, namespace string) (*unstructured.Unstructured, []error) { errors := make([]error, 0) @@ -110,7 +117,7 @@ func (i *Installer) Install(data []byte, catalog, namespace string) (*unstructur if err != nil { // If error is notFoundError then create the resource if kErr.IsNotFound(err) { - resp, err := i.createRes(newRes, catalog, namespace) + resp, err := i.createRes(newRes, hubType, org, catalog, namespace) if err != nil { errors = append(errors, err) } @@ -259,7 +266,7 @@ func checkLabels(res *unstructured.Unstructured) error { } _, versionOk := labels[versionLabel] - _, catalogOk := labels[catalogLabel] + _, catalogOk := labels[tektonHubCatalogLabel] // If both label exist then return nil if versionOk == catalogOk && versionOk { @@ -277,9 +284,11 @@ func checkLabels(res *unstructured.Unstructured) error { return nil } -func (i *Installer) createRes(obj *unstructured.Unstructured, catalog, namespace string) (*unstructured.Unstructured, error) { +func (i *Installer) createRes(obj *unstructured.Unstructured, hubType, org, catalog, namespace string) (*unstructured.Unstructured, error) { - addCatalogLabel(obj, catalog) + if err := addCatalogLabel(obj, hubType, org, catalog); err != nil { + return nil, err + } res, err := i.create(obj, namespace, metav1.CreateOptions{}) if err != nil { return nil, err @@ -289,7 +298,11 @@ func (i *Installer) createRes(obj *unstructured.Unstructured, catalog, namespace func (i *Installer) updateRes(existing, new *unstructured.Unstructured, catalog, namespace string) (*unstructured.Unstructured, error) { - addCatalogLabel(new, catalog) + // TODO: update addCatalogLabel() params when supporting upgrade/downgrade command for artifact type + if err := addCatalogLabel(new, hub.TektonHubType, "", catalog); err != nil { + return nil, err + } + // replace label, annotation and spec of old resource with new existing.SetLabels(new.GetLabels()) existing.SetAnnotations(new.GetAnnotations()) @@ -314,11 +327,27 @@ func toUnstructured(data []byte) (*unstructured.Unstructured, error) { return res, nil } -func addCatalogLabel(obj *unstructured.Unstructured, catalog string) { +func addCatalogLabel(obj *unstructured.Unstructured, hubType, org, catalog string) error { labels := obj.GetLabels() if len(labels) == 0 { labels = make(map[string]string) } - labels[catalogLabel] = catalog + + switch hubType { + case hub.TektonHubType: + labels[tektonHubCatalogLabel] = catalog + case hub.ArtifactHubType: + labels[artifactHubCatalogLabel] = catalog + if org == verifiedCatOrg { + labels[supportTierLabel] = verifiedSupportTier + } else { + labels[supportTierLabel] = communitySupportTier + } + labels[artifactHubOrgLabel] = org + default: + return fmt.Errorf("hub type: %s not supported in addCatalogLabel", hubType) + } + obj.SetLabels(labels) + return nil } diff --git a/api/pkg/cli/installer/action_test.go b/api/pkg/cli/installer/action_test.go index 07453d53fd..444aa062f8 100644 --- a/api/pkg/cli/installer/action_test.go +++ b/api/pkg/cli/installer/action_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/tektoncd/hub/api/pkg/cli/hub" "github.com/tektoncd/hub/api/pkg/cli/test" cb "github.com/tektoncd/hub/api/pkg/cli/test/builder" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" @@ -43,12 +44,64 @@ spec: ` func TestToUnstructuredAndAddLabel(t *testing.T) { - obj, err := toUnstructured([]byte(res)) - assert.NoError(t, err) - assert.Equal(t, "foo", obj.GetName()) + testCases := []struct { + name string + data string + hubType string + org string + catalog string + wantSupportTier string + wantCatalog string + wantOrg string + }{ + { + name: "Install From Tekton Hub", + data: res, + hubType: hub.TektonHubType, + org: "", + catalog: "tekton", + wantCatalog: "tekton", + }, + { + name: "Install Verified Catalog From Artifact Hub", + data: res, + hubType: hub.ArtifactHubType, + org: verifiedCatOrg, + catalog: "golang-build", + wantSupportTier: verifiedSupportTier, + wantCatalog: "golang-build", + wantOrg: "tektoncd", + }, + { + name: "Install Community Catalog From Artifact Hub", + data: res, + hubType: hub.ArtifactHubType, + org: "tekton-legacy", + catalog: "tekton-catalog-tasks", + wantSupportTier: communitySupportTier, + wantCatalog: "tekton-catalog-tasks", + wantOrg: "tekton-legacy", + }, + } + + for _, tc := range testCases { + obj, err := toUnstructured([]byte(res)) + assert.NoError(t, err) + assert.Equal(t, "foo", obj.GetName()) - addCatalogLabel(obj, "tekton") - assert.Equal(t, "tekton", obj.GetLabels()[catalogLabel]) + if err := addCatalogLabel(obj, tc.hubType, tc.org, tc.catalog); err != nil { + t.Errorf("%s", err.Error()) + } + + if tc.hubType == hub.ArtifactHubType { + assert.Equal(t, tc.wantSupportTier, obj.GetLabels()[supportTierLabel]) + assert.Equal(t, tc.wantCatalog, obj.GetLabels()[artifactHubCatalogLabel]) + assert.Equal(t, tc.wantOrg, obj.GetLabels()[artifactHubOrgLabel]) + assert.Equal(t, "", obj.GetLabels()[tektonHubCatalogLabel]) + } else { + assert.Equal(t, "tekton", obj.GetLabels()[tektonHubCatalogLabel]) + } + } } func TestListInstalled(t *testing.T) {