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
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
go-version-file: go.mod

- name: Run linter
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v9
with:
version: v2.1.6
version: v2.12.2
15 changes: 15 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ linters:
- unparam
- unused
settings:
goconst:
ignore-tests: true
ignore-string-values:
- "string"
- "integer"
- "name"
- "object"
- "boolean"
- "array"
- "json-f64"
- "tid"
- "proplet_id"
- "propeller.propeller.absmach.eu"
- "propeller.absmach.fr/proplet"
- "broadcast"
revive:
rules:
- name: comment-spacings
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
esac

.PHONY: test-e2e
test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
test-e2e: setup-test-e2e manifests generate fmt vet ## Run e2e tests (Kind). Creates a temporary Kind cluster.
KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v
$(MAKE) cleanup-test-e2e

Expand Down Expand Up @@ -111,7 +111,7 @@ build: manifests generate fmt vet ## Build manager binary.

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go
go run ./cmd/main.go $(ARGS)

# If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
Expand Down
6 changes: 3 additions & 3 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# and allow the plugins properly work.
# More info: https://book.kubebuilder.io/reference/project-config.html
cliVersion: 4.7.1
domain: propeller.abstractmachines.fr
domain: propeller.absmach.eu
layout:
- go.kubebuilder.io/v4
projectName: propeller-k8s-operator
Expand All @@ -13,7 +13,7 @@ resources:
crdVersion: v1
namespaced: true
controller: true
domain: propeller.abstractmachines.fr
domain: propeller.absmach.eu
group: propeller
kind: Task
path: github.com/absmach/propeller/api/v1
Expand All @@ -22,7 +22,7 @@ resources:
crdVersion: v1
namespaced: true
controller: true
domain: propeller.abstractmachines.fr
domain: propeller.absmach.eu
group: propeller
kind: Proplet
path: github.com/absmach/propeller/api/v1
Expand Down
474 changes: 149 additions & 325 deletions README.md

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions api/v1/federatedjob_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ type FederatedJob struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`

Spec FederatedJobSpec `json:"spec"`
Status FederatedJobStatus `json:"status"`
Spec FederatedJobSpec `json:"spec"`
// +optional
Status FederatedJobStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
4 changes: 2 additions & 2 deletions api/v1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

// Package v1 contains API Schema definitions for the propeller v1 API group.
// +kubebuilder:object:generate=true
// +groupName=propeller.propeller.abstractmachines.fr
// +groupName=propeller.propeller.absmach.eu
package v1

import (
Expand All @@ -26,7 +26,7 @@ import (

var (
// GroupVersion is group version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "propeller.propeller.abstractmachines.fr", Version: "v1"}
GroupVersion = schema.GroupVersion{Group: "propeller.propeller.absmach.eu", Version: "v1"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
Expand Down
16 changes: 8 additions & 8 deletions api/v1/proplet_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,20 @@ type K8sPropletSpec struct {
type ConnectionConfig struct {
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
DomainID string `json:"domainId"`
TenantID string `json:"tenantId"`
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
ChannelID string `json:"channelId"`
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
ClientID string `json:"clientId"`
// ClientKey is the MQTT client secret. Exactly one of ClientKey or
// ClientKeySecretRef must be set.
ClientKey string `json:"clientKey,omitempty"`
// ClientKeySecretRef references a Kubernetes Secret whose key holds the
// MQTT client secret. Exactly one of ClientKey or ClientKeySecretRef must
EntityID string `json:"entityId"`
// APIKey is the MQTT API key. Exactly one of APIKey or
// APIKeySecretRef must be set.
APIKey string `json:"apiKey,omitempty"`
// APIKeySecretRef references a Kubernetes Secret whose key holds the
// MQTT API key. Exactly one of APIKey or APIKeySecretRef must
// be set.
ClientKeySecretRef *corev1.SecretKeySelector `json:"clientKeySecretRef,omitempty"`
APIKeySecretRef *corev1.SecretKeySelector `json:"apiKeySecretRef,omitempty"`
// +kubebuilder:validation:Required
MQTTAddress string `json:"mqttAddress"`
// +kubebuilder:default="30s"
Expand Down
18 changes: 9 additions & 9 deletions api/v1/proplet_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func SetupPropletWebhookWithManager(mgr ctrl.Manager) error {
Complete()
}

// +kubebuilder:webhook:path=/validate-propeller-propeller-abstractmachines-fr-v1-proplet,mutating=false,failurePolicy=fail,sideEffects=None,groups=propeller.propeller.abstractmachines.fr,resources=proplets,verbs=create;update,versions=v1,name=vproplet-v1.kb.io,admissionReviewVersions=v1
// +kubebuilder:webhook:path=/validate-propeller-propeller-abstractmachines-fr-v1-proplet,mutating=false,failurePolicy=fail,sideEffects=None,groups=propeller.propeller.absmach.eu,resources=proplets,verbs=create;update,versions=v1,name=vproplet-v1.kb.io,admissionReviewVersions=v1

// PropletCustomValidator validates Proplet resources.
type PropletCustomValidator struct{}
Expand Down Expand Up @@ -76,28 +76,28 @@ func (v *PropletCustomValidator) validateProplet(proplet *Proplet) error {
var allErrs field.ErrorList

cfg := proplet.Spec.ConnectionConfig
hasPlain := cfg.ClientKey != ""
hasRef := cfg.ClientKeySecretRef != nil
hasPlain := cfg.APIKey != ""
hasRef := cfg.APIKeySecretRef != nil

if !hasPlain && !hasRef {
allErrs = append(allErrs, field.Required(
field.NewPath("spec", "connectionConfig", "clientKey"),
"exactly one of clientKey or clientKeySecretRef must be set",
field.NewPath("spec", "connectionConfig", "apiKey"),
"exactly one of apiKey or apiKeySecretRef must be set",
))
}
if hasPlain && hasRef {
allErrs = append(allErrs, field.Invalid(
field.NewPath("spec", "connectionConfig", "clientKey"),
cfg.ClientKey,
"clientKey and clientKeySecretRef are mutually exclusive; set exactly one",
field.NewPath("spec", "connectionConfig", "apiKey"),
cfg.APIKey,
"apiKey and apiKeySecretRef are mutually exclusive; set exactly one",
))
}

if len(allErrs) == 0 {
return nil
}
return apierrors.NewInvalid(
schema.GroupKind{Group: "propeller.propeller.abstractmachines.fr", Kind: "Proplet"},
schema.GroupKind{Group: "propeller.propeller.absmach.eu", Kind: "Proplet"},
proplet.Name,
allErrs,
)
Expand Down
6 changes: 3 additions & 3 deletions api/v1/task_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func SetupTaskWebhookWithManager(mgr ctrl.Manager) error {
Complete()
}

// +kubebuilder:webhook:path=/mutate-propeller-propeller-abstractmachines-fr-v1-task,mutating=true,failurePolicy=fail,sideEffects=None,groups=propeller.propeller.abstractmachines.fr,resources=tasks,verbs=create;update,versions=v1,name=mtask-v1.kb.io,admissionReviewVersions=v1
// +kubebuilder:webhook:path=/mutate-propeller-propeller-abstractmachines-fr-v1-task,mutating=true,failurePolicy=fail,sideEffects=None,groups=propeller.propeller.absmach.eu,resources=tasks,verbs=create;update,versions=v1,name=mtask-v1.kb.io,admissionReviewVersions=v1

// TaskCustomDefaulter applies defaults to Task resources.
type TaskCustomDefaulter struct{}
Expand All @@ -69,7 +69,7 @@ func (d *TaskCustomDefaulter) Default(_ context.Context, obj runtime.Object) err
return nil
}

// +kubebuilder:webhook:path=/validate-propeller-propeller-abstractmachines-fr-v1-task,mutating=false,failurePolicy=fail,sideEffects=None,groups=propeller.propeller.abstractmachines.fr,resources=tasks,verbs=create;update,versions=v1,name=vtask-v1.kb.io,admissionReviewVersions=v1
// +kubebuilder:webhook:path=/validate-propeller-propeller-abstractmachines-fr-v1-task,mutating=false,failurePolicy=fail,sideEffects=None,groups=propeller.propeller.absmach.eu,resources=tasks,verbs=create;update,versions=v1,name=vtask-v1.kb.io,admissionReviewVersions=v1

// TaskCustomValidator validates Task resources.
type TaskCustomValidator struct{}
Expand Down Expand Up @@ -147,7 +147,7 @@ func (v *TaskCustomValidator) validateTask(task *Task) error {
}

return apierrors.NewInvalid(
schema.GroupKind{Group: "propeller.propeller.abstractmachines.fr", Kind: "Task"},
schema.GroupKind{Group: "propeller.propeller.absmach.eu", Kind: "Task"},
task.Name,
allErrs,
)
Expand Down
5 changes: 3 additions & 2 deletions api/v1/traininground_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ type TrainingRound struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`

