Skip to content

Commit

Permalink
Add channels support to the Habitat type
Browse files Browse the repository at this point in the history
Habitat applications can exist in the different "channels" -
like "staging", "production", "unstable" etc. Those names
can be configured by the user.

Those channels will be expressed as labels in Kubernetes.

Fixes #126

Signed-off-by: Michal Rostecki <michal@kinvolk.io>
  • Loading branch information
Michal Rostecki committed Dec 1, 2017
1 parent 269a0d9 commit 83fedaf
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 75 deletions.
14 changes: 4 additions & 10 deletions cmd/habitat-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/go-kit/kit/log/level"
flag "github.com/spf13/pflag"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"

Expand Down Expand Up @@ -66,15 +65,10 @@ func run() int {
return 1
}

// Create Habitat CRD.
_, crdErr := habitatclient.CreateCRD(apiextensionsclientset)
if crdErr != nil {
if !apierrors.IsAlreadyExists(crdErr) {
level.Error(logger).Log("msg", crdErr)
return 1
}

level.Info(logger).Log("msg", "Habitat CRD already exists, continuing")
// Create Habitat CRDs.
if crdErr := habitatclient.CreateCRDs(apiextensionsclientset, log.With(logger, "coponent", "crd")); crdErr != nil {
level.Error(logger).Log("msg", crdErr)
return 1
} else {
level.Info(logger).Log("msg", "created Habitat CRD")
}
Expand Down
9 changes: 9 additions & 0 deletions examples/standalone/habitat-promote.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: habitat.sh/v1
kind: HabitatPromote
metadata:
name: example-standalone-habitat
spec:
habitatName: example-standalone-habitat
oldChannel: staging
newChannel: production
replace: false
5 changes: 4 additions & 1 deletion examples/standalone/habitat.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
apiVersion: habitat.sh/v1
kind: Habitat
metadata:
name: example-standalone-habitat
name: example-standalone-habitat-qqg4x
labels:
habitat-name: example-standalone-habitat
spec:
# the core/nginx habitat service packaged as a Docker image
image: kinvolk/nginx-hab
count: 1
channel: staging
service:
topology: standalone
# if not present, defaults to "default"
Expand Down
2 changes: 2 additions & 0 deletions pkg/habitat/apis/cr/v1/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Habitat{},
&HabitatList{},
&HabitatPromote{},
&HabitatPromoteList{},
)

metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
Expand Down
36 changes: 36 additions & 0 deletions pkg/habitat/apis/cr/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ const (
HabitatNameLabel = "habitat-name"

TopologyLabel = "topology"

// HabitatChannelLabel contains the information about stability of application.
// Example: 'channel: production'
HabitatChannelLabel = "channel"

HabitatPromoteResourcePlural = "habitatpromotes"
)

type Habitat struct {
Expand All @@ -44,6 +50,9 @@ type HabitatSpec struct {
// Image is the Docker image of the Habitat Service.
Image string `json:"image"`
Service Service `json:"service"`
// Channel is the information about stability of the application, expressed as a label in Kubernetes.
// Optional.
Channel string `json:"channel,omitempty"`
}

type HabitatStatus struct {
Expand Down Expand Up @@ -94,8 +103,35 @@ const (
TopologyLeader Topology = "leader"
)

type HabitatPromote struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec HabitatPromoteSpec `json:"spec"`
Status HabitatPromoteStatus `json:"status,omitempty"`
}

type HabitatPromoteSpec struct {
HabitatName string `json:"habitatName"`
OldChannel string `json:"oldChannel"`
NewChannel string `json:"newChannel"`
Replace bool `json:"replace"`
}

type HabitatPromoteStatus struct {
State HabitatPromoteState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}

type HabitatPromoteState string

type HabitatList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Habitat `json:"items"`
}

type HabitatPromoteList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []HabitatPromote `json:"items"`
}
67 changes: 44 additions & 23 deletions pkg/habitat/client/cr.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,64 @@
package client

