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

Upgrade k8s dependencies & add documentation #3

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 5 additions & 3 deletions .github/workflows/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ jobs:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v1
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.26
version: v1.40
only-new-issues: true

features-tests:
name: Features tests (${{ matrix.go }})
runs-on: ubuntu-latest
strategy:
matrix:
go: [ '1.14', '1.13' ]
go: [ '1.16', '1.15' ]
steps:
- uses: actions/checkout@v2
- name: Setup Go environment
Expand Down
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/xunleii/godog-kubernetes.svg)](https://pkg.go.dev/github.com/xunleii/godog-kubernetes)
![](https://github.com/xunleii/godog-kubernetes/actions/workflows/golang.yml/badge.svg)
![](https://github.com/xunleii/godog-kubernetes/actions/workflows/code-quality.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/xunleii/godog-kubernetes)](https://goreportcard.com/report/github.com/xunleii/godog-kubernetes)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=xunleii_godog-kubernetes&metric=alert_status)](https://sonarcloud.io/dashboard?id=xunleii_godog-kubernetes)

# Godog - Kubernetes feature context

Package **godog-kubernetes** provides a *feature context* to test code that require a Kubernetes cluster,
like controllers or operators. It provides some **Gherkin** rules to easily create, get, list, update
and delete resources on Kubernetes.

## How to use it

To inject the Kubernetes feature context, you just need to call `NewFeatureContext` on the `InitializeScenario`
function.

For example :
```go
package main

import (
"github.com/cucumber/godog"
kubernetes_ctx "github.com/xunleii/godog-kubernetes"
kubernetes_ctx_helpers "github.com/xunleii/godog-kubernetes/helpers"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
)

func InitializeScenario(ctx *godog.ScenarioContext) {
// This line injects the kubernetes context feature to the current *godog.ScenarioContext
kubectx, _ := kubernetes_ctx.NewFeatureContext(ctx, kubernetes_ctx.WithFakeClient(runtime.NewScheme()))

// You can use kubectx to make want you want on Kubernetes ...
ns, _ := kubectx.Get(schema.FromAPIVersionAndKind("v1", "Namespace"), types.NamespacedName{Name: "default"}) // get the default namespace object
_ = kubectx.Patch(ns.GroupVersionKind(), types.NamespacedName{Name: ns.GetName()}, types.MergePatchType, []byte(`{"metadata": {"labels": {"key": "value"}}}`)) // add label `key=value` to namespace default
_, _ = kubectx.Delete(ns.GroupVersionKind(), types.NamespacedName{Name: ns.GetName()}) // remove the default namespace

// .. or use it with your controller
client = kubectx.Client() // get the Kubernetes client if you want to use it in your controller
ctrl := NewMyController(client)
ctx.Step(
`^my controller reconciles '(`+kubernetes_ctx.RxNamespacedName+`)'$`,
func(target string) error {
nn, err := kubernetes_ctx_helpers.NamespacedNameFrom(target)
if err != nil {
return err
}

_, err := ctrl.Request{NamespacedName: nn}
return err
},
)
}
```

## List of available rules

| Type | Rule | Description |
|:----:|:----:|:------------:|
| **CREATE** | `Kubernetes must have <ApiGroupVersionKind> '<NamespacedName>'` | Creates a new resource, without any specific fields. |
| **CREATE** | `Kubernetes creates a new <ApiGroupVersionKind> '<NamespacedName>'` | Creates a new resource, without any specific fields. |
| **CREATE** | `Kubernetes must have <ApiGroupVersionKind> '<NamespacedName>' with <YAML>` | Creates a new resource, with the given definition. |
| **CREATE** | `Kubernetes creates a new <ApiGroupVersionKind> '<NamespacedName>' with <YAML>` | Creates a new resource, with the given definition. |
| **CREATE** | `Kubernetes must have <ApiGroupVersionKind> '<NamespacedName>' from <filename>` | Creates a new resource, with then definition available in the given filename. |
| **CREATE** | `Kubernetes creates a new <ApiGroupVersionKind> '<NamespacedName>' from <filename>` | Creates a new resource, with then definition available in the given filename. |
| **CREATE** | `Kubernetes must have the following resources <RESOURCES_TABLE>` | Creates several resources in a row, without any specific fields (useful for Namespaces). |
| **CREATE** | `Kubernetes creates the following resources <RESOURCES_TABLE>` | Creates several resources in a row, without any specific fields (useful for Namespaces). |
| **LIST** | `Kubernetes has <NumberResources> <ApiGroupVersionKind>` | Compare the current number of a specific resource with the given number. |
| **LIST** | `Kubernetes has <NumberResources> <ApiGroupVersionKind> in namespace '<Namespace>'` | Compare the current number of a specific resource with the given number. |
| **GET** | `Kubernetes has <ApiGroupVersionKind> '<NamespacedName>'` | Validates the fact that Kubernetes has the specified resource. |
| **GET** | `Kubernetes doesn't have <ApiGroupVersionKind> '<NamespacedName>'` | Validates the fact that Kubernetes doesn't have the specified resource. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' is similar to '<NamespacedName>'` | Compares two resources in order to determine if they are similar. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' is not similar to '<NamespacedName>'` | Compares two resources in order to determine if they are not similar. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' is equal to '<NamespacedName>'` | Compares two resources in order to determine if they are equal. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' is not equal to '<NamespacedName>'` | Compares two resources in order to determine if they are not equal. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has '<FieldPath>'` | Validates the fact that the specific resource has the field. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' doesn't have '<FieldPath>'` | Validates the fact that the specific resource doesn't have the field. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has '<FieldPath>=<FieldValue>'` | Validates the fact that the specific resource field has the given value. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has '<FieldPath>!=<FieldValue>'` | Validates the fact that the specific resource field is different than the given value. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has label '<LabelName>'` | Validates the fact that the specific resource has the given label. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' doesn't have label '<LabelName>'` | Validates the fact that the specific resource doesn't have the given label. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has label '<LabelName>=<LabelValue>'` | Validates the fact that the specific resource label has the given value. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has label '<LabelName>!=<LabelValue>'` | Validates the fact that the specific resource label doesn't have the given value. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has annotation '<AnnotationName>'` | Validates the fact that the specific resource has the given annotation. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' doesn't have annotation '<AnnotationName>'` | Validates the fact that the specific resource doesn't have the given annotation. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has annotation '<AnnotationName>=<AnnotationValue>'` | Validates the fact that the specific resource annotation has the given value. |
| **GET** | `Kubernetes resource <ApiGroupVersionKind> '<NamespacedName>' has annotation '<AnnotationName>!=<AnnotationValue>'` | Validates the fact that the specific resource annotation doesn't have the given value. |
| **PATCH** | `Kubernetes labelizes <ApiGroupVersionKind> '<NamespacedName>' with '<LabelName>=<LabelValue>'` | Adds or modifies a specific resource label with the given value. |
| **PATCH** | `Kubernetes removes label <LabelName> on <ApiGroupVersionKind> '<NamespacedName>'` | Removes the given label on the specified resource. |
| **PATCH** | `Kubernetes updates label <LabelName> on <ApiGroupVersionKind> '<NamespacedName>' with '<LabelValue>'` | Updates the given label on the specified resource with the given value. |
| **PATCH** | `Kubernetes annotates <ApiGroupVersionKind> '<NamespacedName>' with '<AnnotationName>=<AnnotationValue>'` | Adds or modifies a specific resource annotation with the given value. |
| **PATCH** | `Kubernetes removes annotation <AnnotationName> on <ApiGroupVersionKind> '<NamespacedName>'` | Removes the given annotation on the specified resource. |
| **PATCH** | `Kubernetes updates annotation <AnnotationName> on <ApiGroupVersionKind> '<NamespacedName>' with '<AnnotationValue>'` | Updates the given annotation on the specified resource with the given value. |
| **PATCH** | `Kubernetes patches <ApiGroupVersionKind> '<NamespacedName>' with <YAML>` | Patches a specific resource with the given patch (it use StrategicMergePatchType... |
| **DELETE** | `Kubernetes removes <ApiGroupVersionKind> '<NamespacedName>'` | Removes the specified resource. |
| **DELETE** | `Kubernetes removes the following resources <RESOURCES_TABLE>` | Creates several resources in a row. |

## LICENSE
**godog-kubernetes** is licensed under the [Apache v2](https://www.apache.org/licenses/LICENSE-2.0).
2 changes: 1 addition & 1 deletion context_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func WithClient(scheme Scheme, client client.Client) FeatureContextOptionFnc {
func WithFakeClient(scheme *runtime.Scheme) FeatureContextOptionFnc {
return func(ctx *FeatureContext) {
ctx.scheme = scheme
ctx.client = fake.NewFakeClientWithScheme(scheme)
ctx.client = fake.NewClientBuilder().WithScheme(scheme).Build()
if ctx.gc == nil {
ctx.gc = NaiveGC
}
Expand Down
10 changes: 5 additions & 5 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

var (
scheme = runtime.NewScheme()
client = fake.NewFakeClientWithScheme(scheme)
client = fake.NewClientBuilder().WithScheme(scheme).Build()
goctx, _ = context.WithCancel(context.TODO())
gc = func(*kubernetes_ctx.FeatureContext, *unstructured.Unstructured) error { return nil }
)
Expand Down Expand Up @@ -165,8 +165,8 @@ func TestMain(m *testing.M) {
&unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]string{"key": "value"},
"labels": map[string]string{"key": "value"},
"annotations": map[string]interface{}{"key": "value"},
"labels": map[string]interface{}{"key": "value"},
},
},
},
Expand All @@ -181,8 +181,8 @@ func TestMain(m *testing.M) {
&unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]string{"key": "value"},
"labels": map[string]string{"key": "value"},
"annotations": map[string]interface{}{"key": "value"},
"labels": map[string]interface{}{"key": "value"},
},
"spec": map[string]interface{}{"type": "ClusterIP", "clusterIP": "None"},
},
Expand Down
20 changes: 10 additions & 10 deletions features_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ func CreateSingleResource(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes (?:must have|creates a new) (`+RxGroupVersionKind+`) '(`+RxNamespacedName+`)'$`,
func(groupVersionKindStr, resourceName string) error {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return err
}
namespacedName, _ := helpers.NamespacedNameFrom(resourceName)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(resourceName)

return ctx.Create(groupVersionKind, namespacedName, &unstructured.Unstructured{})
},
Expand All @@ -35,14 +35,14 @@ func CreateSingleResource(ctx *FeatureContext, s ScenarioContext) {
func CreateSingleResourceWith(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes (?:must have|creates a new) (`+RxGroupVersionKind+`) '(`+RxNamespacedName+`)' with$`,
func(groupVersionKindStr, resourceName string, yamlObj helpers.YamlDocString) error {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
func(groupVersionKindStr, resourceName string, yamlObj kubernetes_ctx_helpers.YamlDocString) error {
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return err
}
namespacedName, _ := helpers.NamespacedNameFrom(resourceName)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(resourceName)

obj, err := helpers.UnmarshalYamlDocString(yamlObj)
obj, err := kubernetes_ctx_helpers.UnmarshalYamlDocString(yamlObj)
if err != nil {
return err
}
Expand All @@ -60,11 +60,11 @@ func CreateSingleResourceFrom(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes (?:must have|creates a new) (`+RxGroupVersionKind+`) '(`+RxNamespacedName+`)' from (.+)$`,
func(groupVersionKindStr, resourceName, fileName string) error {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return err
}
namespacedName, _ := helpers.NamespacedNameFrom(resourceName)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(resourceName)

data, err := ioutil.ReadFile(fileName)
if err != nil {
Expand All @@ -89,8 +89,8 @@ func CreateSingleResourceFrom(ctx *FeatureContext, s ScenarioContext) {
func CreateMultiResources(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes (?:must have|creates) the following resources$`,
func(table helpers.ResourceTable) error {
resources, err := helpers.UnmarshalResourceTable(table)
func(table kubernetes_ctx_helpers.ResourceTable) error {
resources, err := kubernetes_ctx_helpers.UnmarshalResourceTable(table)
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions features_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ func RemoveResource(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes removes (`+RxGroupVersionKind+`) '(`+RxNamespacedName+`)'$`,
func(groupVersionKindStr, name string) error {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return err
}
namespacedName, _ := helpers.NamespacedNameFrom(name)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(name)

_, err = ctx.Delete(groupVersionKind, namespacedName)
return err
Expand All @@ -27,8 +27,8 @@ func RemoveResource(ctx *FeatureContext, s ScenarioContext) {
func RemoveMultiResource(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes removes the following resources$`,
func(table helpers.ResourceTable) error {
resources, err := helpers.UnmarshalResourceTable(table)
func(table kubernetes_ctx_helpers.ResourceTable) error {
resources, err := kubernetes_ctx_helpers.UnmarshalResourceTable(table)
if err != nil {
return err
}
Expand Down
16 changes: 8 additions & 8 deletions features_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ func ResourceExists(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes has (`+RxGroupVersionKind+`) '(`+RxNamespacedName+`)'$`,
func(groupVersionKindStr, name string) error {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return err
}
namespacedName, _ := helpers.NamespacedNameFrom(name)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(name)

_, err = ctx.Get(groupVersionKind, namespacedName)
return err
Expand All @@ -37,11 +37,11 @@ func ResourceNotExists(ctx *FeatureContext, s ScenarioContext) {
s.Step(
`^Kubernetes doesn't have (`+RxGroupVersionKind+`) '(`+RxNamespacedName+`)'$`,
func(groupVersionKindStr, name string) error {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return err
}
namespacedName, _ := helpers.NamespacedNameFrom(name)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(name)

_, err = ctx.Get(groupVersionKind, namespacedName)
switch {
Expand Down Expand Up @@ -115,12 +115,12 @@ func ResourceIsNotSimilarTo(ctx *FeatureContext, s ScenarioContext) {

// getWithoutMetadata returns resource without metadata field.
func getWithoutMetadata(ctx *FeatureContext, groupVersionKindStr, name string) (*unstructured.Unstructured, error) {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return nil, err
}

namespacedName, _ := helpers.NamespacedNameFrom(name)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(name)

obj, err := ctx.Get(groupVersionKind, namespacedName)
if err != nil {
Expand Down Expand Up @@ -193,12 +193,12 @@ func ResourceIsNotEqualTo(ctx *FeatureContext, s ScenarioContext) {
// getWithoutUniqFields returns resources without unique fields ('metadata.name',
// 'metadata.namespace', 'metadata.uid' and 'metadata.resourceVersion').
func getWithoutUniqueFields(ctx *FeatureContext, groupVersionKindStr, name string) (*unstructured.Unstructured, error) {
groupVersionKind, err := helpers.GroupVersionKindFrom(groupVersionKindStr)
groupVersionKind, err := kubernetes_ctx_helpers.GroupVersionKindFrom(groupVersionKindStr)
if err != nil {
return nil, err
}

namespacedName, _ := helpers.NamespacedNameFrom(name)
namespacedName, _ := kubernetes_ctx_helpers.NamespacedNameFrom(name)

obj, err := ctx.Get(groupVersionKind, namespacedName)
if err != nil {
Expand Down
Loading