Skip to content
Merged
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
43 changes: 25 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# func-operator

A Kubernetes operator for managing serverless functions using the `func` CLI. This operator automates the deployment and lifecycle management of functions from Git repositories to Kubernetes clusters with Knative.
A Kubernetes operator for managing middleware updates for serverless functions deployed with the `func` CLI. This operator monitors deployed functions and automatically rebuilds them when outdated middleware is detected, ensuring functions stay up-to-date with the latest middleware versions.

## Prerequisites

Expand All @@ -21,9 +21,9 @@ kubectl apply -f https://github.com/functions-dev/func-operator/releases/latest/

## Usage

### Create a Function
### Register a Function for Middleware Management

Create a `Function` custom resource to deploy a function from a Git repository:
Create a `Function` custom resource to register an existing function for middleware monitoring and updates:

```yaml
apiVersion: functions.dev/v1alpha1
Expand All @@ -32,12 +32,11 @@ metadata:
name: my-function
namespace: default
spec:
source:
repositoryUrl: https://github.com/your-org/your-function.git
repository:
url: https://github.com/your-org/your-function.git
authSecretRef:
name: git-credentials
registry:
path: quay.io/your-username/my-function
authSecretRef:
name: registry-credentials
```
Expand All @@ -48,6 +47,12 @@ Apply the resource:
kubectl apply -f function.yaml
```

**Note:** This registers an existing function with the operator for middleware management. To initially deploy a function, use the `func` CLI directly:

```bash
func deploy --path <function-path> --registry <registry-path>
```

### Registry Authentication

For private registries, create a secret with registry credentials:
Expand Down Expand Up @@ -100,7 +105,7 @@ data:
password: <base64-encoded-password>
```

Then reference it in the Function under `.spec.source.authSecretRef.name`
Then reference it in the Function under `.spec.repository.authSecretRef.name`

```yaml
apiVersion: functions.dev/v1alpha1
Expand All @@ -109,23 +114,24 @@ metadata:
name: my-function
namespace: default
spec:
source:
repositoryUrl: https://github.com/your-org/your-function.git
repository:
url: https://github.com/your-org/your-function.git
authSecretRef:
name: git-credentials
```

### Check Function Status

View the status of your function:
View the middleware status of your function:

```bash
kubectl get function my-function -o yaml
```

The status will include:
- Function name and runtime
- Deployment conditions
- Middleware update conditions
- Whether the function needs rebuilding due to outdated middleware

## Development

Expand Down Expand Up @@ -205,13 +211,14 @@ make lint

### Function Spec

| Field | Type | Required | Description |
|--------------------------|---------|----------|--------------------------------------------------------|
| `source.repositoryUrl` | string | Yes | Git repository URL containing the function source code |
| `source.authSecretRef` | object | No | Reference to Git repository authentication secret |
| `registry.path` | string | Yes | Container registry path for the function image |
| `registry.insecure` | boolean | No | Allow insecure registry connections |
| `registry.authSecretRef` | object | No | Reference to registry authentication secret |
| Field | Type | Required | Description |
|-----------------------------|---------|----------|--------------------------------------------------------------------------------------------------|
| `repository.url` | string | Yes | URL of the Git repository containing the function |
| `repository.branch` | string | No | Branch of the repository |
| `repository.path` | string | No | Path to the function inside the repository. Defaults to "." |
| `repository.authSecretRef` | object | No | Reference to the auth secret for private repository authentication |
| `registry.authSecretRef` | object | No | Reference to the secret containing credentials for registry authentication |
| `autoUpdateMiddleware` | boolean | No | Defines if the operator should rebuild when outdated middleware is detected. Defaults to global operator config |

### Function Status

