mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
feat(*): add api version checks
This commit is contained in:
parent
dbb84a1b9e
commit
e0d02e6e5b
8 changed files with 184 additions and 41 deletions
|
|
@ -25,6 +25,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/technosophos/moniker"
|
||||
ctx "golang.org/x/net/context"
|
||||
|
|
@ -67,6 +69,8 @@ var (
|
|||
errMissingChart = errors.New("no chart provided")
|
||||
// errMissingRelease indicates that a release (name) was not provided.
|
||||
errMissingRelease = errors.New("no release provided")
|
||||
// errIncompatibleVersion indicates incompatible client/server versions.
|
||||
errIncompatibleVersion = errors.New("client version is incompatible")
|
||||
)
|
||||
|
||||
// ListDefaultLimit is the default limit for number of items returned in a list.
|
||||
|
|
@ -76,7 +80,19 @@ type releaseServer struct {
|
|||
env *environment.Environment
|
||||
}
|
||||
|
||||
func getVersion(c ctx.Context) string {
|
||||
if md, ok := metadata.FromContext(c); ok {
|
||||
if v, ok := md["x-helm-api-client"]; ok {
|
||||
return v[0]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error {
|
||||
if !checkClientVersion(stream.Context()) {
|
||||
return errIncompatibleVersion
|
||||
}
|
||||
|
||||
if len(req.StatusCodes) == 0 {
|
||||
req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED}
|
||||
|
|
@ -181,7 +197,16 @@ func (s *releaseServer) GetVersion(c ctx.Context, req *services.GetVersionReques
|
|||
return &services.GetVersionResponse{Version: v}, nil
|
||||
}
|
||||
|
||||
func checkClientVersion(c ctx.Context) bool {
|
||||
v := getVersion(c)
|
||||
return version.IsCompatible(v, version.Version)
|
||||
}
|
||||
|
||||
func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) {
|
||||
if !checkClientVersion(c) {
|
||||
return nil, errIncompatibleVersion
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
return nil, errMissingRelease
|
||||
}
|
||||
|
|
@ -225,6 +250,10 @@ func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetRelease
|
|||
}
|
||||
|
||||
func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) {
|
||||
if !checkClientVersion(c) {
|
||||
return nil, errIncompatibleVersion
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
return nil, errMissingRelease
|
||||
}
|
||||
|
|
@ -238,6 +267,10 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas
|
|||
}
|
||||
|
||||
func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
|
||||
if !checkClientVersion(c) {
|
||||
return nil, errIncompatibleVersion
|
||||
}
|
||||
|
||||
currentRelease, updatedRelease, err := s.prepareUpdate(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -402,6 +435,10 @@ func (s *releaseServer) engine(ch *chart.Chart) environment.Engine {
|
|||
}
|
||||
|
||||
func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
|
||||
if !checkClientVersion(c) {
|
||||
return nil, errIncompatibleVersion
|
||||
}
|
||||
|
||||
rel, err := s.prepareRelease(req)
|
||||
if err != nil {
|
||||
log.Printf("Failed install prepare step: %s", err)
|
||||
|
|
@ -631,6 +668,10 @@ func (s *releaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
|
|||
}
|
||||
|
||||
func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
|
||||
if !checkClientVersion(c) {
|
||||
return nil, errIncompatibleVersion
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
log.Printf("uninstall: Release not found: %s", req.Name)
|
||||
return nil, errMissingRelease
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"k8s.io/helm/cmd/tiller/environment"
|
||||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
"k8s.io/helm/pkg/proto/hapi/services"
|
||||
|
|
@ -175,7 +176,7 @@ func TestUniqName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallRelease(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
|
||||
// TODO: Refactor this into a mock.
|
||||
|
|
@ -235,7 +236,7 @@ func TestInstallRelease(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallReleaseWithNotes(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
|
||||
// TODO: Refactor this into a mock.
|
||||
|
|
@ -300,7 +301,7 @@ func TestInstallReleaseWithNotes(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallReleaseWithNotesRendered(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
|
||||
// TODO: Refactor this into a mock.
|
||||
|
|
@ -366,7 +367,7 @@ func TestInstallReleaseWithNotesRendered(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallReleaseDryRun(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
|
||||
req := &services.InstallReleaseRequest{
|
||||
|
|
@ -415,7 +416,7 @@ func TestInstallReleaseDryRun(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallReleaseNoHooks(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rs.env.Releases.Create(releaseStub())
|
||||
|
||||
|
|
@ -434,7 +435,7 @@ func TestInstallReleaseNoHooks(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallReleaseFailedHooks(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rs.env.Releases.Create(releaseStub())
|
||||
rs.env.KubeClient = newHookFailingKubeClient()
|
||||
|
|
@ -453,7 +454,7 @@ func TestInstallReleaseFailedHooks(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInstallReleaseReuseName(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
rel.Info.Status.Code = release.Status_DELETED
|
||||
|
|
@ -484,7 +485,7 @@ func TestInstallReleaseReuseName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateRelease(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
rs.env.Releases.Create(rel)
|
||||
|
|
@ -554,7 +555,7 @@ func TestUpdateRelease(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateReleaseNoHooks(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
rs.env.Releases.Create(rel)
|
||||
|
|
@ -583,7 +584,7 @@ func TestUpdateReleaseNoHooks(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateReleaseNoChanges(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
rs.env.Releases.Create(rel)
|
||||
|
|
@ -601,7 +602,7 @@ func TestUpdateReleaseNoChanges(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUninstallRelease(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rs.env.Releases.Create(releaseStub())
|
||||
|
||||
|
|
@ -632,7 +633,7 @@ func TestUninstallRelease(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUninstallPurgeRelease(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rs.env.Releases.Create(releaseStub())
|
||||
|
||||
|
|
@ -664,7 +665,7 @@ func TestUninstallPurgeRelease(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUninstallPurgeDeleteRelease(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rs.env.Releases.Create(releaseStub())
|
||||
|
||||
|
|
@ -689,7 +690,7 @@ func TestUninstallPurgeDeleteRelease(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUninstallReleaseNoHooks(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rs.env.Releases.Create(releaseStub())
|
||||
|
||||
|
|
@ -710,7 +711,7 @@ func TestUninstallReleaseNoHooks(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetReleaseContent(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
if err := rs.env.Releases.Create(rel); err != nil {
|
||||
|
|
@ -728,7 +729,7 @@ func TestGetReleaseContent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetReleaseStatus(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
if err := rs.env.Releases.Create(rel); err != nil {
|
||||
|
|
@ -746,7 +747,7 @@ func TestGetReleaseStatus(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetReleaseStatusDeleted(t *testing.T) {
|
||||
c := context.Background()
|
||||
c := helm.NewContext()
|
||||
rs := rsFixture()
|
||||
rel := releaseStub()
|
||||
rel.Info.Status.Code = release.Status_DELETED
|
||||
|
|
@ -960,7 +961,7 @@ func (l *mockListServer) Send(res *services.ListReleasesResponse) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (l *mockListServer) Context() context.Context { return context.TODO() }
|
||||
func (l *mockListServer) Context() context.Context { return helm.NewContext() }
|
||||
func (l *mockListServer) SendMsg(v interface{}) error { return nil }
|
||||
func (l *mockListServer) RecvMsg(v interface{}) error { return nil }
|
||||
func (l *mockListServer) SendHeader(m metadata.MD) error { return nil }
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
package helm // import "k8s.io/helm/pkg/helm"
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
|
|
@ -59,8 +57,7 @@ func (h *Client) Option(opts ...Option) *Client {
|
|||
|
||||
// Init initializes the helm client with default options
|
||||
func (h *Client) Init() *Client {
|
||||
return h.Option(Host(DefaultHelmHost)).
|
||||
Option(Home(os.ExpandEnv(DefaultHelmHome)))
|
||||
return h
|
||||
}
|
||||
|
||||
// ListReleases lists the current releases.
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ package helm
|
|||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
cpb "k8s.io/helm/pkg/proto/hapi/chart"
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
rls "k8s.io/helm/pkg/proto/hapi/services"
|
||||
"k8s.io/helm/pkg/version"
|
||||
)
|
||||
|
||||
// Option allows specifying various settings configurable by
|
||||
|
|
@ -30,12 +33,8 @@ type Option func(*options)
|
|||
|
||||
// options specify optional settings used by the helm client.
|
||||
type options struct {
|
||||
// value of helm host override
|
||||
home string
|
||||
// value of helm home override
|
||||
host string
|
||||
// name of chart
|
||||
chart string
|
||||
// if set dry-run helm client calls
|
||||
dryRun bool
|
||||
// if set, re-use an existing name
|
||||
|
|
@ -56,13 +55,6 @@ type options struct {
|
|||
contentReq rls.GetReleaseContentRequest
|
||||
}
|
||||
|
||||
// Home specifies the location of helm home, (default = "$HOME/.helm").
|
||||
func Home(home string) Option {
|
||||
return func(opts *options) {
|
||||
opts.home = home
|
||||
}
|
||||
}
|
||||
|
||||
// Host specifies the host address of the Tiller release server, (default = ":44134").
|
||||
func Host(host string) Option {
|
||||
return func(opts *options) {
|
||||
|
|
@ -249,7 +241,7 @@ func (o *options) rpcListReleases(rlc rls.ReleaseServiceClient, opts ...ReleaseL
|
|||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
s, err := rlc.ListReleases(context.TODO(), &o.listReq)
|
||||
s, err := rlc.ListReleases(NewContext(), &o.listReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -257,6 +249,12 @@ func (o *options) rpcListReleases(rlc rls.ReleaseServiceClient, opts ...ReleaseL
|
|||
return s.Recv()
|
||||
}
|
||||
|
||||
// NewContext creates a versioned context.
|
||||
func NewContext() context.Context {
|
||||
md := metadata.Pairs("x-helm-api-client", version.Version)
|
||||
return metadata.NewContext(context.TODO(), md)
|
||||
}
|
||||
|
||||
// Executes tiller.InstallRelease RPC.
|
||||
func (o *options) rpcInstallRelease(chr *cpb.Chart, rlc rls.ReleaseServiceClient, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
|
||||
// apply the install options
|
||||
|
|
@ -269,7 +267,7 @@ func (o *options) rpcInstallRelease(chr *cpb.Chart, rlc rls.ReleaseServiceClient
|
|||
o.instReq.DisableHooks = o.disableHooks
|
||||
o.instReq.ReuseName = o.reuseName
|
||||
|
||||
return rlc.InstallRelease(context.TODO(), &o.instReq)
|
||||
return rlc.InstallRelease(NewContext(), &o.instReq)
|
||||
}
|
||||
|
||||
// Executes tiller.UninstallRelease RPC.
|
||||
|
|
@ -289,7 +287,7 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient,
|
|||
o.uninstallReq.Name = rlsName
|
||||
o.uninstallReq.DisableHooks = o.disableHooks
|
||||
|
||||
return rlc.UninstallRelease(context.TODO(), &o.uninstallReq)
|
||||
return rlc.UninstallRelease(NewContext(), &o.uninstallReq)
|
||||
}
|
||||
|
||||
// Executes tiller.UpdateRelease RPC.
|
||||
|
|
@ -302,7 +300,7 @@ func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.Relea
|
|||
o.updateReq.DryRun = o.dryRun
|
||||
o.updateReq.Name = rlsName
|
||||
|
||||
return rlc.UpdateRelease(context.TODO(), &o.updateReq)
|
||||
return rlc.UpdateRelease(NewContext(), &o.updateReq)
|
||||
}
|
||||
|
||||
// Executes tiller.GetReleaseStatus RPC.
|
||||
|
|
@ -311,7 +309,7 @@ func (o *options) rpcGetReleaseStatus(rlsName string, rlc rls.ReleaseServiceClie
|
|||
opt(o)
|
||||
}
|
||||
o.statusReq.Name = rlsName
|
||||
return rlc.GetReleaseStatus(context.TODO(), &o.statusReq)
|
||||
return rlc.GetReleaseStatus(NewContext(), &o.statusReq)
|
||||
}
|
||||
|
||||
// Executes tiller.GetReleaseContent.
|
||||
|
|
@ -320,11 +318,11 @@ func (o *options) rpcGetReleaseContent(rlsName string, rlc rls.ReleaseServiceCli
|
|||
opt(o)
|
||||
}
|
||||
o.contentReq.Name = rlsName
|
||||
return rlc.GetReleaseContent(context.TODO(), &o.contentReq)
|
||||
return rlc.GetReleaseContent(NewContext(), &o.contentReq)
|
||||
}
|
||||
|
||||
// Executes tiller.GetVersion RPC.
|
||||
func (o *options) rpcGetVersion(rlc rls.ReleaseServiceClient, opts ...VersionOption) (*rls.GetVersionResponse, error) {
|
||||
req := &rls.GetVersionRequest{}
|
||||
return rlc.GetVersion(context.TODO(), req)
|
||||
return rlc.GetVersion(NewContext(), req)
|
||||
}
|
||||
|
|
|
|||
46
pkg/version/compatible.go
Normal file
46
pkg/version/compatible.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 "k8s.io/helm/pkg/version"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
// IsCompatible tests if a client and server version are compatible.
|
||||
func IsCompatible(client, server string) bool {
|
||||
cv, err := semver.NewVersion(client)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
sv, err := semver.NewVersion(server)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
constraint := fmt.Sprintf("^%d.%d.x", cv.Major(), cv.Minor())
|
||||
if cv.Prerelease() != "" || sv.Prerelease() != "" {
|
||||
constraint = cv.String()
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(constraint)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return c.Check(sv)
|
||||
}
|
||||
43
pkg/version/compatible_test.go
Normal file
43
pkg/version/compatible_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 represents the current version of the project.
|
||||
package version // import "k8s.io/helm/pkg/version"
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIsCompatible(t *testing.T) {
|
||||
tests := []struct {
|
||||
client string
|
||||
server string
|
||||
expected bool
|
||||
}{
|
||||
{"v2.0.0-alpha.4", "v2.0.0-alpha.4", true},
|
||||
{"v2.0.0-alpha.3", "v2.0.0-alpha.4", false},
|
||||
{"v2.0.0", "v2.0.0-alpha.4", false},
|
||||
{"v2.0.0-alpha.4", "v2.0.0", false},
|
||||
{"v2.0.0", "v2.0.1", true},
|
||||
{"v2.0.1", "v2.0.0", true},
|
||||
{"v2.0.0", "v2.1.1", true},
|
||||
{"v2.1.0", "v2.0.1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if IsCompatible(tt.client, tt.server) != tt.expected {
|
||||
t.Errorf("expected client(%s) and server(%s) to be %v", tt.client, tt.server, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
18
pkg/version/doc.go
Normal file
18
pkg/version/doc.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 represents the current version of the project.
|
||||
package version // import "k8s.io/helm/pkg/version"
|
||||
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package version represents the current version of the project.
|
||||
package version // import "k8s.io/helm/pkg/version"
|
||||
|
||||
import "k8s.io/helm/pkg/proto/hapi/version"
|
||||
|
|
|
|||
Loading…
Reference in a new issue