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

feat: labeler logic #1

Merged
merged 16 commits into from
Feb 21, 2024
35 changes: 19 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: CI

permissions:
contents: write
packages: write

on:
push:
branches:
Expand All @@ -14,23 +18,23 @@ on:

env:
# Common versions
GO_VERSION: '1.21.3'
GOLANGCI_VERSION: 'v1.54.2'
DOCKER_BUILDX_VERSION: 'v0.11.2'
GO_VERSION: "1.21.3"
GOLANGCI_VERSION: "v1.54.2"
DOCKER_BUILDX_VERSION: "v0.11.2"

# These environment variables are important to the Crossplane CLI install.sh
# script. They determine what version it installs.
XP_CHANNEL: master # TODO(negz): Pin to stable once v1.14 is released.
XP_VERSION: current # TODO(negz): Pin to a version once v1.14 is released.
XP_CHANNEL: stable # TODO(negz): Pin to stable once v1.14 is released.
XP_VERSION: v1.14.0 # TODO(negz): Pin to a version once v1.14 is released.

# This CI job will automatically push new builds to xpkg.upbound.io if the
# XPKG_ACCESS_ID and XPKG_TOKEN secrets are set in the GitHub respository (or
# organization) settings. Create a token at https://accounts.upbound.io.
XPKG_ACCESS_ID: ${{ secrets.XPKG_ACCESS_ID }}
XPKG_ACCESS_ID: ${{ github.repository_owner }}

# The package to push, without a version tag. The default matches GitHub. For
# example xpkg.upbound.io/crossplane/function-template-go.
XPKG: xpkg.upbound.io/${{ github.repository}}
XPKG: ghcr.io/${{ github.repository}}

# The package version to push. The default is 0.0.0-gitsha.
XPKG_VERSION: ${{ inputs.version }}
Expand All @@ -46,7 +50,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false # The golangci-lint action does its own caching.
cache: false # The golangci-lint action does its own caching.

- name: Lint
uses: golangci/golangci-lint-action@v3
Expand All @@ -70,7 +74,7 @@ jobs:
# We want to build most packages for the amd64 and arm64 architectures. To
# speed this up we build single-platform packages in parallel. We then upload
# those packages to GitHub as a build artifact. The push job downloads those
# artifacts and pushes them as a single multi-platform package.
# artifacts and pushes them as a single multi-platform package.
build:
runs-on: ubuntu-22.04
strategy:
Expand Down Expand Up @@ -105,16 +109,15 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
target: image
build-args:
GO_VERSION=${{ env.GO_VERSION }}
build-args: GO_VERSION=${{ env.GO_VERSION }}
outputs: type=docker,dest=runtime-${{ matrix.arch }}.tar

- name: Setup the Crossplane CLI
run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"

- name: Build Package
run: ./crossplane xpkg build --package-file=${{ matrix.arch }}.xpkg --package-root=package/ --embed-runtime-image-tarball=runtime-${{ matrix.arch }}.tar

- name: Upload Single-Platform Package
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -147,9 +150,9 @@ jobs:
uses: docker/login-action@v3
if: env.XPKG_ACCESS_ID != ''
with:
registry: xpkg.upbound.io
username: ${{ secrets.XPKG_ACCESS_ID }}
password: ${{ secrets.XPKG_TOKEN }}
registry: ghcr.io
username: ${{ env.XPKG_ACCESS_ID }}
password: ${{ secrets.GITHUB_TOKEN }}

# If a version wasn't explicitly passed as a workflow_dispatch input we
# default to version v0.0.0-<git-commit-date>-<git-short-sha>, for example
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@

# Go workspace file
go.work
tmp
27 changes: 17 additions & 10 deletions example/composition.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: function-template-go
name: logging-composition
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1
kind: XR
mode: Pipeline
resources:
- name: Logging
base:
apiVersion: logging.banzaicloud.io/v1beta1
kind: Logging
spec:
controlNamespace: ""
fluentd:
disablePvc: true
compositeTypeRef:
apiVersion: caas.telekom.de/v1alpha1
kind: XLogging
pipeline:
- step: run-the-template
- step: get-labels
functionRef:
name: function-template-go
input:
apiVersion: template.fn.crossplane.io/v1beta1
kind: Input
example: "Hello world"
name: logging-labeler
input: {}

4 changes: 2 additions & 2 deletions example/functions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-template-go
name: logging-labeler
annotations:
# This tells crossplane beta render to connect to the function locally.
render.crossplane.io/runtime: Development
spec:
# This is ignored when using the Development runtime.
package: function-template-go
package: logging-labeler
5 changes: 3 additions & 2 deletions example/xr.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Replace this with your XR!
apiVersion: example.crossplane.io/v1
kind: XR
apiVersion: caas.telekom.de/v1alpha1
kind: Logging
metadata:
name: example-xr
namespace: microservices
spec: {}
70 changes: 58 additions & 12 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,82 @@ import (

"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/logging"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/request"
"github.com/crossplane/function-sdk-go/resource"
"github.com/crossplane/function-sdk-go/resource/composed"
"github.com/crossplane/function-sdk-go/response"

"github.com/crossplane/function-template-go/input/v1beta1"
inputv1beta1 "github.com/crossplane/logging-labeler/input/v1beta1"
"github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1"
)

// Function returns whatever response you ask it to.
type Function struct {
fnv1beta1.UnimplementedFunctionRunnerServiceServer

cs kubernetes.Interface
log logging.Logger
}

