Delete internal directory

Signed-off-by: HARPHUNA <109001160+HARPHUNA@users.noreply.github.com>
pull/11491/head
HARPHUNA 3 years ago committed by GitHub
parent 7ff00a3099
commit 0f3826af7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,51 +0,0 @@
/*
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 fileutil
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"helm.sh/helm/v3/internal/third_party/dep/fs"
)
// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk.
func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename))
if err != nil {
return err
}
tempName := tempFile.Name()
if _, err := io.Copy(tempFile, reader); err != nil {
tempFile.Close() // return value is ignored as we are already on error path
return err
}
if err := tempFile.Close(); err != nil {
return err
}
if err := os.Chmod(tempName, mode); err != nil {
return err
}
return fs.RenameWithFallback(tempName, filename)
}

@ -1,58 +0,0 @@
/*
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 fileutil
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestAtomicWriteFile(t *testing.T) {
dir := t.TempDir()
testpath := filepath.Join(dir, "test")
stringContent := "Test content"
reader := bytes.NewReader([]byte(stringContent))
mode := os.FileMode(0644)
err := AtomicWriteFile(testpath, reader, mode)
if err != nil {
t.Errorf("AtomicWriteFile error: %s", err)
}
got, err := ioutil.ReadFile(testpath)
if err != nil {
t.Fatal(err)
}
if stringContent != string(got) {
t.Fatalf("expected: %s, got: %s", stringContent, string(got))
}
gotinfo, err := os.Stat(testpath)
if err != nil {
t.Fatal(err)
}
if mode != gotinfo.Mode() {
t.Fatalf("expected %s: to be the same mode as %s",
mode, gotinfo.Mode())
}
}

@ -1,67 +0,0 @@
/*
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 ignore provides tools for writing ignore files (a la .gitignore).
This provides both an ignore parser and a file-aware processor.
The format of ignore files closely follows, but does not exactly match, the
format for .gitignore files (https://git-scm.com/docs/gitignore).
The formatting rules are as follows:
- Parsing is line-by-line
- Empty lines are ignored
- Lines the begin with # (comments) will be ignored
- Leading and trailing spaces are always ignored
- Inline comments are NOT supported ('foo* # Any foo' does not contain a comment)
- There is no support for multi-line patterns
- Shell glob patterns are supported. See Go's "path/filepath".Match
- If a pattern begins with a leading !, the match will be negated.
- If a pattern begins with a leading /, only paths relatively rooted will match.
- If the pattern ends with a trailing /, only directories will match
- If a pattern contains no slashes, file basenames are tested (not paths)
- The pattern sequence "**", while legal in a glob, will cause an error here
(to indicate incompatibility with .gitignore).
Example:
# Match any file named foo.txt
foo.txt
# Match any text file
*.txt
# Match only directories named mydir
mydir/
# Match only text files in the top-level directory
/*.txt
# Match only the file foo.txt in the top-level directory
/foo.txt
# Match any file named ab.txt, ac.txt, or ad.txt
a[b-d].txt
Notable differences from .gitignore:
- The '**' syntax is not supported.
- The globbing library is Go's 'filepath.Match', not fnmatch(3)
- Trailing spaces are always ignored (there is no supported escape sequence)
- The evaluation of escape sequences has not been tested for compatibility
- There is no support for '\!' as a special leading sequence.
*/
package ignore // import "helm.sh/helm/v3/internal/ignore"

@ -1,228 +0,0 @@
/*
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 ignore
import (
"bufio"
"bytes"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// HelmIgnore default name of an ignorefile.
const HelmIgnore = ".helmignore"
// Rules is a collection of path matching rules.
//
// Parse() and ParseFile() will construct and populate new Rules.
// Empty() will create an immutable empty ruleset.
type Rules struct {
patterns []*pattern
}
// Empty builds an empty ruleset.
func Empty() *Rules {
return &Rules{patterns: []*pattern{}}
}
// AddDefaults adds default ignore patterns.
//
// Ignore all dotfiles in "templates/"
func (r *Rules) AddDefaults() {
r.parseRule(`templates/.?*`)
}
// ParseFile parses a helmignore file and returns the *Rules.
func ParseFile(file string) (*Rules, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
return Parse(f)
}
// Parse parses a rules file
func Parse(file io.Reader) (*Rules, error) {
r := &Rules{patterns: []*pattern{}}
s := bufio.NewScanner(file)
currentLine := 0
utf8bom := []byte{0xEF, 0xBB, 0xBF}
for s.Scan() {
scannedBytes := s.Bytes()
// We trim UTF8 BOM
if currentLine == 0 {
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
}
line := string(scannedBytes)
currentLine++
if err := r.parseRule(line); err != nil {
return r, err
}
}
return r, s.Err()
}
// Ignore evaluates the file at the given path, and returns true if it should be ignored.
//
// Ignore evaluates path against the rules in order. Evaluation stops when a match
// is found. Matching a negative rule will stop evaluation.
func (r *Rules) Ignore(path string, fi os.FileInfo) bool {
// Don't match on empty dirs.
if path == "" {
return false
}
// Disallow ignoring the current working directory.
// See issue:
// 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?"
if path == "." || path == "./" {
return false
}
for _, p := range r.patterns {
if p.match == nil {
log.Printf("ignore: no matcher supplied for %q", p.raw)
return false
}
// For negative rules, we need to capture and return non-matches,
// and continue for matches.
if p.negate {
if p.mustDir && !fi.IsDir() {
return true
}
if !p.match(path, fi) {
return true
}
continue
}
// If the rule is looking for directories, and this is not a directory,
// skip it.
if p.mustDir && !fi.IsDir() {
continue
}
if p.match(path, fi) {
return true
}
}
return false
}
// parseRule parses a rule string and creates a pattern, which is then stored in the Rules object.
func (r *Rules) parseRule(rule string) error {
rule = strings.TrimSpace(rule)
// Ignore blank lines
if rule == "" {
return nil
}
// Comment
if strings.HasPrefix(rule, "#") {
return nil
}
// Fail any rules that contain **
if strings.Contains(rule, "**") {
return errors.New("double-star (**) syntax is not supported")
}
// Fail any patterns that can't compile. A non-empty string must be
// given to Match() to avoid optimization that skips rule evaluation.
if _, err := filepath.Match(rule, "abc"); err != nil {
return err
}
p := &pattern{raw: rule}
// Negation is handled at a higher level, so strip the leading ! from the
// string.
if strings.HasPrefix(rule, "!") {
p.negate = true
rule = rule[1:]
}
// Directory verification is handled by a higher level, so the trailing /
// is removed from the rule. That way, a directory named "foo" matches,
// even if the supplied string does not contain a literal slash character.
if strings.HasSuffix(rule, "/") {
p.mustDir = true
rule = strings.TrimSuffix(rule, "/")
}
if strings.HasPrefix(rule, "/") {
// Require path matches the root path.
p.match = func(n string, fi os.FileInfo) bool {
rule = strings.TrimPrefix(rule, "/")
ok, err := filepath.Match(rule, n)
if err != nil {
log.Printf("Failed to compile %q: %s", rule, err)
return false
}
return ok
}
} else if strings.Contains(rule, "/") {
// require structural match.
p.match = func(n string, fi os.FileInfo) bool {
ok, err := filepath.Match(rule, n)
if err != nil {
log.Printf("Failed to compile %q: %s", rule, err)
return false
}
return ok
}
} else {
p.match = func(n string, fi os.FileInfo) bool {
// When there is no slash in the pattern, we evaluate ONLY the
// filename.
n = filepath.Base(n)
ok, err := filepath.Match(rule, n)
if err != nil {
log.Printf("Failed to compile %q: %s", rule, err)
return false
}
return ok
}
}
r.patterns = append(r.patterns, p)
return nil
}
// matcher is a function capable of computing a match.
//
// It returns true if the rule matches.
type matcher func(name string, fi os.FileInfo) bool
// pattern describes a pattern to be matched in a rule set.
type pattern struct {
// raw is the unparsed string, with nothing stripped.
raw string
// match is the matcher function.
match matcher
// negate indicates that the rule's outcome should be negated.
negate bool
// mustDir indicates that the matched file must be a directory.
mustDir bool
}

@ -1,155 +0,0 @@
/*
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 ignore
import (
"bytes"
"os"
"path/filepath"
"testing"
)
var testdata = "./testdata"
func TestParse(t *testing.T) {
rules := `#ignore
#ignore
foo
bar/*
baz/bar/foo.txt
one/more
`
r, err := parseString(rules)
if err != nil {
t.Fatalf("Error parsing rules: %s", err)
}
if len(r.patterns) != 4 {
t.Errorf("Expected 4 rules, got %d", len(r.patterns))
}
expects := []string{"foo", "bar/*", "baz/bar/foo.txt", "one/more"}
for i, p := range r.patterns {
if p.raw != expects[i] {
t.Errorf("Expected %q, got %q", expects[i], p.raw)
}
if p.match == nil {
t.Errorf("Expected %s to have a matcher function.", p.raw)
}
}
}
func TestParseFail(t *testing.T) {
shouldFail := []string{"foo/**/bar", "[z-"}
for _, fail := range shouldFail {
_, err := parseString(fail)
if err == nil {
t.Errorf("Rule %q should have failed", fail)
}
}
}
func TestParseFile(t *testing.T) {
f := filepath.Join(testdata, HelmIgnore)
if _, err := os.Stat(f); err != nil {
t.Fatalf("Fixture %s missing: %s", f, err)
}
r, err := ParseFile(f)
if err != nil {
t.Fatalf("Failed to parse rules file: %s", err)
}
if len(r.patterns) != 3 {
t.Errorf("Expected 3 patterns, got %d", len(r.patterns))
}
}
func TestIgnore(t *testing.T) {
// Test table: Given pattern and name, Ignore should return expect.
tests := []struct {
pattern string
name string
expect bool
}{
// Glob tests
{`helm.txt`, "helm.txt", true},
{`helm.*`, "helm.txt", true},
{`helm.*`, "rudder.txt", false},
{`*.txt`, "tiller.txt", true},
{`*.txt`, "cargo/a.txt", true},
{`cargo/*.txt`, "cargo/a.txt", true},
{`cargo/*.*`, "cargo/a.txt", true},
{`cargo/*.txt`, "mast/a.txt", false},
{`ru[c-e]?er.txt`, "rudder.txt", true},
{`templates/.?*`, "templates/.dotfile", true},
// "." should never get ignored. https://github.com/helm/helm/issues/1776
{`.*`, ".", false},
{`.*`, "./", false},
{`.*`, ".joonix", true},
{`.*`, "helm.txt", false},
{`.*`, "", false},
// Directory tests
{`cargo/`, "cargo", true},
{`cargo/`, "cargo/", true},
{`cargo/`, "mast/", false},
{`helm.txt/`, "helm.txt", false},
// Negation tests
{`!helm.txt`, "helm.txt", false},
{`!helm.txt`, "tiller.txt", true},
{`!*.txt`, "cargo", true},
{`!cargo/`, "mast/", true},
// Absolute path tests
{`/a.txt`, "a.txt", true},
{`/a.txt`, "cargo/a.txt", false},
{`/cargo/a.txt`, "cargo/a.txt", true},
}
for _, test := range tests {
r, err := parseString(test.pattern)
if err != nil {
t.Fatalf("Failed to parse: %s", err)
}
fi, err := os.Stat(filepath.Join(testdata, test.name))
if err != nil {
t.Fatalf("Fixture missing: %s", err)
}
if r.Ignore(test.name, fi) != test.expect {
t.Errorf("Expected %q to be %v for pattern %q", test.name, test.expect, test.pattern)
}
}
}
func TestAddDefaults(t *testing.T) {
r := Rules{}
r.AddDefaults()
if len(r.patterns) != 1 {
t.Errorf("Expected 1 default patterns, got %d", len(r.patterns))
}
}
func parseString(str string) (*Rules, error) {
b := bytes.NewBuffer([]byte(str))
return Parse(b)
}

