From f936035d7c4dfd94405456ae95645d3d8c94bb65 Mon Sep 17 00:00:00 2001 From: saumanbiswas Date: Fri, 3 Feb 2017 16:58:59 +0600 Subject: [PATCH 1/2] Use TillerC api instead of gRPC Tiller. (#2) --- api/extensions/helm.yaml | 15 + api/install/install.go | 24 ++ api/register.go | 49 +++ api/register_v1beta1.go | 32 ++ api/types.go | 139 +++++++++ client/clientset/codec.go | 163 ++++++++++ client/clientset/extensions.go | 104 +++++++ client/clientset/imports.go | 26 ++ client/clientset/release.go | 137 +++++++++ client/clientset/release_expansion.go | 22 ++ client/clientset/release_version.go | 123 ++++++++ cmd/helm/delete.go | 4 +- cmd/helm/install.go | 11 +- cmd/helm/rollback.go | 3 + cmd/helm/status.go | 4 +- cmd/helm/upgrade.go | 3 +- pkg/helm/client.go | 417 ++++++++++++++++++++++---- pkg/helm/helm_test.go | 2 +- pkg/helm/interface.go | 10 +- pkg/kube/client.go | 3 +- 20 files changed, 1226 insertions(+), 65 deletions(-) create mode 100644 api/extensions/helm.yaml create mode 100644 api/install/install.go create mode 100644 api/register.go create mode 100644 api/register_v1beta1.go create mode 100644 api/types.go create mode 100644 client/clientset/codec.go create mode 100644 client/clientset/extensions.go create mode 100644 client/clientset/imports.go create mode 100644 client/clientset/release.go create mode 100644 client/clientset/release_expansion.go create mode 100644 client/clientset/release_version.go diff --git a/api/extensions/helm.yaml b/api/extensions/helm.yaml new file mode 100644 index 000000000..5b79b3774 --- /dev/null +++ b/api/extensions/helm.yaml @@ -0,0 +1,15 @@ +metadata: + name: release.helm.sh +apiVersion: extensions/v1beta1 +kind: ThirdPartyResource +description: "A specification of Helm release" +versions: + - name: v1beta1 +--- +metadata: + name: release-version.helm.sh +apiVersion: extensions/v1beta1 +kind: ThirdPartyResource +description: "A specification of Helm release version" +versions: + - name: v1beta1 diff --git a/api/install/install.go b/api/install/install.go new file mode 100644 index 000000000..2403a52da --- /dev/null +++ b/api/install/install.go @@ -0,0 +1,24 @@ +package install + +import ( + aci "k8s.io/helm/api" + "k8s.io/kubernetes/pkg/apimachinery/announced" + "k8s.io/kubernetes/pkg/util/sets" +) + +func init() { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: aci.GroupName, + VersionPreferenceOrder: []string{aci.V1beta1SchemeGroupVersion.Version}, + ImportPrefix: "github.com/appscode/tillerc/api", + RootScopedKinds: sets.NewString("ThirdPartyResource"), + AddInternalObjectsToScheme: aci.AddToScheme, + }, + announced.VersionToSchemeFunc{ + aci.V1beta1SchemeGroupVersion.Version: aci.V1betaAddToScheme, + }, + ).Announce().RegisterAndEnable(); err != nil { + panic(err) + } +} diff --git a/api/register.go b/api/register.go new file mode 100644 index 000000000..0ee39e812 --- /dev/null +++ b/api/register.go @@ -0,0 +1,49 @@ +package kube + +import ( + "k8s.io/kubernetes/pkg/api" + schema "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +// GroupName is the group name use in this package +const GroupName = "helm.sh" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns back a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Release{}, + &ReleaseList{}, + + &ReleaseVersion{}, + &ReleaseVersionList{}, + + &api.ListOptions{}, + &api.DeleteOptions{}, + ) + return nil +} + +func (obj *Release) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta } +func (obj *ReleaseList) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta } + +func (obj *ReleaseVersion) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta } +func (obj *ReleaseVersionList) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta } diff --git a/api/register_v1beta1.go b/api/register_v1beta1.go new file mode 100644 index 000000000..331bfe14a --- /dev/null +++ b/api/register_v1beta1.go @@ -0,0 +1,32 @@ +package kube + +import ( + schema "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" +) + +// SchemeGroupVersion is group version used to register these objects +var V1beta1SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +var ( + V1beta1SchemeBuilder = runtime.NewSchemeBuilder(v1addKnownTypes) + V1betaAddToScheme = V1beta1SchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func v1addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(V1beta1SchemeGroupVersion, + &Release{}, + &ReleaseList{}, + + &ReleaseVersion{}, + &ReleaseVersionList{}, + + &v1.ListOptions{}, + &v1.DeleteOptions{}, + ) + versionedwatch.AddToGroupVersion(scheme, V1beta1SchemeGroupVersion) + return nil +} diff --git a/api/types.go b/api/types.go new file mode 100644 index 000000000..19d9f1fec --- /dev/null +++ b/api/types.go @@ -0,0 +1,139 @@ +package kube + +import ( + google_protobuf1 "github.com/golang/protobuf/ptypes/any" + hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" + hapi_release "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +//------------------------------------------------------------------------------------------- +// Chart represents a chart that is installed in a Release. +// The ChartSource represents the location and type of a chart to install. +// This is modelled like Volume in Pods, which allows specifying a chart +// inline (like today) or pulling a chart object from a (potentially private) chart registry similar to pulling a Docker image. +// +optional +type ChartSource struct { + // Inline charts are what is done today with Helm cli. Release request + // contains the chart definition in the release spec, sent by Helm cli. + Inline *hapi_chart.Chart `json:"inline,omitempty"` +} + +//------------------------------------------------------------ + +// Release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. +type Release struct { + unversioned.TypeMeta `json:",inline,omitempty"` + api.ObjectMeta `json:"metadata,omitempty"` + Spec ReleaseSpec `json:"spec,omitempty"` + Status ReleaseStatus `json:"status,omitempty"` +} + +type ReleaseSpec struct { + // The ChartSource represents the location and type of a chart to install. + // This is modelled like Volume in Pods, which allows specifying a chart + // inline (like today) or pulling a chart object from a (potentially private) + // chart registry similar to pulling a Docker image. + Chart ChartSource `protobuf:"bytes,1,opt,name=chart" json:"chart,omitempty"` + + //// Values is a string containing (unparsed) YAML values. + //Values *Config `protobuf:"bytes,2,opt,name=values" json:"values,omitempty"` + + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. + Config *hapi_chart.Config `protobuf:"bytes,4,opt,name=config" json:"config,omitempty"` + + // DisableHooks causes the server to skip running any hooks for the install. + DisableHooks bool `protobuf:"varint,5,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + + // Manifest is the string representation of the rendered template. + Manifest string `protobuf:"bytes,5,opt,name=manifest" json:"manifest,omitempty"` + + // Hooks are all of the hooks declared for this release. + Hooks []*hapi_release.Hook `protobuf:"bytes,6,rep,name=hooks" json:"hooks,omitempty"` + + // Version is an int32 which represents the version of the release. + Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` + + // Performs pods restart for resources if applicable + Recreate bool `protobuf:"varint,6,opt,name=recreate" json:"recreate,omitempty"` + + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,7,opt,name=timeout" json:"timeout,omitempty"` + + Purge bool `protobuf:"varint,3,opt,name=purge" json:"purge,omitempty"` + + // dry_run, if true, will run through the release logic, but neither create + DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` +} + +/*type ReleaseStatus struct { + // Info contains information about the release. + //Info *Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` + + Status *hapi_release.Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + FirstDeployed unversioned.Time `protobuf:"bytes,2,opt,name=first_deployed,json=firstDeployed" json:"first_deployed,omitempty"` + LastDeployed unversioned.Time `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"` + // Deleted tracks when this object was deleted. + Deleted unversioned.Time `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"` +}*/ + +type ReleaseList struct { + unversioned.TypeMeta `json:",inline"` + unversioned.ListMeta `json:"metadata,omitempty"` + Items []Release `json:"items,omitempty"` +} + +// --------------------------------------------------------------------------------------------------------------------- + +// ReleaseVersion captures the state of a individual release and are immutable. +// ReleaseVersion replaces the version wise configmaps used by Tiller 2.0 +type ReleaseVersion struct { + unversioned.TypeMeta `json:",inline,omitempty"` + api.ObjectMeta `json:"metadata,omitempty"` + Spec ReleaseVersionSpec `json:"spec,omitempty"` + Status ReleaseVersionStatus `json:"status,omitempty"` +} + +type ReleaseVersionSpec struct { + ReleaseSpec ReleaseSpec `json:"inline,omitempty"` +} + +/*type ReleaseVersionStatus struct { + Status *hapi_release.Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + Deployed unversioned.Time `protobuf:"bytes,2,opt,name=deployed" json:"deployed,omitempty"` +}*/ + +type ReleaseVersionList struct { + unversioned.TypeMeta `json:",inline"` + unversioned.ListMeta `json:"metadata,omitempty"` + Items []ReleaseVersion `json:"items,omitempty"` +} + +type ReleaseStatus struct { + //LastDeploymentStatus *hapi_release.Status `json:"last_deployment_status,omitempty"` + Code hapi_release.Status_Code `protobuf:"varint,1,opt,name=code,enum=hapi.release.Status_Code" json:"code,omitempty"` + Details *google_protobuf1.Any `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"` + // Cluster resources as kubectl would print them. + //Resources string `protobuf:"bytes,3,opt,name=resources" json:"resources,omitempty"` + // Contains the rendered templates/NOTES.txt if available + Notes string `protobuf:"bytes,4,opt,name=notes" json:"notes,omitempty"` + LastDeployedVersion int32 `json:"last_deployed_version,omitempty"` + LastDeployed unversioned.Time `json:"last_deployed,omitempty"` + FirstDeployed unversioned.Time `json:"first_deployed,omitempty"` +} + +type ReleaseVersionStatus struct { + //Status *hapi_release.Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + // Version is an int32 which represents the version of the release. + Code hapi_release.Status_Code `protobuf:"varint,1,opt,name=code,enum=hapi.release.Status_Code" json:"code,omitempty"` + Details *google_protobuf1.Any `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"` + // Cluster resources as kubectl would print them. + //Resources string `protobuf:"bytes,3,opt,name=resources" json:"resources,omitempty"` + // Contains the rendered templates/NOTES.txt if available + Notes string `protobuf:"bytes,4,opt,name=notes" json:"notes,omitempty"` + Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` + Deployed unversioned.Time `protobuf:"bytes,2,opt,name=deployed,json=firstDeployed" json:"deployed,omitempty"` +} diff --git a/client/clientset/codec.go b/client/clientset/codec.go new file mode 100644 index 000000000..dfa9555ef --- /dev/null +++ b/client/clientset/codec.go @@ -0,0 +1,163 @@ +package client + +import ( + "encoding/json" + "io" + "net/url" + "reflect" + "strings" + + "github.com/ghodss/yaml" + aci "k8s.io/helm/api" + //"github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + schema "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" + kubejson "k8s.io/kubernetes/pkg/runtime/serializer/json" +) + +// TODO(@sadlil): Find a better way to replace ExtendedCodec to encode and decode objects. +// Follow the guide to replace it with api.Codec and api.ParameterCodecs. +var ExtendedCodec = &extendedCodec{} + +// DirectCodecFactory provides methods for retrieving "DirectCodec"s, which do not do conversion. +type DirectCodecFactory struct { + *extendedCodec +} + +// EncoderForVersion returns an encoder that does not do conversion. gv is ignored. +func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder { + return serializer +} + +// DecoderToVersion returns an decoder that does not do conversion. gv is ignored. +func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { + return serializer +} + +// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. +func (f DirectCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{ + { + MediaType: "application/json", + EncodesAsText: true, + Serializer: &extendedCodec{}, + PrettySerializer: &extendedCodec{pretty: true}, + StreamSerializer: &runtime.StreamSerializerInfo{ + Framer: kubejson.Framer, + EncodesAsText: true, + Serializer: &extendedCodec{}, + }, + }, + { + MediaType: "application/yaml", + EncodesAsText: true, + Serializer: &extendedCodec{yaml: true}, + PrettySerializer: &extendedCodec{yaml: true}, + }, + } +} + +type extendedCodec struct { + pretty bool + yaml bool +} + +func (e *extendedCodec) Decode(data []byte, gvk *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + if e.yaml { + altered, err := yaml.YAMLToJSON(data) + if err != nil { + return nil, nil, err + } + data = altered + } + if obj == nil { + metadata := &schema.TypeMeta{} + err := json.Unmarshal(data, metadata) + if err != nil { + return obj, gvk, err + } + //glog.V(7).Infoln("Detected metadata type for nil object, got", metadata.APIVersion, metadata.Kind) + obj, err = setDefaultType(metadata) + if err != nil { + // glog.Errorln("faild to create type", err) + } + } + err := json.Unmarshal(data, obj) + if err != nil { + return obj, gvk, err + } + return obj, gvk, nil +} + +func (e *extendedCodec) Encode(obj runtime.Object, w io.Writer) error { + setDefaultVersionKind(obj) + if e.yaml { + json, err := json.Marshal(obj) + if err != nil { + return err + } + data, err := yaml.JSONToYAML(json) + if err != nil { + return err + } + _, err = w.Write(data) + } + + if e.pretty { + data, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + return json.NewEncoder(w).Encode(obj) +} + +// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then +// converts that object to into (if necessary). Returns an error if the operation cannot be completed. +func (*extendedCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into runtime.Object) error { + if len(parameters) == 0 { + return nil + } + _, okDelete := into.(*api.DeleteOptions) + if _, okList := into.(*api.ListOptions); okList || okDelete { + from = schema.GroupVersion{Version: "v1"} + } + return runtime.NewParameterCodec(api.Scheme).DecodeParameters(parameters, from, into) +} + +// EncodeParameters converts the provided object into the to version, then converts that object to url.Values. +// Returns an error if conversion is not possible. +func (c *extendedCodec) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) { + result := url.Values{} + if obj == nil { + return result, nil + } + _, okDelete := obj.(*api.DeleteOptions) + if _, okList := obj.(*api.ListOptions); okList || okDelete { + to = schema.GroupVersion{Version: "v1"} + } + return runtime.NewParameterCodec(api.Scheme).EncodeParameters(obj, to) +} + +func setDefaultVersionKind(obj runtime.Object) { + // Check the values can are In type Extended Ingress + defaultGVK := schema.GroupVersionKind{ + Group: aci.V1beta1SchemeGroupVersion.Group, + Version: aci.V1beta1SchemeGroupVersion.Version, + } + + fullyQualifiedKind := reflect.ValueOf(obj).Type().String() + lastIndexOfDot := strings.LastIndex(fullyQualifiedKind, ".") + if lastIndexOfDot > 0 { + defaultGVK.Kind = fullyQualifiedKind[lastIndexOfDot+1:] + } + + obj.GetObjectKind().SetGroupVersionKind(defaultGVK) +} + +func setDefaultType(metadata *schema.TypeMeta) (runtime.Object, error) { + return api.Scheme.New(metadata.GroupVersionKind()) +} diff --git a/client/clientset/extensions.go b/client/clientset/extensions.go new file mode 100644 index 000000000..e3cab37b1 --- /dev/null +++ b/client/clientset/extensions.go @@ -0,0 +1,104 @@ +package client + +import ( + "errors" + + schema "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" + rest "k8s.io/kubernetes/pkg/client/restclient" +) + +const ( + defaultAPIPath = "/apis" +) + +type ExtensionInterface interface { + RESTClient() rest.Interface + ReleaseNamespacer + ReleaseVersionNamespacer +} + +// AppsCodeExtensionsClient is used to interact with experimental Kubernetes features. +// Features of Extensions group are not supported and may be changed or removed in +// incompatible ways at any time. +type ExtensionsClient struct { + restClient rest.Interface +} + +func (a *ExtensionsClient) Release(namespace string) ReleaseInterface { + return newRelease(a, namespace) +} + +func (a *ExtensionsClient) ReleaseVersion(namespace string) ReleaseVersionInterface { + return newReleaseVersion(a, namespace) +} + +// NewAppsCodeExtensions creates a new AppsCodeExtensionsClient for the given config. This client +// provides access to experimental Kubernetes features. +// Features of Extensions group are not supported and may be changed or removed in +// incompatible ways at any time. +func NewExtensionsForConfig(c *rest.Config) (*ExtensionsClient, error) { + config := *c + if err := setExtensionsDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &ExtensionsClient{client}, nil +} + +// NewAppsCodeExtensionsOrDie creates a new AppsCodeExtensionsClient for the given config and +// panics if there is an error in the config. +// Features of Extensions group are not supported and may be changed or removed in +// incompatible ways at any time. +func NewExtensionsForConfigOrDie(c *rest.Config) *ExtensionsClient { + client, err := NewExtensionsForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ExtensionsV1beta1Client for the given RESTClient. +func NewNewExtensions(c rest.Interface) *ExtensionsClient { + return &ExtensionsClient{c} +} + +func setExtensionsDefaults(config *rest.Config) error { + gv, err := schema.ParseGroupVersion("helm.sh/v1beta1") + if err != nil { + return err + } + // if helm.sh/v1beta1 is not enabled, return an error + if !registered.IsEnabledVersion(gv) { + return errors.New("helm.sh/v1beta1 is not enabled") + } + config.APIPath = defaultAPIPath + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + if config.GroupVersion == nil || config.GroupVersion.Group != "helm.sh" { + g, err := registered.Group("helm.sh") + if err != nil { + return err + } + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + } + + config.NegotiatedSerializer = DirectCodecFactory{extendedCodec: ExtendedCodec} + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *ExtensionsClient) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/client/clientset/imports.go b/client/clientset/imports.go new file mode 100644 index 000000000..89c455187 --- /dev/null +++ b/client/clientset/imports.go @@ -0,0 +1,26 @@ +package client + +// These imports are the API groups the client will support. +import ( + "fmt" + + _ "k8s.io/helm/api/install" + _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/apimachinery/registered" + _ "k8s.io/kubernetes/pkg/apis/apps/install" + _ "k8s.io/kubernetes/pkg/apis/authentication/install" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/certificates/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" + _ "k8s.io/kubernetes/pkg/apis/policy/install" + _ "k8s.io/kubernetes/pkg/apis/rbac/install" + _ "k8s.io/kubernetes/pkg/apis/storage/install" +) + +func init() { + if missingVersions := registered.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { + panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions)) + } +} diff --git a/client/clientset/release.go b/client/clientset/release.go new file mode 100644 index 000000000..a5e598adc --- /dev/null +++ b/client/clientset/release.go @@ -0,0 +1,137 @@ +package client + +import ( + aci "k8s.io/helm/api" + "k8s.io/kubernetes/pkg/api" + rest "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/watch" +) + +type ReleaseNamespacer interface { + Release(namespace string) ReleaseInterface +} + +// ReleaseInterface has methods to work with Release resources. +type ReleaseInterface interface { + Create(*aci.Release) (*aci.Release, error) + Update(*aci.Release) (*aci.Release, error) + UpdateStatus(*aci.Release) (*aci.Release, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*aci.Release, error) + List(opts api.ListOptions) (*aci.ReleaseList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReleaseExpansion +} + +// releases implements ReleaseInterface +type releases struct { + client rest.Interface + ns string +} + +func newRelease(c *ExtensionsClient, namespace string) *releases { + return &releases{c.restClient, namespace} +} + +// newReleases returns a Releases +func newReleases(c *ExtensionsClient, namespace string) *releases { + return &releases{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Create takes the representation of a release and creates it. Returns the server's representation of the release, and an error, if there is any. +func (c *releases) Create(release *aci.Release) (result *aci.Release, err error) { + result = &aci.Release{} + err = c.client.Post(). + Namespace(c.ns). + Resource("releases"). + Body(release). + Do(). + Into(result) + return +} + +// Update takes the representation of a release and updates it. Returns the server's representation of the release, and an error, if there is any. +func (c *releases) Update(release *aci.Release) (result *aci.Release, err error) { + result = &aci.Release{} + err = c.client.Put(). + Namespace(c.ns). + Resource("releases"). + Name(release.Name). + Body(release). + Do(). + Into(result) + return +} + +func (c *releases) UpdateStatus(release *aci.Release) (result *aci.Release, err error) { + result = &aci.Release{} + err = c.client.Put(). + Namespace(c.ns). + Resource("releases"). + Name(release.Name). + SubResource("status"). + Body(release). + Do(). + Into(result) + return +} + +// Delete takes name of the release and deletes it. Returns an error if one occurs. +func (c *releases) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("releases"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *releases) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("releases"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the release, and returns the corresponding release object, and an error if there is any. +func (c *releases) Get(name string) (result *aci.Release, err error) { + result = &aci.Release{} + err = c.client.Get(). + Namespace(c.ns). + Resource("releases"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Releases that match those selectors. +func (c *releases) List(opts api.ListOptions) (result *aci.ReleaseList, err error) { + result = &aci.ReleaseList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("releases"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested releases. +func (c *releases) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("releases"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/client/clientset/release_expansion.go b/client/clientset/release_expansion.go new file mode 100644 index 000000000..eb01592b3 --- /dev/null +++ b/client/clientset/release_expansion.go @@ -0,0 +1,22 @@ +package client + +import aci "k8s.io/helm/api" + +// The ReleaseExpansion interface allows manually adding extra methods to the ReleaseInterface. +type ReleaseExpansion interface { + Dryrun(*aci.Release) (*aci.Release, error) +} + +// Dryrun applied the provided ReleaseDryrun to the named release in the current namespace. +func (c *releases) Dryrun(release *aci.Release) (result *aci.Release, err error) { + result = &aci.Release{} + err = c.client.Put(). + Namespace(c.ns). + Resource("releases"). + Name(release.Name). + SubResource("dryrun"). + Body(release). + Do(). + Into(result) + return +} diff --git a/client/clientset/release_version.go b/client/clientset/release_version.go new file mode 100644 index 000000000..aeeb9b733 --- /dev/null +++ b/client/clientset/release_version.go @@ -0,0 +1,123 @@ +package client + +import ( + aci "k8s.io/helm/api" + "k8s.io/kubernetes/pkg/api" + rest "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/watch" +) + +type ReleaseVersionNamespacer interface { + ReleaseVersion(namespace string) ReleaseVersionInterface +} + +// ReleaseVersionInterface has methods to work with ReleaseVersion resources. +type ReleaseVersionInterface interface { + Create(*aci.ReleaseVersion) (*aci.ReleaseVersion, error) + Update(*aci.ReleaseVersion) (*aci.ReleaseVersion, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*aci.ReleaseVersion, error) + List(opts api.ListOptions) (*aci.ReleaseVersionList, error) + Watch(opts api.ListOptions) (watch.Interface, error) +} + +// release-versions implements ReleaseVersionInterface +type releaseVersions struct { + client rest.Interface + ns string +} + +func newReleaseVersion(c *ExtensionsClient, namespace string) *releaseVersions { + return &releaseVersions{c.restClient, namespace} +} + +// newReleaseVersions returns a ReleaseVersions +func newReleaseVersions(c *ExtensionsClient, namespace string) *releaseVersions { + return &releaseVersions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Create takes the representation of a release and creates it. Returns the server's representation of the release, and an error, if there is any. +func (c *releaseVersions) Create(version *aci.ReleaseVersion) (result *aci.ReleaseVersion, err error) { + result = &aci.ReleaseVersion{} + err = c.client.Post(). + Namespace(c.ns). + Resource("releaseVersions"). + Body(version). + Do(). + Into(result) + return +} + +// Delete takes name of the release and deletes it. Returns an error if one occurs. +func (c *releaseVersions) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("releaseVersions"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *releaseVersions) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("releaseVersions"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the release, and returns the corresponding release object, and an error if there is any. +func (c *releaseVersions) Get(name string) (result *aci.ReleaseVersion, err error) { + result = &aci.ReleaseVersion{} + err = c.client.Get(). + Namespace(c.ns). + Resource("releaseVersions"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReleaseVersions that match those selectors. +func (c *releaseVersions) List(opts api.ListOptions) (result *aci.ReleaseVersionList, err error) { + result = &aci.ReleaseVersionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("releaseVersions"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested release-versions. +func (c *releaseVersions) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("releaseVersions"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} + +//updates release-version + +func (c *releaseVersions) Update(version *aci.ReleaseVersion) (result *aci.ReleaseVersion, err error) { + result = &aci.ReleaseVersion{} + err = c.client.Put(). + Namespace(c.ns). + Resource("releaseVersions"). + Name(version.Name). + Body(version). + Do(). + Into(result) + return +} diff --git a/cmd/helm/delete.go b/cmd/helm/delete.go index f65b9d4d8..e339dd227 100644 --- a/cmd/helm/delete.go +++ b/cmd/helm/delete.go @@ -40,6 +40,7 @@ type deleteCmd struct { disableHooks bool purge bool timeout int64 + namespace string out io.Writer client helm.Interface @@ -75,6 +76,7 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command { } f := cmd.Flags() + f.StringVar(&del.namespace, "namespace", "default", "namespace to uninstall the release") f.BoolVar(&del.dryRun, "dry-run", false, "simulate a delete") f.BoolVar(&del.disableHooks, "no-hooks", false, "prevent hooks from running during deletion") f.BoolVar(&del.purge, "purge", false, "remove the release from the store and make its name free for later use") @@ -90,7 +92,7 @@ func (d *deleteCmd) run() error { helm.DeletePurge(d.purge), helm.DeleteTimeout(d.timeout), } - res, err := d.client.DeleteRelease(d.name, opts...) + res, err := d.client.DeleteRelease(d.name, d.namespace, opts...) if res != nil && res.Info != "" { fmt.Fprintln(d.out, res.Info) } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 6917a6e6a..180e3d8ff 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -198,7 +198,6 @@ func (i *installCmd) run() error { // Print the final name so the user knows what the final name of the release is. fmt.Printf("FINAL NAME: %s\n", i.name) } - res, err := i.client.InstallRelease( i.chartPath, i.namespace, @@ -213,19 +212,23 @@ func (i *installCmd) run() error { return prettyError(err) } - rel := res.GetRelease() + +/* rel := res.GetRelease() + if rel == nil { + return nil + }*/ + rel := res.Release if rel == nil { return nil } i.printRelease(rel) - // If this is a dry run, we can't display status. if i.dryRun { return nil } // Print the status like status command does - status, err := i.client.ReleaseStatus(rel.Name) + status, err := i.client.ReleaseStatus(rel.Name, rel.Namespace, helm.StatusReleaseVersion(1)) if err != nil { return prettyError(err) } diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 267bfa0dd..7824cbf45 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -36,6 +36,7 @@ second is a revision (version) number. To see revision numbers, run type rollbackCmd struct { name string + namespace string revision int32 dryRun bool recreate bool @@ -77,6 +78,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback") + f.StringVar(&rollback.namespace, "namespace", "default", "namespace of the release") f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") @@ -88,6 +90,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command { func (r *rollbackCmd) run() error { _, err := r.client.RollbackRelease( r.name, + r.namespace, helm.RollbackDryRun(r.dryRun), helm.RollbackRecreate(r.recreate), helm.RollbackDisableHooks(r.disableHooks), diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 400429625..3111d2e0f 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -41,6 +41,7 @@ The status consists of: type statusCmd struct { release string + namespace string out io.Writer client helm.Interface version int32 @@ -70,12 +71,13 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command { } cmd.PersistentFlags().Int32Var(&status.version, "revision", 0, "if set, display the status of the named release with revision") + cmd.PersistentFlags().StringVar(&status.namespace, "namespace", "default", "namespace of the release") return cmd } func (s *statusCmd) run() error { - res, err := s.client.ReleaseStatus(s.release, helm.StatusReleaseVersion(s.version)) + res, err := s.client.ReleaseStatus(s.release, s.namespace, helm.StatusReleaseVersion(s.version)) if err != nil { return prettyError(err) } diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 5f6d36142..08e815aac 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -163,6 +163,7 @@ func (u *upgradeCmd) run() error { resp, err := u.client.UpdateRelease( u.release, chartPath, + u.namespace, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun), helm.UpgradeRecreate(u.recreate), @@ -181,7 +182,7 @@ func (u *upgradeCmd) run() error { fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release) // Print the status like status command does - status, err := u.client.ReleaseStatus(u.release) + status, err := u.client.ReleaseStatus(u.release, u.namespace) if err != nil { return prettyError(err) } diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 049c6af60..4c07b04e0 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -17,19 +17,45 @@ limitations under the License. package helm // import "k8s.io/helm/pkg/helm" import ( + "bytes" + "errors" + "fmt" + "github.com/golang/protobuf/ptypes" + "github.com/technosophos/moniker" "golang.org/x/net/context" "google.golang.org/grpc" - + "gopkg.in/square/go-jose.v1/json" + hapi "k8s.io/helm/api" + cs "k8s.io/helm/client/clientset" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/kube" + hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" + rs "k8s.io/helm/pkg/proto/hapi/release" rls "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/restclient" + rest "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + "log" + "math/rand" + "strconv" + "time" ) // Client manages client side of the helm-tiller protocol +func init() { + rand.Seed(time.Now().UnixNano()) +} + type Client struct { opts options } +type ExtensionsClient struct { + restClient rest.Interface +} + // NewClient creates a new client. func NewClient(opts ...Option) *Client { var c Client @@ -72,7 +98,7 @@ func (h *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*rls.I } // InstallReleaseFromChart installs a new chart and returns the release response. -func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { +func (h *Client) InstallReleaseFromChart(chart *hapi_chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { // apply the install options for _, opt := range opts { opt(&h.opts) @@ -94,7 +120,7 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ... } // DeleteRelease uninstalls a named release and returns the response. -func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { +func (h *Client) DeleteRelease(rlsName string, namespace string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { // apply the uninstall options for _, opt := range opts { opt(&h.opts) @@ -119,22 +145,22 @@ func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.Unins return nil, err } } - return h.delete(ctx, req) + return h.delete(ctx, namespace, req) } -// UpdateRelease loads a chart from chstr and updates a release to a new/different chart -func (h *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { +// UpdateRelease updates a release to a new/different chart +func (h *Client) UpdateRelease(rlsName string, chstr string, namespace string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { // load the chart to update chart, err := chartutil.Load(chstr) if err != nil { return nil, err } - return h.UpdateReleaseFromChart(rlsName, chart, opts...) + return h.UpdateReleaseFromChart(rlsName, chart, namespace, opts...) } // UpdateReleaseFromChart updates a release to a new/different chart -func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { +func (h *Client) UpdateReleaseFromChart(rlsName string, chart *hapi_chart.Chart, namespace string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { // apply the update options for _, opt := range opts { @@ -148,13 +174,12 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts req.Recreate = h.opts.recreate req.ResetValues = h.opts.resetValues ctx := NewContext() - if h.opts.before != nil { if err := h.opts.before(ctx, req); err != nil { return nil, err } } - return h.update(ctx, req) + return h.update(ctx, namespace, req) } // GetVersion returns the server version @@ -174,7 +199,7 @@ func (h *Client) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, err } // RollbackRelease rolls back a release to the previous version -func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) { +func (h *Client) RollbackRelease(rlsName string, namespace string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) { for _, opt := range opts { opt(&h.opts) } @@ -189,24 +214,24 @@ func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.R return nil, err } } - return h.rollback(ctx, req) + return h.rollback(ctx, namespace, req) } // ReleaseStatus returns the given release's status. -func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { +func (h *Client) ReleaseStatus(rlsName string, namespace string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { for _, opt := range opts { opt(&h.opts) } req := &h.opts.statusReq req.Name = rlsName + req.Version = h.opts.statusReq.Version ctx := NewContext() - if h.opts.before != nil { if err := h.opts.before(ctx, req); err != nil { return nil, err } } - return h.status(ctx, req) + return h.status(ctx, namespace, req) } // ReleaseContent returns the configuration for a given release. @@ -217,7 +242,6 @@ func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.Get req := &h.opts.contentReq req.Name = rlsName ctx := NewContext() - if h.opts.before != nil { if err := h.opts.before(ctx, req); err != nil { return nil, err @@ -263,62 +287,252 @@ func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.L // Executes tiller.InstallRelease RPC. func (h *Client) install(ctx context.Context, req *rls.InstallReleaseRequest) (*rls.InstallReleaseResponse, error) { - c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + /* c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer c.Close() + rlc := rls.NewReleaseServiceClient(c) + return rlc.InstallRelease(ctx, req)*/ + resp := &rls.InstallReleaseResponse{} + maxTries := 5 + releaseNameMaxLen := 53 + name := req.Name + client, err := getRESTClient() if err != nil { - return nil, err + return resp, err + } + // make uniq name + if len(name) == 0 { + for i := 0; i < maxTries; i++ { + namer := moniker.New() + name = namer.NameSep("-") + if len(name) > releaseNameMaxLen { + name = name[:releaseNameMaxLen] + } + rv := name + "-v1" + err = client.RESTClient().Get().Namespace(req.Namespace).Resource("releaseversion").Name(rv).Do().Error() + if err == nil { + fmt.Println("info: Name %q is taken. Searching again.", name) + } else { + break + } + } } - defer c.Close() - - rlc := rls.NewReleaseServiceClient(c) - return rlc.InstallRelease(ctx, req) + req.Name = name + releaseObj := makeReleaseObject(req) + releaseObj.Spec.Version = 1 + release := new(hapi.Release) + err = client.RESTClient().Post().Namespace(req.Namespace).Resource("releases").Body(releaseObj).Do().Into(release) + if err != nil { + return resp, err + } + resp.Release = new(rs.Release) + resp.Release.Name = release.Name + resp.Release.Namespace = release.Namespace + resp.Release.Hooks = release.Spec.Hooks + resp.Release.Config = release.Spec.Config + resp.Release.Chart = new(hapi_chart.Chart) + resp.Release.Chart = release.Spec.Chart.Inline + resp.Release.Manifest = release.Spec.Manifest + resp.Release.Info = new(rs.Info) + resp.Release.Info.Status = new(rs.Status) + resp.Release.Info.Status.Notes = release.Status.Notes + resp.Release.Info.Status.Code = release.Status.Code + resp.Release.Info.Status.Details = release.Status.Details + firstDeployed, err := ptypes.TimestampProto(release.Status.FirstDeployed.Time) + if err != nil { + return resp, err + } + resp.Release.Info.FirstDeployed = firstDeployed + lastDeployed, err := ptypes.TimestampProto(release.Status.LastDeployed.Time) + if err != nil { + return resp, err + } + resp.Release.Info.FirstDeployed = lastDeployed + return resp, nil } // Executes tiller.UninstallRelease RPC. -func (h *Client) delete(ctx context.Context, req *rls.UninstallReleaseRequest) (*rls.UninstallReleaseResponse, error) { - c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) +func (h *Client) delete(ctx context.Context, namespace string, req *rls.UninstallReleaseRequest) (*rls.UninstallReleaseResponse, error) { + /* c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.UninstallRelease(ctx, req)*/ + resp := &rls.UninstallReleaseResponse{} + client, err := getRESTClient() + // TODO handle response + //release := new(hapi.Release) + err = client.RESTClient().Delete().Namespace(namespace).Resource("releases").Name(req.Name).Do().Error() if err != nil { - return nil, err + return resp, err } - defer c.Close() - - rlc := rls.NewReleaseServiceClient(c) - return rlc.UninstallRelease(ctx, req) + return resp, nil } // Executes tiller.UpdateRelease RPC. -func (h *Client) update(ctx context.Context, req *rls.UpdateReleaseRequest) (*rls.UpdateReleaseResponse, error) { - c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) +func (h *Client) update(ctx context.Context, namespace string, req *rls.UpdateReleaseRequest) (*rls.UpdateReleaseResponse, error) { + /* c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.UpdateRelease(ctx, req)*/ + resp := &rls.UpdateReleaseResponse{} + client, err := getRESTClient() + // get the release + release := new(hapi.Release) + err = client.RESTClient().Get().Namespace(namespace).Resource("releases").Name(req.Name).Do().Into(release) if err != nil { - return nil, err - } - defer c.Close() - - rlc := rls.NewReleaseServiceClient(c) - return rlc.UpdateRelease(ctx, req) + return resp, err + } + release.Spec.Config = req.Values + release.Spec.DryRun = req.DryRun + release.Spec.DisableHooks = req.DisableHooks + release.Spec.Recreate = req.Recreate + release.Spec.Timeout = req.Timeout + release.Spec.Chart.Inline = req.Chart + release.Spec.Version = release.Spec.Version + 1 + // update the release + updatedRelease := new(hapi.Release) + err = client.RESTClient().Put().Namespace(namespace).Resource("releases").Name(release.Name).Body(release).Do().Into(updatedRelease) + if err != nil { + return resp, err + } + resp.Release = new(rs.Release) + resp.Release.Name = updatedRelease.Name + resp.Release.Chart = updatedRelease.Spec.Chart.Inline + resp.Release.Config = updatedRelease.Spec.Config + resp.Release.Manifest = updatedRelease.Spec.Manifest + resp.Release.Hooks = updatedRelease.Spec.Hooks + resp.Release.Version = updatedRelease.Spec.Version + resp.Release.Info = new(rs.Info) + resp.Release.Info.Status = new(rs.Status) + resp.Release.Info.Status.Code = updatedRelease.Status.Code + resp.Release.Info.Status.Details = updatedRelease.Status.Details + resp.Release.Info.Status.Notes = updatedRelease.Status.Notes + return resp, nil } // Executes tiller.RollbackRelease RPC. -func (h *Client) rollback(ctx context.Context, req *rls.RollbackReleaseRequest) (*rls.RollbackReleaseResponse, error) { - c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) +func (h *Client) rollback(ctx context.Context, namespace string, req *rls.RollbackReleaseRequest) (*rls.RollbackReleaseResponse, error) { + /* c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.RollbackRelease(ctx, req)*/ + resp := &rls.RollbackReleaseResponse{} + config, err := getConfig() if err != nil { - return nil, err + return resp, err } - defer c.Close() - - rlc := rls.NewReleaseServiceClient(c) - return rlc.RollbackRelease(ctx, req) + extClient, err := getRESTClient() + client, err := clientset.NewForConfig(config) + err = extClient.RESTClient().Get().Namespace(namespace).Resource("releases").Name(req.Name).Do().Error() + if err != nil { + return resp, errors.New("Release not found") + } + event, err := makeEventForRollBack(req) + event.ObjectMeta.Namespace = namespace + event.InvolvedObject.Namespace = namespace + event.InvolvedObject.Name = (req.Name + "-v" + strconv.Itoa(int(req.Version))) + event.ObjectMeta.Name = event.InvolvedObject.Name + "-" + RandStringRunes(5) + if err != nil { + return resp, err + } + err = extClient.RESTClient().Get().Namespace(namespace).Resource("releaseversions").Name(event.InvolvedObject.Name).Do().Error() + if err != nil { + return resp, errors.New("Release Version not found") + } + _, err = client.Core().Events(namespace).Create(event) + if err != nil { + return resp, err + } + return resp, nil } // Executes tiller.GetReleaseStatus RPC. -func (h *Client) status(ctx context.Context, req *rls.GetReleaseStatusRequest) (*rls.GetReleaseStatusResponse, error) { - c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) +func (h *Client) status(ctx context.Context, namespace string, req *rls.GetReleaseStatusRequest) (*rls.GetReleaseStatusResponse, error) { + /* + c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.GetReleaseStatus(ctx, req) + + */ + resp := &rls.GetReleaseStatusResponse{} + client, err := getRESTClient() if err != nil { - return nil, err + return resp, err } - defer c.Close() + release := new(hapi.Release) + resp.Info = new(rs.Info) + resp.Info.Status = new(rs.Status) + if req.Version == 0 { + err = client.RESTClient().Get().Namespace(namespace).Resource("releases").Name(req.Name).Do().Into(release) + if err != nil { + e := client.RESTClient().Get().Namespace(namespace).Resource("releaseversions").Name(req.Name + "-v1").Do().Error() + if e != nil { + return resp, err + } else { + resp.Namespace = namespace + resp.Info.Status.Code = rs.Status_DELETED + return resp, nil + } + } + req.Version = release.Spec.Version + } + duration := time.Duration(2) * time.Second + releaseVersion := new(hapi.ReleaseVersion) + name := req.Name + "-v" + strconv.Itoa(int(req.Version)) + for i := 0; i <= 20; i++ { + err = client.RESTClient().Get().Namespace(namespace).Resource("releaseversions").Name(name).Do().Into(releaseVersion) + if err != nil { + time.Sleep(duration) + continue + } else { + break + } + } + if err != nil { + return resp, err + } + err = client.RESTClient().Get().Namespace(namespace).Resource("releases").Name(req.Name).Do().Into(release) + if err != nil { + return resp, err + } + resp.Name = release.Name + resp.Namespace = release.Namespace - rlc := rls.NewReleaseServiceClient(c) - return rlc.GetReleaseStatus(ctx, req) + resp.Info.Status.Code = release.Status.Code + resp.Info.Status.Notes = release.Status.Notes + resp.Info.Status.Details = release.Status.Details + resp.Info.Status.Resources = GetLatestResourceStatus(release.Namespace, release.Spec.Manifest) + f, err := ptypes.TimestampProto(release.Status.FirstDeployed.Time) + if err != nil { + return resp, err + } + resp.Info.FirstDeployed = f + l, err := ptypes.TimestampProto(release.Status.LastDeployed.Time) + if err != nil { + return resp, err + } + resp.Info.LastDeployed = l + + return resp, nil } // Executes tiller.GetReleaseContent RPC. @@ -356,3 +570,104 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls. rlc := rls.NewReleaseServiceClient(c) return rlc.GetHistory(ctx, req) } + +func makeReleaseObject(req *rls.InstallReleaseRequest) *hapi.Release { + release := &hapi.Release{} + release.TypeMeta.Kind = "Release" + release.TypeMeta.APIVersion = "helm.sh/v1beta1" + release.ObjectMeta.Name = req.Name + release.ObjectMeta.Namespace = req.Namespace + release.Spec = makeObjectSpec(req) + return release +} +func makeObjectSpec(req *rls.InstallReleaseRequest) hapi.ReleaseSpec { + spec := hapi.ReleaseSpec{} + spec.DryRun = req.DryRun + spec.DisableHooks = req.DisableHooks + // spec.Reuse = req.ReuseName TODO To enable reuse in installation + spec.Config = req.Values + spec.Chart.Inline = new(hapi_chart.Chart) + spec.Chart.Inline.Files = req.Chart.Files + spec.Chart.Inline.Metadata = req.Chart.Metadata + spec.Chart.Inline.Templates = req.Chart.Templates + spec.Chart.Inline.Values = req.Chart.Values + return spec +} + +func getConfig() (*restclient.Config, error) { + rules := clientcmd.NewDefaultClientConfigLoadingRules() + rules.DefaultClientConfig = &clientcmd.DefaultClientConfig + overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides).ClientConfig() + if err != nil { + return nil, fmt.Errorf("Could not get kubernetes config: %s", err) + } + return config, nil +} + +func getRESTClient() (*cs.ExtensionsClient, error) { + c, err := getConfig() + config := *c + if err != nil { + return nil, err + } + client, err := cs.NewExtensionsForConfig(&config) + if err != nil { + return nil, err + } + return client, nil +} + +type RollbackReq struct { + // dry_run, if true, will run through the release logic but no create + DryRun bool `protobuf:"varint,2,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` + // DisableHooks causes the server to skip running any hooks for the rollback + DisableHooks bool `protobuf:"varint,3,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + // Performs pods restart for resources if applicable + Recreate bool `protobuf:"varint,5,opt,name=recreate" json:"recreate,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,6,opt,name=timeout" json:"timeout,omitempty"` +} + +func makeEventForRollBack(req *rls.RollbackReleaseRequest) (*api.Event, error) { + r := RollbackReq{ + DryRun: req.DryRun, + Recreate: req.Recreate, + DisableHooks: req.DisableHooks, + Timeout: req.Timeout, + } + message, err := json.Marshal(r) + if err != nil { + return &api.Event{}, err + } + event := &api.Event{ + InvolvedObject: api.ObjectReference{ + Kind: "release", + }, + Reason: "releaseRollback", + Message: string(message), + Type: api.EventTypeNormal, + Count: 1, + } + return event, nil +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func RandStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +func GetLatestResourceStatus(namespace, manifest string) string { + KubeClient := kube.New(nil) + resource, err := KubeClient.Get(namespace, bytes.NewBufferString(manifest)) + if err != nil { + log.Println(err) + return "" + } + return resource +} diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go index a6289f2c8..81afa3180 100644 --- a/pkg/helm/helm_test.go +++ b/pkg/helm/helm_test.go @@ -93,7 +93,7 @@ func TestInstallRelease_VerifyOptions(t *testing.T) { var disableHooks = true var releaseName = "test" var namespace = "default" - var reuseName = true + var reuseName = false var dryRun = true var chartName = "alpine" var overrides = []byte("key1=value1,key2=value2") diff --git a/pkg/helm/interface.go b/pkg/helm/interface.go index 6b88463b7..bf1ff846a 100644 --- a/pkg/helm/interface.go +++ b/pkg/helm/interface.go @@ -26,11 +26,11 @@ type Interface interface { ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) InstallRelease(chStr, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) InstallReleaseFromChart(chart *chart.Chart, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) - DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) - ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) - UpdateRelease(rlsName, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) - UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) - RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) + UpdateReleaseFromChart(rlsName string, chart *chart.Chart, namespace string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) + DeleteRelease(rlsName string, namespace string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) + ReleaseStatus(rlsName string, namespace string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) + UpdateRelease(rlsName, chStr string, namespace string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) + RollbackRelease(rlsName string, namespace string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 0022bb71f..0447d5914 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -120,12 +120,13 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { // Since we don't know what order the objects come in, let's group them by the types, so // that when we print them, they come looking good (headers apply to subgroups, etc.) objs := make(map[string][]runtime.Object) + infos, err := c.Build(namespace, reader) if err != nil { return "", err } err = perform(c, namespace, infos, func(info *resource.Info) error { - log.Printf("Doing get for: '%s'", info.Name) + //log.Printf("Doing get for: '%s'", info.Name) obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export) if err != nil { return err From e9116ceb2b98b76e0fd3e917fca588dcda5a94ce Mon Sep 17 00:00:00 2001 From: sauman Date: Fri, 3 Feb 2017 20:26:24 +0600 Subject: [PATCH 2/2] wait flag added --- api/types.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/types.go b/api/types.go index 19d9f1fec..c03a75111 100644 --- a/api/types.go +++ b/api/types.go @@ -67,6 +67,8 @@ type ReleaseSpec struct { // dry_run, if true, will run through the release logic, but neither create DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` + + Wait bool `protobuf:"varint,4,opt,name=wait,json=wait" json:"wait,omitempty"` } /*type ReleaseStatus struct {