Commit a50567f1 authored by André Carvalho's avatar André Carvalho

update deployment in case of changes

parent bebdf40a
......@@ -11,6 +11,7 @@ import (
"github.com/operator-framework/operator-sdk/pkg/sdk"
"github.com/sirupsen/logrus"
appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
......@@ -73,9 +74,12 @@ func reconcile(ctx context.Context, event sdk.Event, nginx *v1alpha1.Nginx, logg
}
func reconcileDeployment(ctx context.Context, nginx *v1alpha1.Nginx, logger *logrus.Entry) error {
deployment := k8s.NewDeployment(nginx)
newDeploy, err := k8s.NewDeployment(nginx)
if err != nil {
return fmt.Errorf("failed to assemble deployment from nginx: %v", err)
}
err := sdk.Create(deployment)
err = sdk.Create(newDeploy)
if err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("failed to create deployment: %v", err)
}
......@@ -84,19 +88,33 @@ func reconcileDeployment(ctx context.Context, nginx *v1alpha1.Nginx, logger *log
return nil
}
if err := sdk.Get(deployment); err != nil {
currDeploy := &appv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: newDeploy.Name,
Namespace: newDeploy.Namespace,
},
}
if err := sdk.Get(currDeploy); err != nil {
return fmt.Errorf("failed to retrieve deployment: %v", err)
}
// TODO: reconcile deployment fields with nginx fields
// call sdk.Update if there were any changes
var changed bool
if !changed {
currSpec, err := k8s.ExtractNginxSpec(*currDeploy)
if err != nil {
return fmt.Errorf("failed to extract nginx from deployment: %v", err)
}
if nginx.Spec == currSpec {
logger.Debug("nothing changed")
return nil
}
if err := sdk.Update(deployment); err != nil {
currDeploy.Spec = newDeploy.Spec
if err := sdk.Update(currDeploy); err != nil {
return fmt.Errorf("failed to update deployment: %v", err)
}
......@@ -168,7 +186,13 @@ func listPods(nginx *v1alpha1.Nginx) ([]v1alpha1.NginxPod, error) {
var pods []v1alpha1.NginxPod
for _, p := range podList.Items {
pods = append(pods, v1alpha1.NginxPod{Name: p.Name, PodIP: p.Status.PodIP})
if p.Status.PodIP == "" {
p.Status.PodIP = "<pending>"
}
pods = append(pods, v1alpha1.NginxPod{
Name: p.Name,
PodIP: p.Status.PodIP,
})
}
sort.Slice(pods, func(i, j int) bool {
return pods[i].Name < pods[j].Name
......@@ -195,7 +219,14 @@ func listServices(nginx *v1alpha1.Nginx) ([]v1alpha1.NginxService, error) {
var services []v1alpha1.NginxService
for _, s := range serviceList.Items {
services = append(services, v1alpha1.NginxService{Name: s.Name, Type: string(s.Spec.Type), ServiceIP: s.Spec.ClusterIP})
if s.Spec.ClusterIP == "" {
s.Spec.ClusterIP = "<pending>"
}
services = append(services, v1alpha1.NginxService{
Name: s.Name,
Type: string(s.Spec.Type),
ServiceIP: s.Spec.ClusterIP,
})
}
sort.Slice(services, func(i, j int) bool {
......
package k8s
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
......@@ -26,14 +27,14 @@ const (
// Mount path where certificate and key pair will be placed
certMountPath = configMountPath + "/certs"
// Annotation key used to stored the nginx that created the deployment
generatedFromAnnotation = "nginx.tsuru.io/generated-from"
)
// NewDeployment creates a deployment for a given Nginx resource.
func NewDeployment(n *v1alpha1.Nginx) *appv1.Deployment {
image := n.Spec.Image
if image == "" {
image = defaultNginxImage
}
func NewDeployment(n *v1alpha1.Nginx) (*appv1.Deployment, error) {
n.Spec.Image = valueOrDefault(n.Spec.Image, defaultNginxImage)
deployment := appv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
......@@ -64,7 +65,7 @@ func NewDeployment(n *v1alpha1.Nginx) *appv1.Deployment {
Containers: []corev1.Container{
{
Name: "nginx",
Image: image,
Image: n.Spec.Image,
Ports: []corev1.ContainerPort{
{
Name: defaultHTTPPortName,
......@@ -80,7 +81,17 @@ func NewDeployment(n *v1alpha1.Nginx) *appv1.Deployment {
}
setupConfig(n.Spec.Config, &deployment)
setupTLS(n.Spec.TLSSecret, &deployment)
return &deployment
// This is done on the last step because n.Spec may have mutated during these methods
origSpec, err := json.Marshal(n.Spec)
if err != nil {
return nil, err
}
deployment.Annotations = map[string]string{
generatedFromAnnotation: string(origSpec),
}
return &deployment, nil
}
// NewService assembles the ClusterIP service for the Nginx
......@@ -134,6 +145,19 @@ func LabelsForNginx(name string) map[string]string {
}
}
// ExtractNginxSpec extracts the nginx used to create the object
func ExtractNginxSpec(o metav1.ObjectMeta) (v1alpha1.NginxSpec, error) {
ann, ok := o.Annotations[generatedFromAnnotation]
if !ok {
return v1alpha1.NginxSpec{}, fmt.Errorf("missing %q annotation in deployment", generatedFromAnnotation)
}
var spec v1alpha1.NginxSpec
if err := json.Unmarshal([]byte(ann), &spec); err != nil {
return v1alpha1.NginxSpec{}, fmt.Errorf("failed to unmarshal nginx from annotation: %v", err)
}
return spec, nil
}
func setupConfig(conf *v1alpha1.ConfigRef, dep *appv1.Deployment) {
if conf == nil {
return
......
package k8s
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
......@@ -30,8 +31,9 @@ func baseDeployment() appv1.Deployment {
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-nginx-deployment",
Namespace: "default",
Name: "my-nginx-deployment",
Namespace: "default",
Annotations: make(map[string]string),
},
Spec: appv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
......@@ -277,7 +279,12 @@ func Test_NewDeployment(t *testing.T) {
Kind: "Nginx",
}),
}
assertDeployment(t, &want, NewDeployment(&nginx))
dep, err := NewDeployment(&nginx)
assert.Nil(t, err)
spec, err := json.Marshal(nginx.Spec)
assert.Nil(t, err)
want.Annotations[generatedFromAnnotation] = string(spec)
assertDeployment(t, &want, dep)
})
}
}
......@@ -342,3 +349,45 @@ func TestNewService(t *testing.T) {
})
}
}
func TestExtractNginxSpec(t *testing.T) {
mustMarshal := func(t *testing.T, n v1alpha1.NginxSpec) string {
data, err := json.Marshal(n)
assert.Nil(t, err)
return string(data)
}
tests := []struct {
name string
annotations map[string]string
want v1alpha1.NginxSpec
wantedErr string
}{
{
name: "missing-annotation",
want: v1alpha1.NginxSpec{},
wantedErr: `missing "nginx.tsuru.io/generated-from" annotation in deployment`,
},
{
name: "default",
annotations: map[string]string{
generatedFromAnnotation: mustMarshal(t, v1alpha1.NginxSpec{
Image: "custom-image",
}),
},
want: v1alpha1.NginxSpec{Image: "custom-image"},
wantedErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := metav1.ObjectMeta{
Annotations: tt.annotations,
}
got, err := ExtractNginxSpec(o)
if tt.wantedErr != "" {
assert.EqualError(t, err, tt.wantedErr)
}
assert.Equal(t, tt.want, got)
})
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment