mirror of https://github.com/helm/helm
Merge pull request #6187 from mattfarina/hub-sdk
Adding a monocular client as a packagepull/6208/head
commit
dfe37b9ccd
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package monocular
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ErrHostnameNotProvided indicates the url is missing a hostname
|
||||
var ErrHostnameNotProvided = errors.New("no hostname provided")
|
||||
|
||||
// Client represents a client capable of communicating with the Monocular API.
|
||||
type Client struct {
|
||||
|
||||
// The base URL for requests
|
||||
BaseURL string
|
||||
|
||||
// The internal logger to use
|
||||
Log func(string, ...interface{})
|
||||
}
|
||||
|
||||
// New creates a new client
|
||||
func New(u string) (*Client, error) {
|
||||
|
||||
// Validate we have a URL
|
||||
if err := validate(u); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
BaseURL: u,
|
||||
Log: nopLogger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var nopLogger = func(_ string, _ ...interface{}) {}
|
||||
|
||||
// Validate if the base URL for monocular is valid.
|
||||
func validate(u string) error {
|
||||
|
||||
// Check if it is parsable
|
||||
p, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check that a host is attached
|
||||
if p.Hostname() == "" {
|
||||
return ErrHostnameNotProvided
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package monocular
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
c, err := New("https://hub.helm.sh")
|
||||
if err != nil {
|
||||
t.Errorf("error creating client: %s", err)
|
||||
}
|
||||
if c.BaseURL != "https://hub.helm.sh" {
|
||||
t.Errorf("incorrect BaseURL. Expected \"https://hub.helm.sh\" but got %q", c.BaseURL)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package monocular contains the logic for interacting with monocular instances
|
||||
// like the Helm Hub.
|
||||
//
|
||||
// This is a library for interacting with monocular
|
||||
package monocular
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package monocular
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/internal/version"
|
||||
"helm.sh/helm/pkg/chart"
|
||||
)
|
||||
|
||||
// SearchPath is the url path to the search API in monocular.
|
||||
const SearchPath = "api/chartsvc/v1/charts/search"
|
||||
|
||||
// The structs below represent the structure of the response from the monocular
|
||||
// search API. The structs were not imported from monocular because monocular
|
||||
// imports from Helm v2 (avoiding circular version dependency) and the mappings
|
||||
// are slightly different (monocular search results do not directly reflect
|
||||
// the struct definitions).
|
||||
|
||||
// SearchResult represents an individual chart result
|
||||
type SearchResult struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Attributes Chart `json:"attributes"`
|
||||
Links Links `json:"links"`
|
||||
Relationships Relationships `json:"relationships"`
|
||||
}
|
||||
|
||||
// Chart is the attributes for the chart
|
||||
type Chart struct {
|
||||
Name string `json:"name"`
|
||||
Repo Repo `json:"repo"`
|
||||
Description string `json:"description"`
|
||||
Home string `json:"home"`
|
||||
Keywords []string `json:"keywords"`
|
||||
Maintainers []chart.Maintainer `json:"maintainers"`
|
||||
Sources []string `json:"sources"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
// Repo contains the name in monocular the the url for the repository
|
||||
type Repo struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// Links provides a set of links relative to the chartsvc base
|
||||
type Links struct {
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
// Relationships provides information on the latest version of the chart
|
||||
type Relationships struct {
|
||||
LatestChartVersion LatestChartVersion `json:"latestChartVersion"`
|
||||
}
|
||||
|
||||
// LatestChartVersion provides the details on the latest version of the chart
|
||||
type LatestChartVersion struct {
|
||||
Data ChartVersion `json:"data"`
|
||||
Links Links `json:"links"`
|
||||
}
|
||||
|
||||
// ChartVersion provides the specific data on the chart version
|
||||
type ChartVersion struct {
|
||||
Version string `json:"version"`
|
||||
AppVersion string `json:"app_version"`
|
||||
Created time.Time `json:"created"`
|
||||
Digest string `json:"digest"`
|
||||
Urls []string `json:"urls"`
|
||||
Readme string `json:"readme"`
|
||||
Values string `json:"values"`
|
||||
}
|
||||
|
||||
// Search performs a search against the monocular search API
|
||||
func (c *Client) Search(term string) ([]SearchResult, error) {
|
||||
|
||||
// Create the URL to the search endpoint
|
||||
// Note, this is currently an internal API for the Hub. This should be
|
||||
// formatted without showing how monocular operates.
|
||||
p, err := url.Parse(c.BaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the path to the monocular API endpoint for search
|
||||
p.Path = path.Join(p.Path, SearchPath)
|
||||
|
||||
p.RawQuery = "q=" + url.QueryEscape(term)
|
||||
|
||||
// Create request
|
||||
req, err := http.NewRequest("GET", p.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the user agent so that monocular can identify where the request
|
||||
// is coming from
|
||||
req.Header.Set("User-Agent", version.GetUserAgent())
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status)
|
||||
}
|
||||
|
||||
result := &searchResponse{}
|
||||
|
||||
json.NewDecoder(res.Body).Decode(result)
|
||||
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
type searchResponse struct {
|
||||
Data []SearchResult `json:"data"`
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package monocular
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A search response for phpmyadmin containing 2 results
|
||||
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://kubernetes-charts.storage.googleapis.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://kubernetes-charts.storage.googleapis.com/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, searchResult)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
c, err := New(ts.URL)
|
||||
if err != nil {
|
||||
t.Errorf("unable to create monocular client: %s", err)
|
||||
}
|
||||
|
||||
results, err := c.Search("phpmyadmin")
|
||||
if err != nil {
|
||||
t.Errorf("unable to search monocular: %s", err)
|
||||
}
|
||||
|
||||
if len(results) != 2 {
|
||||
t.Error("Did not receive the expected number of results")
|
||||
}
|
||||
}
|
Loading…
Reference in new issue