Spec TrainingRoundSpec `json:"spec"`
Status TrainingRoundStatus `json:"status"`
Spec TrainingRoundSpec `json:"spec"`
// +optional
Status TrainingRoundStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
4 changes: 2 additions & 2 deletions api/v1/zz_generated.deepcopy.go

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

95 changes: 84 additions & 11 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ limitations under the License.
package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"flag"
"math/big"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -50,6 +56,63 @@ var (
setupLog = ctrl.Log.WithName("setup")
)

func ensureCerts(certDir, certName, certKey string) error {
certFile := filepath.Join(certDir, certName)
keyFile := filepath.Join(certDir, certKey)
if _, err := os.Stat(certFile); err == nil {
if _, err := os.Stat(keyFile); err == nil {
return nil
}
}

setupLog.Info("Generating self-signed webhook TLS certificates", "dir", certDir)

key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}

template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "propeller-webhook"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}

certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return err
}

if err := os.MkdirAll(certDir, 0755); err != nil {
return err
}

certOut, err := os.Create(certFile)
if err != nil {
return err
}
defer func() { _ = certOut.Close() }()
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
return err
}

keyOut, err := os.Create(keyFile)
if err != nil {
return err
}
defer func() { _ = keyOut.Close() }()
block := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
if err := pem.Encode(keyOut, &block); err != nil {
return err
}

