Adding a monocular client as a package

Includes client creation and search

Signed-off-by: Matt Farina <matt@mattfarina.com>
pull/6187/head
Matt Farina 5 years ago
parent d677952165
commit 2613c3cda3
No known key found for this signature in database
GPG Key ID: 9436E80BFBA46909

@ -0,0 +1,74 @@
/*
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"
"strings"
"helm.sh/helm/internal/version"
)
// 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 user agent to identify as when making requests
UserAgent string
// 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{
UserAgent: "Helm/" + strings.TrimPrefix(version.GetVersion(), "v"),
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,39 @@
/*
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 (
"strings"
"testing"
"helm.sh/helm/internal/version"
)
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)
}
ua := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v")
if c.UserAgent != ua {
t.Errorf("incorrect user agent. Expected %q but got %q", ua, c.UserAgent)
}
}

@ -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,132 @@
/*
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/pkg/chart"
)
// The structs below represent the structure of the response from the monocular
// search API.
// SearchResult represents an individual chart result
type SearchResult struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes Attributes `json:"attributes"`
Links Links `json:"links"`
Relationships Relationships `json:"relationships"`
}
// Attributes is the attributes for the chart
type Attributes 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 Data `json:"data"`
Links Links `json:"links"`
}
// Data provides the specific data on the chart version
type Data 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, "api/chartsvc/v1/charts/search")
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", c.UserAgent)
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…
Cancel
Save