Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --force flag to push command #35

Merged
merged 5 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@
[[constraint]]
name = "k8s.io/helm"
version = "2.6.2"

[[constraint]]
name = "github.com/Masterminds/semver"
version = "1.4.0"
12 changes: 11 additions & 1 deletion cmd/helms3/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type Action interface {
}

func main() {
log.SetFlags(0)

if len(os.Args) == 5 && !isAction(os.Args[1]) {
cmd := proxyCmd{uri: os.Args[4]}
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
Expand All @@ -55,6 +57,8 @@ func main() {
pushTargetRepository := pushCmd.Arg("repo", "Target repository to push to").
Required().
String()
pushForce := pushCmd.Flag("force", "Replace the chart if it already exists. This can cause the repository to lose existing chart; use it with care").
Bool()

reindexCmd := cli.Command(actionReindex, "Reindex the repository.")
reindexTargetRepository := reindexCmd.Arg("repo", "Target repository to reindex").
Expand Down Expand Up @@ -94,6 +98,7 @@ func main() {
act = pushAction{
chartPath: *pushChartPath,
repoName: *pushTargetRepository,
force: *pushForce,
}

case actionReindex:
Expand All @@ -116,7 +121,12 @@ func main() {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()

if err := act.Run(ctx); err != nil {
err := act.Run(ctx)
switch err {
case nil:
case ErrChartExists:
log.Fatalf("The chart already exists in the repository and cannot be overwritten without an explicit intent. If you want to replace existing chart, use --force flag:\n\n\thelm s3 push --force %s %s\n\n", *pushChartPath, *pushTargetRepository)
default:
log.Fatal(err)
}
}
Expand Down
28 changes: 27 additions & 1 deletion cmd/helms3/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@ import (
"github.com/pkg/errors"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/provenance"
"k8s.io/helm/pkg/repo"

"github.com/hypnoglow/helm-s3/internal/awss3"
"github.com/hypnoglow/helm-s3/internal/awsutil"
"github.com/hypnoglow/helm-s3/internal/helmutil"
"github.com/hypnoglow/helm-s3/internal/index"
)

var (
// ErrChartExists signals that chart already exists in the repository
// and cannot be pushed without --force flag.
ErrChartExists = errors.New("chart already exists")
)

type pushAction struct {
chartPath string
repoName string
force bool
}

func (act pushAction) Run(ctx context.Context) error {
Expand Down Expand Up @@ -54,6 +62,13 @@ func (act pushAction) Run(ctx context.Context) error {
return err
}

if cachedIndex, err := repo.LoadIndexFile(repoEntry.Cache); err == nil {
// if cached index exists, check if the same chart version exists in it.
if cachedIndex.Has(chart.Metadata.Name, chart.Metadata.Version) && !act.force {
return ErrChartExists
}
}

hash, err := provenance.DigestFile(fname)
if err != nil {
return errors.WithMessage(err, "get chart digest")
Expand All @@ -69,6 +84,15 @@ func (act pushAction) Run(ctx context.Context) error {
return errors.Wrap(err, "encode chart metadata to json")
}

exists, err := storage.Exists(ctx, repoEntry.URL+"/"+fname)
if err != nil {
return errors.WithMessage(err, "check if chart already exists in the repository")
}

if exists && !act.force {
return ErrChartExists
}

if _, err := storage.PutChart(ctx, repoEntry.URL+"/"+fname, fchart, string(serializedChartMeta), hash); err != nil {
return errors.WithMessage(err, "upload chart to s3")
}
Expand All @@ -89,7 +113,9 @@ func (act pushAction) Run(ctx context.Context) error {
return errors.WithMessage(err, "load index from downloaded file")
}

idx.Add(chart.GetMetadata(), fname, repoEntry.URL, hash)
if err := idx.AddOrReplace(chart.GetMetadata(), fname, repoEntry.URL, hash); err != nil {
return errors.WithMessage(err, "add/replace chart in the index")
}
idx.SortEntries()

idxReader, err := idx.Reader()
Expand Down
50 changes: 42 additions & 8 deletions hack/integration-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
set -euo pipefail
set -x

#
# Set up
#

# Prepare chart to play with.
helm fetch stable/postgresql --version 0.8.3

#
# Test: init repo
#

helm s3 init s3://test-bucket/charts
if [ $? -ne 0 ]; then
echo "Failed to initialize repo"
Expand All @@ -20,8 +31,9 @@ if [ $? -ne 0 ]; then
exit 1
fi

# Prepare chart to play with.
helm fetch stable/postgresql --version 0.8.3
#
# Test: push chart
#

helm s3 push postgresql-0.8.3.tgz test-repo
if [ $? -ne 0 ]; then
Expand All @@ -35,23 +47,44 @@ if [ $? -ne 0 ]; then
exit 1
fi

# Update the index so we can find the uploaded chart.
helm repo update

helm search test-repo/postgres | grep -q 0.8.3
if [ $? -ne 0 ]; then
echo "Failed to find uploaded chart"
exit 1
fi

#
# Test: push the same chart again
#

set +e # next command should return non-zero status

helm s3 push postgresql-0.8.3.tgz test-repo
if [ $? -eq 0 ]; then
echo "The same chart must not be pushed again"
exit 1
fi

set -e

helm s3 push --force postgresql-0.8.3.tgz test-repo
if [ $? -ne 0 ]; then
echo "The same chart must be pushed again using --force"
exit 1
fi

#
# Test: fetch chart
#

helm fetch test-repo/postgresql --version 0.8.3
if [ $? -ne 0 ]; then
echo "Failed to fetch chart from repo"
exit 1
fi

#
# Delete
# Test: delete chart
#

helm s3 delete postgresql --version 0.8.3 test-repo
Expand All @@ -65,14 +98,15 @@ if mc ls -q helm-s3-minio/test-bucket/charts/postgresql-0.8.3.tgz 2>/dev/null ;
exit 1
fi

helm repo update

if helm search test-repo/postgres | grep -q 0.8.3 ; then
echo "Failed to delete chart from index"
exit 1
fi

#
# Tear down
#

rm postgresql-0.8.3.tgz
helm repo remove test-repo
set +x
Expand Down
22 changes: 22 additions & 0 deletions internal/awss3/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,28 @@ func (s *Storage) FetchRaw(ctx context.Context, uri string) ([]byte, error) {
return buf.Bytes(), nil
}

// Exists returns true if an object exists in the storage.
func (s *Storage) Exists(ctx context.Context, uri string) (bool, error) {
bucket, key, err := parseURI(uri)
if err != nil {
return false, err
}

_, err = s3.New(s.session).HeadObject(&s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
// That's weird that there is no NotFound constant in aws sdk.
if ae, ok := err.(awserr.Error); ok && ae.Code() == "NotFound" {
return false, nil
}
return false, errors.Wrap(err, "head s3 object")
}

return true, nil
}

// PutChart puts the chart file to the storage.
// uri must be in the form of s3 protocol: s3://bucket-name/key[...].
func (s *Storage) PutChart(ctx context.Context, uri string, r io.Reader, chartMeta, chartDigest string) (string, error) {
Expand Down
60 changes: 59 additions & 1 deletion internal/index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import (
"bytes"
"fmt"
"io"
"path/filepath"
"time"

"github.com/Masterminds/semver"
"github.com/ghodss/yaml"

"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/urlutil"
)

// Index of a helm chart repository.
Expand Down Expand Up @@ -42,6 +46,60 @@ func (idx *Index) UnmarshalBinary(data []byte) error {
return nil
}

// AddOrReplace is the same as Add but replaces the version if it exists instead
// of adding it to the list of versions.
func (idx *Index) AddOrReplace(md *chart.Metadata, filename, baseURL, digest string) error {
// TODO: this looks like a workaround.
// Think how we can rework this in the future.
// Ref: https://github.com/kubernetes/helm/issues/3230

u := filename
if baseURL != "" {
var err error
_, file := filepath.Split(filename)
u, err = urlutil.URLJoin(baseURL, file)
if err != nil {
u = filepath.Join(baseURL, file)
}
}
cr := &repo.ChartVersion{
URLs: []string{u},
Metadata: md,
Digest: digest,
Created: time.Now(),
}

// If no chart with such name exists in the index, just create a new
// list of versions.
entry, ok := idx.Entries[md.Name]
if !ok {
idx.Entries[md.Name] = repo.ChartVersions{cr}
return nil
}

chartSemVer, err := semver.NewVersion(md.Version)
if err != nil {
return err
}

// If such version exists, replace it.
for i, v := range entry {
itemSemVer, err := semver.NewVersion(v.Version)
if err != nil {
return err
}

if chartSemVer.Equal(itemSemVer) {
idx.Entries[md.Name][i] = cr
return nil
}
}

// Otherwise just add to the list of versions
idx.Entries[md.Name] = append(entry, cr)
return nil
}

// Delete removes chart version from index and returns deleted item.
func (idx *Index) Delete(name, version string) (*repo.ChartVersion, error) {
for chartName, chartVersions := range idx.Entries {
Expand Down
Loading