setupLog.Info("Self-signed webhook TLS certificates generated", "cert", certFile, "key", keyFile)
return nil
}

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))

Expand All @@ -72,13 +135,13 @@ func main() {
var mqttAddress string
var mqttQoS uint
var mqttTimeout time.Duration
var domainID string
var tenantID string
var channelID string
var clientID string
var clientKey string
var entityID string
var apiKey string
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8181", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
Expand All @@ -100,10 +163,10 @@ func main() {
flag.StringVar(&mqttAddress, "mqtt-address", "", "The address of the MQTT broker.")
flag.UintVar(&mqttQoS, "mqtt-qos", 0, "The QoS level of the MQTT messages.")
flag.DurationVar(&mqttTimeout, "mqtt-timeout", 30*time.Second, "The timeout for MQTT operations.")
flag.StringVar(&domainID, "domain-id", "", "The domain ID.")
flag.StringVar(&tenantID, "tenant-id", "", "The tenant ID.")
flag.StringVar(&channelID, "channel-id", "", "The channel ID.")
flag.StringVar(&clientID, "client-id", "", "The client ID.")
flag.StringVar(&clientKey, "client-key", "", "The client key.")
flag.StringVar(&entityID, "entity-id", "", "The entity ID.")
flag.StringVar(&apiKey, "api-key", "", "The API key.")
opts := zap.Options{
Development: true,
}
Expand Down Expand Up @@ -152,7 +215,17 @@ func main() {
})
}

webhookCertDir := webhookCertPath
if webhookCertDir == "" {
webhookCertDir = filepath.Join(os.TempDir(), "k8s-webhook-server", "serving-certs")
}
if err := ensureCerts(webhookCertDir, webhookCertName, webhookCertKey); err != nil {
setupLog.Error(err, "Failed to ensure webhook certificates")
os.Exit(1)
}

webhookServer := webhook.NewServer(webhook.Options{
CertDir: webhookCertDir,
TLSOpts: webhookTLSOpts,
})

Expand Down Expand Up @@ -207,7 +280,7 @@ func main() {
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "fa27fa49.propeller.abstractmachines.fr",
LeaderElectionID: "fa27fa49.propeller.absmach.eu",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
Expand All @@ -228,7 +301,7 @@ func main() {
var mqttPubSub mqtt.PubSub
if mqttAddress != "" {
mqttPubSub, err = mqtt.NewPubSub(
mqttAddress, byte(mqttQoS), "propeller-controller", clientID, clientKey, domainID, channelID, mqttTimeout,
mqttAddress, byte(mqttQoS), "propeller-controller", entityID, apiKey, tenantID, channelID, mqttTimeout,
)
if err != nil {
setupLog.Error(err, "failed to initialize mqtt pubsub")
Expand All @@ -249,15 +322,15 @@ func main() {
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Namespace: watchNamespace,
}).SetupWithManager(domainID, channelID, mgr, livelinessInterval, lastSeenThreshold, mqttPubSub); err != nil {
}).SetupWithManager(tenantID, channelID, mgr, livelinessInterval, lastSeenThreshold, mqttPubSub); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Proplet")
os.Exit(1)
}
if err := (&controller.TaskReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Namespace: watchNamespace,
}).SetupWithManager(domainID, channelID, mgr, mqttPubSub, scheduler.NewRoundRobin()); err != nil {
}).SetupWithManager(tenantID, channelID, mgr, mqttPubSub, scheduler.NewRoundRobin()); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Task")
os.Exit(1)
}
Expand Down
Loading
Loading