import (
"fmt"
"reflect"
"time"

"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
crv1 "github.com/kinvolk/habitat-operator/pkg/habitat/apis/cr/v1"
apiv1 "k8s.io/api/core/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
)

const (
habitatCRDName = crv1.HabitatResourcePlural + "." + crv1.GroupName
habitatResourceShortName = "hab"
habitatCRDName = crv1.HabitatResourcePlural + "." + crv1.GroupName
habitatPromoteCRDName = crv1.HabitatPromoteResourcePlural + "." + crv1.GroupName
habitatResourceShortName = "hab"
habitatPromoteResourceShortName = "habprom"

pollInterval = 500 * time.Millisecond
timeOut = 10 * time.Second
)

// CreateCRD creates the Habitat Custom Resource Definition.
// createCRD creates the Custom Resource Definition with the given name.
// It checks if creation has completed successfully, and deletes the CRD in case of error.
func CreateCRD(clientset apiextensionsclient.Interface) (*apiextensionsv1beta1.CustomResourceDefinition, error) {
func createCRD(clientset apiextensionsclient.Interface, logger log.Logger, crdName, plural, shortName string, kind interface{}) error {
crd := &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: habitatCRDName,
Name: crdName,
},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: crv1.GroupName,
Version: crv1.SchemeGroupVersion.Version,
Scope: apiextensionsv1beta1.NamespaceScoped,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Plural: crv1.HabitatResourcePlural,
Kind: reflect.TypeOf(crv1.Habitat{}).Name(),
ShortNames: []string{habitatResourceShortName},
Plural: plural,
Kind: reflect.TypeOf(kind).Name(),
ShortNames: []string{shortName},
},
},
}

_, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
if err != nil {
return nil, err
if _, err := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil {
if !apierrors.IsAlreadyExists(err) {
return err
}
level.Info(logger).Log("msg", fmt.Sprintf("%s CRD already exists, continuing", crdName))
}

// wait for CRD being established.
err = wait.Poll(pollInterval, timeOut, func() (bool, error) {
crd, err = clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Get(habitatCRDName, metav1.GetOptions{})
if err := wait.Poll(pollInterval, timeOut, func() (bool, error) {
var err error

crd, err = clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})

if err != nil {
return false, err
Expand All @@ -76,26 +86,37 @@ func CreateCRD(clientset apiextensionsclient.Interface) (*apiextensionsv1beta1.C
}
case apiextensionsv1beta1.NamesAccepted:
if cond.Status == apiextensionsv1beta1.ConditionFalse {
// TODO re-introduce logging?
// fmt.Printf("Error: Name conflict: %v\n", cond.Reason)
level.Error(logger).Log("msg", fmt.Sprintf("Error: Name conflict: %v\n", cond.Reason))
}
}
}

return false, err
})

// delete CRD if there was an error.
if err != nil {
deleteErr := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(habitatCRDName, nil)
}); err != nil {
// delete CRD if there was an error.
deleteErr := clientset.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crdName, nil)
if deleteErr != nil {
return nil, errors.NewAggregate([]error{err, deleteErr})
return errors.NewAggregate([]error{err, deleteErr})
}

return nil, err
return err
}

return nil
}

func CreateCRDs(clientset apiextensionsclient.Interface, logger log.Logger) error {
// Create Habitat CRD.
if err := createCRD(clientset, logger, habitatCRDName, crv1.HabitatResourcePlural, habitatResourceShortName, crv1.Habitat{}); err != nil {
return err
}

// Create HabitatPromote CRD.
if err := createCRD(clientset, logger, habitatPromoteCRDName, crv1.HabitatPromoteResourcePlural, habitatPromoteResourceShortName, crv1.HabitatPromote{}); err != nil {
return err
}

return crd, nil
return nil
}

// WaitForHabitatInstanceProcessed polls the API for a specific Habitat with a state of "Processed".
Expand Down
Loading

0 comments on commit 83fedaf

Please sign in to comment.