// RunFunction runs the Function.
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
f.log.Info("Running function", "tag", req.GetMeta().GetTag())

func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
rsp := response.To(req, response.DefaultTTL)

in := &v1beta1.Input{}
in := &inputv1beta1.Input{}
if err := request.GetInput(req, in); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
response.Fatal(rsp, errors.Wrapf(err, "cannot get input from %T", req))
return rsp, nil
}

xr, err := request.GetObservedCompositeResource(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composed resources"))
return rsp, nil
}

desired, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired composed resources"))
return rsp, nil
}

_ = v1beta1.AddToScheme(composed.Scheme)
niklastreml marked this conversation as resolved.
Show resolved Hide resolved

ns := xr.Resource.GetClaimReference().Namespace

targetns, err := f.cs.CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{})
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get namespace %s", ns))
return rsp, nil
}

// TODO: Add your Function logic here!
response.Normalf(rsp, "I was run with input %q!", in.Example)
f.log.Info("I was run!", "input", in.Example)
projectid, ok := targetns.GetLabels()[in.TargetLabel]
if !ok {
response.Fatal(rsp, errors.New("cannot get project id"))
return rsp, nil
}

l := &v1beta1.Logging{}
l.Spec.ControlNamespace = ns
l.Spec.WatchNamespaceSelector = &metav1.LabelSelector{}
l.Spec.WatchNamespaceSelector.MatchLabels = map[string]string{in.TargetLabel: projectid}
l.Spec.FluentdSpec = &v1beta1.FluentdSpec{}

cd, err := composed.From(l)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", l, &composed.Unstructured{}))
return rsp, nil
}

desired[resource.Name("logging")] = &resource.DesiredComposed{Resource: cd}
niklastreml marked this conversation as resolved.
Show resolved Hide resolved

f.log.Info("Desired composed resources", "desired", desired)

if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources"))
return rsp, nil
}

return rsp, nil
}
107 changes: 93 additions & 14 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import (
"context"
"testing"

"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/durationpb"

"github.com/crossplane/crossplane-runtime/pkg/logging"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"

fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/resource"
"github.com/crossplane/function-sdk-go/response"
inputv1beta1 "github.com/crossplane/logging-labeler/input/v1beta1"
)

func TestRunFunction(t *testing.T) {

type args struct {
ctx context.Context
req *fnv1beta1.RunFunctionRequest
Expand All @@ -33,34 +35,111 @@ func TestRunFunction(t *testing.T) {
want want
}{
"ResponseIsReturned": {
reason: "The Function should return a fatal result if no input was specified",
reason: "The function should return a response with the desired state.",
args: args{
req: &fnv1beta1.RunFunctionRequest{
Input: resource.MustStructObject(&inputv1beta1.Input{
TargetLabel: "testLabel",
}),
Meta: &fnv1beta1.RequestMeta{Tag: "hello"},
Input: resource.MustStructJSON(`{
"apiVersion": "template.fn.crossplane.io/v1beta1",
"kind": "Input",
"example": "Hello, world"
}`),
Observed: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
Resource: resource.MustStructJSON(`{
"apiVersion": "caas.telekom.de/v1alpha1",
"kind": "XLogging",
"metadata": {
"name": "test-logging",
"generation": 1
},
"spec": {
"claimRef": {
"apiVersion": "caas.telekom.de/v1alpha1",
"kind": "Logging",
"name": "test-logging",
"namespace": "unit-test"
}
}
}`),
},
},
},
},
want: want{
rsp: &fnv1beta1.RunFunctionResponse{
Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1beta1.Result{
{
Severity: fnv1beta1.Severity_SEVERITY_NORMAL,
Message: "I was run with input \"Hello, world\"!",
Desired: &fnv1beta1.State{
Resources: map[string]*fnv1beta1.Resource{
"logging": {
Resource: resource.MustStructJSON(`{
"apiVersion": "logging.banzaicloud.io/v1beta1",
"kind": "Logging",
"spec": {
"controlNamespace": "unit-test",
"watchNamespaceSelector": {
"matchLabels": {
"testLabel": "test-project"
}
},
"allowClusterResourcesFromAllNamespaces": false,
"configCheck": {
"timeoutSeconds": 0
},
"enableRecreateWorkloadOnImmutableFieldChange": false,
"flowConfigCheckDisabled": false,
"fluentd": {
"compressConfigFile": false,
"disablePvc": false,
"enableMsgpackTimeSupport": false,
"livenessDefaultCheck": false,
"port": 0,
"readinessDefaultCheck": {
"bufferFileNumber": false,
"bufferFileNumberMax": 0,
"bufferFreeSpace": false,
"bufferFreeSpaceThreshold": 0,
"failureThreshold": 0,
"initialDelaySeconds": 0,
"periodSeconds": 0,
"successThreshold": 0,
"timeoutSeconds": 0
},
"tls": {
"enabled": false
},
"volumeMountChmod": false,
"workers": 0
},
"skipInvalidResources": false
},
"status": {
"problemsCount": 0
}

}`),
},
},
},
},
},
},
}

client := fake.NewSimpleClientset()
if _, err := client.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "unit-test",
Labels: map[string]string{
"testLabel": "test-project",
},
},
}, metav1.CreateOptions{}); err != nil {
t.Errorf("client.CoreV1().Namespaces().Create(...): %v", err)
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
f := &Function{log: logging.NewNopLogger()}

f := &Function{log: logging.NewNopLogger(), cs: client}
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)

if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" {
Expand Down
Loading