Libraries for the equivalent of ks show env and kubectl get all -l mylabel=val

This commit is contained in:
Jesse Suen 2018-02-24 04:13:03 -08:00
parent af83297c83
commit 3082409330
No known key found for this signature in database
GPG key ID: 90C911E8A6106562
12 changed files with 99339 additions and 16 deletions

33
Gopkg.lock generated
View file

@ -579,36 +579,67 @@
packages = [
"discovery",
"discovery/fake",
"dynamic",
"dynamic/fake",
"kubernetes",
"kubernetes/fake",
"kubernetes/scheme",
"kubernetes/typed/admissionregistration/v1alpha1",
"kubernetes/typed/admissionregistration/v1alpha1/fake",
"kubernetes/typed/admissionregistration/v1beta1",
"kubernetes/typed/admissionregistration/v1beta1/fake",
"kubernetes/typed/apps/v1",
"kubernetes/typed/apps/v1/fake",
"kubernetes/typed/apps/v1beta1",
"kubernetes/typed/apps/v1beta1/fake",
"kubernetes/typed/apps/v1beta2",
"kubernetes/typed/apps/v1beta2/fake",
"kubernetes/typed/authentication/v1",
"kubernetes/typed/authentication/v1/fake",
"kubernetes/typed/authentication/v1beta1",
"kubernetes/typed/authentication/v1beta1/fake",
"kubernetes/typed/authorization/v1",
"kubernetes/typed/authorization/v1/fake",
"kubernetes/typed/authorization/v1beta1",
"kubernetes/typed/authorization/v1beta1/fake",
"kubernetes/typed/autoscaling/v1",
"kubernetes/typed/autoscaling/v1/fake",
"kubernetes/typed/autoscaling/v2beta1",
"kubernetes/typed/autoscaling/v2beta1/fake",
"kubernetes/typed/batch/v1",
"kubernetes/typed/batch/v1/fake",
"kubernetes/typed/batch/v1beta1",
"kubernetes/typed/batch/v1beta1/fake",
"kubernetes/typed/batch/v2alpha1",
"kubernetes/typed/batch/v2alpha1/fake",
"kubernetes/typed/certificates/v1beta1",
"kubernetes/typed/certificates/v1beta1/fake",
"kubernetes/typed/core/v1",
"kubernetes/typed/core/v1/fake",
"kubernetes/typed/events/v1beta1",
"kubernetes/typed/events/v1beta1/fake",
"kubernetes/typed/extensions/v1beta1",
"kubernetes/typed/extensions/v1beta1/fake",
"kubernetes/typed/networking/v1",
"kubernetes/typed/networking/v1/fake",
"kubernetes/typed/policy/v1beta1",
"kubernetes/typed/policy/v1beta1/fake",
"kubernetes/typed/rbac/v1",
"kubernetes/typed/rbac/v1/fake",
"kubernetes/typed/rbac/v1alpha1",
"kubernetes/typed/rbac/v1alpha1/fake",
"kubernetes/typed/rbac/v1beta1",
"kubernetes/typed/rbac/v1beta1/fake",
"kubernetes/typed/scheduling/v1alpha1",
"kubernetes/typed/scheduling/v1alpha1/fake",
"kubernetes/typed/settings/v1alpha1",
"kubernetes/typed/settings/v1alpha1/fake",
"kubernetes/typed/storage/v1",
"kubernetes/typed/storage/v1/fake",
"kubernetes/typed/storage/v1alpha1",
"kubernetes/typed/storage/v1alpha1/fake",
"kubernetes/typed/storage/v1beta1",
"kubernetes/typed/storage/v1beta1/fake",
"pkg/version",
"plugin/pkg/client/auth/gcp",
"plugin/pkg/client/auth/oidc",
@ -667,6 +698,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "5c03546d71efcb5e0bad0ae002c1e0a6d627fa25f9854c8f118d2201235e6919"
inputs-digest = "9f0307c913e8dc2b20bc7d25b9dabee66db88830c6d7d488ab944521c32170fd"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -13,6 +13,9 @@ const (
)
var (
// LabelKeyAppInstance refers to the application instance resource name
LabelKeyAppInstance = MetadataPrefix + "/app-instance"
// LabelKeySecretType contains the type of argocd secret (currently this is just 'repo')
LabelKeySecretType = MetadataPrefix + "/secret-type"
)

View file

@ -1,10 +1,16 @@
package ksonnet
import (
"fmt"
"os/exec"
"regexp"
"strings"
"github.com/ghodss/yaml"
"github.com/ksonnet/ksonnet/metadata"
"github.com/ksonnet/ksonnet/metadata/app"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
@ -15,26 +21,82 @@ var (
diffSeparator = regexp.MustCompile("\\n---")
)
// KsonnetApp represents a ksonnet application directory
type KsonnetApp struct {
// Manager abstracts over a ksonnet application's metadata
Manager metadata.Manager
// KsonnetApp represents a ksonnet application directory and provides wrapper functionality around
// the `ks` command.
type KsonnetApp interface {
// Root is the root path ksonnet application directory
Root() string
// Spec is the Ksonnet application spec (app.yaml)
Spec app.Spec
AppSpec() app.Spec
// Show returns a list of unstructured objects that would be applied to an environment
Show(environment string) ([]unstructured.Unstructured, error)
}
func NewKsonnetApp(path string) (*KsonnetApp, error) {
ksApp := KsonnetApp{}
type ksonnetApp struct {
manager metadata.Manager
homeDir string
spec app.Spec
}
func NewKsonnetApp(path string) (KsonnetApp, error) {
ksApp := ksonnetApp{}
mgr, err := metadata.Find(path)
if err != nil {
return nil, err
}
ksApp.Manager = mgr
spec, err := ksApp.Manager.AppSpec()
ksApp.manager = mgr
spec, err := ksApp.manager.AppSpec()
if err != nil {
return nil, err
}
ksApp.Spec = *spec
ksApp.spec = *spec
return &ksApp, nil
}
func (k *ksonnetApp) ksCmd(args ...string) (string, error) {
cmd := exec.Command("ks", args...)
cmd.Dir = k.Root()
cmdStr := strings.Join(cmd.Args, " ")
log.Debug(cmdStr)
out, err := cmd.Output()
if err != nil {
exErr := err.(*exec.ExitError)
errOutput := string(exErr.Stderr)
log.Errorf("`%s` failed: %s", cmdStr, errOutput)
return "", fmt.Errorf(strings.TrimSpace(errOutput))
}
return string(out), nil
}
func (k *ksonnetApp) Root() string {
return k.manager.Root()
}
// Spec is the Ksonnet application spec (app.yaml)
func (k *ksonnetApp) AppSpec() app.Spec {
return k.spec
}
func (k *ksonnetApp) Show(environment string) ([]unstructured.Unstructured, error) {
out, err := k.ksCmd("show", environment)
if err != nil {
return nil, err
}
parts := diffSeparator.Split(string(out), -1)
objs := make([]unstructured.Unstructured, 0)
for _, part := range parts {
if strings.TrimSpace(part) == "" {
continue
}
var obj unstructured.Unstructured
err = yaml.Unmarshal([]byte(part), &obj)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal manifest from `ks show`")
}
objs = append(objs, obj)
}
return objs, nil
}

View file

@ -1,10 +1,12 @@
package ksonnet
import (
"encoding/json"
"path"
"runtime"
"testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
@ -13,7 +15,9 @@ var (
)
const (
testAppName = "test-app"
testAppName = "test-app"
testNamespace = "test-namespace"
testEnvName = "test-env"
)
func init() {
@ -24,7 +28,20 @@ func init() {
func TestKsonnet(t *testing.T) {
ksApp, err := NewKsonnetApp(path.Join(testDataDir, testAppName))
assert.Nil(t, err)
defaultEnv, ok := ksApp.Spec.Environments["default"]
defaultEnv, ok := ksApp.AppSpec().Environments[testEnvName]
assert.True(t, ok)
assert.Equal(t, 1, len(defaultEnv.Destinations))
}
func TestShow(t *testing.T) {
ksApp, err := NewKsonnetApp(path.Join(testDataDir, testAppName))
assert.Nil(t, err)
objs, err := ksApp.Show(testEnvName)
assert.Nil(t, err)
assert.Equal(t, 2, len(objs))
for _, obj := range objs {
jsonBytes, err := json.Marshal(obj)
assert.Nil(t, err)
log.Infof("%v", string(jsonBytes))
}
}

View file

@ -1,11 +1,11 @@
apiVersion: 0.0.1
environments:
default:
test-env:
destinations:
- namespace: default
- namespace: test-namespace
server: https://1.2.3.4
k8sVersion: v1.8.0
path: default
path: test-env
kind: ksonnet.io/app
name: test-app
registries:

View file

@ -0,0 +1,80 @@
local k8s = import "k8s.libsonnet";
local apps = k8s.apps;
local core = k8s.core;
local extensions = k8s.extensions;
local hidden = {
mapContainers(f):: {
local podContainers = super.spec.template.spec.containers,
spec+: {
template+: {
spec+: {
// IMPORTANT: This overwrites the 'containers' field
// for this deployment.
containers: std.map(f, podContainers),
},
},
},
},
mapContainersWithName(names, f) ::
local nameSet =
if std.type(names) == "array"
then std.set(names)
else std.set([names]);
local inNameSet(name) = std.length(std.setInter(nameSet, std.set([name]))) > 0;
self.mapContainers(
function(c)
if std.objectHas(c, "name") && inNameSet(c.name)
then f(c)
else c
),
};
k8s + {
apps:: apps + {
v1beta1:: apps.v1beta1 + {
local v1beta1 = apps.v1beta1,
daemonSet:: v1beta1.daemonSet + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
deployment:: v1beta1.deployment + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
},
},
core:: core + {
v1:: core.v1 + {
list:: {
new(items)::
{apiVersion: "v1"} +
{kind: "List"} +
self.items(items),
items(items):: if std.type(items) == "array" then {items+: items} else {items+: [items]},
},
},
},
extensions:: extensions + {
v1beta1:: extensions.v1beta1 + {
local v1beta1 = extensions.v1beta1,
daemonSet:: v1beta1.daemonSet + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
deployment:: v1beta1.deployment + {
mapContainers(f):: hidden.mapContainers(f),
mapContainersWithName(names, f):: hidden.mapContainersWithName(names, f),
},
},
},
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,15 @@
package kube
import (
"encoding/json"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
@ -14,3 +23,82 @@ func GetClientConfig(kubeconfig string) (*rest.Config, error) {
}
return rest.InClusterConfig()
}
type Config struct {
KubeClientset kubernetes.Interface
Disco discovery.DiscoveryInterface
RestConfig *rest.Config
}
type listResult struct {
Items []*unstructured.Unstructured `json:"items"`
}
// ListAPIResources discovers all API resources supported by the Kube API sererver
func ListAPIResources(disco discovery.DiscoveryInterface) ([]metav1.APIResource, error) {
apiResources := make([]metav1.APIResource, 0)
resList, err := disco.ServerResources()
if err != nil {
return nil, errors.WithStack(err)
}
for _, resGroup := range resList {
for _, apiRes := range resGroup.APIResources {
apiResources = append(apiResources, apiRes)
}
}
return apiResources, nil
}
// ListResources returns a list of resources of a particular API type using the dynamic client
func ListResources(dclient dynamic.Interface, apiResource metav1.APIResource, namespace string, listOpts metav1.ListOptions) ([]*unstructured.Unstructured, error) {
reIf := dclient.Resource(&apiResource, namespace)
liveObjs, err := reIf.List(listOpts)
if err != nil {
return nil, errors.WithStack(err)
}
liveObjsBytes, err := json.Marshal(liveObjs)
if err != nil {
return nil, errors.WithStack(err)
}
var objList listResult
err = json.Unmarshal(liveObjsBytes, &objList)
if err != nil {
return nil, errors.WithStack(err)
}
return objList.Items, nil
}
// ListAllResources iterates the list of API resources, and returns all resources with the given filters
func ListAllResources(config *rest.Config, apiResources []metav1.APIResource, namespace string, listOpts metav1.ListOptions) ([]*unstructured.Unstructured, error) {
// itemMap dedups items when there is duplication of a resource in multiple API types
// e.g. extensions/v1beta1/namespaces/default/deployments and apps/v1/namespaces/default/deployments
itemMap := make(map[string]*unstructured.Unstructured)
for _, apiResource := range apiResources {
dynConfig := *config
dynConfig.GroupVersion = &schema.GroupVersion{
Group: apiResource.Group,
Version: apiResource.Kind,
}
dclient, err := dynamic.NewClient(&dynConfig)
if err != nil {
return nil, errors.WithStack(err)
}
resList, err := ListResources(dclient, apiResource, namespace, listOpts)
if err != nil {
return nil, errors.WithStack(err)
}
for _, liveObj := range resList {
itemMap[string(liveObj.GetUID())] = liveObj
}
}
resources := make([]*unstructured.Unstructured, len(itemMap))
i := 0
for _, obj := range itemMap {
resources[i] = obj
i++
}
return resources, nil
}

180
util/kube/kube_test.go Normal file
View file

@ -0,0 +1,180 @@
package kube
import (
"encoding/json"
"log"
"testing"
"github.com/argoproj/argo-cd/common"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apiv1 "k8s.io/api/core/v1"
extv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
fakediscovery "k8s.io/client-go/discovery/fake"
fakedynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
)
const (
testAppName = "test-app"
testNamespace = "test-namespace"
testEnvName = "test-env"
testAppInstanceName = "test-app-instance"
)
func demoService() *apiv1.Service {
return &apiv1.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeyAppInstance: testAppInstanceName,
},
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{
Port: 80,
TargetPort: intstr.FromInt(80),
},
},
Selector: map[string]string{
"app": "demo",
},
Type: "ClusterIP",
},
}
}
func demoDeployment() *appsv1.Deployment {
var two int32 = 2
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1beta1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeyAppInstance: testAppInstanceName,
},
},
Spec: appsv1.DeploymentSpec{
Replicas: &two,
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "demo",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "demo",
Image: "gcr.io/kuar-demo/kuard-amd64:1",
Ports: []apiv1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
}
}
func resourceList() []*metav1.APIResourceList {
return []*metav1.APIResourceList{
{
GroupVersion: apiv1.SchemeGroupVersion.String(),
APIResources: []metav1.APIResource{
{Name: "pods", Namespaced: true, Kind: "Pod"},
{Name: "services", Namespaced: true, Kind: "Service"},
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
{Name: "replicationcontrollers/scale", Namespaced: true, Kind: "Scale", Group: "autoscaling", Version: "v1"},
},
},
{
GroupVersion: extv1beta1.SchemeGroupVersion.String(),
APIResources: []metav1.APIResource{
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
{Name: "replicasets/scale", Namespaced: true, Kind: "Scale"},
},
},
{
GroupVersion: appsv1beta2.SchemeGroupVersion.String(),
APIResources: []metav1.APIResource{
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "deployments/scale", Namespaced: true, Kind: "Scale", Group: "apps", Version: "v1beta2"},
},
},
{
GroupVersion: appsv1beta1.SchemeGroupVersion.String(),
APIResources: []metav1.APIResource{
{Name: "statefulsets", Namespaced: true, Kind: "StatefulSet"},
{Name: "statefulsets/scale", Namespaced: true, Kind: "Scale", Group: "apps", Version: "v1beta1"},
},
},
{
GroupVersion: argoappv1.SchemeGroupVersion.String(),
APIResources: []metav1.APIResource{
{Name: "applications", Namespaced: true, Kind: "Application"},
},
},
}
}
func TestListAPIResources(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(demoService(), demoDeployment())
fakeDiscovery, ok := kubeclientset.Discovery().(*fakediscovery.FakeDiscovery)
assert.True(t, ok)
fakeDiscovery.Fake.Resources = resourceList()
apiRes, err := ListAPIResources(fakeDiscovery)
assert.Nil(t, err)
assert.Equal(t, 11, len(apiRes))
}
func TestListResources(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(demoService(), demoDeployment())
fakeDynClient := fakedynamic.FakeClient{
Fake: &kubetesting.Fake{},
}
fakeDynClient.Fake.AddReactor("list", "services", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
svcList, err := kubeclientset.CoreV1().Services(testNamespace).List(metav1.ListOptions{})
assert.Nil(t, err)
svcList.Kind = "ServiceList"
svcListBytes, err := json.Marshal(svcList)
log.Println(string(svcListBytes))
assert.Nil(t, err)
var uList unstructured.UnstructuredList
err = json.Unmarshal(svcListBytes, &uList)
assert.Nil(t, err)
return true, &uList, nil
})
apiResource := metav1.APIResource{
Name: "services",
Namespaced: true,
Version: "v1",
Kind: "Service",
}
resList, err := ListResources(&fakeDynClient, apiResource, testNamespace, metav1.ListOptions{})
assert.Nil(t, err)
assert.Equal(t, 1, len(resList))
}