lburgazzoli closed pull request #6: Adding install and get commands URL: https://github.com/apache/camel-k/pull/6
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/build/Makefile b/build/Makefile index d8d2b7a..eb1a16c 100644 --- a/build/Makefile +++ b/build/Makefile @@ -1,6 +1,6 @@ VERSION := $(shell ./build/get_version.sh) -build: build-runtime build-operator build-kamel +build: build-runtime build-embed-resources build-operator build-kamel build-operator: go build -o camel-k-operator ./cmd/camel-k-operator/*.go @@ -8,6 +8,9 @@ build-operator: build-kamel: go build -o kamel ./cmd/kamel/*.go +build-embed-resources: + ./build/embed_resources.sh deploy + build-runtime: mvn clean install -f ./runtime/pom.xml diff --git a/build/embed_resources.sh b/build/embed_resources.sh new file mode 100755 index 0000000..3ca6385 --- /dev/null +++ b/build/embed_resources.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +if [[ $# -ne 1 ]] ; then + echo "Error invoking embed_resources.sh: directory argument required" + exit 1 +fi + +location=$(dirname $0) +destdir=$location/../$1 +destfile=$location/../$1/resources.go + +cat > $destfile << EOM +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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. +*/ + +// Code generated by build/embed_resources.sh. DO NOT EDIT. + +package deploy + +var Resources map[string]string + +func init() { + Resources = make(map[string]string) + +EOM + +for f in $(ls $destdir | grep ".yaml"); do + printf "Resources[\"$f\"] =\n\`\n" >> $destfile + cat $destdir/$f >> $destfile + printf "\n\`\n" >> $destfile +done + +printf "\n}\n" >> $destfile \ No newline at end of file diff --git a/deploy/operator-role-binding.yaml b/deploy/operator-role-binding.yaml new file mode 100644 index 0000000..eb4c712 --- /dev/null +++ b/deploy/operator-role-binding.yaml @@ -0,0 +1,11 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: camel-k-operator +subjects: +- kind: ServiceAccount + name: camel-k-operator +roleRef: + kind: Role + name: camel-k-operator + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/deploy/rbac.yaml b/deploy/operator-role.yaml similarity index 60% rename from deploy/rbac.yaml rename to deploy/operator-role.yaml index e1cb15f..7dea8d3 100644 --- a/deploy/rbac.yaml +++ b/deploy/operator-role.yaml @@ -1,7 +1,7 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: - name: camel-k + name: camel-k-operator rules: - apiGroups: - camel.apache.org @@ -29,18 +29,4 @@ rules: - replicasets - statefulsets verbs: - - "*" - ---- - -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - name: default-account-camel-k -subjects: -- kind: ServiceAccount - name: default -roleRef: - kind: Role - name: camel-k - apiGroup: rbac.authorization.k8s.io + - "*" \ No newline at end of file diff --git a/deploy/operator-service-account.yaml b/deploy/operator-service-account.yaml new file mode 100644 index 0000000..eb771aa --- /dev/null +++ b/deploy/operator-service-account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: camel-k-operator \ No newline at end of file diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 863b21c..265b76d 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -2,6 +2,8 @@ apiVersion: apps/v1 kind: Deployment metadata: name: camel-k-operator + labels: + app: "camel-k" spec: replicas: 1 selector: @@ -12,6 +14,7 @@ spec: labels: name: camel-k-operator spec: + serviceAccountName: camel-k-operator containers: - name: camel-k-operator image: docker.io/apache/camel-k:0.0.1-SNAPSHOT @@ -20,7 +23,7 @@ spec: name: metrics command: - camel-k-operator - imagePullPolicy: Always + imagePullPolicy: IfNotPresent env: - name: WATCH_NAMESPACE valueFrom: diff --git a/deploy/resources.go b/deploy/resources.go new file mode 100644 index 0000000..1048333 --- /dev/null +++ b/deploy/resources.go @@ -0,0 +1,180 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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. +*/ + +// Code generated by build/embed_resources.sh. DO NOT EDIT. + +package deploy + +var Resources map[string]string + +func init() { + Resources = make(map[string]string) + +Resources["crd.yaml"] = +` +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: integrations.camel.apache.org +spec: + group: camel.apache.org + names: + kind: Integration + listKind: IntegrationList + plural: integrations + singular: integration + scope: Namespaced + version: v1alpha1 + +` +Resources["cr.yaml"] = +` +apiVersion: "camel.apache.org/v1alpha1" +kind: "Integration" +metadata: + name: "example" +spec: + replicas: 1 + source: + code: |- + package kamel; + + import org.apache.camel.builder.RouteBuilder; + + public class Routes extends RouteBuilder { + + @Override + public void configure() throws Exception { + from("timer:tick") + .setBody(constant("Hello World!!!")) + .to("log:info"); + } + + } + +` +Resources["operator-role-binding.yaml"] = +` +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: camel-k-operator +subjects: +- kind: ServiceAccount + name: camel-k-operator +roleRef: + kind: Role + name: camel-k-operator + apiGroup: rbac.authorization.k8s.io +` +Resources["operator-role.yaml"] = +` +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: camel-k-operator +rules: +- apiGroups: + - camel.apache.org + resources: + - "*" + verbs: + - "*" +- apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - "*" +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - "*" +` +Resources["operator-service-account.yaml"] = +` +apiVersion: v1 +kind: ServiceAccount +metadata: + name: camel-k-operator +` +Resources["operator.yaml"] = +` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: camel-k-operator + labels: + app: "camel-k" +spec: + replicas: 1 + selector: + matchLabels: + name: camel-k-operator + template: + metadata: + labels: + name: camel-k-operator + spec: + serviceAccountName: camel-k-operator + containers: + - name: camel-k-operator + image: docker.io/apache/camel-k:0.0.1-SNAPSHOT + ports: + - containerPort: 60000 + name: metrics + command: + - camel-k-operator + imagePullPolicy: IfNotPresent + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "camel-k-operator" + +` +Resources["user-cluster-role.yaml"] = +` +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: camel-k:edit + labels: + # Add these permissions to the "admin" and "edit" default roles. + rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" +rules: +- apiGroups: ["camel.apache.org"] + resources: ["*"] + verbs: ["*"] + +` + +} diff --git a/deploy/user-cluster-role.yaml b/deploy/user-cluster-role.yaml new file mode 100644 index 0000000..2fde6d1 --- /dev/null +++ b/deploy/user-cluster-role.yaml @@ -0,0 +1,12 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: camel-k:edit + labels: + # Add these permissions to the "admin" and "edit" default roles. + rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" +rules: +- apiGroups: ["camel.apache.org"] + resources: ["*"] + verbs: ["*"] diff --git a/pkg/build/local/local_builder.go b/pkg/build/local/local_builder.go index 98830d6..d37a56e 100644 --- a/pkg/build/local/local_builder.go +++ b/pkg/build/local/local_builder.go @@ -27,9 +27,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/api/core/v1" "github.com/operator-framework/operator-sdk/pkg/sdk" - "k8s.io/client-go/rest" - "github.com/operator-framework/operator-sdk/pkg/k8sclient" - "k8s.io/apimachinery/pkg/runtime/schema" imagev1 "github.com/openshift/api/image/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/operator-framework/operator-sdk/pkg/util/k8sutil" @@ -39,6 +36,7 @@ import ( "github.com/apache/camel-k/pkg/util/kubernetes" "github.com/apache/camel-k/version" "github.com/apache/camel-k/pkg/util/maven" + "github.com/apache/camel-k/pkg/util/kubernetes/customclient" ) type localBuilder struct { @@ -203,34 +201,17 @@ func (b *localBuilder) publish(tarFile string, source build.BuildSource) (string return "", errors.Wrap(err, "cannot create image stream") } - inConfig := k8sclient.GetKubeConfig() - config := rest.CopyConfig(inConfig) - config.GroupVersion = &schema.GroupVersion{ - Group: "build.openshift.io", - Version: "v1", - } - config.APIPath = "/apis" - config.AcceptContentTypes = "application/json" - config.ContentType = "application/json" - - // this gets used for discovery and error handling types - config.NegotiatedSerializer = basicNegotiatedSerializer{} - if config.UserAgent == "" { - config.UserAgent = rest.DefaultKubernetesUserAgent() - } - - restClient, err := rest.RESTClientFor(config) + resource, err := ioutil.ReadFile(tarFile) if err != nil { - return "", err + return "", errors.Wrap(err, "cannot fully read tar file "+tarFile) } - resource, err := ioutil.ReadFile(tarFile) + restClient, err := customclient.GetClientFor("build.openshift.io", "v1") if err != nil { - return "", errors.Wrap(err, "cannot fully read tar file "+tarFile) + return "", err } - result := restClient. - Post(). + result := restClient.Post(). Namespace(b.namespace). Body(resource). Resource("buildconfigs"). diff --git a/pkg/client/cmd/get.go b/pkg/client/cmd/get.go new file mode 100644 index 0000000..39a45d9 --- /dev/null +++ b/pkg/client/cmd/get.go @@ -0,0 +1,64 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 cmd + +import ( + "github.com/spf13/cobra" + "text/tabwriter" + "os" + "fmt" + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/operator-framework/operator-sdk/pkg/sdk" +) + +func NewCmdGet() *cobra.Command { + cmd := cobra.Command{ + Use: "get", + Short: "Get all integrations deployed on Kubernetes", + Long: `Get the status of all integrations deployed on on Kubernetes.`, + RunE: run, + } + + return &cmd +} + +func run(cmd *cobra.Command, args []string) error { + integrationList := v1alpha1.IntegrationList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: "Integration", + }, + } + + namespace := cmd.Flag("namespace").Value.String() + + err := sdk.List(namespace, &integrationList) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0) + fmt.Fprintln(w, "NAME\tSTATUS") + for _, integration := range integrationList.Items { + fmt.Fprintln(w, integration.Name+"\t"+string(integration.Status.Phase)) + } + w.Flush() + + return nil +} diff --git a/pkg/client/cmd/install.go b/pkg/client/cmd/install.go new file mode 100644 index 0000000..edeece4 --- /dev/null +++ b/pkg/client/cmd/install.go @@ -0,0 +1,58 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 cmd + +import ( + "github.com/spf13/cobra" + installutils "github.com/apache/camel-k/pkg/install" + "fmt" + "k8s.io/apimachinery/pkg/api/errors" +) + +type InstallCmdOptions struct { +} + +func NewCmdInstall() *cobra.Command { + options := InstallCmdOptions{} + cmd := cobra.Command{ + Use: "install", + Short: "Install Camel K on a Kubernetes cluster", + Long: `Installs Camel K on a Kubernetes or Openshift cluster.`, + RunE: options.install, + } + return &cmd +} + +func (o *InstallCmdOptions) install(cmd *cobra.Command, args []string) error { + err := installutils.SetupClusterwideResources() + if err != nil && errors.IsForbidden(err) { + // TODO explain that this is a one time operation and add a flag to do cluster-level operations only when logged as admin + fmt.Println("Current user is not authorized to create cluster-wide objects like custom resource definitions or cluster roles: ", err) + fmt.Println("Please login as cluster-admin to continue the installation.") + return nil // TODO better error handling: if here we return err the help page is shown + } + + namespace := cmd.Flag("namespace").Value.String() + + err = installutils.InstallOperator(namespace) + if err != nil { + return err + } + fmt.Println("Camel K installed in namespace", namespace) + return nil +} diff --git a/pkg/client/cmd/root.go b/pkg/client/cmd/root.go index e49da28..f310f10 100644 --- a/pkg/client/cmd/root.go +++ b/pkg/client/cmd/root.go @@ -18,21 +18,42 @@ limitations under the License. package cmd import ( + "os" + "github.com/spf13/cobra" + "github.com/apache/camel-k/pkg/util/kubernetes" + "github.com/pkg/errors" ) +type rootCmdOptions struct { + KubeConfig string + Namespace string +} + func NewKamelCommand() (*cobra.Command, error) { + options := rootCmdOptions{} var cmd = cobra.Command{ Use: "kamel", Short: "Kamel is a awesome client tool for running Apache Camel integrations natively on Kubernetes", Long: "Apache Camel K (a.k.a. Kamel) is a lightweight integration framework\nbuilt from Apache Camel that runs natively on Kubernetes and is\nspecifically designed for serverless and microservice architectures.", } - var kubeconfig string - cmd.PersistentFlags().StringVar(&kubeconfig, "config", "", "Path to the config file to use for CLI requests") + cmd.PersistentFlags().StringVar(&options.KubeConfig, "config", "", "Path to the config file to use for CLI requests") + cmd.PersistentFlags().StringVarP(&options.Namespace, "namespace", "n", "", "Namespace to use for all operations") + + // Parse the flags before setting the defaults + cmd.ParseFlags(os.Args) + + if options.Namespace == "" { + current, err := kubernetes.GetClientCurrentNamespace(options.KubeConfig) + if err != nil { + return nil, errors.Wrap(err, "cannot get current namespace") + } + cmd.Flag("namespace").Value.Set(current) + } // Initialize the Kubernetes client to allow using the operator-sdk - err := initKubeClient(&cmd) + err := kubernetes.InitKubeClient(options.KubeConfig) if err != nil { return nil, err } @@ -40,6 +61,8 @@ func NewKamelCommand() (*cobra.Command, error) { cmd.AddCommand(NewCmdCompletion()) cmd.AddCommand(NewCmdVersion()) cmd.AddCommand(NewCmdRun()) + cmd.AddCommand(NewCmdGet()) + cmd.AddCommand(NewCmdInstall()) return &cmd, nil } diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go index ca0872a..1fb90d4 100644 --- a/pkg/client/cmd/run.go +++ b/pkg/client/cmd/run.go @@ -32,27 +32,27 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func NewCmdRun() *cobra.Command { - impl := runCmd{} +type RunCmdOptions struct { + Language string +} +func NewCmdRun() *cobra.Command { + options := RunCmdOptions{} cmd := cobra.Command{ Use: "run [file to run]", Short: "Run a integration on Kubernetes", Long: `Deploys and execute a integration pod on Kubernetes.`, - Args: impl.validateArgs, - RunE: impl.execute, + Args: options.validateArgs, + RunE: options.run, } - cmd.Flags().StringVarP(&impl.language, "language", "l", "", "Programming Language used to write the file") + cmd.Flags().StringVarP(&options.Language, "language", "l", "", "Programming Language used to write the file") + cmd.ParseFlags(os.Args) return &cmd } -type runCmd struct { - language string -} - -func (target runCmd) validateArgs(cmd *cobra.Command, args []string) error { +func (*RunCmdOptions) validateArgs(cmd *cobra.Command, args []string) error { if len(args) != 1 { return errors.New("accepts 1 arg, received " + strconv.Itoa(len(args))) } @@ -65,12 +65,14 @@ func (target runCmd) validateArgs(cmd *cobra.Command, args []string) error { return nil } -func (target runCmd) execute(cmd *cobra.Command, args []string) error { - code, err := target.loadCode(args[0]) +func (o *RunCmdOptions) run(cmd *cobra.Command, args []string) error { + code, err := o.loadCode(args[0]) if err != nil { return err } + namespace := cmd.Flag("namespace").Value.String() + name := kubernetes.SanitizeName(args[0]) if name == "" { name = "integration" @@ -82,7 +84,7 @@ func (target runCmd) execute(cmd *cobra.Command, args []string) error { APIVersion: v1alpha1.SchemeGroupVersion.String(), }, ObjectMeta: v1.ObjectMeta{ - Namespace: "test", // TODO discover current namespace dynamically (and with command option) + Namespace: namespace, Name: name, }, Spec: v1alpha1.IntegrationSpec{ @@ -117,7 +119,7 @@ func (target runCmd) execute(cmd *cobra.Command, args []string) error { return nil } -func (target runCmd) loadCode(fileName string) (string, error) { +func (*RunCmdOptions) loadCode(fileName string) (string, error) { content, err := ioutil.ReadFile(fileName) if err != nil { return "", err diff --git a/pkg/install/cluster.go b/pkg/install/cluster.go new file mode 100644 index 0000000..a6b60d5 --- /dev/null +++ b/pkg/install/cluster.go @@ -0,0 +1,124 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 install + +import ( + "github.com/operator-framework/operator-sdk/pkg/k8sclient" + "github.com/apache/camel-k/deploy" + "k8s.io/apimachinery/pkg/util/yaml" + "github.com/apache/camel-k/pkg/util/kubernetes/customclient" + "k8s.io/apimachinery/pkg/api/errors" + "github.com/apache/camel-k/pkg/util/kubernetes" + "github.com/operator-framework/operator-sdk/pkg/sdk" + "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func SetupClusterwideResources() error { + // Installing CRD + crdInstalled, err := isCRDInstalled() + if err != nil { + return err + } + if !crdInstalled { + if err := installCRD(); err != nil { + return err + } + } + + // Installing ClusterRole + clusterRoleInstalled, err := isClusterRoleInstalled() + if err != nil { + return err + } + if !clusterRoleInstalled { + err := installClusterRole() + if err != nil { + return err + } + } + + return nil +} + +func isCRDInstalled() (bool, error) { + lst, err := k8sclient.GetKubeClient().Discovery().ServerResourcesForGroupVersion("camel.apache.org/v1alpha1") + if err != nil && errors.IsNotFound(err) { + return false, nil + } else if err != nil { + return false, err + } + for _, res := range lst.APIResources { + if res.Kind == "Integration" { + return true, nil + } + } + return false, nil +} + +func installCRD() error { + crd := []byte(deploy.Resources["crd.yaml"]) + crdJson, err := yaml.ToJSON(crd) + if err != nil { + return err + } + restClient, err := customclient.GetClientFor("apiextensions.k8s.io", "v1beta1") + if err != nil { + return err + } + // Post using dynamic client + result := restClient. + Post(). + Body(crdJson). + Resource("customresourcedefinitions"). + Do() + // Check result + if result.Error() != nil && !errors.IsAlreadyExists(result.Error()) { + return result.Error() + } + + return nil +} + +func isClusterRoleInstalled() (bool, error) { + clusterRole := v1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: "rbac.authorization.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "camel-k:edit", + }, + } + err := sdk.Get(&clusterRole) + if err != nil && errors.IsNotFound(err) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +func installClusterRole() error { + obj, err := kubernetes.LoadResourceFromYaml(deploy.Resources["user-cluster-role.yaml"]) + if err != nil { + return err + } + + return sdk.Create(obj) +} diff --git a/pkg/install/cluster_integration_test.go b/pkg/install/cluster_integration_test.go new file mode 100644 index 0000000..df9f93c --- /dev/null +++ b/pkg/install/cluster_integration_test.go @@ -0,0 +1,36 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 install + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +func TestInstallation(t *testing.T) { + err := SetupClusterwideResources() + assert.Nil(t, err) + + installedCRD, err := isCRDInstalled() + assert.Nil(t, err) + assert.True(t, installedCRD) + + installedClusterRole, err := isClusterRoleInstalled() + assert.Nil(t, err) + assert.True(t, installedClusterRole) +} diff --git a/pkg/install/operator.go b/pkg/install/operator.go new file mode 100644 index 0000000..ad6d5d6 --- /dev/null +++ b/pkg/install/operator.go @@ -0,0 +1,61 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 install + +import ( + "github.com/apache/camel-k/deploy" + "github.com/operator-framework/operator-sdk/pkg/sdk" + "github.com/apache/camel-k/pkg/util/kubernetes" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func InstallOperator(namespace string) error { + return installResources(namespace, + "operator-service-account.yaml", + "operator-role.yaml", + "operator-role-binding.yaml", + "operator.yaml", + ) +} + +func installResources(namespace string, names ...string) error { + for _, name := range names { + if err := installResource(namespace, name); err != nil { + return err + } + } + return nil +} + +func installResource(namespace string, name string) error { + obj, err := kubernetes.LoadResourceFromYaml(deploy.Resources[name]) + if err != nil { + return err + } + + if kObj, ok := obj.(metav1.Object); ok { + kObj.SetNamespace(namespace) + } + + err = sdk.Create(obj) + if err != nil && errors.IsAlreadyExists(err) { + return sdk.Update(obj) + } + return err +} diff --git a/pkg/client/cmd/config.go b/pkg/util/kubernetes/config.go similarity index 79% rename from pkg/client/cmd/config.go rename to pkg/util/kubernetes/config.go index f7b347b..f01a8de 100644 --- a/pkg/client/cmd/config.go +++ b/pkg/util/kubernetes/config.go @@ -15,26 +15,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cmd +package kubernetes import ( "os/user" "path/filepath" - - "github.com/operator-framework/operator-sdk/pkg/k8sclient" - "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" + "github.com/operator-framework/operator-sdk/pkg/k8sclient" ) -func initKubeClient(cmd *cobra.Command) error { - kubeconfig := cmd.Flag("config").Value.String() +func InitKubeClient(kubeconfig string) error { if kubeconfig == "" { - usr, err := user.Current() - if err != nil { - return err - } - - kubeconfig = filepath.Join(usr.HomeDir, ".kube", "config") + kubeconfig = GetDefaultKubeConfigFile() } // use the current context in kubeconfig @@ -46,3 +38,12 @@ func initKubeClient(cmd *cobra.Command) error { k8sclient.CustomConfig = config return nil } + +func GetDefaultKubeConfigFile() string { + usr, err := user.Current() + if err != nil { + panic(err) // TODO handle error + } + + return filepath.Join(usr.HomeDir, ".kube", "config") +} diff --git a/pkg/util/kubernetes/customclient/customclient.go b/pkg/util/kubernetes/customclient/customclient.go new file mode 100644 index 0000000..9dc0735 --- /dev/null +++ b/pkg/util/kubernetes/customclient/customclient.go @@ -0,0 +1,44 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 customclient + +import ( + "k8s.io/client-go/rest" + "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/operator-framework/operator-sdk/pkg/k8sclient" +) + +func GetClientFor(group string, version string) (*rest.RESTClient, error) { + inConfig := k8sclient.GetKubeConfig() + config := rest.CopyConfig(inConfig) + config.GroupVersion = &schema.GroupVersion{ + Group: group, + Version: version, + } + config.APIPath = "/apis" + config.AcceptContentTypes = "application/json" + config.ContentType = "application/json" + + // this gets used for discovery and error handling types + config.NegotiatedSerializer = basicNegotiatedSerializer{} + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return rest.RESTClientFor(config) +} diff --git a/pkg/util/kubernetes/customclient/scheme.go b/pkg/util/kubernetes/customclient/scheme.go new file mode 100644 index 0000000..f20496d --- /dev/null +++ b/pkg/util/kubernetes/customclient/scheme.go @@ -0,0 +1,98 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 customclient + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/versioning" +) + +var watchScheme = runtime.NewScheme() +var basicScheme = runtime.NewScheme() +var deleteScheme = runtime.NewScheme() +var parameterScheme = runtime.NewScheme() +var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme) +var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme) + +var versionV1 = schema.GroupVersion{Version: "v1"} + +func init() { + metav1.AddToGroupVersion(watchScheme, versionV1) + metav1.AddToGroupVersion(basicScheme, versionV1) + metav1.AddToGroupVersion(parameterScheme, versionV1) + metav1.AddToGroupVersion(deleteScheme, versionV1) +} + +var watchJsonSerializerInfo = runtime.SerializerInfo{ + MediaType: "application/json", + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false), + PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true), + StreamSerializer: &runtime.StreamSerializerInfo{ + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false), + Framer: json.Framer, + }, +} + +// watchNegotiatedSerializer is used to read the wrapper of the watch stream +type watchNegotiatedSerializer struct{} + +var watchNegotiatedSerializerInstance = watchNegotiatedSerializer{} + +func (s watchNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{watchJsonSerializerInfo} +} + +func (s watchNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil) +} + +func (s watchNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv) +} + +// basicNegotiatedSerializer is used to handle discovery and error handling serialization +type basicNegotiatedSerializer struct{} + +func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{ + { + MediaType: "application/json", + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false), + PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true), + StreamSerializer: &runtime.StreamSerializerInfo{ + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false), + Framer: json.Framer, + }, + }, + } +} + +func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil) +} + +func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv) +} \ No newline at end of file diff --git a/pkg/util/kubernetes/loader.go b/pkg/util/kubernetes/loader.go new file mode 100644 index 0000000..c1cfc5a --- /dev/null +++ b/pkg/util/kubernetes/loader.go @@ -0,0 +1,40 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 kubernetes + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/operator-framework/operator-sdk/pkg/util/k8sutil" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apimachinery/pkg/runtime" +) + +func LoadResourceFromYaml(data string) (runtime.Object, error) { + role := []byte(data) + roleJson, err := yaml.ToJSON(role) + if err != nil { + return nil, err + } + u := unstructured.Unstructured{} + err = u.UnmarshalJSON(roleJson) + if err != nil { + return nil, err + } + + return k8sutil.RuntimeObjectFromUnstructured(&u) +} diff --git a/pkg/util/kubernetes/namespace.go b/pkg/util/kubernetes/namespace.go new file mode 100644 index 0000000..116865d --- /dev/null +++ b/pkg/util/kubernetes/namespace.go @@ -0,0 +1,56 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 kubernetes + +import ( + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest" + "io/ioutil" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func GetClientCurrentNamespace(kubeconfig string) (string, error) { + if kubeconfig == "" { + kubeconfig = GetDefaultKubeConfigFile() + } + if kubeconfig == "" { + return "default", nil + } + + data, err := ioutil.ReadFile(kubeconfig) + if err != nil { + return "", err + } + config := clientcmdapi.NewConfig() + if len(data) == 0 { + return "", errors.New("kubernetes config file is empty") + } + + decoded, _, err := clientcmdlatest.Codec.Decode(data, &schema.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config"}, config) + if err != nil { + return "", err + } + + clientcmdconfig := decoded.(*clientcmdapi.Config) + + cc := clientcmd.NewDefaultClientConfig(*clientcmdconfig, &clientcmd.ConfigOverrides{}) + ns, _, err := cc.Namespace() + return ns, err +} ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services