Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add channels support to the Habitat type #128

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ To create an example service run:
This will create a single-pod deployment of an `nginx` Habitat service.
More examples are located in the [example directory](https://github.com/kinvolk/habitat-operator/tree/master/examples/).

### Promoting a Habitat application

By default, the example service from `examples/standalone/habitat.yml` uses the `staging` channel. To promote it to the `production` channel, use the following example:

kubectl create -f examples/standalone/habitat-promote.yml

## Contributing

### Dependency management
Expand Down
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
16 changes: 16 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,19 @@ The following is a description of the Habitat operator API. To see manifest exam
| name | Name of the bind specified in the Habitat configuration files. | string | true |
| service | Name of the service this bind refers to. | string | true |
| group | Group of the service this bind refers to. | string | true |

## HabitatPromote

| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
| metadata | | [metav1.ObjectMeta](https://kubernetes.io/docs/api-reference/v1.6/#objectmeta-v1-meta) | true |
| spec | | [HabitatSpec](#habitatspec) | true |
| status | | | false |

## HabitatPromoteSpec

| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
| habitatName | Name of the Habitat object to promote. | string | true |
| oldChannel | Name of the channel the Habitat application is on before the promotion. | string | true |
| newChannel | Name of the channel the Habitat application is moved to as part of the promotion. | string | true |
1 change: 1 addition & 0 deletions examples/rbac/rbac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ rules:
- habitat.sh
resources:
- habitats
- habitatpromotes
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups:
- apps
Expand Down
8 changes: 8 additions & 0 deletions examples/standalone/habitat-promotion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: habitat.sh/v1
kind: HabitatPromotion
metadata:
name: example-standalone-habitat
spec:
habitatName: example-standalone-habitat
oldChannel: staging
newChannel: production
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{},
&HabitatPromotion{},
&HabitatPromotionList{},
)

metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
Expand Down
35 changes: 35 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"

HabitatPromotionResourcePlural = "habitatpromotions"
)

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,34 @@ const (
TopologyLeader Topology = "leader"
)

type HabitatPromotion struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec HabitatPromotionSpec `json:"spec"`
Status HabitatPromotionStatus `json:"status,omitempty"`
}

type HabitatPromotionSpec struct {
HabitatName string `json:"habitatName"`
OldChannel string `json:"oldChannel"`
NewChannel string `json:"newChannel"`
}

type HabitatPromotionStatus struct {
State HabitatPromotionState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}

type HabitatPromotionState string

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

type HabitatPromotionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []HabitatPromotion `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
habitatPromotionCRDName = crv1.HabitatPromotionResourcePlural + "." + crv1.GroupName
habitatResourceShortName = "hab"
habitatPromotionResourceShortName = "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 HabitatPromotion CRD.
if err := createCRD(clientset, logger, habitatPromotionCRDName, crv1.HabitatPromotionResourcePlural, habitatPromotionResourceShortName, crv1.HabitatPromotion{}); 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