Commit 0e44a6e9 authored by tamal's avatar tamal Committed by Pål Karlsrud
Browse files

Pass user authentication to Tiller

parent e1027fae
......@@ -34,4 +34,7 @@ message Info {
// Description is human-friendly "log entry" about this release.
string Description = 5;
// Username is the authenticated user who performed this release.
string Username = 6;
}
......@@ -64,7 +64,7 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
get.release = args[0]
if get.client == nil {
get.client = newClient()
get.client = helm.NewClient(helm.Host(settings.TillerHost), helm.WithContext(loadAuthHeaders))
}
return get.run()
},
......
......@@ -56,7 +56,7 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
get.release = args[0]
if get.client == nil {
get.client = helm.NewClient(helm.Host(settings.TillerHost))
get.client = helm.NewClient(helm.Host(settings.TillerHost), helm.WithContext(loadAuthHeaders))
}
return get.run()
},
......
......@@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"fmt"
"io"
"testing"
......@@ -32,7 +33,7 @@ func TestGetCmd(t *testing.T) {
name: "get with a release",
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
args: []string{"thomas-guide"},
expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + helm.MockHookTemplate + "\nMANIFEST:",
expected: fmt.Sprintf("REVISION: 1\nRELEASED: (.*)\nRELEASED BY: %s\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n"+mockHookTemplate+"\nMANIFEST:", username),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})},
},
{
......
......@@ -17,6 +17,7 @@ limitations under the License.
package main // import "k8s.io/helm/cmd/helm"
import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
......@@ -25,8 +26,10 @@ import (
"strings"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
......@@ -255,6 +258,43 @@ func getInternalKubeClient(context string) (internalclientset.Interface, error)
return client, nil
}
func loadAuthHeaders(ctx context.Context) context.Context {
c, err := kube.GetConfig(kubeContext).ClientConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to extract authentication headers: %s\n", err)
os.Exit(1)
}
m := map[string]string{}
if c.AuthProvider != nil {
switch c.AuthProvider.Name {
case "gcp":
m[string(kube.Authorization)] = "Bearer " + c.AuthProvider.Config["access-token"]
case "oidc":
m[string(kube.Authorization)] = "Bearer " + c.AuthProvider.Config["id-token"]
default:
fmt.Fprintf(os.Stderr, "Unknown auth provider: %s\n", c.AuthProvider.Name)
os.Exit(1)
}
}
if len(c.BearerToken) != 0 {
m[string(kube.Authorization)] = "Bearer " + c.BearerToken
}
if len(c.Username) != 0 && len(c.Password) != 0 {
m[string(kube.Authorization)] = "Basic " + base64.StdEncoding.EncodeToString([]byte(c.Username+":"+c.Password))
}
md, _ := metadata.FromContext(ctx)
return metadata.NewContext(ctx, metadata.Join(md, metadata.New(m)))
}
// getKubeCmd is a convenience method for creating kubernetes cmd client
// for a given kubeconfig context
func getKubeCmd(context string) *kube.Client {
return kube.New(kube.GetConfig(context))
}
// ensureHelmClient returns a new helm client impl. if h is not nil.
func ensureHelmClient(h helm.Interface) helm.Interface {
if h != nil {
......@@ -264,7 +304,7 @@ func ensureHelmClient(h helm.Interface) helm.Interface {
}
func newClient() helm.Interface {
options := []helm.Option{helm.Host(settings.TillerHost)}
options := []helm.Option{helm.Host(settings.TillerHost), helm.WithContext(loadAuthHeaders)}
if tlsVerify || tlsEnable {
if tlsCaCertFile == "" {
......
......@@ -38,11 +38,11 @@ configures the maximum length of the revision list returned.
The historical release set is printed as a formatted table, e.g:
$ helm history angry-bird --max=4
REVISION UPDATED STATUS CHART DESCRIPTION
1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Initial install
2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Upgraded successfully
3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Rolled back to 2
4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0 Upgraded successfully
REVISION UPDATED STATUS CHART DESCRIPTION RELEASED BY
1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Initial install x
2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Upgraded successfully y
3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Rolled back to 2 z
4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0 Upgraded successfully x
`
type historyCmd struct {
......@@ -66,7 +66,7 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
case len(args) == 0:
return errReleaseRequired
case his.helmc == nil:
his.helmc = newClient()
his.helmc = helm.NewClient(helm.Host(settings.TillerHost), helm.WithContext(loadAuthHeaders))
}
his.rls = args[0]
return his.run()
......@@ -94,7 +94,7 @@ func (cmd *historyCmd) run() error {
func formatHistory(rls []*release.Release) string {
tbl := uitable.New()
tbl.MaxColWidth = 60
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION")
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION", "RELEASED BY")
for i := len(rls) - 1; i >= 0; i-- {
r := rls[i]
c := formatChartname(r.Chart)
......@@ -102,7 +102,8 @@ func formatHistory(rls []*release.Release) string {
s := r.Info.Status.Code.String()
v := r.Version
d := r.Info.Description
tbl.AddRow(v, t, s, c, d)
u := r.Info.Username
tbl.AddRow(v, t, s, c, d, u)
}
return tbl.String()
}
......
......@@ -18,6 +18,7 @@ package main
import (
"bytes"
"fmt"
"regexp"
"testing"
......@@ -51,7 +52,7 @@ func TestHistoryCmd(t *testing.T) {
mk("angry-bird", 2, rpb.Status_SUPERSEDED),
mk("angry-bird", 1, rpb.Status_SUPERSEDED),
},
xout: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
xout: fmt.Sprintf("REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \tRELEASED BY\n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\t%s \n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\t%s \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\t%s \n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\t%s \n", username, username, username, username),
},
{
cmds: "helm history --max=MAX RELEASE_NAME",
......@@ -61,7 +62,7 @@ func TestHistoryCmd(t *testing.T) {
mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
},
xout: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
xout: fmt.Sprintf("REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \tRELEASED BY\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\t%s \n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\t%s \n", username, username),
},
}
......
......@@ -93,7 +93,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
list.filter = strings.Join(args, " ")
}
if list.client == nil {
list.client = newClient()
list.client = helm.NewClient(helm.Host(settings.TillerHost), helm.WithContext(loadAuthHeaders))
}
return list.run()
},
......@@ -210,14 +210,15 @@ func (l *listCmd) statusCodes() []release.Status_Code {
func formatList(rels []*release.Release) string {
table := uitable.New()
table.MaxColWidth = 60
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE")
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE", "RELEASED BY")
for _, r := range rels {
c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version)
t := timeconv.String(r.Info.LastDeployed)
s := r.Info.Status.Code.String()
v := r.Version
n := r.Namespace
table.AddRow(r.Name, v, t, s, c, n)
u := r.Info.Username
table.AddRow(r.Name, v, t, s, c, n, u)
}
return table.String()
}
......@@ -18,6 +18,7 @@ package main
import (
"bytes"
"fmt"
"regexp"
"testing"
......@@ -46,7 +47,7 @@ func TestListCmd(t *testing.T) {
resp: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}),
},
expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n",
expected: fmt.Sprintf("NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\tRELEASED BY\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \t%s \n", username),
},
{
name: "list, one deployed, one failed",
......
......@@ -29,6 +29,7 @@ import (
var printReleaseTemplate = `REVISION: {{.Release.Version}}
RELEASED: {{.ReleaseDate}}
RELEASED BY: {{.ReleasedBy}}
CHART: {{.Release.Chart.Metadata.Name}}-{{.Release.Chart.Metadata.Version}}
USER-SUPPLIED VALUES:
{{.Release.Config.Raw}}
......@@ -62,6 +63,7 @@ func printRelease(out io.Writer, rel *release.Release) error {
"Release": rel,
"ComputedValues": cfgStr,
"ReleaseDate": timeconv.Format(rel.Info.LastDeployed, time.ANSIC),
"ReleasedBy": rel.Info.Username,
}
return tpl(printReleaseTemplate, data, out)
}
......
......@@ -40,6 +40,7 @@ The status consists of:
- last deployment time
- k8s namespace in which the release lives
- state of the release (can be: UNKNOWN, DEPLOYED, DELETED, SUPERSEDED, FAILED or DELETING)
- name of the user who deployed the release
- list of resources that this release consists of, sorted by kind
- details on last test suite run, if applicable
- additional notes provided by the chart
......@@ -70,7 +71,7 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
}
status.release = args[0]
if status.client == nil {
status.client = newClient()
status.client = helm.NewClient(helm.Host(settings.TillerHost), helm.WithContext(loadAuthHeaders))
}
return status.run()
},
......@@ -119,6 +120,7 @@ func PrintStatus(out io.Writer, res *services.GetReleaseStatusResponse) {
}
fmt.Fprintf(out, "NAMESPACE: %s\n", res.Namespace)
fmt.Fprintf(out, "STATUS: %s\n", res.Info.Status.Code)
fmt.Fprintf(out, "RELEASED BY: %s\n", res.Info.Username)
fmt.Fprintf(out, "\n")
if len(res.Info.Status.Resources) > 0 {
re := regexp.MustCompile(" +")
......
......@@ -51,7 +51,7 @@ func TestStatusCmd(t *testing.T) {
{
name: "get status of a deployed release",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\n"),
expected: outputWithStatus("DEPLOYED", username+"\n\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
}),
......@@ -59,7 +59,7 @@ func TestStatusCmd(t *testing.T) {
{
name: "get status of a deployed release with notes",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nNOTES:\nrelease notes\n"),
expected: outputWithStatus("DEPLOYED", username+"\n\nNOTES:\nrelease notes\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
......@@ -78,7 +78,7 @@ func TestStatusCmd(t *testing.T) {
{
name: "get status of a deployed release with resources",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nRESOURCES:\nresource A\nresource B\n\n"),
expected: outputWithStatus("DEPLOYED", username+"\n\nRESOURCES:\nresource A\nresource B\n\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
......@@ -152,10 +152,11 @@ func TestStatusCmd(t *testing.T) {
}
}
func outputWithStatus(status string) string {
return fmt.Sprintf("LAST DEPLOYED: %s\nNAMESPACE: \nSTATUS: %s",
func outputWithStatus(status string, username string) string {
return fmt.Sprintf("LAST DEPLOYED: %s\nNAMESPACE: \nSTATUS: %s\nRELEASED BY: %s",
dateString,
status)
status,
username)
}
func releaseMockWithStatus(status *release.Status) *release.Release {
......@@ -165,6 +166,7 @@ func releaseMockWithStatus(status *release.Status) *release.Release {
FirstDeployed: &date,
LastDeployed: &date,
Status: status,
Username: username,
},
}
}
......@@ -2,9 +2,7 @@
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
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
......@@ -111,8 +109,7 @@ func main() {
start()
}
func start() {
func start(c *cobra.Command, args []string) {
clientset, err := kube.New(nil).ClientSet()
if err != nil {
logger.Fatalf("Cannot initialize Kubernetes connection: %s", err)
......@@ -163,7 +160,7 @@ func start() {
}))
}
rootServer = tiller.NewServer(opts...)
rootServer = tiller.NewServer(client, opts...)
lstn, err := net.Listen("tcp", *grpcAddr)
if err != nil {
......
......@@ -57,6 +57,9 @@ func (h *Client) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesRespo
req := &h.opts.listReq
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -89,7 +92,9 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...
req.DisableHooks = h.opts.disableHooks
req.ReuseName = h.opts.reuseName
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -127,7 +132,9 @@ func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.Unins
req.Name = rlsName
req.DisableHooks = h.opts.disableHooks
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -164,7 +171,9 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts
req.ResetValues = h.opts.resetValues
req.ReuseValues = h.opts.reuseValues
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -189,7 +198,9 @@ func (h *Client) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, err
}
req := &rls.GetVersionRequest{}
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -210,7 +221,9 @@ func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.R
req.DryRun = h.opts.dryRun
req.Name = rlsName
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -227,7 +240,9 @@ func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetRe
req := &h.opts.statusReq
req.Name = rlsName
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -244,7 +259,9 @@ func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.Get
req := &h.opts.contentReq
req.Name = rlsName
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......@@ -262,7 +279,9 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get
req := &h.opts.histReq
req.Name = rlsName
ctx := NewContext()
if h.opts.withContext != nil {
ctx = h.opts.withContext(ctx)
}
if h.opts.before != nil {
if err := h.opts.before(ctx, req); err != nil {
return nil, err
......
......@@ -22,7 +22,6 @@ import (
"github.com/golang/protobuf/proto"
"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"
......@@ -68,6 +67,8 @@ type options struct {
contentReq rls.GetReleaseContentRequest
// release rollback options are applied directly to the rollback release request
rollbackReq rls.RollbackReleaseRequest
// withContext adds metadata to context before sending
withContext func(context.Context) context.Context
// before intercepts client calls before sending
before func(context.Context, proto.Message) error
// release history options are applied directly to the get release history request
......@@ -95,6 +96,14 @@ func WithTLS(cfg *tls.Config) Option {
}
}
// WithContext returns an option that allows adding metadata to context of a helm client rpc
// before being sent OTA to tiller.
func WithContext(fn func(context.Context) context.Context) Option {
return func(opts *options) {
opts.withContext = fn
}
}
// BeforeCall returns an option that allows intercepting a helm client rpc
// before being sent OTA to tiller. The intercepting function should return
// an error to indicate that the call should not proceed or nil otherwise.
......
/*
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 kube
//AuthHeader is key type for context
type AuthKey string
const (
Authorization AuthKey = "authorization"
UserInfo AuthKey = "k8s-user-info"
UserClient AuthKey = "k8s-user-client"
SystemClient AuthKey = "k8s-sys-client"
ImpersonateUser AuthKey = "k8s-impersonate-user"
)
......@@ -42,9 +42,13 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/clientcmd"
batchinternal "k8s.io/kubernetes/pkg/apis/batch"
batch "k8s.io/kubernetes/pkg/apis/batch/v1"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion"
conditions "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
......@@ -82,9 +86,25 @@ func New(config clientcmd.ClientConfig) *Client {
// ResourceActorFunc performs an action on a single resource.
type ResourceActorFunc func(*resource.Info) error
// Discovery retrieves the DiscoveryClient
func (c *Client) Discovery() (discovery.DiscoveryInterface, error) {
client, err := c.ClientSet()
if err != nil {
return nil, err
}
return client.Discovery(), nil
}
// Authorization retrieves the AuthorizationInterface
func (c *Client) Authorization() (internalversion.AuthorizationInterface, error) {
client, err := c.ClientSet()
if err != nil {
return nil, err
}
return client.Authorization(), nil
}
// Create creates Kubernetes resources from an io.reader.
//
// Namespace will set the namespace.
func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
client, err := c.ClientSet()
if err != nil {
......@@ -108,6 +128,7 @@ func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shoul
return nil
}
// Namespace will set the namespace.
func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result {
return c.NewBuilder().
Internal().
......
......@@ -22,6 +22,8 @@ type Info struct {
Deleted *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"`
// Description is human-friendly "log entry" about this release.
Description string `protobuf:"bytes,5,opt,name=Description" json:"Description,omitempty"`
// Username is the authenticated user who performed this release.
Username string `protobuf:"bytes,6,opt,name=Username" json:"Username,omitempty"`
}
func (m *Info) Reset() { *m = Info{} }
......@@ -64,6 +66,13 @@ func (m *Info) GetDescription() string {
return ""
}
func (m *Info) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func init() {
proto.RegisterType((*Info)(nil), "hapi.release.Info")
}
......@@ -71,20 +80,21 @@ func init() {
func init() { proto.RegisterFile("hapi/release/info.proto", fileDescriptor1) }
var fileDescriptor1 = []byte{
// 235 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0x31, 0x4f, 0xc3, 0x30,
0x10, 0x85, 0x95, 0x52, 0x5a, 0xd5, 0x6d, 0x19, 0x2c, 0x24, 0x42, 0x16, 0x22, 0xa6, 0x0e, 0xc8,
0x91, 0x80, 0x1d, 0x81, 0xba, 0xb0, 0x06, 0x26, 0x16, 0xe4, 0xe2, 0x73, 0xb1, 0xe4, 0xe6, 0x2c,
0xfb, 0x3a, 0xf0, 0x2f, 0xf8, 0xc9, 0xa8, 0xb6, 0x83, 0xd2, 0xa9, 0xab, 0xbf, 0xf7, 0x3e, 0xbf,
0x63, 0x57, 0xdf, 0xd2, 0x99, 0xc6, 0x83, 0x05, 0x19, 0xa0, 0x31, 0x9d, 0x46, 0xe1, 0x3c, 0x12,
0xf2, 0xc5, 0x01, 0x88, 0x0c, 0xaa, 0x9b, 0x2d, 0xe2, 0xd6, 0x42, 0x13, 0xd9, 0x66, 0xaf, 0x1b,
0x32, 0x3b, 0x08, 0x24, 0x77, 0x2e, 0xc5, 0xab, 0xeb, 0x23, 0x4f, 0x20, 0x49, 0xfb, 0x90, 0xd0,
0xed, 0xef, 0x88, 0x8d, 0x5f, 0x3b, 0x8d, 0xfc, 0x8e, 0x4d, 0x12, 0x28, 0x8b, 0xba, 0x58, 0xcd,
0xef, 0x2f, 0xc5, 0xf0, 0x0f, 0xf1, 0x16, 0x59, 0x9b, 0x33, 0xfc, 0x99, 0x5d, 0x68, 0xe3, 0x03,
0x7d, 0x2a, 0x70, 0x16, 0x7f, 0x40, 0x95, 0xa3, 0xd8, 0xaa, 0x44, 0xda, 0x22, 0xfa, 0x2d, 0xe2,
0xbd, 0xdf, 0xd2, 0x2e, 0x63, 0x63, 0x9d, 0x0b, 0xfc, 0x89, 0x2d, 0xad, 0x1c, 0x1a, 0xce, 0x4e,
0x1a, 0x16, 0x87, 0xc2, 0xbf, 0xe0, 0x91, 0x4d, 0x15, 0x58, 0x20, 0x50, 0xe5, 0xf8, 0x64, 0xb5,
0x8f, 0xf2, 0x9a, 0xcd, 0xd7, 0x10, 0xbe, 0xbc, 0x71, 0x64, 0xb0, 0x2b, 0xcf, 0xeb, 0x62, 0x35,
0x6b, 0x87, 0x4f, 0x2f, 0xb3, 0x8f, 0x69, 0xbe, 0x7a, 0x33, 0x89, 0xa6, 0x87, 0xbf, 0x00, 0x00,
0x00, 0xff, 0xff, 0x1a, 0x52, 0x8f, 0x9c, 0x89, 0x01, 0x00, 0x00,
// 249 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0x31, 0x4f, 0xc3, 0x30,
0x10, 0x85, 0x95, 0x52, 0x52, 0xea, 0xb6, 0x0c, 0x16, 0x12, 0x26, 0x0b, 0x11, 0x53, 0x07, 0xe4,
0x48, 0xc0, 0x8e, 0x40, 0x5d, 0x58, 0x03, 0x2c, 0x2c, 0xc8, 0x25, 0x97, 0x62, 0xc9, 0xc9, 0x59,
0xf6, 0x75, 0xe0, 0x3f, 0xf1, 0x23, 0x51, 0x1d, 0xa7, 0x0a, 0x53, 0xc6, 0xe4, 0x7b, 0xdf, 0xbb,