feat(*): add api version checks

pull/1222/head
Adam Reese 8 years ago
parent dbb84a1b9e
commit e0d02e6e5b

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

@ -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.
*/
// 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…
Cancel
Save