@ -1,3 +0,0 @@
mast/a.txt
.DS_Store
.git

@ -1,68 +0,0 @@
/*
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
}

@ -1,31 +0,0 @@
/*
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)
}
}

@ -1,22 +0,0 @@
/*
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 a Monocular
// compatible search API endpoint. For example, as implemented by the Artifact
// Hub.
//
// This is a library for interacting with a monocular compatible search API
package monocular

@ -1,145 +0,0 @@
/*
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/v3/internal/version"
"helm.sh/helm/v3/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"`
ArtifactHub ArtifactHub `json:"artifactHub"`
Type string `json:"type"`
Attributes Chart `json:"attributes"`
Links Links `json:"links"`
Relationships Relationships `json:"relationships"`
}
// ArtifactHub represents data specific to Artifact Hub instances
type ArtifactHub struct {
PackageURL string `json:"packageUrl"`
}
// 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 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"`
}

@ -1,49 +0,0 @@
/*
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://charts.helm.sh/stable"},"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://charts.helm.sh/stable/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")
}
}

@ -1,263 +0,0 @@
/*
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 resolver
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo"
)
// Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct {
chartpath string
cachepath string
registryClient *registry.Client
}
// New creates a new resolver for a given chart, helm home and registry client.
func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver {
return &Resolver{
chartpath: chartpath,
cachepath: cachepath,
registryClient: registryClient,
}
}
// Resolve resolves dependencies and returns a lock file with the resolution.
func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
// Now we clone the dependencies, locking as we go.
locked := make([]*chart.Dependency, len(reqs))
missing := []string{}
for i, d := range reqs {
constraint, err := semver.NewConstraint(d.Version)
if err != nil {
return nil, errors.Wrapf(err, "dependency %q has an invalid version/constraint format", d.Name)
}
if d.Repository == "" {
// Local chart subfolder
if _, err := GetLocalPath(filepath.Join("charts", d.Name), r.chartpath); err != nil {
return nil, err
}
locked[i] = &chart.Dependency{
Name: d.Name,
Repository: "",
Version: d.Version,
}
continue
}
if strings.HasPrefix(d.Repository, "file://") {
chartpath, err := GetLocalPath(d.Repository, r.chartpath)
if err != nil {
return nil, err
}
ch, err := loader.LoadDir(chartpath)
if err != nil {
return nil, err
}
v, err := semver.NewVersion(ch.Metadata.Version)
if err != nil {
// Not a legit entry.
continue
}
if !constraint.Check(v) {
missing = append(missing, d.Name)
continue
}
locked[i] = &chart.Dependency{
Name: d.Name,
Repository: d.Repository,
Version: ch.Metadata.Version,
}
continue
}
repoName := repoNames[d.Name]
// if the repository was not defined, but the dependency defines a repository url, bypass the cache
if repoName == "" && d.Repository != "" {
locked[i] = &chart.Dependency{
Name: d.Name,
Repository: d.Repository,
Version: d.Version,
}
continue
}
var vs repo.ChartVersions
var version string
var ok bool
found := true
if !registry.IsOCI(d.Repository) {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
if err != nil {
return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
}
vs, ok = repoIndex.Entries[d.Name]
if !ok {
return nil, errors.Errorf("%s chart not found in repo %s", d.Name, d.Repository)
}
found = false
} else {
version = d.Version
// Check to see if an explicit version has been provided
_, err := semver.NewVersion(version)
// Use an explicit version, otherwise search for tags
if err == nil {
vs = []*repo.ChartVersion{{
Metadata: &chart.Metadata{
Version: version,
},
}}
} else {
// Retrieve list of tags for repository
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
tags, err := r.registryClient.Tags(ref)
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve list of tags for repository %s", d.Repository)
}
vs = make(repo.ChartVersions, len(tags))
for ti, t := range tags {
// Mock chart version objects
version := &repo.ChartVersion{
Metadata: &chart.Metadata{
Version: t,
},
}
vs[ti] = version
}
}
}
locked[i] = &chart.Dependency{
Name: d.Name,
Repository: d.Repository,
Version: version,
}
// The version are already sorted and hence the first one to satisfy the constraint is used
for _, ver := range vs {
v, err := semver.NewVersion(ver.Version)
// OCI does not need URLs
if err != nil || (!registry.IsOCI(d.Repository) && len(ver.URLs) == 0) {
// Not a legit entry.
continue
}
if constraint.Check(v) {
found = true
locked[i].Version = v.Original()
break
}
}
if !found {
missing = append(missing, d.Name)
}
}
if len(missing) > 0 {
return nil, errors.Errorf("can't get a valid version for repositories %s. Try changing the version constraint in Chart.yaml", strings.Join(missing, ", "))
}
digest, err := HashReq(reqs, locked)
if err != nil {
return nil, err
}
return &chart.Lock{
Generated: time.Now(),
Digest: digest,
Dependencies: locked,
}, nil
}
// HashReq generates a hash of the dependencies.
//
// This should be used only to compare against another hash generated by this
// function.
func HashReq(req, lock []*chart.Dependency) (string, error) {
data, err := json.Marshal([2][]*chart.Dependency{req, lock})
if err != nil {
return "", err
}
s, err := provenance.Digest(bytes.NewBuffer(data))
return "sha256:" + s, err
}
// HashV2Req generates a hash of requirements generated in Helm v2.
//
// This should be used only to compare against another hash generated by the
// Helm v2 hash function. It is to handle issue:
// https://github.com/helm/helm/issues/7233
func HashV2Req(req []*chart.Dependency) (string, error) {
dep := make(map[string][]*chart.Dependency)
dep["dependencies"] = req
data, err := json.Marshal(dep)
if err != nil {
return "", err
}
s, err := provenance.Digest(bytes.NewBuffer(data))
return "sha256:" + s, err
}
// GetLocalPath generates absolute local path when use
// "file://" in repository of dependencies
func GetLocalPath(repo, chartpath string) (string, error) {
var depPath string
var err error
p := strings.TrimPrefix(repo, "file://")
// root path is absolute
if strings.HasPrefix(p, "/") {
if depPath, err = filepath.Abs(p); err != nil {
return "", err
}
} else {
depPath = filepath.Join(chartpath, p)
}
if _, err = os.Stat(depPath); os.IsNotExist(err) {
return "", errors.Errorf("directory %s not found", depPath)
} else if err != nil {
return "", err
}
return depPath, nil
}

@ -1,310 +0,0 @@
/*
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 resolver
import (
"runtime"
"testing"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/registry"
)
func TestResolve(t *testing.T) {
tests := []struct {
name string
req []*chart.Dependency
expect *chart.Lock
err bool
}{
{
name: "repo from invalid version",
req: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "1.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "0.1.0"},
},
},
err: true,
},
{
name: "version failure",
req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "http://example.com", Version: ">a1"},
},
err: true,
},
{
name: "cache index failure",
req: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "http://example.com", Version: "1.0.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "oedipus-rex", Repository: "http://example.com", Version: "1.0.0"},
},
},
},
{
name: "chart not found failure",
req: []*chart.Dependency{
{Name: "redis", Repository: "http://example.com", Version: "1.0.0"},
},
err: true,
},
{
name: "constraint not satisfied failure",
req: []*chart.Dependency{
{Name: "alpine", Repository: "http://example.com", Version: ">=1.0.0"},
},
err: true,
},
{
name: "valid lock",
req: []*chart.Dependency{
{Name: "alpine", Repository: "http://example.com", Version: ">=0.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "alpine", Repository: "http://example.com", Version: "0.2.0"},
},
},
},
{
name: "repo from valid local path",
req: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "0.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "0.1.0"},
},
},
},
{
name: "repo from valid local path with range resolution",
req: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "^0.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "base", Repository: "file://base", Version: "0.1.0"},
},
},
},
{
name: "repo from invalid local path",
req: []*chart.Dependency{
{Name: "nonexistent", Repository: "file://testdata/nonexistent", Version: "0.1.0"},
},
err: true,
},
{
name: "repo from valid path under charts path",
req: []*chart.Dependency{
{Name: "localdependency", Repository: "", Version: "0.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "localdependency", Repository: "", Version: "0.1.0"},
},
},
},
{
name: "repo from invalid path under charts path",
req: []*chart.Dependency{
{Name: "nonexistentdependency", Repository: "", Version: "0.1.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "nonexistentlocaldependency", Repository: "", Version: "0.1.0"},
},
},
err: true,
},
}
repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"}
registryClient, _ := registry.NewClient()
r := New("testdata/chartpath", "testdata/repository", registryClient)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l, err := r.Resolve(tt.req, repoNames)
if err != nil {
if tt.err {
return
}
t.Fatal(err)
}
if tt.err {
t.Fatalf("Expected error in test %q", tt.name)
}
if h, err := HashReq(tt.req, tt.expect.Dependencies); err != nil {
t.Fatal(err)
} else if h != l.Digest {
t.Errorf("%q: hashes don't match.", tt.name)
}
// Check fields.
if len(l.Dependencies) != len(tt.req) {
t.Errorf("%s: wrong number of dependencies in lock", tt.name)
}
d0 := l.Dependencies[0]
e0 := tt.expect.Dependencies[0]
if d0.Name != e0.Name {
t.Errorf("%s: expected name %s, got %s", tt.name, e0.Name, d0.Name)
}
if d0.Repository != e0.Repository {
t.Errorf("%s: expected repo %s, got %s", tt.name, e0.Repository, d0.Repository)
}
if d0.Version != e0.Version {
t.Errorf("%s: expected version %s, got %s", tt.name, e0.Version, d0.Version)
}
})
}
}
func TestHashReq(t *testing.T) {
expect := "sha256:fb239e836325c5fa14b29d1540a13b7d3ba13151b67fe719f820e0ef6d66aaaf"
tests := []struct {
name string
chartVersion string
lockVersion string
wantError bool
}{
{
name: "chart with the expected digest",
chartVersion: "0.1.0",
lockVersion: "0.1.0",
wantError: false,
},
{
name: "ranged version but same resolved lock version",
chartVersion: "^0.1.0",
lockVersion: "0.1.0",
wantError: true,
},
{
name: "ranged version resolved as higher version",
chartVersion: "^0.1.0",
lockVersion: "0.1.2",
wantError: true,
},
{
name: "different version",
chartVersion: "0.1.2",
lockVersion: "0.1.2",
wantError: true,
},
{
name: "different version with a range",
chartVersion: "^0.1.2",
lockVersion: "0.1.2",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := []*chart.Dependency{
{Name: "alpine", Version: tt.chartVersion, Repository: "http://localhost:8879/charts"},
}
lock := []*chart.Dependency{
{Name: "alpine", Version: tt.lockVersion, Repository: "http://localhost:8879/charts"},
}
h, err := HashReq(req, lock)
if err != nil {
t.Fatal(err)
}
if !tt.wantError && expect != h {
t.Errorf("Expected %q, got %q", expect, h)
} else if tt.wantError && expect == h {
t.Errorf("Expected not %q, but same", expect)
}
})
}
}
func TestGetLocalPath(t *testing.T) {
tests := []struct {
name string
repo string
chartpath string
expect string
winExpect string
err bool
}{
{
name: "absolute path",
repo: "file:////",
expect: "/",
winExpect: "\\",
},
{
name: "relative path",
repo: "file://../../testdata/chartpath/base",
chartpath: "foo/bar",
expect: "testdata/chartpath/base",
winExpect: "testdata\\chartpath\\base",
},
{
name: "current directory path",
repo: "../charts/localdependency",
chartpath: "testdata/chartpath/charts",
expect: "testdata/chartpath/charts/localdependency",
winExpect: "testdata\\chartpath\\charts\\localdependency",
},
{
name: "invalid local path",
repo: "file://testdata/nonexistent",
chartpath: "testdata/chartpath",
err: true,
},
{
name: "invalid path under current directory",
repo: "charts/nonexistentdependency",
chartpath: "testdata/chartpath/charts",
err: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p, err := GetLocalPath(tt.repo, tt.chartpath)
if err != nil {
if tt.err {
return
}
t.Fatal(err)
}
if tt.err {
t.Fatalf("Expected error in test %q", tt.name)
}
expect := tt.expect
if runtime.GOOS == "windows" {
expect = tt.winExpect
}
if p != expect {
t.Errorf("%q: expected %q, got %q", tt.name, expect, p)
}
})
}
}

@ -1,3 +0,0 @@
apiVersion: v2
name: base
version: 0.1.0

@ -1,3 +0,0 @@
description: A Helm chart for Kubernetes
name: localdependency
version: 0.1.0

@ -1,49 +0,0 @@
apiVersion: v1
entries:
alpine:
- name: alpine
urls:
- https://charts.helm.sh/stable/alpine-0.1.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
version: 0.2.0
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
icon: ""
apiVersion: v2
- name: alpine
urls:
- https://charts.helm.sh/stable/alpine-0.2.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
version: 0.1.0
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
icon: ""
apiVersion: v2
mariadb:
- name: mariadb
urls:
- https://charts.helm.sh/stable/mariadb-0.3.0.tgz
checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
home: https://mariadb.org
sources:
- https://github.com/bitnami/bitnami-docker-mariadb
version: 0.3.0
description: Chart for MariaDB
keywords:
- mariadb
- mysql
- database
- sql
maintainers:
- name: Bitnami
email: containers@bitnami.com
icon: ""
apiVersion: v2

@ -1,119 +0,0 @@
/*
Copyright (c) for portions of walk.go are held by The Go Authors, 2009 and are
provided under the BSD license.
https://github.com/golang/go/blob/master/LICENSE
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 sympath
import (
"log"
"os"
"path/filepath"
"sort"
"github.com/pkg/errors"
)
// Walk walks the file tree rooted at root, calling walkFn for each file or directory
// in the tree, including root. All errors that arise visiting files and directories
// are filtered by walkFn. The files are walked in lexical order, which makes the
// output deterministic but means that for very large directories Walk can be
// inefficient. Walk follows symbolic links.
func Walk(root string, walkFn filepath.WalkFunc) error {
info, err := os.Lstat(root)
if err != nil {
err = walkFn(root, nil, err)
} else {
err = symwalk(root, info, walkFn)
}
if err == filepath.SkipDir {
return nil
}
return err
}
// readDirNames reads the directory named by dirname and returns
// a sorted list of directory entries.
func readDirNames(dirname string) ([]string, error) {
f, err := os.Open(dirname)
if err != nil {
return nil, err
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
return nil, err
}
sort.Strings(names)
return names, nil
}
// symwalk recursively descends path, calling walkFn.
func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
// Recursively walk symlinked directories.
if IsSymlink(info) {
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
return errors.Wrapf(err, "error evaluating symlink %s", path)
}
log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved)
if info, err = os.Lstat(resolved); err != nil {
return err
}
if err := symwalk(path, info, walkFn); err != nil && err != filepath.SkipDir {
return err
}
return nil
}
if err := walkFn(path, info, nil); err != nil {
return err
}
if !info.IsDir() {
return nil
}
names, err := readDirNames(path)
if err != nil {
return walkFn(path, info, err)
}
for _, name := range names {
filename := filepath.Join(path, name)
fileInfo, err := os.Lstat(filename)
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = symwalk(filename, fileInfo, walkFn)
if err != nil {
if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir {
return err
}
}
}
}
return nil
}
// IsSymlink is used to determine if the fileinfo is a symbolic link.
func IsSymlink(fi os.FileInfo) bool {
return fi.Mode()&os.ModeSymlink != 0
}

@ -1,151 +0,0 @@
/*
Copyright (c) for portions of walk_test.go are held by The Go Authors, 2009 and are
provided under the BSD license.
https://github.com/golang/go/blob/master/LICENSE
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 sympath
import (
"os"
"path/filepath"
"testing"
)
type Node struct {
name string
entries []*Node // nil if the entry is a file
marks int
expectedMarks int
symLinkedTo string
}
var tree = &Node{
"testdata",
[]*Node{
{"a", nil, 0, 1, ""},
{"b", []*Node{}, 0, 1, ""},
{"c", nil, 0, 2, ""},
{"d", nil, 0, 0, "c"},
{
"e",
[]*Node{
{"x", nil, 0, 1, ""},
{"y", []*Node{}, 0, 1, ""},
{
"z",
[]*Node{
{"u", nil, 0, 1, ""},
{"v", nil, 0, 1, ""},
{"w", nil, 0, 1, ""},
},
0,
1,
"",
},
},
0,
1,
"",
},
},
0,
1,
"",
}
func walkTree(n *Node, path string, f func(path string, n *Node)) {
f(path, n)
for _, e := range n.entries {
walkTree(e, filepath.Join(path, e.name), f)
}
}
func makeTree(t *testing.T) {
walkTree(tree, tree.name, func(path string, n *Node) {
if n.entries == nil {
if n.symLinkedTo != "" {
if err := os.Symlink(n.symLinkedTo, path); err != nil {
t.Fatalf("makeTree: %v", err)
}
} else {
fd, err := os.Create(path)
if err != nil {
t.Fatalf("makeTree: %v", err)
return
}
fd.Close()
}
} else {
if err := os.Mkdir(path, 0770); err != nil {
t.Fatalf("makeTree: %v", err)
}
}
})
}
func checkMarks(t *testing.T, report bool) {
walkTree(tree, tree.name, func(path string, n *Node) {
if n.marks != n.expectedMarks && report {
t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks)
}
n.marks = 0
})
}
// Assumes that each node name is unique. Good enough for a test.
// If clear is true, any incoming error is cleared before return. The errors
// are always accumulated, though.
func mark(info os.FileInfo, err error, errors *[]error, clear bool) error {
if err != nil {
*errors = append(*errors, err)
if clear {
return nil
}
return err
}
name := info.Name()
walkTree(tree, tree.name, func(path string, n *Node) {
if n.name == name {
n.marks++
}
})
return nil
}
func TestWalk(t *testing.T) {
makeTree(t)
errors := make([]error, 0, 10)
clear := true
markFn := func(path string, info os.FileInfo, err error) error {
return mark(info, err, &errors, clear)
}
// Expect no errors.
err := Walk(tree.name, markFn)
if err != nil {
t.Fatalf("no error expected, found: %s", err)
}
if len(errors) != 0 {
t.Fatalf("unexpected errors: %s", errors)
}
checkMarks(t, true)
// cleanup
if err := os.RemoveAll(tree.name); err != nil {
t.Errorf("removeTree: %v", err)
}
}

@ -1,70 +0,0 @@
/*
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 ensure
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/helmpath/xdg"
)
// HelmHome sets up a Helm Home in a temp dir.
func HelmHome(t *testing.T) func() {
t.Helper()
base := TempDir(t)
os.Setenv(xdg.CacheHomeEnvVar, base)
os.Setenv(xdg.ConfigHomeEnvVar, base)
os.Setenv(xdg.DataHomeEnvVar, base)
os.Setenv(helmpath.CacheHomeEnvVar, "")
os.Setenv(helmpath.ConfigHomeEnvVar, "")
os.Setenv(helmpath.DataHomeEnvVar, "")
return func() {
os.RemoveAll(base)
}
}
// TempDir ensures a scratch test directory for unit testing purposes.
func TempDir(t *testing.T) string {
t.Helper()
d, err := ioutil.TempDir("", "helm")
if err != nil {
t.Fatal(err)
}
return d
}
// TempFile ensures a temp file for unit testing purposes.
//
// It returns the path to the directory (to which you will still need to join the filename)
//
// You must clean up the directory that is returned.
//
// tempdir := TempFile(t, "foo", []byte("bar"))
// defer os.RemoveAll(tempdir)
// filename := filepath.Join(tempdir, "foo")
func TempFile(t *testing.T, name string, data []byte) string {
path := TempDir(t)
filename := filepath.Join(path, name)
if err := ioutil.WriteFile(filename, data, 0755); err != nil {
t.Fatal(err)
}
return path
}

@ -1,96 +0,0 @@
/*
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 test
import (
"bytes"
"flag"
"io/ioutil"
"path/filepath"
"github.com/pkg/errors"
)
// UpdateGolden writes out the golden files with the latest values, rather than failing the test.
var updateGolden = flag.Bool("update", false, "update golden files")
// TestingT describes a testing object compatible with the critical functions from the testing.T type
type TestingT interface {
Fatal(...interface{})
Fatalf(string, ...interface{})
HelperT
}
// HelperT describes a test with a helper function
type HelperT interface {
Helper()
}
// AssertGoldenString asserts that the given string matches the contents of the given file.
func AssertGoldenString(t TestingT, actual, filename string) {
t.Helper()
if err := compare([]byte(actual), path(filename)); err != nil {
t.Fatalf("%v\n", err)
}
}
// AssertGoldenFile asserts that the content of the actual file matches the contents of the expected file
func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string) {
t.Helper()
actual, err := ioutil.ReadFile(actualFileName)
if err != nil {
t.Fatalf("%v", err)
}
AssertGoldenString(t, string(actual), expectedFilename)
}
func path(filename string) string {
if filepath.IsAbs(filename) {
return filename
}
return filepath.Join("testdata", filename)
}
func compare(actual []byte, filename string) error {
actual = normalize(actual)
if err := update(filename, actual); err != nil {
return err
}
expected, err := ioutil.ReadFile(filename)
if err != nil {
return errors.Wrapf(err, "unable to read testdata %s", filename)
}
expected = normalize(expected)
if !bytes.Equal(expected, actual) {
return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual)
}
return nil
}
func update(filename string, in []byte) error {
if !*updateGolden {
return nil
}
return ioutil.WriteFile(filename, normalize(in), 0666)
}
func normalize(in []byte) []byte {
return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1)
}

@ -1,372 +0,0 @@
/*
Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under
the BSD license.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package fs
import (
"io"
"os"
"path/filepath"
"runtime"
"syscall"
"github.com/pkg/errors"
)
// fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep.
// This code is copied from https://github.com/golang/dep/blob/37d6c560cdf407be7b6cd035b23dba89df9275cf/internal/fs/fs.go
// No changes to the code were made other than removing some unused functions
// RenameWithFallback attempts to rename a file or directory, but falls back to
// copying in the event of a cross-device link error. If the fallback copy
// succeeds, src is still removed, emulating normal rename behavior.
func RenameWithFallback(src, dst string) error {
_, err := os.Stat(src)
if err != nil {
return errors.Wrapf(err, "cannot stat %s", src)
}
err = os.Rename(src, dst)
if err == nil {
return nil
}
return renameFallback(err, src, dst)
}
// renameByCopy attempts to rename a file or directory by copying it to the
// destination and then removing the src thus emulating the rename behavior.
func renameByCopy(src, dst string) error {
var cerr error
if dir, _ := IsDir(src); dir {
cerr = CopyDir(src, dst)
if cerr != nil {
cerr = errors.Wrap(cerr, "copying directory failed")
}
} else {
cerr = copyFile(src, dst)
if cerr != nil {
cerr = errors.Wrap(cerr, "copying file failed")
}
}
if cerr != nil {
return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst)
}
return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src)
}
var (
errSrcNotDir = errors.New("source is not a directory")
errDstExist = errors.New("destination already exists")
)
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
func CopyDir(src, dst string) error {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
// We use os.Lstat() here to ensure we don't fall in a loop where a symlink
// actually links to a one of its parent directories.
fi, err := os.Lstat(src)
if err != nil {
return err
}
if !fi.IsDir() {
return errSrcNotDir
}
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
return errDstExist
}
if err = os.MkdirAll(dst, fi.Mode()); err != nil {
return errors.Wrapf(err, "cannot mkdir %s", dst)
}
entries, err := os.ReadDir(src)
if err != nil {
return errors.Wrapf(err, "cannot read directory %s", dst)
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
if err = CopyDir(srcPath, dstPath); err != nil {
return errors.Wrap(err, "copying directory failed")
}
} else {
// This will include symlinks, which is what we want when
// copying things.
if err = copyFile(srcPath, dstPath); err != nil {
return errors.Wrap(err, "copying file failed")
}
}
}
return nil
}
// copyFile copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all its contents will be replaced by the contents
// of the source file. The file mode will be copied from the source.
func copyFile(src, dst string) (err error) {
if sym, err := IsSymlink(src); err != nil {
return errors.Wrap(err, "symlink check failed")
} else if sym {
if err := cloneSymlink(src, dst); err != nil {
if runtime.GOOS == "windows" {
// If cloning the symlink fails on Windows because the user
// does not have the required privileges, ignore the error and
// fall back to copying the file contents.
//
// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
return err
}
} else {
return err
}
} else {
return nil
}
}
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
if _, err = io.Copy(out, in); err != nil {
out.Close()
return
}
// Check for write errors on Close
if err = out.Close(); err != nil {
return
}
si, err := os.Stat(src)
if err != nil {
return
}
// Temporary fix for Go < 1.9
//
// See: https://github.com/golang/dep/issues/774
// and https://github.com/golang/go/issues/20829
if runtime.GOOS == "windows" {
dst = fixLongPath(dst)
}
err = os.Chmod(dst, si.Mode())
return
}
// cloneSymlink will create a new symlink that points to the resolved path of sl.
// If sl is a relative symlink, dst will also be a relative symlink.
func cloneSymlink(sl, dst string) error {
resolved, err := os.Readlink(sl)
if err != nil {
return err
}
return os.Symlink(resolved, dst)
}
// IsDir determines is the path given is a directory or not.
func IsDir(name string) (bool, error) {
fi, err := os.Stat(name)
if err != nil {
return false, err
}
if !fi.IsDir() {
return false, errors.Errorf("%q is not a directory", name)
}
return true, nil
}
// IsSymlink determines if the given path is a symbolic link.
func IsSymlink(path string) (bool, error) {
l, err := os.Lstat(path)
if err != nil {
return false, err
}
return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil
}
// fixLongPath returns the extended-length (\\?\-prefixed) form of
// path when needed, in order to avoid the default 260 character file
// path limit imposed by Windows. If path is not easily converted to
// the extended-length form (for example, if path is a relative path
// or contains .. elements), or is short enough, fixLongPath returns
// path unmodified.
//
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
func fixLongPath(path string) string {
// Do nothing (and don't allocate) if the path is "short".
// Empirically (at least on the Windows Server 2013 builder),
// the kernel is arbitrarily okay with < 248 bytes. That
// matches what the docs above say:
// "When using an API to create a directory, the specified
// path cannot be so long that you cannot append an 8.3 file
// name (that is, the directory name cannot exceed MAX_PATH
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
//
// The MSDN docs appear to say that a normal path that is 248 bytes long
// will work; empirically the path must be less then 248 bytes long.
if len(path) < 248 {
// Don't fix. (This is how Go 1.7 and earlier worked,
// not automatically generating the \\?\ form)
return path
}
// The extended form begins with \\?\, as in
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
// The extended form disables evaluation of . and .. path
// elements and disables the interpretation of / as equivalent
// to \. The conversion here rewrites / to \ and elides
// . elements as well as trailing or duplicate separators. For
// simplicity it avoids the conversion entirely for relative
// paths or paths containing .. elements. For now,
// \\server\share paths are not converted to
// \\?\UNC\server\share paths because the rules for doing so
// are less well-specified.
if len(path) >= 2 && path[:2] == `\\` {
// Don't canonicalize UNC paths.
return path
}
if !isAbs(path) {
// Relative path
return path
}
const prefix = `\\?`
pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
copy(pathbuf, prefix)
n := len(path)
r, w := 0, len(prefix)
for r < n {
switch {
case os.IsPathSeparator(path[r]):
// empty block
r++
case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
// /./
r++
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
// /../ is currently unhandled
return path
default:
pathbuf[w] = '\\'
w++
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
pathbuf[w] = path[r]
w++
}
}
}
// A drive's root directory needs a trailing \
if w == len(`\\?\c:`) {
pathbuf[w] = '\\'
w++
}
return string(pathbuf[:w])
}
func isAbs(path string) (b bool) {
v := volumeName(path)
if v == "" {
return false
}
path = path[len(v):]
if path == "" {
return false
}
return os.IsPathSeparator(path[0])
}
func volumeName(path string) (v string) {
if len(path) < 2 {
return ""
}
// with drive letter
c := path[0]
if path[1] == ':' &&
('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
'A' <= c && c <= 'Z') {
return path[:2]
}
// is it UNC
if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) &&
!os.IsPathSeparator(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if os.IsPathSeparator(path[n]) {
n++
// third, following something characters. its share name.
if !os.IsPathSeparator(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if os.IsPathSeparator(path[n]) {
break
}
}
return path[:n]
}
break
}
}
}
return ""
}

@ -1,643 +0,0 @@
/*
Copyright (c) for portions of fs_test.go are held by The Go Authors, 2016 and are provided under
the BSD license.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package fs
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"
)
var (
mu sync.Mutex
)
func TestRenameWithFallback(t *testing.T) {
dir := t.TempDir()
if err := RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil {
t.Fatal("expected an error for non existing file, but got nil")
}
srcpath := filepath.Join(dir, "src")
if srcf, err := os.Create(srcpath); err != nil {
t.Fatal(err)
} else {
srcf.Close()
}
if err := RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil {
t.Fatal(err)
}
srcpath = filepath.Join(dir, "a")
if err := os.MkdirAll(srcpath, 0777); err != nil {
t.Fatal(err)
}
dstpath := filepath.Join(dir, "b")
if err := os.MkdirAll(dstpath, 0777); err != nil {
t.Fatal(err)
}
if err := RenameWithFallback(srcpath, dstpath); err == nil {
t.Fatal("expected an error if dst is an existing directory, but got nil")
}
}
func TestCopyDir(t *testing.T) {
dir := t.TempDir()
srcdir := filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0755); err != nil {
t.Fatal(err)
}
files := []struct {
path string
contents string
fi os.FileInfo
}{
{path: "myfile", contents: "hello world"},
{path: filepath.Join("subdir", "file"), contents: "subdir file"},
}
// Create structure indicated in 'files'
for i, file := range files {
fn := filepath.Join(srcdir, file.path)
dn := filepath.Dir(fn)
if err := os.MkdirAll(dn, 0755); err != nil {
t.Fatal(err)
}
fh, err := os.Create(fn)
if err != nil {
t.Fatal(err)
}
if _, err = fh.Write([]byte(file.contents)); err != nil {
t.Fatal(err)
}
fh.Close()
files[i].fi, err = os.Stat(fn)
if err != nil {
t.Fatal(err)
}
}
destdir := filepath.Join(dir, "dest")
if err := CopyDir(srcdir, destdir); err != nil {
t.Fatal(err)
}
// Compare copy against structure indicated in 'files'
for _, file := range files {
fn := filepath.Join(srcdir, file.path)
dn := filepath.Dir(fn)
dirOK, err := IsDir(dn)
if err != nil {
t.Fatal(err)
}
if !dirOK {
t.Fatalf("expected %s to be a directory", dn)
}
got, err := ioutil.ReadFile(fn)
if err != nil {
t.Fatal(err)
}
if file.contents != string(got) {
t.Fatalf("expected: %s, got: %s", file.contents, string(got))
}
gotinfo, err := os.Stat(fn)
if err != nil {
t.Fatal(err)
}
if file.fi.Mode() != gotinfo.Mode() {
t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v",
file.path, file.fi.Mode(), fn, gotinfo.Mode())
}
}
}
func TestCopyDirFail_SrcInaccessible(t *testing.T) {
if runtime.GOOS == "windows" {
// XXX: setting permissions works differently in
// Microsoft Windows. Skipping this until a
// compatible implementation is provided.
t.Skip("skipping on windows")
}
var currentUID = os.Getuid()
if currentUID == 0 {
// Skipping if root, because all files are accessible
t.Skip("Skipping for root user")
}
var srcdir, dstdir string
cleanup := setupInaccessibleDir(t, func(dir string) error {
srcdir = filepath.Join(dir, "src")
return os.MkdirAll(srcdir, 0755)
})
defer cleanup()
dir := t.TempDir()
dstdir = filepath.Join(dir, "dst")
if err := CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
}
func TestCopyDirFail_DstInaccessible(t *testing.T) {
if runtime.GOOS == "windows" {
// XXX: setting permissions works differently in
// Microsoft Windows. Skipping this until a
// compatible implementation is provided.
t.Skip("skipping on windows")
}
var currentUID = os.Getuid()
if currentUID == 0 {
// Skipping if root, because all files are accessible
t.Skip("Skipping for root user")
}
var srcdir, dstdir string
dir := t.TempDir()
srcdir = filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0755); err != nil {
t.Fatal(err)
}
cleanup := setupInaccessibleDir(t, func(dir string) error {
dstdir = filepath.Join(dir, "dst")
return nil
})
defer cleanup()
if err := CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
}
func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
var srcdir, dstdir string
var err error
dir := t.TempDir()
srcdir = filepath.Join(dir, "src")
if _, err = os.Create(srcdir); err != nil {
t.Fatal(err)
}
dstdir = filepath.Join(dir, "dst")
if err = CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
if err != errSrcNotDir {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
}
}
func TestCopyDirFail_DstExists(t *testing.T) {
var srcdir, dstdir string
var err error
dir := t.TempDir()
srcdir = filepath.Join(dir, "src")
if err = os.MkdirAll(srcdir, 0755); err != nil {
t.Fatal(err)
}
dstdir = filepath.Join(dir, "dst")
if err = os.MkdirAll(dstdir, 0755); err != nil {
t.Fatal(err)
}
if err = CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
if err != errDstExist {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err)
}
}
func TestCopyDirFailOpen(t *testing.T) {
if runtime.GOOS == "windows" {
// XXX: setting permissions works differently in
// Microsoft Windows. os.Chmod(..., 0222) below is not
// enough for the file to be readonly, and os.Chmod(...,
// 0000) returns an invalid argument error. Skipping
// this until a compatible implementation is
// provided.
t.Skip("skipping on windows")
}
var currentUID = os.Getuid()
if currentUID == 0 {
// Skipping if root, because all files are accessible
t.Skip("Skipping for root user")
}
var srcdir, dstdir string
dir := t.TempDir()
srcdir = filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0755); err != nil {
t.Fatal(err)
}
srcfn := filepath.Join(srcdir, "file")
srcf, err := os.Create(srcfn)
if err != nil {
t.Fatal(err)
}
srcf.Close()
// setup source file so that it cannot be read
if err = os.Chmod(srcfn, 0222); err != nil {
t.Fatal(err)
}
dstdir = filepath.Join(dir, "dst")
if err = CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
}
func TestCopyFile(t *testing.T) {
dir := t.TempDir()
srcf, err := os.Create(filepath.Join(dir, "srcfile"))
if err != nil {
t.Fatal(err)
}
want := "hello world"
if _, err := srcf.Write([]byte(want)); err != nil {
t.Fatal(err)
}
srcf.Close()
destf := filepath.Join(dir, "destf")
if err := copyFile(srcf.Name(), destf); err != nil {
t.Fatal(err)
}
got, err := ioutil.ReadFile(destf)
if err != nil {
t.Fatal(err)
}
if want != string(got) {
t.Fatalf("expected: %s, got: %s", want, string(got))
}
wantinfo, err := os.Stat(srcf.Name())
if err != nil {
t.Fatal(err)
}
gotinfo, err := os.Stat(destf)
if err != nil {
t.Fatal(err)
}
if wantinfo.Mode() != gotinfo.Mode() {
t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode())
}
}
func cleanUpDir(dir string) {
// NOTE(mattn): It seems that sometimes git.exe is not dead
// when cleanUpDir() is called. But we do not know any way to wait for it.
if runtime.GOOS == "windows" {
mu.Lock()
exec.Command(`taskkill`, `/F`, `/IM`, `git.exe`).Run()
mu.Unlock()
}
if dir != "" {
os.RemoveAll(dir)
}
}
func TestCopyFileSymlink(t *testing.T) {
tempdir := t.TempDir()
testcases := map[string]string{
filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(tempdir, "dst-file"),
filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(tempdir, "windows-dst-file"),
filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(tempdir, "invalid-symlink"),
}
for symlink, dst := range testcases {
t.Run(symlink, func(t *testing.T) {
var err error
if err = copyFile(symlink, dst); err != nil {
t.Fatalf("failed to copy symlink: %s", err)
}
var want, got string
if runtime.GOOS == "windows" {
// Creating symlinks on Windows require an additional permission
// regular users aren't granted usually. So we copy the file
// content as a fall back instead of creating a real symlink.
srcb, err := ioutil.ReadFile(symlink)
if err != nil {
t.Fatalf("%+v", err)
}
dstb, err := ioutil.ReadFile(dst)
if err != nil {
t.Fatalf("%+v", err)
}
want = string(srcb)
got = string(dstb)
} else {
want, err = os.Readlink(symlink)
if err != nil {
t.Fatalf("%+v", err)
}
got, err = os.Readlink(dst)
if err != nil {
t.Fatalf("could not resolve symlink: %s", err)
}
}
if want != got {
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
}
})
}
}
func TestCopyFileFail(t *testing.T) {
if runtime.GOOS == "windows" {
// XXX: setting permissions works differently in
// Microsoft Windows. Skipping this until a
// compatible implementation is provided.
t.Skip("skipping on windows")
}
var currentUID = os.Getuid()
if currentUID == 0 {
// Skipping if root, because all files are accessible
t.Skip("Skipping for root user")
}
dir := t.TempDir()
srcf, err := os.Create(filepath.Join(dir, "srcfile"))
if err != nil {
t.Fatal(err)
}
srcf.Close()
var dstdir string
cleanup := setupInaccessibleDir(t, func(dir string) error {
dstdir = filepath.Join(dir, "dir")
return os.Mkdir(dstdir, 0777)
})
defer cleanup()
fn := filepath.Join(dstdir, "file")
if err := copyFile(srcf.Name(), fn); err == nil {
t.Fatalf("expected error for %s, got none", fn)
}
}
// setupInaccessibleDir creates a temporary location with a single
// directory in it, in such a way that directory is not accessible
// after this function returns.
//
// op is called with the directory as argument, so that it can create
// files or other test artifacts.
//
// If setupInaccessibleDir fails in its preparation, or op fails, t.Fatal
// will be invoked.
//
// This function returns a cleanup function that removes all the temporary
// files this function creates. It is the caller's responsibility to call
// this function before the test is done running, whether there's an error or not.
func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
dir := t.TempDir()
subdir := filepath.Join(dir, "dir")
cleanup := func() {
if err := os.Chmod(subdir, 0777); err != nil {
t.Error(err)
}
}
if err := os.Mkdir(subdir, 0777); err != nil {
cleanup()
t.Fatal(err)
return nil
}
if err := op(subdir); err != nil {
cleanup()
t.Fatal(err)
return nil
}
if err := os.Chmod(subdir, 0666); err != nil {
cleanup()
t.Fatal(err)
return nil
}
return cleanup
}
func TestIsDir(t *testing.T) {
var currentUID = os.Getuid()
if currentUID == 0 {
// Skipping if root, because all files are accessible
t.Skip("Skipping for root user")
}
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
var dn string
cleanup := setupInaccessibleDir(t, func(dir string) error {
dn = filepath.Join(dir, "dir")
return os.Mkdir(dn, 0777)
})
defer cleanup()
tests := map[string]struct {
exists bool
err bool
}{
wd: {true, false},
filepath.Join(wd, "testdata"): {true, false},
filepath.Join(wd, "main.go"): {false, true},
filepath.Join(wd, "this_file_does_not_exist.thing"): {false, true},
dn: {false, true},
}
if runtime.GOOS == "windows" {
// This test doesn't work on Microsoft Windows because
// of the differences in how file permissions are
// implemented. For this to work, the directory where
// the directory exists should be inaccessible.
delete(tests, dn)
}
for f, want := range tests {
got, err := IsDir(f)
if err != nil && !want.err {
t.Fatalf("expected no error, got %v", err)
}
if got != want.exists {
t.Fatalf("expected %t for %s, got %t", want.exists, f, got)
}
}
}
func TestIsSymlink(t *testing.T) {
var currentUID = os.Getuid()
if currentUID == 0 {
// Skipping if root, because all files are accessible
t.Skip("Skipping for root user")
}
dir := t.TempDir()
dirPath := filepath.Join(dir, "directory")
if err := os.MkdirAll(dirPath, 0777); err != nil {
t.Fatal(err)
}
filePath := filepath.Join(dir, "file")
f, err := os.Create(filePath)
if err != nil {
t.Fatal(err)
}
f.Close()
dirSymlink := filepath.Join(dir, "dirSymlink")
fileSymlink := filepath.Join(dir, "fileSymlink")
if err = os.Symlink(dirPath, dirSymlink); err != nil {
t.Fatal(err)
}
if err = os.Symlink(filePath, fileSymlink); err != nil {
t.Fatal(err)
}
var (
inaccessibleFile string
inaccessibleSymlink string
)
cleanup := setupInaccessibleDir(t, func(dir string) error {
inaccessibleFile = filepath.Join(dir, "file")
if fh, err := os.Create(inaccessibleFile); err != nil {
return err
} else if err = fh.Close(); err != nil {
return err
}
inaccessibleSymlink = filepath.Join(dir, "symlink")
return os.Symlink(inaccessibleFile, inaccessibleSymlink)
})
defer cleanup()
tests := map[string]struct{ expected, err bool }{
dirPath: {false, false},
filePath: {false, false},
dirSymlink: {true, false},
fileSymlink: {true, false},
inaccessibleFile: {false, true},
inaccessibleSymlink: {false, true},
}
if runtime.GOOS == "windows" {
// XXX: setting permissions works differently in Windows. Skipping
// these cases until a compatible implementation is provided.
delete(tests, inaccessibleFile)
delete(tests, inaccessibleSymlink)
}
for path, want := range tests {
got, err := IsSymlink(path)
if err != nil {
if !want.err {
t.Errorf("expected no error, got %v", err)
}
}
if got != want.expected {
t.Errorf("expected %t for %s, got %t", want.expected, path, got)
}
}
}

@ -1,58 +0,0 @@
//go:build !windows
/*
Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under
the BSD license.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package fs
import (
"os"
"syscall"
"github.com/pkg/errors"
)
// renameFallback attempts to determine the appropriate fallback to failed rename
// operation depending on the resulting error.
func renameFallback(err error, src, dst string) error {
// Rename may fail if src and dst are on different devices; fall back to
// copy if we detect that case. syscall.EXDEV is the common name for the
// cross device link error which has varying output text across different
// operating systems.
terr, ok := err.(*os.LinkError)
if !ok {
return err
} else if terr.Err != syscall.EXDEV {
return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst)
}
return renameByCopy(src, dst)
}

@ -1,69 +0,0 @@
//go:build windows
/*
Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under
the BSD license.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package fs
import (
"os"
"syscall"
"github.com/pkg/errors"
)
// renameFallback attempts to determine the appropriate fallback to failed rename
// operation depending on the resulting error.
func renameFallback(err error, src, dst string) error {
// Rename may fail if src and dst are on different devices; fall back to
// copy if we detect that case. syscall.EXDEV is the common name for the
// cross device link error which has varying output text across different
// operating systems.
terr, ok := err.(*os.LinkError)
if !ok {
return err
}
if terr.Err != syscall.EXDEV {
// In windows it can drop down to an operating system call that
// returns an operating system error with a different number and
// message. Checking for that as a fall back.
noerr, ok := terr.Err.(syscall.Errno)
// 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error.
// See https://msdn.microsoft.com/en-us/library/cc231199.aspx
if ok && noerr != 0x11 {
return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst)
}
}
return renameByCopy(src, dst)
}

@ -1 +0,0 @@
C:/Users/ibrahim/go/src/github.com/golang/dep/internal/fs/testdata/test.file

@ -1,178 +0,0 @@
/*
Copyright 2016 The Kubernetes 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 util
import (
"context"
"sort"
apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
)
// deploymentutil contains a copy of a few functions from Kubernetes controller code to avoid a dependency on k8s.io/kubernetes.
// This code is copied from https://github.com/kubernetes/kubernetes/blob/e856613dd5bb00bcfaca6974431151b5c06cbed5/pkg/controller/deployment/util/deployment_util.go
// No changes to the code were made other than removing some unused functions
// RsListFunc returns the ReplicaSet from the ReplicaSet namespace and the List metav1.ListOptions.
type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error)
// ListReplicaSets returns a slice of RSes the given deployment targets.
// Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
// because only the controller itself should do that.
// However, it does filter out anything whose ControllerRef doesn't match.
func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) {
// TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
// should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830.
namespace := deployment.Namespace
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
if err != nil {
return nil, err
}
options := metav1.ListOptions{LabelSelector: selector.String()}
all, err := getRSList(namespace, options)
if err != nil {
return nil, err
}
// Only include those whose ControllerRef matches the Deployment.
owned := make([]*apps.ReplicaSet, 0, len(all))
for _, rs := range all {
if metav1.IsControlledBy(rs, deployment) {
owned = append(owned, rs)
}
}
return owned, nil
}
// ReplicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
type ReplicaSetsByCreationTimestamp []*apps.ReplicaSet
func (o ReplicaSetsByCreationTimestamp) Len() int { return len(o) }
func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool {
if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
return o[i].Name < o[j].Name
}
return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
}
// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet {
sort.Sort(ReplicaSetsByCreationTimestamp(rsList))
for i := range rsList {
if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) {
// In rare cases, such as after cluster upgrades, Deployment may end up with
// having more than one new ReplicaSets that have the same template as its template,
// see https://github.com/kubernetes/kubernetes/issues/40415
// We deterministically choose the oldest new ReplicaSet.
return rsList[i]
}
}
// new ReplicaSet does not exist.
return nil
}
// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because:
// 1. The hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// 2. The deployment template won't have hash labels
func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
t1Copy := template1.DeepCopy()
t2Copy := template2.DeepCopy()
// Remove hash labels from template.Labels before comparing
delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
}
// GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface.
// Returns nil if the new replica set doesn't exist yet.
func GetNewReplicaSet(deployment *apps.Deployment, c appsclient.AppsV1Interface) (*apps.ReplicaSet, error) {
rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
if err != nil {
return nil, err
}
return FindNewReplicaSet(deployment, rsList), nil
}
// RsListFromClient returns an rsListFunc that wraps the given client.
func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc {
return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) {
rsList, err := c.ReplicaSets(namespace).List(context.Background(), options)
if err != nil {
return nil, err
}
var ret []*apps.ReplicaSet
for i := range rsList.Items {
ret = append(ret, &rsList.Items[i])
}
return ret, err
}
}
// IsRollingUpdate returns true if the strategy type is a rolling update.
func IsRollingUpdate(deployment *apps.Deployment) bool {
return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType
}
// MaxUnavailable returns the maximum unavailable pods a rolling deployment can take.
func MaxUnavailable(deployment apps.Deployment) int32 {
if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 {
return int32(0)
}
// Error caught by validation
_, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
if maxUnavailable > *deployment.Spec.Replicas {
return *deployment.Spec.Replicas
}
return maxUnavailable
}
// ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
// step. For example:
//
// 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
// 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
// 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
// 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
// 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
// 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
surge, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt(0)), int(desired), true)
if err != nil {
return 0, 0, err
}
unavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt(0)), int(desired), false)
if err != nil {
return 0, 0, err
}
if surge == 0 && unavailable == 0 {
// Validation should never allow the user to explicitly use zero values for both maxSurge
// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
// theory that surge might not work due to quota.
unavailable = 1
}
return int32(surge), int32(unavailable), nil
}

@ -1,58 +0,0 @@
/*
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 tlsutil
import (
"crypto/tls"
"crypto/x509"
"os"
"github.com/pkg/errors"
)
// Options represents configurable options used to create client and server TLS configurations.
type Options struct {
CaCertFile string
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them.
KeyFile string
CertFile string
// Client-only options
InsecureSkipVerify bool
}
// ClientConfig returns a TLS configuration for use by a Helm client.
func ClientConfig(opts Options) (cfg *tls.Config, err error) {
var cert *tls.Certificate
var pool *x509.CertPool
if opts.CertFile != "" || opts.KeyFile != "" {
if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
if os.IsNotExist(err) {
return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
}
return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
}
}
if !opts.InsecureSkipVerify && opts.CaCertFile != "" {
if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil {
return nil, err
}
}
cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
return cfg, nil
}

@ -1,76 +0,0 @@
/*
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 tlsutil
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"github.com/pkg/errors"
)
// NewClientTLS returns tls.Config appropriate for client auth.
func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) {
config := tls.Config{}
if certFile != "" && keyFile != "" {
cert, err := CertFromFilePair(certFile, keyFile)
if err != nil {
return nil, err
}
config.Certificates = []tls.Certificate{*cert}
}
if caFile != "" {
cp, err := CertPoolFromFile(caFile)
if err != nil {
return nil, err
}
config.RootCAs = cp
}
return &config, nil
}
// CertPoolFromFile returns an x509.CertPool containing the certificates
// in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not
// be parsed, or if the file does not contain any certificates
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Errorf("can't read CA file: %v", filename)
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, errors.Errorf("failed to append certificates from file: %s", filename)
}
return cp, nil
}
// CertFromFilePair returns an tls.Certificate containing the
// certificates public/private key pair from a pair of given PEM-encoded files.
// Returns an error if the file could not be read, a certificate could not
// be parsed, or if the file does not contain any certificates
func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, errors.Wrapf(err, "can't load key pair from cert %s and key %s", certFile, keyFile)
}
return &cert, err
}

@ -1,113 +0,0 @@
/*
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 tlsutil
import (
"path/filepath"
"testing"
)
const tlsTestDir = "../../testdata"
const (
testCaCertFile = "rootca.crt"
testCertFile = "crt.pem"
testKeyFile = "key.pem"
)
func TestClientConfig(t *testing.T) {
opts := Options{
CaCertFile: testfile(t, testCaCertFile),
CertFile: testfile(t, testCertFile),
KeyFile: testfile(t, testKeyFile),
InsecureSkipVerify: false,
}
cfg, err := ClientConfig(opts)
if err != nil {
t.Fatalf("error building tls client config: %v", err)
}
if got := len(cfg.Certificates); got != 1 {
t.Fatalf("expecting 1 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mismatch, expecting false")
}
if cfg.RootCAs == nil {
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
}
}
func testfile(t *testing.T, file string) (path string) {
var err error
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
}
return path
}
func TestNewClientTLS(t *testing.T) {
certFile := testfile(t, testCertFile)
keyFile := testfile(t, testKeyFile)
caCertFile := testfile(t, testCaCertFile)
cfg, err := NewClientTLS(certFile, keyFile, caCertFile)
if err != nil {
t.Error(err)
}
if got := len(cfg.Certificates); got != 1 {
t.Fatalf("expecting 1 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mismatch, expecting false")
}
if cfg.RootCAs == nil {
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
}
cfg, err = NewClientTLS("", "", caCertFile)
if err != nil {
t.Error(err)
}
if got := len(cfg.Certificates); got != 0 {
t.Fatalf("expecting 0 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mismatch, expecting false")
}
if cfg.RootCAs == nil {
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
}
cfg, err = NewClientTLS(certFile, keyFile, "")
if err != nil {
t.Error(err)
}
if got := len(cfg.Certificates); got != 1 {
t.Fatalf("expecting 1 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mismatch, expecting false")
}
if cfg.RootCAs != nil {
t.Fatalf("mismatch tls RootCAs, expecting nil")
}
}

@ -1,73 +0,0 @@
/*
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 urlutil
import (
"net/url"
"path"
"path/filepath"
)
// URLJoin joins a base URL to one or more path components.
//
// It's like filepath.Join for URLs. If the baseURL is pathish, this will still
// perform a join.
//
// If the URL is unparsable, this returns an error.
func URLJoin(baseURL string, paths ...string) (string, error) {
u, err := url.Parse(baseURL)
if err != nil {
return "", err
}
// We want path instead of filepath because path always uses /.
all := []string{u.Path}
all = append(all, paths...)
u.Path = path.Join(all...)
return u.String(), nil
}
// Equal normalizes two URLs and then compares for equality.
func Equal(a, b string) bool {
au, err := url.Parse(a)
if err != nil {
a = filepath.Clean(a)
b = filepath.Clean(b)
// If urls are paths, return true only if they are an exact match
return a == b
}
bu, err := url.Parse(b)
if err != nil {
return false
}
for _, u := range []*url.URL{au, bu} {
if u.Path == "" {
u.Path = "/"
}
u.Path = filepath.Clean(u.Path)
}
return au.String() == bu.String()
}
// ExtractHostname returns hostname from URL
func ExtractHostname(addr string) (string, error) {
u, err := url.Parse(addr)
if err != nil {
return "", err
}
return u.Hostname(), nil
}

@ -1,81 +0,0 @@
/*
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 urlutil
import "testing"
func TestURLJoin(t *testing.T) {
tests := []struct {
name, url, expect string
paths []string
}{
{name: "URL, one path", url: "http://example.com", paths: []string{"hello"}, expect: "http://example.com/hello"},
{name: "Long URL, one path", url: "http://example.com/but/first", paths: []string{"slurm"}, expect: "http://example.com/but/first/slurm"},
{name: "URL, two paths", url: "http://example.com", paths: []string{"hello", "world"}, expect: "http://example.com/hello/world"},
{name: "URL, no paths", url: "http://example.com", paths: []string{}, expect: "http://example.com"},
{name: "basepath, two paths", url: "../example.com", paths: []string{"hello", "world"}, expect: "../example.com/hello/world"},
}
for _, tt := range tests {
if got, err := URLJoin(tt.url, tt.paths...); err != nil {
t.Errorf("%s: error %q", tt.name, err)
} else if got != tt.expect {
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
}
}
}
func TestEqual(t *testing.T) {
for _, tt := range []struct {
a, b string
match bool
}{
{"http://example.com", "http://example.com", true},
{"http://example.com", "http://another.example.com", false},
{"https://example.com", "https://example.com", true},
{"http://example.com/", "http://example.com", true},
{"https://example.com", "http://example.com", false},
{"http://example.com/foo", "http://example.com/foo/", true},
{"http://example.com/foo//", "http://example.com/foo/", true},
{"http://example.com/./foo/", "http://example.com/foo/", true},
{"http://example.com/bar/../foo/", "http://example.com/foo/", true},
{"/foo", "/foo", true},
{"/foo", "/foo/", true},
{"/foo/.", "/foo/", true},
{"%/1234", "%/1234", true},
{"%/1234", "%/123", false},
{"/1234", "%/1234", false},
} {
if tt.match != Equal(tt.a, tt.b) {
t.Errorf("Expected %q==%q to be %t", tt.a, tt.b, tt.match)
}
}
}
func TestExtractHostname(t *testing.T) {
tests := map[string]string{
"http://example.com": "example.com",
"https://example.com/foo": "example.com",
"https://example.com:31337/not/with/a/bang/but/a/whimper": "example.com",
}
for start, expect := range tests {
if got, _ := ExtractHostname(start); got != expect {
t.Errorf("Got %q, expected %q", got, expect)
}
}
}

@ -1,81 +0,0 @@
/*
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 version // import "helm.sh/helm/v3/internal/version"
import (
"flag"
"runtime"
"strings"
)
var (
// version is the current version of Helm.
// Update this whenever making a new release.
// The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata]
//
// Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements.
version = "v3.10"
// metadata is extra build time data
metadata = ""
// gitCommit is the git sha1
gitCommit = ""
// gitTreeState is the state of the git tree
gitTreeState = ""
)
// BuildInfo describes the compile time information.
type BuildInfo struct {
// Version is the current semver.
Version string `json:"version,omitempty"`
// GitCommit is the git sha1.
GitCommit string `json:"git_commit,omitempty"`
// GitTreeState is the state of the git tree.
GitTreeState string `json:"git_tree_state,omitempty"`
// GoVersion is the version of the Go compiler used.
GoVersion string `json:"go_version,omitempty"`
}
// GetVersion returns the semver string of the version
func GetVersion() string {
if metadata == "" {
return version
}
return version + "+" + metadata
}
// GetUserAgent returns a user agent for user with an HTTP client
func GetUserAgent() string {
return "Helm/" + strings.TrimPrefix(GetVersion(), "v")
}
// Get returns build info
func Get() BuildInfo {
v := BuildInfo{
Version: GetVersion(),
GitCommit: gitCommit,
GitTreeState: gitTreeState,
GoVersion: runtime.Version(),
}
// HACK(bacongobbler): strip out GoVersion during a test run for consistent test output
if flag.Lookup("test.v") != nil {
v.GoVersion = ""
}
return v
}
Loading…
Cancel
Save