diff --git a/build/Dockerfile b/build/Dockerfile index 300867a..f64b038 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -4,4 +4,4 @@ RUN apk upgrade --update --no-cache USER nobody -ADD build/_output/bin/git-operator /usr/local/bin/git-operator \ No newline at end of file +ADD build/_output/bin/git-operator /usr/local/bin/git-operator diff --git a/build/_output/bin/client-gen b/build/_output/bin/client-gen new file mode 100755 index 0000000..40b5b22 Binary files /dev/null and b/build/_output/bin/client-gen differ diff --git a/build/_output/bin/deepcopy-gen b/build/_output/bin/deepcopy-gen new file mode 100755 index 0000000..506bf46 Binary files /dev/null and b/build/_output/bin/deepcopy-gen differ diff --git a/build/_output/bin/defaulter-gen b/build/_output/bin/defaulter-gen new file mode 100755 index 0000000..aabef4f Binary files /dev/null and b/build/_output/bin/defaulter-gen differ diff --git a/build/_output/bin/informer-gen b/build/_output/bin/informer-gen new file mode 100755 index 0000000..752550d Binary files /dev/null and b/build/_output/bin/informer-gen differ diff --git a/build/_output/bin/lister-gen b/build/_output/bin/lister-gen new file mode 100755 index 0000000..49002e9 Binary files /dev/null and b/build/_output/bin/lister-gen differ diff --git a/build/_output/bin/openapi-gen b/build/_output/bin/openapi-gen new file mode 100755 index 0000000..3fa24a3 Binary files /dev/null and b/build/_output/bin/openapi-gen differ diff --git a/build/bin/entrypoint b/build/bin/entrypoint new file mode 100755 index 0000000..091cca2 --- /dev/null +++ b/build/bin/entrypoint @@ -0,0 +1,12 @@ +#!/bin/sh -e + +# This is documented here: +# https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines + +if ! whoami &>/dev/null; then + if [ -w /etc/passwd ]; then + echo "${USER_NAME:-git-service2}:x:$(id -u):$(id -g):${USER_NAME:-git-service2} user:${HOME}:/sbin/nologin" >> /etc/passwd + fi +fi + +exec ${OPERATOR} $@ diff --git a/build/bin/user_setup b/build/bin/user_setup new file mode 100755 index 0000000..1e36064 --- /dev/null +++ b/build/bin/user_setup @@ -0,0 +1,13 @@ +#!/bin/sh +set -x + +# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be) +mkdir -p ${HOME} +chown ${USER_UID}:0 ${HOME} +chmod ug+rwx ${HOME} + +# runtime user will need to be able to self-insert in /etc/passwd +chmod g+rw /etc/passwd + +# no need for this script to remain in the image after running +rm $0 diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 59168e4..4ba4fe6 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -19,6 +19,7 @@ import ( "github.com/operator-framework/operator-sdk/pkg/metrics" sdkVersion "github.com/operator-framework/operator-sdk/version" "github.com/spf13/pflag" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" @@ -77,7 +78,9 @@ func main() { ctx := context.TODO() // Become the leader before proceeding + err = leader.Become(ctx, "git-operator-lock") + if err != nil { log.Error(err, "") os.Exit(1) diff --git a/deploy/role.yaml b/deploy/role.yaml index 98f7bd8..7d7e051 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -50,5 +50,6 @@ rules: - devconsole.openshift.io resources: - '*' + - gitpushes verbs: - '*' diff --git a/deploy/service_account.yaml b/deploy/service_account.yaml index a0e7472..2224630 100644 --- a/deploy/service_account.yaml +++ b/deploy/service_account.yaml @@ -2,3 +2,4 @@ apiVersion: v1 kind: ServiceAccount metadata: name: git-operator + diff --git a/pkg/apis/devconsole/v1alpha1/gitpush_types.go b/pkg/apis/devconsole/v1alpha1/gitpush_types.go new file mode 100644 index 0000000..1689aa3 --- /dev/null +++ b/pkg/apis/devconsole/v1alpha1/gitpush_types.go @@ -0,0 +1,49 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// GitPushSpec defines the desired state of GitPush +// +k8s:openapi-gen=true +type GitPushSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html +} + +// GitPushStatus defines the observed state of GitPush +// +k8s:openapi-gen=true +type GitPushStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// GitPush is the Schema for the gitpushes API +// +k8s:openapi-gen=true +type GitPush struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GitPushSpec `json:"spec,omitempty"` + Status GitPushStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// GitPushList contains a list of GitPush +type GitPushList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GitPush `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GitPush{}, &GitPushList{}) +} diff --git a/pkg/apis/devconsole/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/devconsole/v1alpha1/zz_generated.deepcopy.go index 1b00a4c..8d93158 100644 --- a/pkg/apis/devconsole/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/devconsole/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,99 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitPush) DeepCopyInto(out *GitPush) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitPush. +func (in *GitPush) DeepCopy() *GitPush { + if in == nil { + return nil + } + out := new(GitPush) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitPush) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitPushList) DeepCopyInto(out *GitPushList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GitPush, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitPushList. +func (in *GitPushList) DeepCopy() *GitPushList { + if in == nil { + return nil + } + out := new(GitPushList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitPushList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitPushSpec) DeepCopyInto(out *GitPushSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitPushSpec. +func (in *GitPushSpec) DeepCopy() *GitPushSpec { + if in == nil { + return nil + } + out := new(GitPushSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitPushStatus) DeepCopyInto(out *GitPushStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitPushStatus. +func (in *GitPushStatus) DeepCopy() *GitPushStatus { + if in == nil { + return nil + } + out := new(GitPushStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitSource) DeepCopyInto(out *GitSource) { *out = *in diff --git a/pkg/apis/devconsole/v1alpha1/zz_generated.openapi.go b/pkg/apis/devconsole/v1alpha1/zz_generated.openapi.go index 86d5dbf..0b22cad 100644 --- a/pkg/apis/devconsole/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/devconsole/v1alpha1/zz_generated.openapi.go @@ -13,12 +13,82 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ + "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPush": schema_pkg_apis_devconsole_v1alpha1_GitPush(ref), + "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPushSpec": schema_pkg_apis_devconsole_v1alpha1_GitPushSpec(ref), + "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPushStatus": schema_pkg_apis_devconsole_v1alpha1_GitPushStatus(ref), "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitSource": schema_pkg_apis_devconsole_v1alpha1_GitSource(ref), "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitSourceSpec": schema_pkg_apis_devconsole_v1alpha1_GitSourceSpec(ref), "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitSourceStatus": schema_pkg_apis_devconsole_v1alpha1_GitSourceStatus(ref), } } +func schema_pkg_apis_devconsole_v1alpha1_GitPush(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GitPush is the Schema for the gitpushes API", + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPushSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPushStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPushSpec", "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.GitPushStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_devconsole_v1alpha1_GitPushSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GitPushSpec defines the desired state of GitPush", + Properties: map[string]spec.Schema{}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_devconsole_v1alpha1_GitPushStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GitPushStatus defines the observed state of GitPush", + Properties: map[string]spec.Schema{}, + }, + }, + Dependencies: []string{}, + } +} + func schema_pkg_apis_devconsole_v1alpha1_GitSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -67,10 +137,68 @@ func schema_pkg_apis_devconsole_v1alpha1_GitSourceSpec(ref common.ReferenceCallb Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "GitSourceSpec defines the desired state of GitSource", - Properties: map[string]spec.Schema{}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL of the git repo", + Type: []string{"string"}, + Format: "", + }, + }, + "ref": { + SchemaProps: spec.SchemaProps{ + Description: "Ref is a git reference. Optional. \"master\" is used by default.", + Type: []string{"string"}, + Format: "", + }, + }, + "contextDir": { + SchemaProps: spec.SchemaProps{ + Description: "ContextDir is a path to subfolder in the repo. Optional.", + Type: []string{"string"}, + Format: "", + }, + }, + "httpProxy": { + SchemaProps: spec.SchemaProps{ + Description: "HttpProxy is optional.", + Type: []string{"string"}, + Format: "", + }, + }, + "httpsProxy": { + SchemaProps: spec.SchemaProps{ + Description: "HttpsProxy is optional.", + Type: []string{"string"}, + Format: "", + }, + }, + "noProxy": { + SchemaProps: spec.SchemaProps{ + Description: "NoProxy can be used to specify domains for which no proxying should be performed. Optional.", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "SecretRef refers to the secret that contains credentials to access the git repo. Optional.", + Ref: ref("github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.SecretRef"), + }, + }, + "flavor": { + SchemaProps: spec.SchemaProps{ + Description: "Flavor of the git provider like github, gitlab, bitbucket, generic, etc. Optional.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"url"}, }, }, - Dependencies: []string{}, + Dependencies: []string{ + "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1.SecretRef"}, } } diff --git a/pkg/controller/add_gitpush.go b/pkg/controller/add_gitpush.go new file mode 100644 index 0000000..f8adca7 --- /dev/null +++ b/pkg/controller/add_gitpush.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/redhat-developer/git-service/pkg/controller/gitpush" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, gitpush.Add) +} diff --git a/pkg/controller/gitpush/gitpush_controller.go b/pkg/controller/gitpush/gitpush_controller.go new file mode 100644 index 0000000..89e7ae0 --- /dev/null +++ b/pkg/controller/gitpush/gitpush_controller.go @@ -0,0 +1,153 @@ +package gitpush + +import ( + "context" + + devconsolev1alpha1 "github.com/redhat-developer/git-service/pkg/apis/devconsole/v1alpha1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log.WithName("controller_gitpush") + +/** +* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller +* business logic. Delete these comments after modifying this file.* + */ + +// Add creates a new GitPush Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileGitPush{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("gitpush-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource GitPush + err = c.Watch(&source.Kind{Type: &devconsolev1alpha1.GitPush{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // TODO(user): Modify this to be the types you create that are owned by the primary resource + // Watch for changes to secondary resource Pods and requeue the owner GitPush + err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &devconsolev1alpha1.GitPush{}, + }) + if err != nil { + return err + } + + return nil +} + +var _ reconcile.Reconciler = &ReconcileGitPush{} + +// ReconcileGitPush reconciles a GitPush object +type ReconcileGitPush struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme +} + +// Reconcile reads that state of the cluster for a GitPush object and makes changes based on the state read +// and what is in the GitPush.Spec +// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates +// a Pod as an example +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileGitPush) Reconcile(request reconcile.Request) (reconcile.Result, error) { + reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + reqLogger.Info("Reconciling GitPush") + + // Fetch the GitPush instance + instance := &devconsolev1alpha1.GitPush{} + err := r.client.Get(context.TODO(), request.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // Define a new Pod object + pod := newPodForCR(instance) + + // Set GitPush instance as the owner and controller + if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil { + return reconcile.Result{}, err + } + + // Check if this Pod already exists + found := &corev1.Pod{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, found) + if err != nil && errors.IsNotFound(err) { + reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name) + err = r.client.Create(context.TODO(), pod) + if err != nil { + return reconcile.Result{}, err + } + + // Pod created successfully - don't requeue + return reconcile.Result{}, nil + } else if err != nil { + return reconcile.Result{}, err + } + + // Pod already exists - don't requeue + reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name) + return reconcile.Result{}, nil +} + +// newPodForCR returns a busybox pod with the same name/namespace as the cr +func newPodForCR(cr *devconsolev1alpha1.GitPush) *corev1.Pod { + labels := map[string]string{ + "app": cr.Name, + } + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-pod", + Namespace: cr.Namespace, + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + Command: []string{"sleep", "3600"}, + }, + }, + }, + } +}