Runtime refactoring and NetworkPolicy support (#2016)
This commit is contained in:
@@ -34,32 +34,52 @@ func (k *kubernetes) Init(opts ...runtime.Option) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kubernetes) Logs(s *runtime.Service, options ...runtime.LogsOption) (runtime.Logs, error) {
|
func (k *kubernetes) Logs(resource runtime.Resource, options ...runtime.LogsOption) (runtime.Logs, error) {
|
||||||
klo := newLog(k.client, s.Name, options...)
|
|
||||||
|
|
||||||
if !klo.options.Stream {
|
// Handle the various different types of resources:
|
||||||
records, err := klo.Read()
|
switch resource.Type() {
|
||||||
|
case runtime.TypeNamespace:
|
||||||
|
// noop (Namespace is not supported by *kubernetes.Logs())
|
||||||
|
return nil, nil
|
||||||
|
case runtime.TypeNetworkPolicy:
|
||||||
|
// noop (NetworkPolicy is not supported by *kubernetes.Logs()))
|
||||||
|
return nil, nil
|
||||||
|
case runtime.TypeService:
|
||||||
|
|
||||||
|
// Assert the resource back into a *runtime.Service
|
||||||
|
s, ok := resource.(*runtime.Service)
|
||||||
|
if !ok {
|
||||||
|
return nil, runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
|
|
||||||
|
klo := newLog(k.client, s.Name, options...)
|
||||||
|
|
||||||
|
if !klo.options.Stream {
|
||||||
|
records, err := klo.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get logs for service '%v' from k8s: %v", s.Name, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kstream := &kubeStream{
|
||||||
|
stream: make(chan runtime.Log),
|
||||||
|
stop: make(chan bool),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for _, record := range records {
|
||||||
|
kstream.Chan() <- record
|
||||||
|
}
|
||||||
|
kstream.Stop()
|
||||||
|
}()
|
||||||
|
return kstream, nil
|
||||||
|
}
|
||||||
|
stream, err := klo.Stream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get logs for service '%v' from k8s: %v", s.Name, err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
kstream := &kubeStream{
|
return stream, nil
|
||||||
stream: make(chan runtime.Log),
|
default:
|
||||||
stop: make(chan bool),
|
return nil, runtime.ErrInvalidResource
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for _, record := range records {
|
|
||||||
kstream.Chan() <- record
|
|
||||||
}
|
|
||||||
kstream.Stop()
|
|
||||||
}()
|
|
||||||
return kstream, nil
|
|
||||||
}
|
}
|
||||||
stream, err := klo.Stream()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return stream, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type kubeStream struct {
|
type kubeStream struct {
|
||||||
@@ -92,11 +112,14 @@ func (k *kubeStream) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a service
|
// Create a resource
|
||||||
func (k *kubernetes) Create(s *runtime.Service, opts ...runtime.CreateOption) error {
|
func (k *kubernetes) Create(resource runtime.Resource, opts ...runtime.CreateOption) error {
|
||||||
k.Lock()
|
k.Lock()
|
||||||
defer k.Unlock()
|
defer k.Unlock()
|
||||||
|
return k.create(resource, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubernetes) create(resource runtime.Resource, opts ...runtime.CreateOption) error {
|
||||||
// parse the options
|
// parse the options
|
||||||
options := &runtime.CreateOptions{
|
options := &runtime.CreateOptions{
|
||||||
Type: k.options.Type,
|
Type: k.options.Type,
|
||||||
@@ -107,47 +130,74 @@ func (k *kubernetes) Create(s *runtime.Service, opts ...runtime.CreateOption) er
|
|||||||
o(options)
|
o(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// default the service's source and version
|
// Handle the various different types of resources:
|
||||||
if len(s.Source) == 0 {
|
switch resource.Type() {
|
||||||
s.Source = k.options.Source
|
case runtime.TypeNamespace:
|
||||||
}
|
// Assert the resource back into a *runtime.Namespace
|
||||||
if len(s.Version) == 0 {
|
namespace, ok := resource.(*runtime.Namespace)
|
||||||
s.Version = "latest"
|
if !ok {
|
||||||
}
|
return runtime.ErrInvalidResource
|
||||||
|
|
||||||
// ensure the namespace exists
|
|
||||||
if err := k.ensureNamepaceExists(options.Namespace); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a secret for the deployment
|
|
||||||
if err := k.createCredentials(s, options); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the deployment
|
|
||||||
if err := k.client.Create(client.NewDeployment(s, options), client.CreateNamespace(options.Namespace)); err != nil {
|
|
||||||
if parseError(err).Reason == "AlreadyExists" {
|
|
||||||
return runtime.ErrAlreadyExists
|
|
||||||
}
|
}
|
||||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
return k.createNamespace(namespace)
|
||||||
logger.Errorf("Runtime failed to create deployment: %v", err)
|
case runtime.TypeNetworkPolicy:
|
||||||
|
// Assert the resource back into a *runtime.NetworkPolicy
|
||||||
|
networkPolicy, ok := resource.(*runtime.NetworkPolicy)
|
||||||
|
if !ok {
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
}
|
}
|
||||||
return err
|
return k.createNetworkPolicy(networkPolicy)
|
||||||
}
|
case runtime.TypeService:
|
||||||
|
|
||||||
// create the service, one could already exist for another version so ignore ErrAlreadyExists
|
// Assert the resource back into a *runtime.Service
|
||||||
if err := k.client.Create(client.NewService(s, options), client.CreateNamespace(options.Namespace)); err != nil {
|
s, ok := resource.(*runtime.Service)
|
||||||
if parseError(err).Reason == "AlreadyExists" {
|
if !ok {
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// default the service's source and version
|
||||||
|
if len(s.Source) == 0 {
|
||||||
|
s.Source = k.options.Source
|
||||||
|
}
|
||||||
|
if len(s.Version) == 0 {
|
||||||
|
s.Version = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure the namespace exists
|
||||||
|
if err := k.ensureNamepaceExists(options.Namespace); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
|
||||||
logger.Errorf("Runtime failed to create service: %v", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
// create a secret for the deployment
|
||||||
|
if err := k.createCredentials(s, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the deployment
|
||||||
|
if err := k.client.Create(client.NewDeployment(s, options), client.CreateNamespace(options.Namespace)); err != nil {
|
||||||
|
if parseError(err).Reason == "AlreadyExists" {
|
||||||
|
return runtime.ErrAlreadyExists
|
||||||
|
}
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Runtime failed to create deployment: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the service, one could already exist for another version so ignore ErrAlreadyExists
|
||||||
|
if err := k.client.Create(client.NewService(s, options), client.CreateNamespace(options.Namespace)); err != nil {
|
||||||
|
if parseError(err).Reason == "AlreadyExists" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Runtime failed to create service: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read returns all instances of given service
|
// Read returns all instances of given service
|
||||||
@@ -180,8 +230,8 @@ func (k *kubernetes) Read(opts ...runtime.ReadOption) ([]*runtime.Service, error
|
|||||||
return k.getServices(client.GetNamespace(options.Namespace), client.GetLabels(labels))
|
return k.getServices(client.GetNamespace(options.Namespace), client.GetLabels(labels))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the service in place
|
// Update a resource in place
|
||||||
func (k *kubernetes) Update(s *runtime.Service, opts ...runtime.UpdateOption) error {
|
func (k *kubernetes) Update(resource runtime.Resource, opts ...runtime.UpdateOption) error {
|
||||||
k.Lock()
|
k.Lock()
|
||||||
defer k.Unlock()
|
defer k.Unlock()
|
||||||
|
|
||||||
@@ -193,69 +243,91 @@ func (k *kubernetes) Update(s *runtime.Service, opts ...runtime.UpdateOption) er
|
|||||||
o(&options)
|
o(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct the query
|
// Handle the various different types of resources:
|
||||||
labels := map[string]string{}
|
switch resource.Type() {
|
||||||
if len(s.Name) > 0 {
|
case runtime.TypeNamespace:
|
||||||
labels["name"] = client.Format(s.Name)
|
// noop (Namespace is not supported by *kubernetes.Update())
|
||||||
}
|
return nil
|
||||||
if len(s.Version) > 0 {
|
case runtime.TypeNetworkPolicy:
|
||||||
labels["version"] = client.Format(s.Version)
|
// Assert the resource back into a *runtime.NetworkPolicy
|
||||||
}
|
networkPolicy, ok := resource.(*runtime.NetworkPolicy)
|
||||||
|
if !ok {
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
|
return k.updateNetworkPolicy(networkPolicy)
|
||||||
|
case runtime.TypeService:
|
||||||
|
|
||||||
// get the existing deployments
|
// Assert the resource back into a *runtime.Service
|
||||||
depList := new(client.DeploymentList)
|
s, ok := resource.(*runtime.Service)
|
||||||
d := &client.Resource{
|
if !ok {
|
||||||
Kind: "deployment",
|
return runtime.ErrInvalidResource
|
||||||
Value: depList,
|
|
||||||
}
|
|
||||||
depOpts := []client.GetOption{
|
|
||||||
client.GetNamespace(options.Namespace),
|
|
||||||
client.GetLabels(labels),
|
|
||||||
}
|
|
||||||
if err := k.client.Get(d, depOpts...); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(depList.Items) == 0 {
|
|
||||||
return runtime.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the deployments which match the query
|
|
||||||
for _, dep := range depList.Items {
|
|
||||||
// the service wan't created by the k8s runtime
|
|
||||||
if dep.Metadata == nil || dep.Metadata.Annotations == nil {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update metadata
|
// construct the query
|
||||||
for k, v := range s.Metadata {
|
labels := map[string]string{}
|
||||||
dep.Metadata.Annotations[k] = v
|
if len(s.Name) > 0 {
|
||||||
|
labels["name"] = client.Format(s.Name)
|
||||||
|
}
|
||||||
|
if len(s.Version) > 0 {
|
||||||
|
labels["version"] = client.Format(s.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update build time annotation
|
// get the existing deployments
|
||||||
dep.Spec.Template.Metadata.Annotations["updated"] = fmt.Sprintf("%d", time.Now().Unix())
|
depList := new(client.DeploymentList)
|
||||||
|
d := &client.Resource{
|
||||||
// update the deployment
|
|
||||||
res := &client.Resource{
|
|
||||||
Kind: "deployment",
|
Kind: "deployment",
|
||||||
Name: resourceName(s),
|
Value: depList,
|
||||||
Value: &dep,
|
|
||||||
}
|
}
|
||||||
if err := k.client.Update(res, client.UpdateNamespace(options.Namespace)); err != nil {
|
depOpts := []client.GetOption{
|
||||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
client.GetNamespace(options.Namespace),
|
||||||
logger.Errorf("Runtime failed to update deployment: %v", err)
|
client.GetLabels(labels),
|
||||||
}
|
}
|
||||||
|
if err := k.client.Get(d, depOpts...); err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if len(depList.Items) == 0 {
|
||||||
|
return runtime.ErrNotFound
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
// update the deployments which match the query
|
||||||
|
for _, dep := range depList.Items {
|
||||||
|
// the service wan't created by the k8s runtime
|
||||||
|
if dep.Metadata == nil || dep.Metadata.Annotations == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// update metadata
|
||||||
|
for k, v := range s.Metadata {
|
||||||
|
dep.Metadata.Annotations[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// update build time annotation
|
||||||
|
dep.Spec.Template.Metadata.Annotations["updated"] = fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
|
||||||
|
// update the deployment
|
||||||
|
res := &client.Resource{
|
||||||
|
Kind: "deployment",
|
||||||
|
Name: resourceName(s),
|
||||||
|
Value: &dep,
|
||||||
|
}
|
||||||
|
if err := k.client.Update(res, client.UpdateNamespace(options.Namespace)); err != nil {
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Runtime failed to update deployment: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes a service
|
// Delete removes a resource
|
||||||
func (k *kubernetes) Delete(s *runtime.Service, opts ...runtime.DeleteOption) error {
|
func (k *kubernetes) Delete(resource runtime.Resource, opts ...runtime.DeleteOption) error {
|
||||||
k.Lock()
|
k.Lock()
|
||||||
defer k.Unlock()
|
defer k.Unlock()
|
||||||
|
|
||||||
// parse the options
|
|
||||||
options := runtime.DeleteOptions{
|
options := runtime.DeleteOptions{
|
||||||
Namespace: client.DefaultNamespace,
|
Namespace: client.DefaultNamespace,
|
||||||
}
|
}
|
||||||
@@ -263,51 +335,78 @@ func (k *kubernetes) Delete(s *runtime.Service, opts ...runtime.DeleteOption) er
|
|||||||
o(&options)
|
o(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the deployment
|
// Handle the various different types of resources:
|
||||||
dep := client.NewDeployment(s, &runtime.CreateOptions{
|
switch resource.Type() {
|
||||||
Type: k.options.Type,
|
case runtime.TypeNamespace:
|
||||||
Namespace: options.Namespace,
|
// Assert the resource back into a *runtime.Namespace
|
||||||
})
|
namespace, ok := resource.(*runtime.Namespace)
|
||||||
if err := k.client.Delete(dep, client.DeleteNamespace(options.Namespace)); err != nil {
|
if !ok {
|
||||||
if err == api.ErrNotFound {
|
return runtime.ErrInvalidResource
|
||||||
return runtime.ErrNotFound
|
|
||||||
}
|
}
|
||||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
return k.deleteNamespace(namespace)
|
||||||
logger.Errorf("Runtime failed to delete deployment: %v", err)
|
case runtime.TypeNetworkPolicy:
|
||||||
|
// Assert the resource back into a *runtime.NetworkPolicy
|
||||||
|
networkPolicy, ok := resource.(*runtime.NetworkPolicy)
|
||||||
|
if !ok {
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
}
|
}
|
||||||
return err
|
return k.deleteNetworkPolicy(networkPolicy)
|
||||||
}
|
case runtime.TypeService:
|
||||||
|
|
||||||
// delete the credentials
|
// Assert the resource back into a *runtime.Service
|
||||||
if err := k.deleteCredentials(s, &runtime.CreateOptions{Namespace: options.Namespace}); err != nil {
|
s, ok := resource.(*runtime.Service)
|
||||||
return err
|
if !ok {
|
||||||
}
|
return runtime.ErrInvalidResource
|
||||||
|
|
||||||
// if there are more deployments for this service, then don't delete it
|
|
||||||
labels := map[string]string{}
|
|
||||||
if len(s.Name) > 0 {
|
|
||||||
labels["name"] = client.Format(s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the existing services. todo: refactor to just get the deployments
|
|
||||||
services, err := k.getServices(client.GetNamespace(options.Namespace), client.GetLabels(labels))
|
|
||||||
if err != nil || len(services) > 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the service
|
|
||||||
srv := client.NewService(s, &runtime.CreateOptions{
|
|
||||||
Type: k.options.Type,
|
|
||||||
Namespace: options.Namespace,
|
|
||||||
})
|
|
||||||
if err := k.client.Delete(srv, client.DeleteNamespace(options.Namespace)); err != nil {
|
|
||||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
|
||||||
logger.Errorf("Runtime failed to delete service: %v", err)
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
// delete the deployment
|
||||||
|
dep := client.NewDeployment(s, &runtime.CreateOptions{
|
||||||
|
Type: k.options.Type,
|
||||||
|
Namespace: options.Namespace,
|
||||||
|
})
|
||||||
|
if err := k.client.Delete(dep, client.DeleteNamespace(options.Namespace)); err != nil {
|
||||||
|
if err == api.ErrNotFound {
|
||||||
|
return runtime.ErrNotFound
|
||||||
|
}
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Runtime failed to delete deployment: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the credentials
|
||||||
|
if err := k.deleteCredentials(s, &runtime.CreateOptions{Namespace: options.Namespace}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are more deployments for this service, then don't delete it
|
||||||
|
labels := map[string]string{}
|
||||||
|
if len(s.Name) > 0 {
|
||||||
|
labels["name"] = client.Format(s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the existing services. todo: refactor to just get the deployments
|
||||||
|
services, err := k.getServices(client.GetNamespace(options.Namespace), client.GetLabels(labels))
|
||||||
|
if err != nil || len(services) > 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the service
|
||||||
|
srv := client.NewService(s, &runtime.CreateOptions{
|
||||||
|
Type: k.options.Type,
|
||||||
|
Namespace: options.Namespace,
|
||||||
|
})
|
||||||
|
if err := k.client.Delete(srv, client.DeleteNamespace(options.Namespace)); err != nil {
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Runtime failed to delete service: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the runtime
|
// Start starts the runtime
|
||||||
|
@@ -11,6 +11,9 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/micro/go-micro/v3/runtime"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupClient(t *testing.T) {
|
func setupClient(t *testing.T) {
|
||||||
@@ -47,19 +50,45 @@ func setupClient(t *testing.T) {
|
|||||||
|
|
||||||
func TestNamespaceCreateDelete(t *testing.T) {
|
func TestNamespaceCreateDelete(t *testing.T) {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
exec.Command("kubectl", "-n", "foobar", "delete", "networkpolicy", "baz").Run()
|
||||||
exec.Command("kubectl", "delete", "namespace", "foobar").Run()
|
exec.Command("kubectl", "delete", "namespace", "foobar").Run()
|
||||||
}()
|
}()
|
||||||
setupClient(t)
|
setupClient(t)
|
||||||
r := NewRuntime()
|
r := NewRuntime()
|
||||||
if err := r.CreateNamespace("foobar"); err != nil {
|
|
||||||
t.Fatalf("Unexpected error creating namespace %s", err)
|
// Create a namespace
|
||||||
|
testNamespace, err := runtime.NewNamespace("foobar")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if err := r.Create(testNamespace); err != nil {
|
||||||
|
t.Fatalf("Unexpected error creating namespace: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that the namespace exists
|
||||||
if !namespaceExists(t, "foobar") {
|
if !namespaceExists(t, "foobar") {
|
||||||
t.Fatalf("Namespace foobar not found")
|
t.Fatalf("Namespace foobar not found")
|
||||||
}
|
}
|
||||||
if err := r.DeleteNamespace("foobar"); err != nil {
|
|
||||||
t.Fatalf("Unexpected error deleting namespace %s", err)
|
// Create a networkpolicy:
|
||||||
|
testNetworkPolicy, err := runtime.NewNetworkPolicy("baz", "foobar", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if err := r.Create(testNetworkPolicy); err != nil {
|
||||||
|
t.Fatalf("Unexpected error creating networkpolicy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the networkpolicy exists:
|
||||||
|
if !networkPolicyExists(t, "foobar", "baz") {
|
||||||
|
t.Fatalf("NetworkPolicy foobar.baz not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tidy up
|
||||||
|
if err := r.Delete(testNetworkPolicy); err != nil {
|
||||||
|
t.Fatalf("Unexpected error deleting networkpolicy: %v", err)
|
||||||
|
}
|
||||||
|
if networkPolicyExists(t, "foobar", "baz") {
|
||||||
|
t.Fatalf("NetworkPolicy foobar.baz still exists")
|
||||||
|
}
|
||||||
|
if err := r.Delete(testNamespace); err != nil {
|
||||||
|
t.Fatalf("Unexpected error deleting namespace: %v", err)
|
||||||
}
|
}
|
||||||
if namespaceExists(t, "foobar") {
|
if namespaceExists(t, "foobar") {
|
||||||
t.Fatalf("Namespace foobar still exists")
|
t.Fatalf("Namespace foobar still exists")
|
||||||
@@ -77,5 +106,17 @@ func namespaceExists(t *testing.T, ns string) bool {
|
|||||||
t.Fatalf("Error listing namespaces %s", err)
|
t.Fatalf("Error listing namespaces %s", err)
|
||||||
}
|
}
|
||||||
return exists
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func networkPolicyExists(t *testing.T, ns, np string) bool {
|
||||||
|
cmd := exec.Command("kubectl", "-n", ns, "get", "networkpolicy")
|
||||||
|
outp, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error listing networkpolicies %s", err)
|
||||||
|
}
|
||||||
|
exists, err := regexp.Match(np, outp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error listing networkpolicies %s", err)
|
||||||
|
}
|
||||||
|
return exists
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/micro/go-micro/v3/logger"
|
"github.com/micro/go-micro/v3/logger"
|
||||||
|
"github.com/micro/go-micro/v3/runtime"
|
||||||
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ func (k *kubernetes) ensureNamepaceExists(ns string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.createNamespace(namespace); err != nil {
|
if err := k.autoCreateNamespace(namespace); err != nil {
|
||||||
if logger.V(logger.WarnLevel, logger.DefaultLogger) {
|
if logger.V(logger.WarnLevel, logger.DefaultLogger) {
|
||||||
logger.Warnf("Error creating namespace %v: %v", namespace, err)
|
logger.Warnf("Error creating namespace %v: %v", namespace, err)
|
||||||
}
|
}
|
||||||
@@ -64,8 +65,8 @@ func (k *kubernetes) namespaceExists(name string) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createNamespace creates a new k8s namespace
|
// autoCreateNamespace creates a new k8s namespace
|
||||||
func (k *kubernetes) createNamespace(namespace string) error {
|
func (k *kubernetes) autoCreateNamespace(namespace string) error {
|
||||||
ns := client.Namespace{Metadata: &client.Metadata{Name: namespace}}
|
ns := client.Namespace{Metadata: &client.Metadata{Name: namespace}}
|
||||||
err := k.client.Create(&client.Resource{Kind: "namespace", Value: ns})
|
err := k.client.Create(&client.Resource{Kind: "namespace", Value: ns})
|
||||||
|
|
||||||
@@ -75,38 +76,54 @@ func (k *kubernetes) createNamespace(namespace string) error {
|
|||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to cache
|
// add to cache and create networkpolicy
|
||||||
if err == nil && k.namespaces != nil {
|
if err == nil && k.namespaces != nil {
|
||||||
k.namespaces = append(k.namespaces, ns)
|
k.namespaces = append(k.namespaces, ns)
|
||||||
|
|
||||||
|
if networkPolicy, err := runtime.NewNetworkPolicy("ingress", namespace, map[string]string{"owner": "micro"}); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return k.create(networkPolicy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kubernetes) CreateNamespace(ns string) error {
|
// createNamespace creates a namespace resource
|
||||||
|
func (k *kubernetes) createNamespace(namespace *runtime.Namespace) error {
|
||||||
err := k.client.Create(&client.Resource{
|
err := k.client.Create(&client.Resource{
|
||||||
Kind: "namespace",
|
Kind: "namespace",
|
||||||
|
Name: namespace.Name,
|
||||||
Value: client.Namespace{
|
Value: client.Namespace{
|
||||||
Metadata: &client.Metadata{
|
Metadata: &client.Metadata{
|
||||||
Name: ns,
|
Name: namespace.Name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}, client.CreateNamespace(namespace.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
logger.Errorf("Error creating namespace %v: %v", ns, err)
|
logger.Errorf("Error creating namespace %s: %v", namespace.String(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kubernetes) DeleteNamespace(ns string) error {
|
// deleteNamespace deletes a namespace resource
|
||||||
|
func (k *kubernetes) deleteNamespace(namespace *runtime.Namespace) error {
|
||||||
err := k.client.Delete(&client.Resource{
|
err := k.client.Delete(&client.Resource{
|
||||||
Kind: "namespace",
|
Kind: "namespace",
|
||||||
Name: ns,
|
Name: namespace.Name,
|
||||||
})
|
Value: client.Namespace{
|
||||||
if err != nil && logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
Metadata: &client.Metadata{
|
||||||
logger.Errorf("Error deleting namespace %v: %v", ns, err)
|
Name: namespace.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, client.DeleteNamespace(namespace.Name))
|
||||||
|
if err != nil {
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Error deleting namespace %s: %v", namespace.String(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
67
runtime/kubernetes/networkpolicy.go
Normal file
67
runtime/kubernetes/networkpolicy.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/micro/go-micro/v3/logger"
|
||||||
|
"github.com/micro/go-micro/v3/runtime"
|
||||||
|
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createNetworkPolicy creates a networkpolicy resource
|
||||||
|
func (k *kubernetes) createNetworkPolicy(networkPolicy *runtime.NetworkPolicy) error {
|
||||||
|
err := k.client.Create(&client.Resource{
|
||||||
|
Kind: "networkpolicy",
|
||||||
|
Value: client.NetworkPolicy{
|
||||||
|
AllowedLabels: networkPolicy.AllowedLabels,
|
||||||
|
Metadata: &client.Metadata{
|
||||||
|
Name: networkPolicy.Name,
|
||||||
|
Namespace: networkPolicy.Namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, client.CreateNamespace(networkPolicy.Namespace))
|
||||||
|
if err != nil {
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Error creating resource %s: %v", networkPolicy.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateNetworkPolicy updates a networkpolicy resource in-place
|
||||||
|
func (k *kubernetes) updateNetworkPolicy(networkPolicy *runtime.NetworkPolicy) error {
|
||||||
|
err := k.client.Update(&client.Resource{
|
||||||
|
Kind: "networkpolicy",
|
||||||
|
Value: client.NetworkPolicy{
|
||||||
|
AllowedLabels: networkPolicy.AllowedLabels,
|
||||||
|
Metadata: &client.Metadata{
|
||||||
|
Name: networkPolicy.Name,
|
||||||
|
Namespace: networkPolicy.Namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, client.UpdateNamespace(networkPolicy.Namespace))
|
||||||
|
if err != nil {
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Error updating resource %s: %v", networkPolicy.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteNetworkPolicy deletes a networkpolicy resource
|
||||||
|
func (k *kubernetes) deleteNetworkPolicy(networkPolicy *runtime.NetworkPolicy) error {
|
||||||
|
err := k.client.Delete(&client.Resource{
|
||||||
|
Kind: "networkpolicy",
|
||||||
|
Value: client.NetworkPolicy{
|
||||||
|
AllowedLabels: networkPolicy.AllowedLabels,
|
||||||
|
Metadata: &client.Metadata{
|
||||||
|
Name: networkPolicy.Name,
|
||||||
|
Namespace: networkPolicy.Namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, client.DeleteNamespace(networkPolicy.Namespace))
|
||||||
|
if err != nil {
|
||||||
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||||
|
logger.Errorf("Error deleting resource %s: %v", networkPolicy.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
@@ -45,6 +45,20 @@ rules:
|
|||||||
- get
|
- get
|
||||||
- watch
|
- watch
|
||||||
- list
|
- list
|
||||||
|
- apiGroups:
|
||||||
|
- "networking.k8s.io"
|
||||||
|
resources:
|
||||||
|
- networkpolicy
|
||||||
|
- networkpolicies
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- deletecollection
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- watch
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
|
@@ -81,7 +81,7 @@ func serviceKey(s *runtime.Service) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new service which is then started by runtime
|
// Create creates a new service which is then started by runtime
|
||||||
func (r *localRuntime) Create(s *runtime.Service, opts ...runtime.CreateOption) error {
|
func (r *localRuntime) Create(resource runtime.Resource, opts ...runtime.CreateOption) error {
|
||||||
var options runtime.CreateOptions
|
var options runtime.CreateOptions
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&options)
|
o(&options)
|
||||||
@@ -90,55 +90,74 @@ func (r *localRuntime) Create(s *runtime.Service, opts ...runtime.CreateOption)
|
|||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
|
|
||||||
if len(options.Namespace) == 0 {
|
// Handle the various different types of resources:
|
||||||
options.Namespace = defaultNamespace
|
switch resource.Type() {
|
||||||
}
|
case runtime.TypeNamespace:
|
||||||
if len(options.Entrypoint) > 0 {
|
// noop (Namespace is not supported by local)
|
||||||
s.Source = filepath.Join(s.Source, options.Entrypoint)
|
return nil
|
||||||
}
|
case runtime.TypeNetworkPolicy:
|
||||||
if len(options.Command) == 0 {
|
// noop (NetworkPolicy is not supported by local)
|
||||||
ep, err := Entrypoint(s.Source)
|
return nil
|
||||||
if err != nil {
|
case runtime.TypeService:
|
||||||
return err
|
|
||||||
|
// Assert the resource back into a *runtime.Service
|
||||||
|
s, ok := resource.(*runtime.Service)
|
||||||
|
if !ok {
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
}
|
}
|
||||||
|
|
||||||
options.Command = []string{"go"}
|
if len(options.Namespace) == 0 {
|
||||||
options.Args = []string{"run", ep}
|
options.Namespace = defaultNamespace
|
||||||
}
|
}
|
||||||
|
if len(options.Entrypoint) > 0 {
|
||||||
|
s.Source = filepath.Join(s.Source, options.Entrypoint)
|
||||||
|
}
|
||||||
|
if len(options.Command) == 0 {
|
||||||
|
ep, err := Entrypoint(s.Source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// pass secrets as env vars
|
options.Command = []string{"go"}
|
||||||
for key, value := range options.Secrets {
|
options.Args = []string{"run", ep}
|
||||||
options.Env = append(options.Env, fmt.Sprintf("%v=%v", key, value))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := r.namespaces[options.Namespace]; !ok {
|
// pass secrets as env vars
|
||||||
r.namespaces[options.Namespace] = make(map[string]*service)
|
for key, value := range options.Secrets {
|
||||||
}
|
options.Env = append(options.Env, fmt.Sprintf("%v=%v", key, value))
|
||||||
if _, ok := r.namespaces[options.Namespace][serviceKey(s)]; ok {
|
}
|
||||||
return runtime.ErrAlreadyExists
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new service
|
if _, ok := r.namespaces[options.Namespace]; !ok {
|
||||||
service := newService(s, options)
|
r.namespaces[options.Namespace] = make(map[string]*service)
|
||||||
|
}
|
||||||
|
if _, ok := r.namespaces[options.Namespace][serviceKey(s)]; ok {
|
||||||
|
return runtime.ErrAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(logFile(service.Name), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
// create new service
|
||||||
if err != nil {
|
service := newService(s, options)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.output != nil {
|
f, err := os.OpenFile(logFile(service.Name), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
service.output = io.MultiWriter(service.output, f)
|
if err != nil {
|
||||||
} else {
|
log.Fatal(err)
|
||||||
service.output = f
|
}
|
||||||
}
|
|
||||||
// start the service
|
|
||||||
if err := service.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// save service
|
|
||||||
r.namespaces[options.Namespace][serviceKey(s)] = service
|
|
||||||
|
|
||||||
return nil
|
if service.output != nil {
|
||||||
|
service.output = io.MultiWriter(service.output, f)
|
||||||
|
} else {
|
||||||
|
service.output = f
|
||||||
|
}
|
||||||
|
// start the service
|
||||||
|
if err := service.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// save service
|
||||||
|
r.namespaces[options.Namespace][serviceKey(s)] = service
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exists returns whether the given file or directory exists
|
// exists returns whether the given file or directory exists
|
||||||
@@ -157,65 +176,85 @@ func exists(path string) (bool, error) {
|
|||||||
// The reason for this is because it's hard to calculate line offset
|
// The reason for this is because it's hard to calculate line offset
|
||||||
// as opposed to character offset.
|
// as opposed to character offset.
|
||||||
// This logger streams by default and only supports the `StreamCount` option.
|
// This logger streams by default and only supports the `StreamCount` option.
|
||||||
func (r *localRuntime) Logs(s *runtime.Service, options ...runtime.LogsOption) (runtime.Logs, error) {
|
func (r *localRuntime) Logs(resource runtime.Resource, options ...runtime.LogsOption) (runtime.Logs, error) {
|
||||||
lopts := runtime.LogsOptions{}
|
lopts := runtime.LogsOptions{}
|
||||||
for _, o := range options {
|
for _, o := range options {
|
||||||
o(&lopts)
|
o(&lopts)
|
||||||
}
|
}
|
||||||
ret := &logStream{
|
|
||||||
service: s.Name,
|
|
||||||
stream: make(chan runtime.Log),
|
|
||||||
stop: make(chan bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
fpath := logFile(s.Name)
|
// Handle the various different types of resources:
|
||||||
if ex, err := exists(fpath); err != nil {
|
switch resource.Type() {
|
||||||
return nil, err
|
case runtime.TypeNamespace:
|
||||||
} else if !ex {
|
// noop (Namespace is not supported by local)
|
||||||
return nil, fmt.Errorf("Logs not found for service %s", s.Name)
|
return nil, nil
|
||||||
}
|
case runtime.TypeNetworkPolicy:
|
||||||
|
// noop (NetworkPolicy is not supported by local)
|
||||||
|
return nil, nil
|
||||||
|
case runtime.TypeService:
|
||||||
|
|
||||||
// have to check file size to avoid too big of a seek
|
// Assert the resource back into a *runtime.Service
|
||||||
fi, err := os.Stat(fpath)
|
s, ok := resource.(*runtime.Service)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, err
|
return nil, runtime.ErrInvalidResource
|
||||||
}
|
|
||||||
size := fi.Size()
|
|
||||||
|
|
||||||
whence := 2
|
|
||||||
// Multiply by length of an average line of log in bytes
|
|
||||||
offset := lopts.Count * 200
|
|
||||||
|
|
||||||
if offset > size {
|
|
||||||
offset = size
|
|
||||||
}
|
|
||||||
offset *= -1
|
|
||||||
|
|
||||||
t, err := tail.TailFile(fpath, tail.Config{Follow: lopts.Stream, Location: &tail.SeekInfo{
|
|
||||||
Whence: whence,
|
|
||||||
Offset: int64(offset),
|
|
||||||
}, Logger: tail.DiscardingLogger})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.tail = t
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case line, ok := <-t.Lines:
|
|
||||||
if !ok {
|
|
||||||
ret.Stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret.stream <- runtime.Log{Message: line.Text}
|
|
||||||
case <-ret.stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
ret := &logStream{
|
||||||
return ret, nil
|
service: s.Name,
|
||||||
|
stream: make(chan runtime.Log),
|
||||||
|
stop: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
fpath := logFile(s.Name)
|
||||||
|
if ex, err := exists(fpath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !ex {
|
||||||
|
return nil, fmt.Errorf("Logs not found for service %s", s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// have to check file size to avoid too big of a seek
|
||||||
|
fi, err := os.Stat(fpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
size := fi.Size()
|
||||||
|
|
||||||
|
whence := 2
|
||||||
|
// Multiply by length of an average line of log in bytes
|
||||||
|
offset := lopts.Count * 200
|
||||||
|
|
||||||
|
if offset > size {
|
||||||
|
offset = size
|
||||||
|
}
|
||||||
|
offset *= -1
|
||||||
|
|
||||||
|
t, err := tail.TailFile(fpath, tail.Config{Follow: lopts.Stream, Location: &tail.SeekInfo{
|
||||||
|
Whence: whence,
|
||||||
|
Offset: int64(offset),
|
||||||
|
}, Logger: tail.DiscardingLogger})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.tail = t
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case line, ok := <-t.Lines:
|
||||||
|
if !ok {
|
||||||
|
ret.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.stream <- runtime.Log{Message: line.Text}
|
||||||
|
case <-ret.stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
return ret, nil
|
||||||
|
default:
|
||||||
|
return nil, runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type logStream struct {
|
type logStream struct {
|
||||||
@@ -298,84 +337,126 @@ func (r *localRuntime) Read(opts ...runtime.ReadOption) ([]*runtime.Service, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update attempts to update the service
|
// Update attempts to update the service
|
||||||
func (r *localRuntime) Update(s *runtime.Service, opts ...runtime.UpdateOption) error {
|
func (r *localRuntime) Update(resource runtime.Resource, opts ...runtime.UpdateOption) error {
|
||||||
var options runtime.UpdateOptions
|
var options runtime.UpdateOptions
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&options)
|
o(&options)
|
||||||
}
|
}
|
||||||
if len(options.Namespace) == 0 {
|
|
||||||
options.Namespace = defaultNamespace
|
|
||||||
}
|
|
||||||
if len(options.Entrypoint) > 0 {
|
|
||||||
s.Source = filepath.Join(s.Source, options.Entrypoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Lock()
|
// Handle the various different types of resources:
|
||||||
srvs, ok := r.namespaces[options.Namespace]
|
switch resource.Type() {
|
||||||
r.Unlock()
|
case runtime.TypeNamespace:
|
||||||
if !ok {
|
// noop (Namespace is not supported by local)
|
||||||
return errors.New("Service not found")
|
return nil
|
||||||
}
|
case runtime.TypeNetworkPolicy:
|
||||||
|
// noop (NetworkPolicy is not supported by local)
|
||||||
|
return nil
|
||||||
|
case runtime.TypeService:
|
||||||
|
|
||||||
r.Lock()
|
// Assert the resource back into a *runtime.Service
|
||||||
service, ok := srvs[serviceKey(s)]
|
s, ok := resource.(*runtime.Service)
|
||||||
r.Unlock()
|
if !ok {
|
||||||
if !ok {
|
return runtime.ErrInvalidResource
|
||||||
return errors.New("Service not found")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := service.Stop(); err != nil && err.Error() != "no such process" {
|
if len(options.Entrypoint) > 0 {
|
||||||
logger.Errorf("Error stopping service %s: %s", service.Name, err)
|
s.Source = filepath.Join(s.Source, options.Entrypoint)
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// update the source to the new location and restart the service
|
if len(options.Namespace) == 0 {
|
||||||
service.Source = s.Source
|
options.Namespace = defaultNamespace
|
||||||
service.Exec.Dir = s.Source
|
}
|
||||||
return service.Start()
|
|
||||||
|
r.Lock()
|
||||||
|
srvs, ok := r.namespaces[options.Namespace]
|
||||||
|
r.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Service not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Lock()
|
||||||
|
service, ok := srvs[serviceKey(s)]
|
||||||
|
r.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("Service not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := service.Stop(); err != nil && err.Error() != "no such process" {
|
||||||
|
logger.Errorf("Error stopping service %s: %s", service.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the source to the new location and restart the service
|
||||||
|
service.Source = s.Source
|
||||||
|
service.Exec.Dir = s.Source
|
||||||
|
return service.Start()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the service from the runtime and stops it
|
// Delete removes the service from the runtime and stops it
|
||||||
func (r *localRuntime) Delete(s *runtime.Service, opts ...runtime.DeleteOption) error {
|
func (r *localRuntime) Delete(resource runtime.Resource, opts ...runtime.DeleteOption) error {
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
|
|
||||||
var options runtime.DeleteOptions
|
// Handle the various different types of resources:
|
||||||
for _, o := range opts {
|
switch resource.Type() {
|
||||||
o(&options)
|
case runtime.TypeNamespace:
|
||||||
}
|
// noop (Namespace is not supported by local)
|
||||||
if len(options.Namespace) == 0 {
|
|
||||||
options.Namespace = defaultNamespace
|
|
||||||
}
|
|
||||||
|
|
||||||
srvs, ok := r.namespaces[options.Namespace]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
case runtime.TypeNetworkPolicy:
|
||||||
|
// noop (NetworkPolicy is not supported by local)
|
||||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
||||||
logger.Debugf("Runtime deleting service %s", s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
service, ok := srvs[serviceKey(s)]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
case runtime.TypeService:
|
||||||
|
|
||||||
// check if running
|
// Assert the resource back into a *runtime.Service
|
||||||
if !service.Running() {
|
s, ok := resource.(*runtime.Service)
|
||||||
|
if !ok {
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
var options runtime.DeleteOptions
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
if len(options.Namespace) == 0 {
|
||||||
|
options.Namespace = defaultNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
srvs, ok := r.namespaces[options.Namespace]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||||
|
logger.Debugf("Runtime deleting service %s", s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
service, ok := srvs[serviceKey(s)]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if running
|
||||||
|
if !service.Running() {
|
||||||
|
delete(srvs, service.key())
|
||||||
|
r.namespaces[options.Namespace] = srvs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// otherwise stop it
|
||||||
|
if err := service.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// delete it
|
||||||
delete(srvs, service.key())
|
delete(srvs, service.key())
|
||||||
r.namespaces[options.Namespace] = srvs
|
r.namespaces[options.Namespace] = srvs
|
||||||
return nil
|
return nil
|
||||||
|
default:
|
||||||
|
return runtime.ErrInvalidResource
|
||||||
}
|
}
|
||||||
// otherwise stop it
|
|
||||||
if err := service.Stop(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// delete it
|
|
||||||
delete(srvs, service.key())
|
|
||||||
r.namespaces[options.Namespace] = srvs
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the runtime
|
// Start starts the runtime
|
||||||
@@ -461,13 +542,3 @@ func Entrypoint(dir string) (string, error) {
|
|||||||
return "", errors.New("More than one entrypoint found")
|
return "", errors.New("More than one entrypoint found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *localRuntime) CreateNamespace(ns string) error {
|
|
||||||
// noop
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *localRuntime) DeleteNamespace(ns string) error {
|
|
||||||
// noop
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
117
runtime/resource.go
Normal file
117
runtime/resource.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Package runtime is a service runtime manager
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeNamespace = "namespace"
|
||||||
|
TypeNetworkPolicy = "networkpolicy"
|
||||||
|
TypeService = "service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resource represents any resource handled by runtime
|
||||||
|
type Resource interface {
|
||||||
|
String() string
|
||||||
|
Type() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace represents a logical namespace for organising resources
|
||||||
|
type Namespace struct {
|
||||||
|
// Name of the namespace
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNamespace mints a new namespace
|
||||||
|
func NewNamespace(name string) (*Namespace, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, ErrInvalidResource
|
||||||
|
}
|
||||||
|
return &Namespace{
|
||||||
|
Name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Resource
|
||||||
|
func (r *Namespace) String() string {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements Resource
|
||||||
|
func (*Namespace) Type() string {
|
||||||
|
return TypeNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkPolicy represents an ACL of label pairs allowing ignress to a namespace
|
||||||
|
type NetworkPolicy struct {
|
||||||
|
// The labels allowed ingress by this policy
|
||||||
|
AllowedLabels map[string]string
|
||||||
|
// Name of the network policy
|
||||||
|
Name string
|
||||||
|
// Namespace the network policy belongs to
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetworkPolicy mints a new networkpolicy
|
||||||
|
func NewNetworkPolicy(name, namespace string, allowedLabels map[string]string) (*NetworkPolicy, error) {
|
||||||
|
if name == "" || namespace == "" {
|
||||||
|
return nil, ErrInvalidResource
|
||||||
|
}
|
||||||
|
if allowedLabels == nil {
|
||||||
|
allowedLabels = map[string]string{
|
||||||
|
"origin": "micro",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &NetworkPolicy{
|
||||||
|
AllowedLabels: allowedLabels,
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Resource
|
||||||
|
func (r *NetworkPolicy) String() string {
|
||||||
|
return fmt.Sprintf("%s.%s", r.Namespace, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements Resource
|
||||||
|
func (*NetworkPolicy) Type() string {
|
||||||
|
return TypeNetworkPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service represents a Micro service running within a namespace
|
||||||
|
type Service struct {
|
||||||
|
// Name of the service
|
||||||
|
Name string
|
||||||
|
// Version of the service
|
||||||
|
Version string
|
||||||
|
// url location of source
|
||||||
|
Source string
|
||||||
|
// Metadata stores metadata
|
||||||
|
Metadata map[string]string
|
||||||
|
// Status of the service
|
||||||
|
Status ServiceStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService mints a new service
|
||||||
|
func NewService(name, version string) (*Service, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, ErrInvalidResource
|
||||||
|
}
|
||||||
|
if version == "" {
|
||||||
|
version = "latest"
|
||||||
|
}
|
||||||
|
return &Service{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Resource
|
||||||
|
func (r *Service) String() string {
|
||||||
|
return fmt.Sprintf("service://%s@%s:%s", r.Metadata["namespace"], r.Name, r.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements Resource
|
||||||
|
func (*Service) Type() string {
|
||||||
|
return TypeService
|
||||||
|
}
|
65
runtime/resource_test.go
Normal file
65
runtime/resource_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResources(t *testing.T) {
|
||||||
|
|
||||||
|
// Namespace:
|
||||||
|
assert.Equal(t, TypeNamespace, new(Namespace).Type())
|
||||||
|
namespace, err := NewNamespace("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrInvalidResource, err)
|
||||||
|
assert.Nil(t, namespace)
|
||||||
|
|
||||||
|
namespace, err = NewNamespace("test-namespace")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, namespace)
|
||||||
|
assert.Equal(t, TypeNamespace, namespace.Type())
|
||||||
|
assert.Equal(t, "test-namespace", namespace.String())
|
||||||
|
|
||||||
|
// NetworkPolicy:
|
||||||
|
assert.Equal(t, TypeNetworkPolicy, new(NetworkPolicy).Type())
|
||||||
|
networkPolicy, err := NewNetworkPolicy("", "", nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrInvalidResource, err)
|
||||||
|
assert.Nil(t, networkPolicy)
|
||||||
|
|
||||||
|
networkPolicy, err = NewNetworkPolicy("test", "", nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrInvalidResource, err)
|
||||||
|
assert.Nil(t, networkPolicy)
|
||||||
|
|
||||||
|
networkPolicy, err = NewNetworkPolicy("", "test", nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrInvalidResource, err)
|
||||||
|
assert.Nil(t, networkPolicy)
|
||||||
|
|
||||||
|
networkPolicy, err = NewNetworkPolicy("ingress", "test", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, networkPolicy)
|
||||||
|
assert.Equal(t, TypeNetworkPolicy, networkPolicy.Type())
|
||||||
|
assert.Equal(t, "test.ingress", networkPolicy.String())
|
||||||
|
assert.Len(t, networkPolicy.AllowedLabels, 1)
|
||||||
|
|
||||||
|
networkPolicy, err = NewNetworkPolicy("ingress", "test", map[string]string{"foo": "bar", "bar": "foo"})
|
||||||
|
assert.Len(t, networkPolicy.AllowedLabels, 2)
|
||||||
|
|
||||||
|
// Service:
|
||||||
|
assert.Equal(t, TypeService, new(Service).Type())
|
||||||
|
service, err := NewService("", "")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrInvalidResource, err)
|
||||||
|
assert.Nil(t, service)
|
||||||
|
|
||||||
|
service, err = NewService("test-service", "oldest")
|
||||||
|
service.Metadata = map[string]string{"namespace": "testing"}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, service)
|
||||||
|
assert.Equal(t, TypeService, service.Type())
|
||||||
|
assert.Equal(t, "service://testing@test-service:oldest", service.String())
|
||||||
|
assert.Equal(t, "oldest", service.Version)
|
||||||
|
}
|
@@ -7,28 +7,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAlreadyExists = errors.New("already exists")
|
ErrAlreadyExists = errors.New("already exists")
|
||||||
ErrNotFound = errors.New("not found")
|
ErrInvalidResource = errors.New("invalid resource")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runtime is a service runtime manager
|
// Runtime is a service runtime manager
|
||||||
type Runtime interface {
|
type Runtime interface {
|
||||||
// Init initializes runtime
|
// Init initializes runtime
|
||||||
Init(...Option) error
|
Init(...Option) error
|
||||||
// CreateNamespace creates a new namespace in the runtime
|
// Create a resource
|
||||||
CreateNamespace(string) error
|
Create(Resource, ...CreateOption) error
|
||||||
// DeleteNamespace deletes a namespace in the runtime
|
// Read a resource
|
||||||
DeleteNamespace(string) error
|
|
||||||
// Create registers a service
|
|
||||||
Create(*Service, ...CreateOption) error
|
|
||||||
// Read returns the service
|
|
||||||
Read(...ReadOption) ([]*Service, error)
|
Read(...ReadOption) ([]*Service, error)
|
||||||
// Update the service in place
|
// Update a resource
|
||||||
Update(*Service, ...UpdateOption) error
|
Update(Resource, ...UpdateOption) error
|
||||||
// Remove a service
|
// Delete a resource
|
||||||
Delete(*Service, ...DeleteOption) error
|
Delete(Resource, ...DeleteOption) error
|
||||||
// Logs returns the logs for a service
|
// Logs returns the logs for a resource
|
||||||
Logs(*Service, ...LogsOption) (Logs, error)
|
Logs(Resource, ...LogsOption) (Logs, error)
|
||||||
// Start starts the runtime
|
// Start starts the runtime
|
||||||
Start() error
|
Start() error
|
||||||
// Stop shuts down the runtime
|
// Stop shuts down the runtime
|
||||||
@@ -113,20 +110,6 @@ const (
|
|||||||
Error
|
Error
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service is runtime service
|
|
||||||
type Service struct {
|
|
||||||
// Name of the service
|
|
||||||
Name string
|
|
||||||
// Version of the service
|
|
||||||
Version string
|
|
||||||
// url location of source
|
|
||||||
Source string
|
|
||||||
// Metadata stores metadata
|
|
||||||
Metadata map[string]string
|
|
||||||
// Status of the service
|
|
||||||
Status ServiceStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resources which are allocated to a serivce
|
// Resources which are allocated to a serivce
|
||||||
type Resources struct {
|
type Resources struct {
|
||||||
// CPU is the maximum amount of CPU the service will be allocated (unit millicpu)
|
// CPU is the maximum amount of CPU the service will be allocated (unit millicpu)
|
||||||
|
@@ -168,6 +168,9 @@ func (r *Request) request() (*http.Request, error) {
|
|||||||
case "deployment":
|
case "deployment":
|
||||||
// /apis/apps/v1/namespaces/{namespace}/deployments/{name}
|
// /apis/apps/v1/namespaces/{namespace}/deployments/{name}
|
||||||
url = fmt.Sprintf("%s/apis/apps/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
url = fmt.Sprintf("%s/apis/apps/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
||||||
|
case "networkpolicy", "networkpolicies":
|
||||||
|
// /apis/networking.k8s.io/v1/namespaces/{namespace}/networkpolicies
|
||||||
|
url = fmt.Sprintf("%s/apis/networking.k8s.io/v1/namespaces/%s/networkpolicies/", r.host, r.namespace)
|
||||||
default:
|
default:
|
||||||
// /api/v1/namespaces/{namespace}/{resource}
|
// /api/v1/namespaces/{namespace}/{resource}
|
||||||
url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
url = fmt.Sprintf("%s/api/v1/namespaces/%s/%ss/", r.host, r.namespace, r.resource)
|
||||||
|
@@ -156,6 +156,8 @@ func (c *client) Update(r *Resource, opts ...UpdateOption) error {
|
|||||||
req.Body(r.Value.(*Deployment))
|
req.Body(r.Value.(*Deployment))
|
||||||
case "pod":
|
case "pod":
|
||||||
req.Body(r.Value.(*Pod))
|
req.Body(r.Value.(*Pod))
|
||||||
|
case "networkpolicy", "networkpolicies":
|
||||||
|
req.Body(r.Value.(*NetworkPolicy))
|
||||||
default:
|
default:
|
||||||
return errors.New("unsupported resource")
|
return errors.New("unsupported resource")
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
var templates = map[string]string{
|
var templates = map[string]string{
|
||||||
"deployment": deploymentTmpl,
|
"deployment": deploymentTmpl,
|
||||||
"service": serviceTmpl,
|
"service": serviceTmpl,
|
||||||
"namespace": namespaceTmpl,
|
"namespace": namespaceTmpl,
|
||||||
"secret": secretTmpl,
|
"secret": secretTmpl,
|
||||||
"serviceaccount": serviceAccountTmpl,
|
"serviceaccount": serviceAccountTmpl,
|
||||||
|
"networkpolicies": networkPolicyTmpl,
|
||||||
|
"networkpolicy": networkPolicyTmpl,
|
||||||
}
|
}
|
||||||
|
|
||||||
var deploymentTmpl = `
|
var deploymentTmpl = `
|
||||||
@@ -239,3 +241,31 @@ imagePullSecrets:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
var networkPolicyTmpl = `
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Metadata.Name }}"
|
||||||
|
namespace: "{{ .Metadata.Namespace }}"
|
||||||
|
labels:
|
||||||
|
{{- with .Metadata.Labels }}
|
||||||
|
{{- range $key, $value := . }}
|
||||||
|
{{ $key }}: "{{ $value }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
ingress:
|
||||||
|
- from: # Allow pods in this namespace to talk to each other
|
||||||
|
- podSelector: {}
|
||||||
|
- from: # Allow pods in the namespaces bearing the specified labels to talk to pods in this namespace:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
{{- with .AllowedLabels }}
|
||||||
|
{{- range $key, $value := . }}
|
||||||
|
{{ $key }}: "{{ $value }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
`
|
||||||
|
@@ -267,3 +267,9 @@ type VolumeMount struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MountPath string `json:"mountPath"`
|
MountPath string `json:"mountPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkPolicy is a Kubernetes Namespace
|
||||||
|
type NetworkPolicy struct {
|
||||||
|
AllowedLabels map[string]string `json:"allowedLabels,omitempty"`
|
||||||
|
Metadata *Metadata `json:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user