Expand Down
30 changes: 19 additions & 11 deletions api/v1alpha1/function_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,36 @@ type Function struct {

// FunctionSpec defines the desired state of Function.
type FunctionSpec struct {
Source FunctionSpecSource `json:"source,omitempty"`
Registry FunctionSpecRegistry `json:"registry,omitempty"`
Repository FunctionSpecRepository `json:"repository,omitempty"`
Registry FunctionSpecRegistry `json:"registry,omitempty"`

// AutoUpdateMiddleware defines if the operator should rebuild the function when an outdated middleware is detected.
// Defaults to the global operator config.
// TODO: implement logic
AutoUpdateMiddleware *bool `json:"autoUpdateMiddleware,omitempty"`
}

type FunctionSpecSource struct {
type FunctionSpecRepository struct {
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
RepositoryURL string `json:"repositoryUrl"`
// URL of the Git repository containing the function
URL string `json:"url"`

// +kubebuilder:validation:Optional
Reference string `json:"reference"`
// Branch of the repository
Branch string `json:"branch,omitempty"`

// AuthSecretRef defines the reference to the auth secret in case the repository is private and needs authentication
AuthSecretRef *v1.LocalObjectReference `json:"authSecretRef,omitempty"`

// +kubebuilder:validation:Optional
// Path points to the function inside the repository. Defaults to "."
// TODO: implement logic
Path string `json:"path,omitempty"`
}

type FunctionSpecRegistry struct {
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Path string `json:"path"`

Insecure bool `json:"insecure,omitempty"`

// AuthSecretRef is the reference to the secret containing the credentials for the registry authentication
AuthSecretRef *v1.LocalObjectReference `json:"authSecretRef,omitempty"`
}

Expand Down
15 changes: 10 additions & 5 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 19 additions & 17 deletions config/crd/bases/functions.dev_functions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ spec:
spec:
description: FunctionSpec defines the desired state of Function.
properties:
autoUpdateMiddleware:
description: |-
AutoUpdateMiddleware defines if the operator should rebuild the function when an outdated middleware is detected.
Defaults to the global operator config.
type: boolean
registry:
properties:
authSecretRef:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
description: AuthSecretRef is the reference to the secret containing
the credentials for the registry authentication
properties:
name:
default: ""
Expand All @@ -69,20 +73,12 @@ spec:
type: string
type: object
x-kubernetes-map-type: atomic
insecure:
type: boolean
path:
minLength: 1
type: string
required:
- path
type: object
source:
repository:
properties:
authSecretRef:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
description: AuthSecretRef defines the reference to the auth secret
in case the repository is private and needs authentication
properties:
name:
default: ""
Expand All @@ -95,13 +91,19 @@ spec:
type: string
type: object
x-kubernetes-map-type: atomic
reference:
branch:
description: Branch of the repository
type: string
path:
description: Path points to the function inside the repository.
Defaults to "."
type: string
repositoryUrl:
url:
description: URL of the Git repository containing the function
minLength: 1
type: string
required:
- repositoryUrl
- url
type: object
type: object
status:
Expand Down
2 changes: 2 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ rules:
- ""
resources:
- persistentvolumeclaims
- pods
- pods/attach
- secrets
- services
verbs:
Expand Down
19 changes: 7 additions & 12 deletions internal/controller/function_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type FunctionReconciler struct {
// +kubebuilder:rbac:groups=functions.dev,resources=functions,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=functions.dev,resources=functions/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=functions.dev,resources=functions/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=secrets;services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=pods;pods/attach;secrets;services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="serving.knative.dev",resources=services;routes,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="eventing.knative.dev",resources=triggers,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -133,19 +133,19 @@ func (r *FunctionReconciler) reconcile(ctx context.Context, function *v1alpha1.F
// prepareSource clones the git repository and retrieves function metadata
func (r *FunctionReconciler) prepareSource(ctx context.Context, function *v1alpha1.Function) (*git.Repository, *funcfn.Function, error) {
branchReference := "main"
if function.Spec.Source.Reference != "" {
branchReference = function.Spec.Source.Reference
if function.Spec.Repository.Branch != "" {
branchReference = function.Spec.Repository.Branch
}

gitAuthSecret := v1.Secret{}
if function.Spec.Source.AuthSecretRef != nil {
if err := r.Get(ctx, types.NamespacedName{Namespace: function.Namespace, Name: function.Spec.Source.AuthSecretRef.Name}, &gitAuthSecret); err != nil {
if function.Spec.Repository.AuthSecretRef != nil {
if err := r.Get(ctx, types.NamespacedName{Namespace: function.Namespace, Name: function.Spec.Repository.AuthSecretRef.Name}, &gitAuthSecret); err != nil {
function.MarkSourceNotReady("AuthSecretNotFound", "Auth secret not found: %s", err.Error())
return nil, nil, err
}
}

repo, err := r.GitManager.CloneRepository(ctx, function.Spec.Source.RepositoryURL, branchReference, gitAuthSecret.Data)
repo, err := r.GitManager.CloneRepository(ctx, function.Spec.Repository.URL, branchReference, gitAuthSecret.Data)
if err != nil {
function.MarkSourceNotReady("GitCloneFailed", "Failed to clone repository: %s", err.Error())
return nil, nil, fmt.Errorf("failed to setup git repository: %w", err)
Expand Down Expand Up @@ -379,12 +379,7 @@ func (r *FunctionReconciler) deploy(ctx context.Context, function *v1alpha1.Func
}

// deploy function
deployOptions := funccli.DeployOptions{
Registry: function.Spec.Registry.Path,
InsecureRegistry: function.Spec.Registry.Insecure,
GitUrl: function.Spec.Source.RepositoryURL,
Builder: "s2i",
}
deployOptions := funccli.DeployOptions{}

if function.Spec.Registry.AuthSecretRef != nil && function.Spec.Registry.AuthSecretRef.Name != "" {
// we have a registry auth secret referenced -> use this for func deploy
Expand Down
22 changes: 6 additions & 16 deletions internal/controller/function_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,9 @@ var _ = Describe("Function Controller", func() {
}

defaultSpec := functionsdevv1alpha1.FunctionSpec{
Source: functionsdevv1alpha1.FunctionSpecSource{
RepositoryURL: "https://github.com/foo/bar",
Reference: "my-branch",
},
Registry: functionsdevv1alpha1.FunctionSpecRegistry{
Path: "quay.io/foo/bar",
Repository: functionsdevv1alpha1.FunctionSpecRepository{
URL: "https://github.com/foo/bar",
Branch: "my-branch",
},
}

Expand Down Expand Up @@ -115,11 +112,7 @@ var _ = Describe("Function Controller", func() {
}, nil)
funcMock.EXPECT().GetLatestMiddlewareVersion(mock.Anything, mock.Anything, mock.Anything).Return("v2.0.0", nil)
funcMock.EXPECT().GetMiddlewareVersion(mock.Anything, functionName, resourceNamespace).Return("v1.0.0", nil)
funcMock.EXPECT().Deploy(mock.Anything, mock.Anything, resourceNamespace, funccli.DeployOptions{
Registry: "quay.io/foo/bar",
GitUrl: "https://github.com/foo/bar",
Builder: "s2i",
}).Return(nil)
funcMock.EXPECT().Deploy(mock.Anything, mock.Anything, resourceNamespace, funccli.DeployOptions{}).Return(nil)

gitMock.EXPECT().CloneRepository(mock.Anything, "https://github.com/foo/bar", "my-branch", mock.Anything).Return(createTmpGitRepo(functions.Function{Name: "func-go"}), nil)
},
Expand All @@ -140,11 +133,8 @@ var _ = Describe("Function Controller", func() {
}),
Entry("should use main as default branch", reconcileTestCase{
spec: functionsdevv1alpha1.FunctionSpec{
Source: functionsdevv1alpha1.FunctionSpecSource{
RepositoryURL: "https://github.com/foo/bar",
},
Registry: functionsdevv1alpha1.FunctionSpecRegistry{
Path: "quay.io/foo/bar",
Repository: functionsdevv1alpha1.FunctionSpecRepository{
URL: "https://github.com/foo/bar",
},
},
configureMocks: func(funcMock *funccli.MockManager, gitMock *git.MockManager) {
Expand Down
13 changes: 0 additions & 13 deletions internal/funccli/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,7 @@ type Manager interface {
}

type DeployOptions struct {
Registry string
InsecureRegistry bool
RegistryAuthFile string

GitUrl string

Builder string
}

var _ Manager = &managerImpl{}
Expand Down Expand Up @@ -216,19 +210,12 @@ func (m *managerImpl) Deploy(ctx context.Context, repoPath string, namespace str
"deploy",
"--remote",
"--namespace", namespace,
"--registry", opts.Registry,
"--git-url", opts.GitUrl,
"--builder", opts.Builder,
}

if opts.RegistryAuthFile != "" {
deployArgs = append(deployArgs, "--registry-authfile", opts.RegistryAuthFile)
}

if opts.InsecureRegistry {
deployArgs = append(deployArgs, "--registry-insecure")
}

out, err := m.Run(ctx, repoPath, deployArgs...)
if err != nil {
return fmt.Errorf("failed to deploy function: %q. %w", out, err)
Expand Down
Loading
Loading