feature: workspaceitem (#57)
* init commit for workspace item * add support for workspace item
This commit is contained in:
Родитель
0d2b10e078
Коммит
0f722a710f
3
PROJECT
3
PROJECT
|
@ -17,3 +17,6 @@ resources:
|
|||
- group: databricks
|
||||
version: v1
|
||||
kind: DbfsBlock
|
||||
- group: databricks
|
||||
version: v1
|
||||
kind: WorkspaceItem
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Copyright 2019 microsoft.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
dbmodels "github.com/xinsnake/databricks-sdk-golang/azure/models"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// WorkspaceItemSpec defines the desired state of WorkspaceItem
|
||||
type WorkspaceItemSpec struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Language dbmodels.Language `json:"language,omitempty"`
|
||||
Format dbmodels.ExportFormat `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// WorkspaceItemStatus defines the observed state of WorkspaceItem
|
||||
type WorkspaceItemStatus struct {
|
||||
ObjectInfo *dbmodels.ObjectInfo `json:"object_info,omitempty"`
|
||||
ObjectHash string `json:"object_hash,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// WorkspaceItem is the Schema for the workspaceitems API
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",priority=0
|
||||
// +kubebuilder:printcolumn:name="SHA1SUM",type="string",JSONPath=".status.object_hash",priority=0
|
||||
// +kubebuilder:printcolumn:name="Language",type="string",JSONPath=".status.object_info.language",priority=0
|
||||
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".status.object_info.object_type",priority=1
|
||||
// +kubebuilder:printcolumn:name="Path",type="string",JSONPath=".status.object_info.path",priority=1
|
||||
type WorkspaceItem struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec *WorkspaceItemSpec `json:"spec,omitempty"`
|
||||
Status *WorkspaceItemStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (wi *WorkspaceItem) IsBeingDeleted() bool {
|
||||
return !wi.ObjectMeta.DeletionTimestamp.IsZero()
|
||||
}
|
||||
|
||||
func (wi *WorkspaceItem) IsSubmitted() bool {
|
||||
if wi.Status == nil || wi.Status.ObjectInfo == nil || wi.Status.ObjectInfo.Path == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsUpToDate tells you whether the data is up-to-date with the status
|
||||
func (wi *WorkspaceItem) IsUpToDate() bool {
|
||||
if wi.Status == nil {
|
||||
return false
|
||||
}
|
||||
h := wi.GetHash()
|
||||
return h == wi.Status.ObjectHash
|
||||
}
|
||||
|
||||
// GetHash returns the sha1 hash of the decoded data attribute
|
||||
func (wi *WorkspaceItem) GetHash() string {
|
||||
data, err := base64.StdEncoding.DecodeString(wi.Spec.Content)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
h := sha1.New()
|
||||
h.Write(data)
|
||||
bs := h.Sum(nil)
|
||||
return fmt.Sprintf("%x", bs)
|
||||
}
|
||||
|
||||
const WorkspaceItemFinalizerName = "workspaceitem.finalizers.databricks.microsoft.com"
|
||||
|
||||
func (wi *WorkspaceItem) HasFinalizer(finalizerName string) bool {
|
||||
return containsString(wi.ObjectMeta.Finalizers, finalizerName)
|
||||
}
|
||||
|
||||
func (wi *WorkspaceItem) AddFinalizer(finalizerName string) {
|
||||
wi.ObjectMeta.Finalizers = append(wi.ObjectMeta.Finalizers, finalizerName)
|
||||
}
|
||||
|
||||
func (wi *WorkspaceItem) RemoveFinalizer(finalizerName string) {
|
||||
wi.ObjectMeta.Finalizers = removeString(wi.ObjectMeta.Finalizers, finalizerName)
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// WorkspaceItemList contains a list of WorkspaceItem
|
||||
type WorkspaceItemList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []WorkspaceItem `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&WorkspaceItem{}, &WorkspaceItemList{})
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2019 microsoft.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
dbmodels "github.com/xinsnake/databricks-sdk-golang/azure/models"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// These tests are written in BDD-style using Ginkgo framework. Refer to
|
||||
// http://onsi.github.io/ginkgo to learn more.
|
||||
|
||||
var _ = Describe("WorkspaceItem", func() {
|
||||
var (
|
||||
key types.NamespacedName
|
||||
created, fetched *WorkspaceItem
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
// Add any setup steps that needs to be executed before each test
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Add any teardown steps that needs to be executed after each test
|
||||
})
|
||||
|
||||
// Add Tests for OpenAPI validation (or additonal CRD features) specified in
|
||||
// your API definition.
|
||||
// Avoid adding tests for vanilla CRUD operations because they would
|
||||
// test Kubernetes API server, which isn't the goal here.
|
||||
Context("Create API", func() {
|
||||
|
||||
It("should create an object successfully", func() {
|
||||
|
||||
key = types.NamespacedName{
|
||||
Name: "foo",
|
||||
Namespace: "default",
|
||||
}
|
||||
created = &WorkspaceItem{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "default",
|
||||
}}
|
||||
|
||||
By("creating an API obj")
|
||||
Expect(k8sClient.Create(context.TODO(), created)).To(Succeed())
|
||||
|
||||
fetched = &WorkspaceItem{}
|
||||
Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())
|
||||
Expect(fetched).To(Equal(created))
|
||||
|
||||
By("deleting the created object")
|
||||
Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed())
|
||||
Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed())
|
||||
})
|
||||
|
||||
It("should correctly handle isSubmitted", func() {
|
||||
wiItem := &WorkspaceItem{
|
||||
Status: &WorkspaceItemStatus{
|
||||
ObjectInfo: &dbmodels.ObjectInfo{
|
||||
Path: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(wiItem.IsSubmitted()).To(BeFalse())
|
||||
|
||||
wiItem2 := &WorkspaceItem{
|
||||
Status: &WorkspaceItemStatus{
|
||||
ObjectInfo: &dbmodels.ObjectInfo{
|
||||
Path: "/test-path",
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(wiItem2.IsSubmitted()).To(BeTrue())
|
||||
|
||||
wiItem3 := &WorkspaceItem{
|
||||
Status: &WorkspaceItemStatus{
|
||||
ObjectInfo: nil,
|
||||
},
|
||||
}
|
||||
Expect(wiItem3.IsSubmitted()).To(BeFalse())
|
||||
})
|
||||
|
||||
It("should correctly handle finalizers", func() {
|
||||
wiItem := &WorkspaceItem{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
DeletionTimestamp: &metav1.Time{
|
||||
Time: time.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(wiItem.IsBeingDeleted()).To(BeTrue())
|
||||
|
||||
wiItem.AddFinalizer(WorkspaceItemFinalizerName)
|
||||
Expect(len(wiItem.GetFinalizers())).To(Equal(1))
|
||||
Expect(wiItem.HasFinalizer(WorkspaceItemFinalizerName)).To(BeTrue())
|
||||
|
||||
wiItem.RemoveFinalizer(WorkspaceItemFinalizerName)
|
||||
Expect(len(wiItem.GetFinalizers())).To(Equal(0))
|
||||
Expect(wiItem.HasFinalizer(WorkspaceItemFinalizerName)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("should correctly handle file hash", func() {
|
||||
wiItem := &WorkspaceItem{
|
||||
Spec: &WorkspaceItemSpec{
|
||||
Content: "dGVzdA==",
|
||||
},
|
||||
}
|
||||
|
||||
Expect(wiItem.GetHash()).To(Equal("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"))
|
||||
Expect(wiItem.IsUpToDate()).To(BeFalse())
|
||||
|
||||
wiItem.Status = &WorkspaceItemStatus{
|
||||
ObjectHash: "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
|
||||
}
|
||||
Expect(wiItem.IsUpToDate()).To(BeTrue())
|
||||
|
||||
wiItemError := &WorkspaceItem{
|
||||
Spec: &WorkspaceItemSpec{
|
||||
Content: "invalid_base64",
|
||||
},
|
||||
}
|
||||
Expect(wiItemError.GetHash()).To(Equal(""))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -651,3 +651,105 @@ func (in *SecretScopeValueFrom) DeepCopy() *SecretScopeValueFrom {
|
|||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkspaceItem) DeepCopyInto(out *WorkspaceItem) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Spec != nil {
|
||||
in, out := &in.Spec, &out.Spec
|
||||
*out = new(WorkspaceItemSpec)
|
||||
**out = **in
|
||||
}
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(WorkspaceItemStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceItem.
|
||||
func (in *WorkspaceItem) DeepCopy() *WorkspaceItem {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkspaceItem)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WorkspaceItem) 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 *WorkspaceItemList) DeepCopyInto(out *WorkspaceItemList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]WorkspaceItem, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceItemList.
|
||||
func (in *WorkspaceItemList) DeepCopy() *WorkspaceItemList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkspaceItemList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WorkspaceItemList) 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 *WorkspaceItemSpec) DeepCopyInto(out *WorkspaceItemSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceItemSpec.
|
||||
func (in *WorkspaceItemSpec) DeepCopy() *WorkspaceItemSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkspaceItemSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkspaceItemStatus) DeepCopyInto(out *WorkspaceItemStatus) {
|
||||
*out = *in
|
||||
if in.ObjectInfo != nil {
|
||||
in, out := &in.ObjectInfo, &out.ObjectInfo
|
||||
*out = new(models.ObjectInfo)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceItemStatus.
|
||||
func (in *WorkspaceItemStatus) DeepCopy() *WorkspaceItemStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkspaceItemStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: workspaceitems.databricks.microsoft.com
|
||||
spec:
|
||||
additionalPrinterColumns:
|
||||
- JSONPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
- JSONPath: .status.object_hash
|
||||
name: SHA1SUM
|
||||
type: string
|
||||
- JSONPath: .status.object_info.language
|
||||
name: Language
|
||||
type: string
|
||||
- JSONPath: .status.object_info.object_type
|
||||
name: Type
|
||||
priority: 1
|
||||
type: string
|
||||
- JSONPath: .status.object_info.path
|
||||
name: Path
|
||||
priority: 1
|
||||
type: string
|
||||
group: databricks.microsoft.com
|
||||
names:
|
||||
kind: WorkspaceItem
|
||||
plural: workspaceitems
|
||||
scope: ""
|
||||
subresources: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: WorkspaceItem is the Schema for the workspaceitems API
|
||||
properties:
|
||||
apiVersion:
|
||||
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
|
||||
kind:
|
||||
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
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: WorkspaceItemSpec defines the desired state of WorkspaceItem
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
format:
|
||||
type: string
|
||||
language:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: WorkspaceItemStatus defines the observed state of WorkspaceItem
|
||||
properties:
|
||||
object_hash:
|
||||
type: string
|
||||
object_info:
|
||||
properties:
|
||||
language:
|
||||
type: string
|
||||
object_type:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
|
@ -7,6 +7,7 @@ resources:
|
|||
- bases/databricks.microsoft.com_runs.yaml
|
||||
- bases/databricks.microsoft.com_dclusters.yaml
|
||||
- bases/databricks.microsoft.com_dbfsblocks.yaml
|
||||
- bases/databricks.microsoft.com_workspaceitems.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patches:
|
||||
|
@ -16,6 +17,7 @@ patches:
|
|||
#- patches/webhook_in_runs.yaml
|
||||
#- patches/webhook_in_dclusters.yaml
|
||||
#- patches/webhook_in_dbfsblocks.yaml
|
||||
#- patches/webhook_in_workspaceitems.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||
|
||||
# [CAINJECTION] patches here are for enabling the CA injection for each CRD
|
||||
|
@ -24,6 +26,7 @@ patches:
|
|||
#- patches/cainjection_in_runs.yaml
|
||||
#- patches/cainjection_in_dclusters.yaml
|
||||
#- patches/cainjection_in_dbfsblocks.yaml
|
||||
#- patches/cainjection_in_workspaceitems.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||
# CRD conversion requires k8s 1.13 or later.
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME)
|
||||
name: workspaceitems.databricks.microsoft.com
|
|
@ -0,0 +1,17 @@
|
|||
# The following patch enables conversion webhook for CRD
|
||||
# CRD conversion requires k8s 1.13 or later.
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: workspaceitems.databricks.microsoft.com
|
||||
spec:
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhookClientConfig:
|
||||
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
|
||||
# but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
|
||||
caBundle: Cg==
|
||||
service:
|
||||
namespace: system
|
||||
name: webhook-service
|
||||
path: /convert
|
|
@ -152,3 +152,23 @@ rules:
|
|||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- databricks.microsoft.com
|
||||
resources:
|
||||
- workspaceitems
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- databricks.microsoft.com
|
||||
resources:
|
||||
- workspaceitems/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: databricks.microsoft.com/v1
|
||||
kind: WorkspaceItem
|
||||
metadata:
|
||||
name: workspaceitem-sample
|
||||
spec:
|
||||
content: |
|
||||
MSsx
|
||||
path: /ScalaExampleNotebook
|
||||
language: SCALA
|
||||
overwrite: true
|
||||
format: SOURCE
|
|
@ -80,12 +80,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
err = databricksv1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = databricksv1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = databricksv1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{
|
||||
|
@ -146,6 +140,14 @@ var _ = BeforeSuite(func(done Done) {
|
|||
}).SetupWithManager(k8sManager)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = (&WorkspaceItemReconciler{
|
||||
Client: k8sManager.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Run"),
|
||||
Recorder: k8sManager.GetEventRecorderFor("workspaceitem-controller"),
|
||||
APIClient: apiClient,
|
||||
}).SetupWithManager(k8sManager)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
go func() {
|
||||
err = k8sManager.Start(ctrl.SetupSignalHandler())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright 2019 microsoft.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
dbazure "github.com/xinsnake/databricks-sdk-golang/azure"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
databricksv1 "github.com/microsoft/azure-databricks-operator/api/v1"
|
||||
)
|
||||
|
||||
// WorkspaceItemReconciler reconciles a WorkspaceItem object
|
||||
type WorkspaceItemReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
|
||||
Recorder record.EventRecorder
|
||||
APIClient dbazure.DBClient
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=databricks.microsoft.com,resources=workspaceitems,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=databricks.microsoft.com,resources=workspaceitems/status,verbs=get;update;patch
|
||||
|
||||
func (r *WorkspaceItemReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = context.Background()
|
||||
_ = r.Log.WithValues("workspaceitem", req.NamespacedName)
|
||||
|
||||
instance := &databricksv1.WorkspaceItem{}
|
||||
|
||||
r.Log.Info(fmt.Sprintf("Starting reconcile loop for %v", req.NamespacedName))
|
||||
defer r.Log.Info(fmt.Sprintf("Finish reconcile loop for %v", req.NamespacedName))
|
||||
|
||||
if err := r.Get(context.Background(), req.NamespacedName, instance); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if instance.IsBeingDeleted() {
|
||||
r.Log.Info(fmt.Sprintf("HandleFinalizer for %v", req.NamespacedName))
|
||||
if err := r.handleFinalizer(instance); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("error when handling finalizer: %v", err)
|
||||
}
|
||||
r.Recorder.Event(instance, "Normal", "Deleted", "Object finalizer is deleted")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if !instance.HasFinalizer(databricksv1.WorkspaceItemFinalizerName) {
|
||||
r.Log.Info(fmt.Sprintf("AddFinalizer for %v", req.NamespacedName))
|
||||
if err := r.addFinalizer(instance); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("error when adding finalizer: %v", err)
|
||||
}
|
||||
r.Recorder.Event(instance, "Normal", "Added", "Object finalizer is added")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if !instance.IsSubmitted() || !instance.IsUpToDate() {
|
||||
r.Log.Info(fmt.Sprintf("Submit for %v", req.NamespacedName))
|
||||
if err := r.submit(instance); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("error when submitting workspace item: %v", err)
|
||||
}
|
||||
r.Recorder.Event(instance, "Normal", "Submitted", "Object is submitted")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *WorkspaceItemReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&databricksv1.WorkspaceItem{}).
|
||||
Complete(r)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2019 microsoft.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
databricksv1 "github.com/microsoft/azure-databricks-operator/api/v1"
|
||||
)
|
||||
|
||||
func (r *WorkspaceItemReconciler) submit(instance *databricksv1.WorkspaceItem) error {
|
||||
r.Log.Info(fmt.Sprintf("Create item %s", instance.GetName()))
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(instance.Spec.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.APIClient.Workspace().Import(instance.Spec.Path, instance.Spec.Format, instance.Spec.Language, data, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Refresh info
|
||||
objectInfo, err := r.APIClient.Workspace().GetStatus(instance.Spec.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instance.Status = &databricksv1.WorkspaceItemStatus{
|
||||
ObjectInfo: &objectInfo,
|
||||
ObjectHash: instance.GetHash(),
|
||||
}
|
||||
|
||||
return r.Update(context.Background(), instance)
|
||||
}
|
||||
|
||||
func (r *WorkspaceItemReconciler) delete(instance *databricksv1.WorkspaceItem) error {
|
||||
r.Log.Info(fmt.Sprintf("Deleting item %s", instance.GetName()))
|
||||
|
||||
if instance.Status == nil || instance.Status.ObjectInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
path := instance.Status.ObjectInfo.Path
|
||||
|
||||
return r.APIClient.Workspace().Delete(path, true)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2019 microsoft.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
databricksv1 "github.com/microsoft/azure-databricks-operator/api/v1"
|
||||
)
|
||||
|
||||
func (r *WorkspaceItemReconciler) addFinalizer(instance *databricksv1.WorkspaceItem) error {
|
||||
instance.AddFinalizer(databricksv1.WorkspaceItemFinalizerName)
|
||||
return r.Update(context.Background(), instance)
|
||||
}
|
||||
|
||||
func (r *WorkspaceItemReconciler) handleFinalizer(instance *databricksv1.WorkspaceItem) error {
|
||||
if !instance.HasFinalizer(databricksv1.WorkspaceItemFinalizerName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.delete(instance); err != nil {
|
||||
return err
|
||||
}
|
||||
instance.RemoveFinalizer(databricksv1.WorkspaceItemFinalizerName)
|
||||
return r.Update(context.Background(), instance)
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright 2019 microsoft.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
databricksv1 "github.com/microsoft/azure-databricks-operator/api/v1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("WorkspaceItem Controller", func() {
|
||||
|
||||
const timeout = time.Second * 30
|
||||
const interval = time.Second * 1
|
||||
|
||||
BeforeEach(func() {
|
||||
// Add any setup steps that needs to be executed before each test
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Add any teardown steps that needs to be executed after each test
|
||||
})
|
||||
|
||||
// Add Tests for OpenAPI validation (or additonal CRD features) specified in
|
||||
// your API definition.
|
||||
// Avoid adding tests for vanilla CRUD operations because they would
|
||||
// test Kubernetes API server, which isn't the goal here.
|
||||
Context("Workspace Item", func() {
|
||||
It("Should create successfully", func() {
|
||||
|
||||
key := types.NamespacedName{
|
||||
Name: "block-greater-than-1mb",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
created := &databricksv1.WorkspaceItem{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: key.Name,
|
||||
Namespace: key.Namespace,
|
||||
},
|
||||
Spec: &databricksv1.WorkspaceItemSpec{
|
||||
Content: "MSsx",
|
||||
Path: "/test-notebook",
|
||||
Language: "SCALA",
|
||||
Format: "SOURCE",
|
||||
},
|
||||
}
|
||||
|
||||
// Create
|
||||
Expect(k8sClient.Create(context.Background(), created)).Should(Succeed())
|
||||
|
||||
By("Expecting submitted")
|
||||
Eventually(func() bool {
|
||||
f := &databricksv1.WorkspaceItem{}
|
||||
k8sClient.Get(context.Background(), key, f)
|
||||
return f.IsSubmitted()
|
||||
}, timeout, interval).Should(BeTrue())
|
||||
|
||||
// Update
|
||||
updated := &databricksv1.WorkspaceItem{}
|
||||
Expect(k8sClient.Get(context.Background(), key, updated)).Should(Succeed())
|
||||
|
||||
updated.Spec.Content = "MSsy"
|
||||
Expect(k8sClient.Update(context.Background(), updated)).Should(Succeed())
|
||||
|
||||
// Delete
|
||||
By("Expecting to delete successfully")
|
||||
Eventually(func() error {
|
||||
f := &databricksv1.WorkspaceItem{}
|
||||
k8sClient.Get(context.Background(), key, f)
|
||||
return k8sClient.Delete(context.Background(), f)
|
||||
}, timeout, interval).Should(Succeed())
|
||||
|
||||
By("Expecting to delete finish")
|
||||
Eventually(func() error {
|
||||
f := &databricksv1.WorkspaceItem{}
|
||||
return k8sClient.Get(context.Background(), key, f)
|
||||
}, timeout, interval).ShouldNot(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
|
@ -3,14 +3,15 @@
|
|||
- Job (djob)
|
||||
- Run (run)
|
||||
- Secret Scope & Secret (secretscope)
|
||||
- Cluster (dcluster)
|
||||
- DBFS (dbfsblock)
|
||||
- Workspace (workspaceitem)
|
||||
|
||||
# In Progress
|
||||
|
||||
- Cluster (dcluster)
|
||||
- DBFS (dbfsblock)
|
||||
- Libraries
|
||||
|
||||
# Future Development
|
||||
|
||||
- Libraries
|
||||
- Group
|
||||
- Token
|
10
main.go
10
main.go
|
@ -128,6 +128,16 @@ func main() {
|
|||
setupLog.Error(err, "unable to create controller", "controller", "DbfsBlock")
|
||||
os.Exit(1)
|
||||
}
|
||||
err = (&controllers.WorkspaceItemReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("WorkspaceItem"),
|
||||
Recorder: mgr.GetEventRecorderFor("workspaceitem-controller"),
|
||||
APIClient: apiClient,
|
||||
}).SetupWithManager(mgr)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "WorkspaceItem")
|
||||
os.Exit(1)
|
||||
}
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
|
|
Загрузка…
Ссылка в новой задаче