mirror of
https://github.com/niusmallnan/steve.git
synced 2025-05-14 10:49:27 +00:00
Remove clusters and helm release resources
This commit is contained in:
parent
a3fbe499d1
commit
dcea1db3d7
@ -1,174 +0,0 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"github.com/rancher/steve/pkg/podimpersonation"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/switchschema"
|
||||
"github.com/rancher/steve/pkg/stores/switchstore"
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
const (
|
||||
rancherCluster = "management.cattle.io.cluster"
|
||||
localID = "local"
|
||||
)
|
||||
|
||||
var (
|
||||
local = types.APIObject{
|
||||
Type: "cluster",
|
||||
ID: localID,
|
||||
Object: &Cluster{},
|
||||
}
|
||||
localList = types.APIObjectList{
|
||||
Objects: []types.APIObject{
|
||||
local,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func Register(ctx context.Context, schemas *types.APISchemas, cg proxy.ClientGetter, cluster clustercache.ClusterCache) error {
|
||||
k8s, err := cg.AdminK8sInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shell := &shell{
|
||||
cg: cg,
|
||||
namespace: "dashboard-shells",
|
||||
impersonator: podimpersonation.New("shell", cg, time.Hour),
|
||||
}
|
||||
|
||||
picker := &picker{
|
||||
start: time.Now(),
|
||||
discovery: k8s.Discovery(),
|
||||
}
|
||||
|
||||
cluster.OnAdd(ctx, shell.impersonator.PurgeOldRoles)
|
||||
cluster.OnChange(ctx, func(gvr schema.GroupVersionResource, key string, obj, oldObj runtime.Object) error {
|
||||
return shell.impersonator.PurgeOldRoles(gvr, key, obj)
|
||||
})
|
||||
schemas.MustImportAndCustomize(Cluster{}, func(schema *types.APISchema) {
|
||||
schema.CollectionMethods = []string{http.MethodGet}
|
||||
schema.ResourceMethods = []string{http.MethodGet}
|
||||
schema.Formatter = Format
|
||||
schema.Store = &switchstore.Store{
|
||||
Picker: picker.Picker,
|
||||
}
|
||||
schema.LinkHandlers = map[string]http.Handler{
|
||||
"shell": shell,
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Cluster struct {
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ClusterSpec `json:"spec"`
|
||||
Status ClusterStatus `json:"status"`
|
||||
}
|
||||
|
||||
type ClusterSpec struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
type ClusterStatus struct {
|
||||
Driver string `json:"driver"`
|
||||
Version *version.Info `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func Format(request *types.APIRequest, resource *types.RawResource) {
|
||||
copy := [][]string{
|
||||
{"spec", "displayName"},
|
||||
{"metadata", "creationTimestamp"},
|
||||
{"status", "driver"},
|
||||
{"status", "version"},
|
||||
}
|
||||
|
||||
from := resource.APIObject.Data()
|
||||
to := data.New()
|
||||
|
||||
for _, keys := range copy {
|
||||
to.SetNested(data.GetValueN(from, keys...), keys...)
|
||||
}
|
||||
|
||||
resource.APIObject.Object = to
|
||||
resource.Links["api"] = request.URLBuilder.RelativeToRoot("/k8s/clusters/" + resource.ID)
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
empty.Store
|
||||
|
||||
start time.Time
|
||||
discovery discovery.DiscoveryInterface
|
||||
}
|
||||
|
||||
type picker struct {
|
||||
start time.Time
|
||||
discovery discovery.DiscoveryInterface
|
||||
}
|
||||
|
||||
func (p *picker) Picker(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (types.Store, error) {
|
||||
clusters := apiOp.Schemas.LookupSchema(rancherCluster)
|
||||
if clusters == nil {
|
||||
return &Store{
|
||||
start: p.start,
|
||||
discovery: p.discovery,
|
||||
}, nil
|
||||
}
|
||||
return &switchschema.Store{
|
||||
Schema: clusters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
if id == localID {
|
||||
return s.newLocal(), nil
|
||||
}
|
||||
return types.APIObject{}, validation.NotFound
|
||||
}
|
||||
|
||||
func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||
return types.APIObjectList{
|
||||
Objects: []types.APIObject{
|
||||
s.newLocal(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) newLocal() types.APIObject {
|
||||
cluster := &Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
CreationTimestamp: metav1.NewTime(s.start),
|
||||
},
|
||||
Spec: ClusterSpec{
|
||||
DisplayName: "Remote",
|
||||
},
|
||||
Status: ClusterStatus{
|
||||
Driver: "remote",
|
||||
},
|
||||
}
|
||||
version, err := s.discovery.ServerVersion()
|
||||
if err == nil {
|
||||
cluster.Status.Version = version
|
||||
}
|
||||
return types.APIObject{
|
||||
Type: "cluster",
|
||||
ID: localID,
|
||||
Object: cluster,
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/steve/pkg/podimpersonation"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type shell struct {
|
||||
namespace string
|
||||
impersonator *podimpersonation.PodImpersonation
|
||||
cg proxy.ClientGetter
|
||||
}
|
||||
|
||||
func (s *shell) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, user, client, err := s.contextAndClient(req)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
pod, err := s.impersonator.CreatePod(ctx, user, s.createPod(), &podimpersonation.PodOptions{
|
||||
Wait: true,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.proxyRequest(rw, req, pod, client)
|
||||
}
|
||||
|
||||
func (s *shell) proxyRequest(rw http.ResponseWriter, req *http.Request, pod *v1.Pod, client kubernetes.Interface) {
|
||||
attachURL := client.CoreV1().RESTClient().
|
||||
Get().
|
||||
Namespace(pod.Namespace).
|
||||
Resource("pods").
|
||||
Name(pod.Name).
|
||||
SubResource("attach").
|
||||
VersionedParams(&v1.PodAttachOptions{
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
TTY: true,
|
||||
Container: "shell",
|
||||
}, scheme.ParameterCodec).URL()
|
||||
|
||||
httpClient := client.CoreV1().RESTClient().(*rest.RESTClient).Client
|
||||
p := httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL = attachURL
|
||||
req.Host = attachURL.Host
|
||||
delete(req.Header, "Authorization")
|
||||
delete(req.Header, "Cookie")
|
||||
},
|
||||
Transport: httpClient.Transport,
|
||||
FlushInterval: time.Millisecond * 100,
|
||||
}
|
||||
|
||||
p.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (s *shell) contextAndClient(req *http.Request) (context.Context, user.Info, kubernetes.Interface, error) {
|
||||
ctx := req.Context()
|
||||
client, err := s.cg.AdminK8sInterface()
|
||||
if err != nil {
|
||||
return ctx, nil, nil, err
|
||||
}
|
||||
|
||||
user, ok := request.UserFrom(ctx)
|
||||
if !ok {
|
||||
return ctx, nil, nil, validation.Unauthorized
|
||||
}
|
||||
|
||||
return ctx, user, client, nil
|
||||
}
|
||||
|
||||
func (s *shell) createPod() *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "dashboard-shell-",
|
||||
Namespace: s.namespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
TerminationGracePeriodSeconds: new(int64),
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "shell",
|
||||
TTY: true,
|
||||
Stdin: true,
|
||||
StdinOnce: true,
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "KUBECONFIG",
|
||||
Value: "/home/shell/.kube/config",
|
||||
},
|
||||
},
|
||||
Image: "ibuildthecloud/shell:v0.0.1",
|
||||
ImagePullPolicy: v1.PullIfNotPresent,
|
||||
Command: []string{"bash"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -2,11 +2,6 @@ package helm
|
||||
|
||||
import (
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schema/converter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func DropHelmData(request *types.APIRequest, resource *types.RawResource) {
|
||||
@ -18,41 +13,3 @@ func DropHelmData(request *types.APIRequest, resource *types.RawResource) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FormatRelease(request *types.APIRequest, resource *types.RawResource) {
|
||||
obj, ok := resource.APIObject.Object.(runtime.Object)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
release, err := ToRelease(obj, SchemeBasedNamespaceLookup(request.Schemas))
|
||||
if err == ErrNotHelmRelease {
|
||||
return
|
||||
} else if err != nil {
|
||||
logrus.Errorf("failed to render helm release: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
data = resource.APIObject.Data()
|
||||
namespace = data.String("metadata", "namespace")
|
||||
name = data.String("metadata", "name")
|
||||
)
|
||||
|
||||
switch data.String("kind") {
|
||||
case "Secret":
|
||||
resource.ID = namespace + "/s:" + name
|
||||
case "ConfigMap":
|
||||
resource.ID = namespace + "/c:" + name
|
||||
}
|
||||
|
||||
resource.Links["self"] = request.URLBuilder.ResourceLink(request.Schema, resource.ID)
|
||||
resource.APIObject.Object = release
|
||||
}
|
||||
|
||||
func SchemeBasedNamespaceLookup(schemas *types.APISchemas) IsNamespaced {
|
||||
return func(gvk schema.GroupVersionKind) bool {
|
||||
schema := schemas.LookupSchema(converter.GVKToSchemaID(gvk))
|
||||
return schema != nil && attributes.Namespaced(schema)
|
||||
}
|
||||
}
|
||||
|
@ -1,158 +0,0 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/sirupsen/logrus"
|
||||
rspb "k8s.io/helm/pkg/proto/hapi/release"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
readmes = map[string]bool{
|
||||
"readme": true,
|
||||
"readme.txt": true,
|
||||
"readme.md": true,
|
||||
}
|
||||
statusMapping = map[string]Status{
|
||||
"UNKNOWN": StatusUnknown,
|
||||
"DEPLOYED": StatusDeployed,
|
||||
"DELETED": StatusUninstalled,
|
||||
"SUPERSEDED": StatusSuperseded,
|
||||
"FAILED": StatusFailed,
|
||||
"DELETING": StatusUninstalling,
|
||||
"PENDING_INSTALL": StatusPendingInstall,
|
||||
"PENDING_UPGRADE": StatusPendingUpgrade,
|
||||
"PENDING_ROLLBACK": StatusPendingRollback,
|
||||
}
|
||||
)
|
||||
|
||||
func isHelm2(labels map[string]string) bool {
|
||||
return labels["OWNER"] == "TILLER"
|
||||
}
|
||||
|
||||
func fromHelm2Data(data string, isNamespaced IsNamespaced) (*Release, error) {
|
||||
release, err := decodeHelm2(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromHelm2ReleaseToRelease(release, isNamespaced)
|
||||
}
|
||||
|
||||
func toTime(t *timestamp.Timestamp) time.Time {
|
||||
return time.Unix(t.GetSeconds(), int64(t.GetNanos())).UTC()
|
||||
}
|
||||
|
||||
func fromHelm2ReleaseToRelease(release *rspb.Release, isNamespaced IsNamespaced) (*Release, error) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
hr := &Release{
|
||||
Name: release.Name,
|
||||
Info: &Info{
|
||||
FirstDeployed: toTime(release.GetInfo().GetFirstDeployed()),
|
||||
LastDeployed: toTime(release.GetInfo().GetLastDeployed()),
|
||||
Deleted: toTime(release.GetInfo().GetDeleted()),
|
||||
Description: release.GetInfo().GetDescription(),
|
||||
Status: statusMapping[release.GetInfo().GetStatus().GetCode().String()],
|
||||
Notes: release.GetInfo().GetStatus().GetNotes(),
|
||||
},
|
||||
Chart: &Chart{
|
||||
Values: toMap(release.Namespace, release.Name, release.GetChart().GetValues().GetRaw()),
|
||||
Metadata: &Metadata{
|
||||
Name: release.GetChart().GetMetadata().GetName(),
|
||||
Home: release.GetChart().GetMetadata().GetHome(),
|
||||
Sources: release.GetChart().GetMetadata().GetSources(),
|
||||
Version: release.GetChart().GetMetadata().GetVersion(),
|
||||
Description: release.GetChart().GetMetadata().GetDescription(),
|
||||
Keywords: release.GetChart().GetMetadata().GetKeywords(),
|
||||
Icon: release.GetChart().GetMetadata().GetIcon(),
|
||||
Condition: release.GetChart().GetMetadata().GetCondition(),
|
||||
Tags: release.GetChart().GetMetadata().GetTags(),
|
||||
AppVersion: release.GetChart().GetMetadata().GetAppVersion(),
|
||||
Deprecated: release.GetChart().GetMetadata().GetDeprecated(),
|
||||
Annotations: release.GetChart().GetMetadata().GetAnnotations(),
|
||||
KubeVersion: release.GetChart().GetMetadata().GetKubeVersion(),
|
||||
},
|
||||
},
|
||||
Values: toMap(release.Namespace, release.Name, release.GetConfig().GetRaw()),
|
||||
Version: int(release.Version),
|
||||
Namespace: release.Namespace,
|
||||
HelmMajorVersion: 3,
|
||||
}
|
||||
|
||||
for _, m := range release.GetChart().GetMetadata().GetMaintainers() {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
hr.Chart.Metadata.Maintainers = append(hr.Chart.Metadata.Maintainers, Maintainer{
|
||||
Name: m.GetName(),
|
||||
Email: m.GetEmail(),
|
||||
URL: m.GetUrl(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, f := range release.GetChart().GetFiles() {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if readmes[strings.ToLower(f.TypeUrl)] {
|
||||
hr.Info.Readme = string(f.Value)
|
||||
}
|
||||
}
|
||||
|
||||
hr.Resources, err = resourcesFromManifest(release.Namespace, release.Manifest, isNamespaced)
|
||||
return hr, err
|
||||
}
|
||||
|
||||
func toMap(namespace, name string, manifest string) map[string]interface{} {
|
||||
values := map[string]interface{}{}
|
||||
|
||||
if manifest == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(manifest), &values); err != nil {
|
||||
logrus.Errorf("failed to unmarshal yaml for %s/%s", namespace, name)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func decodeHelm2(data string) (*rspb.Release, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For backwards compatibility with releases that were stored before
|
||||
// compression was introduced we skip decompression if the
|
||||
// gzip magic header is not found
|
||||
if bytes.Equal(b[0:3], magicGzip) {
|
||||
r, err := gzip.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = b2
|
||||
}
|
||||
|
||||
var rls rspb.Release
|
||||
// unmarshal protobuf bytes
|
||||
if err := proto.Unmarshal(b, &rls); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rls, nil
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
func isHelm3(labels map[string]string) bool {
|
||||
return labels["owner"] == "helm"
|
||||
}
|
||||
|
||||
func fromHelm3Data(data string, isNamespaced IsNamespaced) (*Release, error) {
|
||||
release, err := decodeHelm3(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromHelm3ReleaseToRelease(release, isNamespaced)
|
||||
}
|
||||
|
||||
func fromHelm3ReleaseToRelease(release *release.Release, isNamespaced IsNamespaced) (*Release, error) {
|
||||
var (
|
||||
info = &Info{}
|
||||
chart = &Chart{}
|
||||
err error
|
||||
)
|
||||
|
||||
if release.Info != nil {
|
||||
info = &Info{
|
||||
FirstDeployed: release.Info.FirstDeployed.Time,
|
||||
LastDeployed: release.Info.LastDeployed.Time,
|
||||
Deleted: release.Info.Deleted.Time,
|
||||
Description: release.Info.Description,
|
||||
Status: Status(release.Info.Status),
|
||||
Notes: release.Info.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
if release.Chart != nil {
|
||||
chart = &Chart{
|
||||
Values: release.Chart.Values,
|
||||
}
|
||||
if release.Chart.Metadata != nil {
|
||||
chart.Metadata = &Metadata{
|
||||
Name: release.Chart.Metadata.Name,
|
||||
Home: release.Chart.Metadata.Home,
|
||||
Sources: release.Chart.Metadata.Sources,
|
||||
Version: release.Chart.Metadata.Version,
|
||||
Description: release.Chart.Metadata.Description,
|
||||
Keywords: release.Chart.Metadata.Keywords,
|
||||
Icon: release.Chart.Metadata.Icon,
|
||||
APIVersion: release.Chart.Metadata.APIVersion,
|
||||
Condition: release.Chart.Metadata.Condition,
|
||||
Tags: release.Chart.Metadata.Tags,
|
||||
AppVersion: release.Chart.Metadata.AppVersion,
|
||||
Deprecated: release.Chart.Metadata.Deprecated,
|
||||
Annotations: release.Chart.Metadata.Annotations,
|
||||
KubeVersion: release.Chart.Metadata.KubeVersion,
|
||||
Type: release.Chart.Metadata.Type,
|
||||
}
|
||||
|
||||
for _, m := range release.Chart.Metadata.Maintainers {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
chart.Metadata.Maintainers = append(chart.Metadata.Maintainers, Maintainer{
|
||||
Name: m.Name,
|
||||
Email: m.Email,
|
||||
URL: m.URL,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range release.Chart.Files {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if readmes[strings.ToLower(f.Name)] {
|
||||
info.Readme = string(f.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr := &Release{
|
||||
Name: release.Name,
|
||||
Info: info,
|
||||
Chart: chart,
|
||||
Values: release.Config,
|
||||
Resources: nil,
|
||||
Version: release.Version,
|
||||
Namespace: release.Namespace,
|
||||
HelmMajorVersion: 3,
|
||||
}
|
||||
|
||||
hr.Resources, err = resourcesFromManifest(release.Namespace, release.Manifest, isNamespaced)
|
||||
return hr, err
|
||||
}
|
||||
|
||||
func decodeHelm3(data string) (*release.Release, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For backwards compatibility with releases that were stored before
|
||||
// compression was introduced we skip decompression if the
|
||||
// gzip magic header is not found
|
||||
if bytes.Equal(b[0:3], magicGzip) {
|
||||
r, err := gzip.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = b2
|
||||
}
|
||||
|
||||
var rls release.Release
|
||||
// unmarshal release object bytes
|
||||
if err := json.Unmarshal(b, &rls); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rls, nil
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/stores/partition"
|
||||
)
|
||||
|
||||
func Register(schemas *types.APISchemas) {
|
||||
schemas.InternalSchemas.TypeName("helmrelease", Release{})
|
||||
schemas.MustImportAndCustomize(Release{}, func(schema *types.APISchema) {
|
||||
schema.CollectionMethods = []string{http.MethodGet}
|
||||
schema.ResourceMethods = []string{http.MethodGet}
|
||||
schema.Store = &partition.Store{
|
||||
Partitioner: &partitioner{},
|
||||
}
|
||||
schema.Formatter = FormatRelease
|
||||
})
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/yaml"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
meta2 "k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotHelmRelease = errors.New("not helm release")
|
||||
magicGzip = []byte{0x1f, 0x8b, 0x08}
|
||||
)
|
||||
|
||||
type IsNamespaced func(gvk schema.GroupVersionKind) bool
|
||||
|
||||
func ToRelease(obj runtime.Object, isNamespaced IsNamespaced) (*Release, error) {
|
||||
releaseData, err := getReleaseDataAndKind(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case isHelm3(meta.GetLabels()):
|
||||
return fromHelm3Data(releaseData, isNamespaced)
|
||||
case isHelm2(meta.GetLabels()):
|
||||
return fromHelm2Data(releaseData, isNamespaced)
|
||||
}
|
||||
|
||||
return nil, ErrNotHelmRelease
|
||||
}
|
||||
|
||||
func getReleaseDataAndKind(obj runtime.Object) (string, error) {
|
||||
switch t := obj.(type) {
|
||||
case *unstructured.Unstructured:
|
||||
releaseData := data.Object(t.Object).String("data", "release")
|
||||
switch t.GetKind() {
|
||||
case "ConfigMap":
|
||||
return releaseData, nil
|
||||
case "Secret":
|
||||
data, err := base64.StdEncoding.DecodeString(releaseData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
case *corev1.ConfigMap:
|
||||
return t.Data["release"], nil
|
||||
case *corev1.Secret:
|
||||
return string(t.Data["release"]), nil
|
||||
}
|
||||
|
||||
return "", ErrNotHelmRelease
|
||||
}
|
||||
|
||||
func resourcesFromManifest(namespace string, manifest string, isNamespaced IsNamespaced) (result []Resource, err error) {
|
||||
objs, err := yaml.ToObjects(bytes.NewReader([]byte(manifest)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
meta, err := meta2.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := Resource{
|
||||
Name: meta.GetName(),
|
||||
Namespace: meta.GetNamespace(),
|
||||
}
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
if isNamespaced != nil && isNamespaced(gvk) && r.Namespace == "" {
|
||||
r.Namespace = namespace
|
||||
}
|
||||
r.APIVersion, r.Kind = gvk.ToAPIVersionAndKind()
|
||||
result = append(result, r)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/stores/partition"
|
||||
"github.com/rancher/steve/pkg/stores/selector"
|
||||
"github.com/rancher/steve/pkg/stores/switchschema"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
var (
|
||||
configMap2 = target{
|
||||
schemaType: "configmap",
|
||||
version: "2",
|
||||
selector: labels.SelectorFromSet(labels.Set{
|
||||
"OWNER": "TILLER",
|
||||
}),
|
||||
}
|
||||
secret2 = target{
|
||||
schemaType: "secret",
|
||||
version: "2",
|
||||
selector: labels.SelectorFromSet(labels.Set{
|
||||
"OWNER": "TILLER",
|
||||
}),
|
||||
}
|
||||
secret3 = target{
|
||||
schemaType: "secret",
|
||||
version: "3",
|
||||
selector: labels.SelectorFromSet(labels.Set{
|
||||
"owner": "helm",
|
||||
}),
|
||||
}
|
||||
all = []partition.Partition{
|
||||
configMap2,
|
||||
secret2,
|
||||
secret3,
|
||||
}
|
||||
)
|
||||
|
||||
type target struct {
|
||||
schemaType string
|
||||
version string
|
||||
selector labels.Selector
|
||||
}
|
||||
|
||||
func (t target) Name() string {
|
||||
return t.schemaType + t.version
|
||||
}
|
||||
|
||||
type partitioner struct {
|
||||
}
|
||||
|
||||
func (p *partitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (partition.Partition, error) {
|
||||
if id == "" {
|
||||
return nil, validation.Unauthorized
|
||||
}
|
||||
t := strings.SplitN(id, ":", 2)[0]
|
||||
if t == "c" {
|
||||
return configMap2, nil
|
||||
} else if t == "s" {
|
||||
return secret2, nil
|
||||
}
|
||||
return nil, validation.NotFound
|
||||
}
|
||||
|
||||
func (p *partitioner) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]partition.Partition, error) {
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (p *partitioner) Store(apiOp *types.APIRequest, partition partition.Partition) (types.Store, error) {
|
||||
target := partition.(target)
|
||||
schema := apiOp.Schemas.LookupSchema(target.schemaType)
|
||||
if schema == nil {
|
||||
return &empty.Store{}, nil
|
||||
}
|
||||
return &stripIDPrefix{
|
||||
Store: &selector.Store{
|
||||
Selector: target.selector,
|
||||
Store: &switchschema.Store{
|
||||
Schema: schema,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type stripIDPrefix struct {
|
||||
types.Store
|
||||
}
|
||||
|
||||
func stripPrefix(s string) string {
|
||||
return strings.TrimPrefix(strings.TrimPrefix(s, "c:"), "s:")
|
||||
}
|
||||
|
||||
func (s *stripIDPrefix) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
return s.Store.Delete(apiOp, schema, stripPrefix(id))
|
||||
}
|
||||
|
||||
func (s *stripIDPrefix) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
return s.Store.ByID(apiOp, schema, stripPrefix(id))
|
||||
}
|
||||
|
||||
func (s *stripIDPrefix) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) {
|
||||
return s.Store.Update(apiOp, schema, data, stripPrefix(id))
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
// Name is the name of the release
|
||||
Name string `json:"name,omitempty"`
|
||||
// Info provides information about a release
|
||||
Info *Info `json:"info,omitempty"`
|
||||
// Chart is the chart that was released.
|
||||
Chart *Chart `json:"chart,omitempty"`
|
||||
// Config is the set of extra Values added to the chart.
|
||||
// These values override the default values inside of the chart.
|
||||
Values map[string]interface{} `json:"values,omitempty"`
|
||||
// Manifest is the string representation of the rendered template.
|
||||
Resources []Resource `json:"resources,omitempty"`
|
||||
// Version is an int which represents the version of the release.
|
||||
Version int `json:"version,omitempty"`
|
||||
// Namespace is the kubernetes namespace of the release.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
HelmMajorVersion int `json:"helmVersion,omitempty"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// Chart is a helm package that contains metadata, a default config, zero or more
|
||||
// optionally parameterizable templates, and zero or more charts (dependencies).
|
||||
type Chart struct {
|
||||
// Metadata is the contents of the Chartfile.
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
// Values are default config for this chart.
|
||||
Values map[string]interface{} `json:"values"`
|
||||
}
|
||||
|
||||
// Metadata for a Chart file. This models the structure of a Chart.yaml file.
|
||||
type Metadata struct {
|
||||
// The name of the chart
|
||||
Name string `json:"name,omitempty"`
|
||||
// The URL to a relevant project page, git repo, or contact person
|
||||
Home string `json:"home,omitempty"`
|
||||
// Source is the URL to the source code of this chart
|
||||
Sources []string `json:"sources,omitempty"`
|
||||
// A SemVer 2 conformant version string of the chart
|
||||
Version string `json:"version,omitempty"`
|
||||
// A one-sentence description of the chart
|
||||
Description string `json:"description,omitempty"`
|
||||
// A list of string keywords
|
||||
Keywords []string `json:"keywords,omitempty"`
|
||||
// A list of name and URL/email address combinations for the maintainer(s)
|
||||
Maintainers []Maintainer `json:"maintainers,omitempty"`
|
||||
// The URL to an icon file.
|
||||
Icon string `json:"icon,omitempty"`
|
||||
// The API Version of this chart.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// The condition to check to enable chart
|
||||
Condition string `json:"condition,omitempty"`
|
||||
// The tags to check to enable chart
|
||||
Tags string `json:"tags,omitempty"`
|
||||
// The version of the application enclosed inside of this chart.
|
||||
AppVersion string `json:"appVersion,omitempty"`
|
||||
// Whether or not this chart is deprecated
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
// Annotations are additional mappings uninterpreted by Helm,
|
||||
// made available for inspection by other applications.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
// KubeVersion is a SemVer constraint specifying the version of Kubernetes required.
|
||||
KubeVersion string `json:"kubeVersion,omitempty"`
|
||||
// Specifies the chart type: application or library
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// Maintainer describes a Chart maintainer.
|
||||
type Maintainer struct {
|
||||
// Name is a user name or organization name
|
||||
Name string `json:"name,omitempty"`
|
||||
// Email is an optional email address to contact the named maintainer
|
||||
Email string `json:"email,omitempty"`
|
||||
// URL is an optional URL to an address for the named maintainer
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Info describes release information.
|
||||
type Info struct {
|
||||
// FirstDeployed is when the release was first deployed.
|
||||
FirstDeployed time.Time `json:"firstDeployed,omitempty"`
|
||||
// LastDeployed is when the release was last deployed.
|
||||
LastDeployed time.Time `json:"lastDeployed,omitempty"`
|
||||
// Deleted tracks when this object was deleted.
|
||||
Deleted time.Time `json:"deleted"`
|
||||
// Description is human-friendly "log entry" about this release.
|
||||
Description string `json:"description,omitempty"`
|
||||
// Status is the current state of the release
|
||||
Status Status `json:"status,omitempty" wrangler:"options=unknown|deployed|uninstalled|superseded|failed|uninstalling|pending-install|pending-upgrade|pending-rollback"`
|
||||
// Contains the rendered templates/NOTES.txt if available
|
||||
Notes string `json:"notes,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
}
|
||||
|
||||
type Status string
|
||||
|
||||
// Describe the status of a release
|
||||
// NOTE: Make sure to update cmd/helm/status.go when adding or modifying any of these statuses.
|
||||
const (
|
||||
// StatusUnknown indicates that a release is in an uncertain state.
|
||||
StatusUnknown Status = "unknown"
|
||||
// StatusDeployed indicates that the release has been pushed to Kubernetes.
|
||||
StatusDeployed Status = "deployed"
|
||||
// StatusUninstalled indicates that a release has been uninstalled from Kubernetes.
|
||||
StatusUninstalled Status = "uninstalled"
|
||||
// StatusSuperseded indicates that this release object is outdated and a newer one exists.
|
||||
StatusSuperseded Status = "superseded"
|
||||
// StatusFailed indicates that the release was not successfully deployed.
|
||||
StatusFailed Status = "failed"
|
||||
// StatusUninstalling indicates that a uninstall operation is underway.
|
||||
StatusUninstalling Status = "uninstalling"
|
||||
// StatusPendingInstall indicates that an install operation is underway.
|
||||
StatusPendingInstall Status = "pending-install"
|
||||
// StatusPendingUpgrade indicates that an upgrade operation is underway.
|
||||
StatusPendingUpgrade Status = "pending-upgrade"
|
||||
// StatusPendingRollback indicates that an rollback operation is underway.
|
||||
StatusPendingRollback Status = "pending-rollback"
|
||||
)
|
@ -12,7 +12,6 @@ import (
|
||||
"github.com/rancher/steve/pkg/client"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"github.com/rancher/steve/pkg/resources/apigroups"
|
||||
"github.com/rancher/steve/pkg/resources/clusters"
|
||||
"github.com/rancher/steve/pkg/resources/common"
|
||||
"github.com/rancher/steve/pkg/resources/counts"
|
||||
"github.com/rancher/steve/pkg/resources/helm"
|
||||
@ -27,10 +26,7 @@ func DefaultSchemas(ctx context.Context, baseSchema *types.APISchemas, ccache cl
|
||||
subscribe.Register(baseSchema)
|
||||
apiroot.Register(baseSchema, []string{"v1"}, "proxy:/apis")
|
||||
userpreferences.Register(baseSchema, cg)
|
||||
helm.Register(baseSchema)
|
||||
|
||||
err := clusters.Register(ctx, baseSchema, cg, ccache)
|
||||
return baseSchema, err
|
||||
return baseSchema, nil
|
||||
}
|
||||
|
||||
func DefaultSchemaTemplates(cf *client.Factory,
|
||||
|
Loading…
Reference in New Issue
Block a user