diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 861a103b..190fcdc3 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -16,6 +16,42 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - "" resources: @@ -158,6 +194,30 @@ rules: - get - patch - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - getporter.org resources: diff --git a/controllers/types.go b/controllers/types.go index fbaa75e1..65329059 100644 --- a/controllers/types.go +++ b/controllers/types.go @@ -5,6 +5,17 @@ import ( installationv1 "get.porter.sh/porter/gen/proto/go/porterapis/installation/v1alpha1" "google.golang.org/grpc" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" +) + +const ( + PorterGRPCName = "porter-grpc-service" + PorterDeploymentImage = "ghcr.io/getporter/server:v1.1.0" ) type PorterClient interface { @@ -15,3 +26,118 @@ type PorterClient interface { type ClientConn interface { Close() error } + +var GrpcDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: PorterGRPCName, + Namespace: operatorNamespace, + Labels: map[string]string{ + "app": "porter-grpc-service", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "porter-grpc-service", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "porter-grpc-service", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "porter-grpc-service", + Image: PorterDeploymentImage, + Ports: []corev1.ContainerPort{ + { + Name: "grpc", + ContainerPort: 3001, + }, + }, + Args: []string{"api-server", "run"}, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/porter-config", + Name: "porter-grpc-service-config-volume", + }, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2000m"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("32Mi"), + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "porter-grpc-service-config-volume", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "porter-grpc-service-config", + }, + Items: []corev1.KeyToPath{ + { + Key: "config", + Path: "config.yaml", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var GrpcService = &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: PorterGRPCName, + Namespace: operatorNamespace, + Labels: map[string]string{ + "app": "porter-grpc-service", + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(3001), + Port: int32(3001), + }, + }, + Selector: map[string]string{"app": "porter-grpc-service"}, + Type: corev1.ServiceTypeClusterIP, + }, +} + +var GrpcConfigMap = &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "porter-grpc-service-config", + Namespace: operatorNamespace, + }, + Data: map[string]string{ + "config": ConfigmMapConfig, + }, +} + +var ConfigmMapConfig = ` +default-secrets-plugin: "kubernetes.secrets" +default-storage: "mongodb" +storage: + - name: "mongodb" + plugin: "mongodb" + config: + url: "mongodb://root:demopasswd@porter-operator-mongodb.demo.svc.cluster.local" +` diff --git a/main.go b/main.go index c918722e..b066b112 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "os" @@ -9,10 +10,13 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" + "golang.org/x/sync/errgroup" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/server" @@ -34,14 +38,17 @@ func init() { } func main() { + ctx := context.Background() var metricsAddr string var enableLeaderElection bool var probeAddr string + var createGrpc bool flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "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.") + flag.BoolVar(&createGrpc, "create-grpc", true, "create grpc deployment for use in operator") opts := zap.Options{ Development: true, } @@ -116,6 +123,53 @@ func main() { os.Exit(1) } + if createGrpc { + g, ctx := errgroup.WithContext(ctx) + g.Go(func() error { + k8sClient := mgr.GetClient() + err := k8sClient.Create(ctx, controllers.GrpcConfigMap, &client.CreateOptions{}) + if err != nil { + if apierrors.IsAlreadyExists(err) { + setupLog.Info("configmap already exists, not creating") + return nil + } + setupLog.Info("error creating configmap, %s", err.Error()) + } + + err = k8sClient.Create(ctx, controllers.GrpcDeployment, &client.CreateOptions{}) + if err != nil { + if apierrors.IsAlreadyExists(err) { + setupLog.Info("deployment already exists, not creating") + return nil + } + setupLog.Info("error creating deployment, %s", err.Error()) + } + // NOTE: Don't crash, just don't deploy if Get fails for any other reason than not found + return nil + }) + + g.Go(func() error { + k8sClient := mgr.GetClient() + err := k8sClient.Create(ctx, controllers.GrpcService, &client.CreateOptions{}) + if err != nil { + if apierrors.IsAlreadyExists(err) { + setupLog.Info("service already exists, not creating") + return nil + } + setupLog.Info("error creating service, %s", err.Error()) + } + return nil + }) + + go func() { + if err := g.Wait(); err != nil { + setupLog.Error(err, "error with async operation of creating grpc deployment") + os.Exit(1) + } + setupLog.Info("grpc server has been created") + }() + } + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager")