Merge pull request #1222 from adamreese/feat/api-version-check

feat(*): add api version checks
pull/1170/merge
Adam Reese 8 years ago committed by GitHub
commit f4cbea140f

@ -25,6 +25,8 @@ import (
"sort" "sort"
"strings" "strings"
"google.golang.org/grpc/metadata"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
ctx "golang.org/x/net/context" ctx "golang.org/x/net/context"
@ -67,6 +69,8 @@ var (
errMissingChart = errors.New("no chart provided") errMissingChart = errors.New("no chart provided")
// errMissingRelease indicates that a release (name) was not provided. // errMissingRelease indicates that a release (name) was not provided.
errMissingRelease = errors.New("no release 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. // ListDefaultLimit is the default limit for number of items returned in a list.
@ -76,7 +80,19 @@ type releaseServer struct {
env *environment.Environment 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 { func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error {
if !checkClientVersion(stream.Context()) {
return errIncompatibleVersion
}
if len(req.StatusCodes) == 0 { if len(req.StatusCodes) == 0 {
req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED} 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 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) { func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) {
if !checkClientVersion(c) {
return nil, errIncompatibleVersion
}
if req.Name == "" { if req.Name == "" {
return nil, errMissingRelease 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) { func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) {
if !checkClientVersion(c) {
return nil, errIncompatibleVersion
}
if req.Name == "" { if req.Name == "" {
return nil, errMissingRelease 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) { 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) currentRelease, updatedRelease, err := s.prepareUpdate(req)
if err != nil { if err != nil {
return nil, err 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) { func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
if !checkClientVersion(c) {
return nil, errIncompatibleVersion
}
rel, err := s.prepareRelease(req) rel, err := s.prepareRelease(req)
if err != nil { if err != nil {
log.Printf("Failed install prepare step: %s", err) 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) { func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
if !checkClientVersion(c) {
return nil, errIncompatibleVersion
}
if req.Name == "" { if req.Name == "" {
log.Printf("uninstall: Release not found: %s", req.Name) log.Printf("uninstall: Release not found: %s", req.Name)
return nil, errMissingRelease return nil, errMissingRelease

@ -30,6 +30,7 @@ import (
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"k8s.io/helm/cmd/tiller/environment" "k8s.io/helm/cmd/tiller/environment"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
@ -175,7 +176,7 @@ func TestUniqName(t *testing.T) {
} }
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
// TODO: Refactor this into a mock. // TODO: Refactor this into a mock.
@ -235,7 +236,7 @@ func TestInstallRelease(t *testing.T) {
} }
func TestInstallReleaseWithNotes(t *testing.T) { func TestInstallReleaseWithNotes(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
// TODO: Refactor this into a mock. // TODO: Refactor this into a mock.
@ -300,7 +301,7 @@ func TestInstallReleaseWithNotes(t *testing.T) {
} }
func TestInstallReleaseWithNotesRendered(t *testing.T) { func TestInstallReleaseWithNotesRendered(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
// TODO: Refactor this into a mock. // TODO: Refactor this into a mock.
@ -366,7 +367,7 @@ func TestInstallReleaseWithNotesRendered(t *testing.T) {
} }
func TestInstallReleaseDryRun(t *testing.T) { func TestInstallReleaseDryRun(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
req := &services.InstallReleaseRequest{ req := &services.InstallReleaseRequest{
@ -415,7 +416,7 @@ func TestInstallReleaseDryRun(t *testing.T) {
} }
func TestInstallReleaseNoHooks(t *testing.T) { func TestInstallReleaseNoHooks(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -434,7 +435,7 @@ func TestInstallReleaseNoHooks(t *testing.T) {
} }
func TestInstallReleaseFailedHooks(t *testing.T) { func TestInstallReleaseFailedHooks(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
rs.env.KubeClient = newHookFailingKubeClient() rs.env.KubeClient = newHookFailingKubeClient()
@ -453,7 +454,7 @@ func TestInstallReleaseFailedHooks(t *testing.T) {
} }
func TestInstallReleaseReuseName(t *testing.T) { func TestInstallReleaseReuseName(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rel.Info.Status.Code = release.Status_DELETED rel.Info.Status.Code = release.Status_DELETED
@ -484,7 +485,7 @@ func TestInstallReleaseReuseName(t *testing.T) {
} }
func TestUpdateRelease(t *testing.T) { func TestUpdateRelease(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -554,7 +555,7 @@ func TestUpdateRelease(t *testing.T) {
} }
func TestUpdateReleaseNoHooks(t *testing.T) { func TestUpdateReleaseNoHooks(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -583,7 +584,7 @@ func TestUpdateReleaseNoHooks(t *testing.T) {
} }
func TestUpdateReleaseNoChanges(t *testing.T) { func TestUpdateReleaseNoChanges(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -601,7 +602,7 @@ func TestUpdateReleaseNoChanges(t *testing.T) {
} }
func TestUninstallRelease(t *testing.T) { func TestUninstallRelease(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -632,7 +633,7 @@ func TestUninstallRelease(t *testing.T) {
} }
func TestUninstallPurgeRelease(t *testing.T) { func TestUninstallPurgeRelease(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -664,7 +665,7 @@ func TestUninstallPurgeRelease(t *testing.T) {
} }
func TestUninstallPurgeDeleteRelease(t *testing.T) { func TestUninstallPurgeDeleteRelease(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -689,7 +690,7 @@ func TestUninstallPurgeDeleteRelease(t *testing.T) {
} }
func TestUninstallReleaseNoHooks(t *testing.T) { func TestUninstallReleaseNoHooks(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -710,7 +711,7 @@ func TestUninstallReleaseNoHooks(t *testing.T) {
} }
func TestGetReleaseContent(t *testing.T) { func TestGetReleaseContent(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil { if err := rs.env.Releases.Create(rel); err != nil {
@ -728,7 +729,7 @@ func TestGetReleaseContent(t *testing.T) {
} }
func TestGetReleaseStatus(t *testing.T) { func TestGetReleaseStatus(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil { if err := rs.env.Releases.Create(rel); err != nil {
@ -746,7 +747,7 @@ func TestGetReleaseStatus(t *testing.T) {
} }
func TestGetReleaseStatusDeleted(t *testing.T) { func TestGetReleaseStatusDeleted(t *testing.T) {
c := context.Background() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rel.Info.Status.Code = release.Status_DELETED rel.Info.Status.Code = release.Status_DELETED
@ -960,7 +961,7 @@ func (l *mockListServer) Send(res *services.ListReleasesResponse) error {
return nil 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) SendMsg(v interface{}) error { return nil }
func (l *mockListServer) RecvMsg(v interface{}) error { return nil } func (l *mockListServer) RecvMsg(v interface{}) error { return nil }
func (l *mockListServer) SendHeader(m metadata.MD) 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" package helm // import "k8s.io/helm/pkg/helm"
import ( import (
"os"
"google.golang.org/grpc" "google.golang.org/grpc"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
@ -59,8 +57,7 @@ func (h *Client) Option(opts ...Option) *Client {
// Init initializes the helm client with default options // Init initializes the helm client with default options
func (h *Client) Init() *Client { func (h *Client) Init() *Client {
return h.Option(Host(DefaultHelmHost)). return h
Option(Home(os.ExpandEnv(DefaultHelmHome)))
} }
// ListReleases lists the current releases. // ListReleases lists the current releases.

@ -18,9 +18,12 @@ package helm
import ( import (
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc/metadata"
cpb "k8s.io/helm/pkg/proto/hapi/chart" cpb "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
rls "k8s.io/helm/pkg/proto/hapi/services" rls "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/version"
) )
// Option allows specifying various settings configurable by // Option allows specifying various settings configurable by
@ -30,12 +33,8 @@ type Option func(*options)
// options specify optional settings used by the helm client. // options specify optional settings used by the helm client.
type options struct { type options struct {
// value of helm host override
home string
// value of helm home override // value of helm home override
host string host string
// name of chart
chart string
// if set dry-run helm client calls // if set dry-run helm client calls
dryRun bool dryRun bool
// if set, re-use an existing name // if set, re-use an existing name
@ -56,13 +55,6 @@ type options struct {
contentReq rls.GetReleaseContentRequest 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"). // Host specifies the host address of the Tiller release server, (default = ":44134").
func Host(host string) Option { func Host(host string) Option {
return func(opts *options) { return func(opts *options) {
@ -249,7 +241,7 @@ func (o *options) rpcListReleases(rlc rls.ReleaseServiceClient, opts ...ReleaseL
for _, opt := range opts { for _, opt := range opts {
opt(o) opt(o)
} }
s, err := rlc.ListReleases(context.TODO(), &o.listReq) s, err := rlc.ListReleases(NewContext(), &o.listReq)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -257,6 +249,12 @@ func (o *options) rpcListReleases(rlc rls.ReleaseServiceClient, opts ...ReleaseL
return s.Recv() 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. // Executes tiller.InstallRelease RPC.
func (o *options) rpcInstallRelease(chr *cpb.Chart, rlc rls.ReleaseServiceClient, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { func (o *options) rpcInstallRelease(chr *cpb.Chart, rlc rls.ReleaseServiceClient, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
// apply the install options // 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.DisableHooks = o.disableHooks
o.instReq.ReuseName = o.reuseName o.instReq.ReuseName = o.reuseName
return rlc.InstallRelease(context.TODO(), &o.instReq) return rlc.InstallRelease(NewContext(), &o.instReq)
} }
// Executes tiller.UninstallRelease RPC. // Executes tiller.UninstallRelease RPC.
@ -289,7 +287,7 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient,
o.uninstallReq.Name = rlsName o.uninstallReq.Name = rlsName
o.uninstallReq.DisableHooks = o.disableHooks o.uninstallReq.DisableHooks = o.disableHooks
return rlc.UninstallRelease(context.TODO(), &o.uninstallReq) return rlc.UninstallRelease(NewContext(), &o.uninstallReq)
} }
// Executes tiller.UpdateRelease RPC. // 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.DryRun = o.dryRun
o.updateReq.Name = rlsName o.updateReq.Name = rlsName
return rlc.UpdateRelease(context.TODO(), &o.updateReq) return rlc.UpdateRelease(NewContext(), &o.updateReq)
} }
// Executes tiller.GetReleaseStatus RPC. // Executes tiller.GetReleaseStatus RPC.
@ -311,7 +309,7 @@ func (o *options) rpcGetReleaseStatus(rlsName string, rlc rls.ReleaseServiceClie
opt(o) opt(o)
} }
o.statusReq.Name = rlsName o.statusReq.Name = rlsName
return rlc.GetReleaseStatus(context.TODO(), &o.statusReq) return rlc.GetReleaseStatus(NewContext(), &o.statusReq)
} }
// Executes tiller.GetReleaseContent. // Executes tiller.GetReleaseContent.
@ -320,11 +318,11 @@ func (o *options) rpcGetReleaseContent(rlsName string, rlc rls.ReleaseServiceCli
opt(o) opt(o)
} }
o.contentReq.Name = rlsName o.contentReq.Name = rlsName
return rlc.GetReleaseContent(context.TODO(), &o.contentReq) return rlc.GetReleaseContent(NewContext(), &o.contentReq)
} }
// Executes tiller.GetVersion RPC. // Executes tiller.GetVersion RPC.
func (o *options) rpcGetVersion(rlc rls.ReleaseServiceClient, opts ...VersionOption) (*rls.GetVersionResponse, error) { func (o *options) rpcGetVersion(rlc rls.ReleaseServiceClient, opts ...VersionOption) (*rls.GetVersionResponse, error) {
req := &rls.GetVersionRequest{} req := &rls.GetVersionRequest{}
return rlc.GetVersion(context.TODO(), req) return rlc.GetVersion(NewContext(), req)
} }

@ -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)
}

@ -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)
}
}
}

@ -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. limitations under the License.
*/ */
// Package version represents the current version of the project.
package version // import "k8s.io/helm/pkg/version" package version // import "k8s.io/helm/pkg/version"
import "k8s.io/helm/pkg/proto/hapi/version" import "k8s.io/helm/pkg/proto/hapi/version"

Loading…
Cancel
Save