Giter Club home page Giter Club logo

redhat-developer / service-binding-operator Goto Github PK

View Code? Open in Web Editor NEW
110.0 17.0 91.0 27.28 MB

[Deprecated] The Service Binding Operator: Connecting Applications with Services, in Kubernetes

Home Page: https://redhat-developer.github.io/service-binding-operator

License: Apache License 2.0

Dockerfile 0.21% Shell 5.57% Go 48.01% Makefile 3.42% Gherkin 30.21% Python 12.30% Handlebars 0.07% Smarty 0.20%
binding kubernetes openshift operator kubernetes-operator

service-binding-operator's Introduction

Deprecation Notice

As of February 2024, Service Binding Operator has been deprecated. No further feature development is expected at this time. Security-related releases may happen on an as-need basis, but no schedule for them can be guaranteed at this time. This repository will be archived at a future date.

Usage of this project for new deployments is no longer recommended, and existing uses of this project are recommended to transition to a new solution.

The Service Binding Operator

Connecting Applications with Services on Kubernetes and OpenShift

GoReport GoDoc Reference Codecov.io - Code Coverage

Introduction

Service Binding manages the data plane for applications and backing services. Service Binding Operator reads data made available by the control plane of backing services and projects the data to applications according to the rules provided via ServiceBinding resource.

service-binding-intro

Why Service Bindings?

Today in Kubernetes, the exposure of secrets for connecting applications to external services such as REST APIs, databases, event buses, and many more is manual and bespoke. Each service provider suggests a different way to access their secrets, and each application developer consumes those secrets in a custom way to their applications. While there is a good deal of value to this flexibility level, large development teams lose overall velocity dealing with each unique solution.

Service Binding:

  • Enables developers to connect their application to backing services with a consistent and predictable experience
  • Removes error-prone manual configuration of binding information
  • Provides service operators a low-touch administrative experience to provision and manage access to services
  • Enriches development lifecycle with a consistent and declarative service binding method that eliminates environments discrepancies

Key Features

  • Support Binding with backing services represented by Kubernetes resources including third-party CRD-backed resources.
  • Support binding with multiple-backing services.
  • Extract binding information based on annotations present in CRDs/CRs/resources.
  • Extract binding values based on annotations present in OLM descriptors.
  • Project binding values as volume mounts.
  • Project binding values as environment variables.
  • Binding of PodSpec-based workloads.
  • Binding of non-PodSpec-based Kubernetes resources.
  • Custom binding variables composed from one or more backing services.
  • Auto-detect binding resources in the absence of binding decorators.

Service Binding Specification Alignment

  • Service Binding Operator provides two different APIs.
    • binding.operators.coreos.com/v1alpha1: This API is compliant with the Service Binding Specification for Kubernetes.
    • servicebinding.io/v1alpha3 (tech preview): This API implements the Service Binding Specification for Kubernetes.

The Service Binding Specification for Kubernetes is still evolving and maturing. We are tracking changes to the spec as it approaches a stable release and are updating our APIs accordingly and as a result our APIs may change in the future.

Getting started

Installing in a Cluster

Follow OperatorHub instructions.

Usage

To get started, consult the quick start tutorial. General documentation can be found here.

Read more

Here are some more places to read about SBO in use:

Known bindable operators

The Service Binding Operator can automatically detect and bind to services created by a limited selection of operators. These operators do not support binding directly. Instead, the service binding operator is able to detect and configure the operator's CRDs so that they become bindable. The long-term intention is to contribute upstream support for service binding and remove the operators that gain native support for service bindings. The operators that currently fall in this category are:

  • OpsTree Redis: bindable with Redis.redis.redis.opstreelabs.in/v1beta1 services
  • CrunchyData Postgres: bindable with PostgresCluster.postgres-operator.crunchydata.com/v1beta1 services
  • Cloud Native PostgreSQL: bindable with Cluster.postgresql.k8s.enterprisedb.io/v1 services
  • Percona XtraDB Cluster: bindable with PerconaXtraDBCluster.pxc.percona.com/v1-8-0 and v1-9-0 services
  • Percona MongoDB: bindable with PerconaServerMongoDB.psmdb.percona.com/v1-9-0, v1-10-0 and v1 services
    • NOTE: Provides administrative access to the cluster by default
  • RabbitMQ Cluster: bindable with RabbitmqCluster.rabbitmq.com/v1beta1 services

OpenShift Streams for Apache Kafka are also bindable, although getting binding to work requires a little more effort. See here for more details.

Roadmap

The direction of this project is tracked under milestones posted here on GitHub.

Community, discussion, contribution, and support

The Service Binding community meets bi-weekly on Thursdays at 1:00 PM UTC via Google Meet, and the meeting agenda is maintained here. If you have a topic you wish to discuss at this meeting, please feel free to add a discussion topic to the agenda.

Please file bug reports on Github. For any other questions, reach out on [email protected].

Join the service-binding-operator channel in the Kubernetes Workspace for any discussions and collaboration with the community.

service-binding-operator's People

Contributors

akashshinde avatar avni-sharma avatar baijum avatar bamachrn avatar cdlliuy avatar dependabot[bot] avatar dhritishikhar avatar fbm3307 avatar feloy avatar filariow avatar jasperchui avatar johnpoth avatar kadel avatar kartikey-star avatar komish avatar ldimaggi avatar noelo avatar openshift-merge-robot avatar otaviof avatar pdettori avatar pedjak avatar pmacik avatar pratikjagrut avatar qibobo avatar sadlerap avatar sbose78 avatar shruthihub avatar srivaralakshmi avatar tisutisu avatar yselkowitz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

service-binding-operator's Issues

Feedback: Handle Backing Services whose CRs link to Secrets/ConfigMaps using CR name-based conventions

Backing services such as the ElasticSearch elastic-cloud operator (https://github.com/operator-framework/community-operators/tree/master/upstream-community-operators/elastic-cloud) have a CR that links to Secrets/ConfigMaps using the CR name as a common prefix. As described here: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-accessing-elastic-services.html#k8s-request-elasticsearch-endpoint, in order to bind to the service endpoint for the Elasticsearch CR, the following secrets must be accessed:

$NAME-es-http-certs-public - to retrieve the CA Certificate with key "tls.crt"
$NAME-es-elastic-user - to retrieve the password of the elastic user with key "elastic"

These secrets are identified via the common CR name prefix "$NAME" followed by well-known suffixes for specific secrets. This is a common pattern used by other operators as well. In the case of the strimzi-kafka-operator KafkaUser CR (https://github.com/operator-framework/community-operators/tree/master/upstream-community-operators/strimzi-kafka-operator) (https://strimzi.io/docs/master/#proc-creating-kafka-user-tls-using-uo), the same name is used for the KafkaUser credentials Secret as the KafkaUser CR itself.

The service-binding-operator should support backing services whose CRs link to Secrets/ConfigMaps using CR name-based conventions. Backing service operator maintainers should not be required to modify their CRDs to support the service-binding-operator. Rather, they should only need to add annotations to the ClusterServiceVersion for their backing service operator to be accessible by the service-binding-operator.

Feature: Add support of adding configmap name dynamically.

What is currently supported

 servicebindingoperator.redhat.io/status.dbConfigMap-db.host: 'binding:env:object:configmap'
    servicebindingoperator.redhat.io/status.dbConfigMap-db.port: 'binding:env:object:configmap'
    servicebindingoperator.redhat.io/status.dbConfigMap-db.name: 'binding:env:object:configmap'
    servicebindingoperator.redhat.io/status.dbConfigMap-db.user: 'binding:env:object:configmap'
    servicebindingoperator.redhat.io/status.dbConfigMap-db.password: 'binding:env:object:configmap'

Here 'dbConfig' is the hard coded configmap name.

What should be supported

I have came across one scenario where above configmap name(dbConfigMap) gets dynamically generated.

Operator Used

Cruncydata's Postgres operator -
OperatorHub - https://operatorhub.io/operator/postgresql
Github URL - https://github.com/CrunchyData/postgres-operator

This operator generates secret with a name in following format _${CR_NAME}_POSTGRES_SECRETS

Trigger rebinding if the BackingServiceCR-owned secret/configmap changes

Description

A BackingService like a Database managed by a Database CR might own a secret or a configmap which stores credentials or connection information on how to connect to the database.

What does a BackingService CR owned object imply ?

By "Database CR owning a secret", it is implied that the secret or configmap has an ownerReferences containing the details of the Database CR which created it.

If such objects change, a rebinding ( "reconcile" ) should be triggered.

Acceptance Criteria

Ensure the following scenario works:

  • Create a Database CR.
  • Import App
  • Create a Binding request
  • Validate the the imported app's CRUD works.
  • Modify credentials in the backing service using an update to the secret owned by the Database CR.
  • Ensure that reconcile is triggered and the imported app's CRUD works with the new credentials.

Multiple Duplicate finalizers for Service Binding Request

I am running with the latest master, and I have noticed that the first time that I apply a SBR and the reconciliation runs, I can see the following finalizers applied:

finalizers:
  - finalizer.servicebindingrequest.openshift.io
  - finalizer.servicebindingrequest.openshift.io

If I edit the SBR, and apply again, the finalizer become:

finalizers:
  - finalizer.servicebindingrequest.openshift.io
  - finalizer.servicebindingrequest.openshift.io
  - finalizer.servicebindingrequest.openshift.io
  - finalizer.servicebindingrequest.openshift.io

I repeated a third time and the list grew by two more. And so on. This does not seem to impact functionality but it seems like a possible bug with finalizers handling in the operator.

Map param names from service's secret to deployment

We need a way to map param names from producer side (the service secret) to consumer side (the application).

Let me walk through an example, using the current model in the ServiceCatalog, but should be good starting point for describing the problem I see.

MongoDB template has 2 set of generated variables. The credentials and the uri. These have defined names in the generated ServiceBinding as: database_name, password, root_password, username and uri. This will always be named the same for any ServiceBinding created by this template.

When consuming the application, the author creator might have named his variables as: DB_NAME, PWD, ADMIN_PWD, USER and JDBC_CONNECT_STRING. This naming, of course, can be changed, but there's a lot of legacy applications that shouldn't need to be modified.

We need a way to say:

  • Inject database_name from ServiceBinding secret into DB_NAME env in deployment
  • Inject password from ServiceBinding secret into PWD env in deployment
  • Inject database_name from ServiceBinding secret into DB_NAME env in deployment
  • and so forth

And also, we need a way to say:

  • Inject into JDBC_CONNECT_STRING the value: mysql://{USER}:{PWD}@{SERVICE_NAME}:{SERVICE_PORT}/{DB_NAME}
    As the URI my application requires is not in the same format the URI is provided in the ServiceBinding, cause every language (Python, Java,...) might need a URI in a different way.

These mappings should be done in the ServiceBindingRequest that this operator is defining, so any application can consume any service and don't require any code change or any changes to the Deployment definition. As the ServiceBindingRequest is the link between the Deployment and the consumed service.

Also, a developer might want to change from one service provider (template) to a different one (operator from ISV1) to yet another (operator from ISV2), and these might have also defined the names in different ways. The developer should not need to change his code (or the deployment) to adapt to how the service is naming these parameters.

Does it make sense what I say?

Trigger re-binding if the BackingService CR's "associated" secret changes

Description

A BackingService like a Database managed by a Database CR might use/refer to a secret or a configmap which stores credentials or connection information on how to connect to the database. When such objects change, a rebinding ( "reconcile" ) should be triggered.

What does a BackingService CR associated object imply ?

By "Database CR being associated with a secret", it is implied that the secret or configmap has an annotation containing the details of the ServiceBindingRequest which consumed it as part of binding an app to a specific Backing Service CR.

Acceptance Criteria

Ensure the following scenario works:

  • Create a Database CR.
  • Import App
  • Create a Binding request
  • Validate the the imported app's CRUD works.
  • Validate that the ServiceBindingRequest has set an annotation on all secrets/configmaps that were to referred to, in the Database CR
  • Modify credentials in the backing service using an update to the secret associated by the Database CR.
  • Ensure that reconcile is triggered and the imported app's CRUD works with the new credentials.

Hint

  • Today, annotations in the Database CR's secrets/configmaps are not set in the binder. We should be doing so as part of this story.

Double base64 encoding for customEnvVar retrieved from a secret

I am using the customEnvVar to retrieve some values from a secret that I want to inject into a DeploymentConfig with a specific name for the environment variables, as those are the names used in existing node.js code. My service binding request looks like:

apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: srb
spec:
  applicationSelector:
    matchLabels:
      connects-to: translator
      environment: demo
    group: apps.openshift.io
    version: v1
    resource: deploymentconfigs
  backingServiceSelector:
    group: ibmcloud.ibm.com
    version: v1alpha1
    kind: Binding
    resourceRef: mytranslator-binding
  customEnvVar:
    - name: LANGUAGE_TRANSLATOR_URL
      value: '{{ index .status.secretName "url" }}'
    - name: LANGUAGE_TRANSLATOR_IAM_APIKEY
      value: '{{ index .status.secretName "apikey" }}'

Expected Result:
LANGUAGE_TRANSLATOR_URL and LANGUAGE_TRANSLATOR_IAM_APIKEY are present in the binding-request secret and the value is base64 encoded, so that I can get the actual value with base64 decode

Actual Result:
LANGUAGE_TRANSLATOR_URL and LANGUAGE_TRANSLATOR_IAM_APIKEY are present in the binding-request secret but they are base64 encoded twice, so doing base64 decode provides a base64 encoded string which needs to be base64 decoded to get the actual value. This does not work with the existing node.js code.

Have the ServiceBindingOperator store DB credentials in vault

The ServiceBindingRequest operator could run it's own Vault server (managed by the Banzai/vault operator ) where it would 'store' credentials for backing services after picking it up from the backing service CR's status/spec/secret .

As per [1],
The service binding controller could then add the required annotations in the DeploymentConfig and depend on banzai/vault operator to react to it, and directly inject credentials into pods.

Note: Depending on banzai/vault[1] from our operator would imply that webhooks would be used to create pods.

[1] https://banzaicloud.com/blog/inject-secrets-into-pods-vault/

However, this needn't be default approach to binding.

We could support a secure:true in our ServiceBindingRequest CRD in which case the controller would store credentials in Vault and inject them into the pods.

If secure:false, the usual intermediate secret creation approach would be used.

The operator does not reconcile (do not watch) on the intermediate secret

When a Backing service is bound with an Application by the operator an intermediate secret is used to store all the binding values that are injected into the Application.

When this intermediate secret's data is modified the operator is expected to detect this, reconcile and revert the intermediate secret back.

That is actually not happening - when the intermediate secret is modified, the operator ignores it.

Possible cause of the e2e failures described in https://jira.coreos.com/browse/APPSVC-147

Consolidate application object updates from different SBRs

I have an application that binds to numerous backing services. Assume the SBRs for this application are created in succession from a single YAML file. Especially when initially deploying this application, it would be most efficient if the application object updates from the different SBRs were consolidated into a single update. In the service-binding-operator SBR reconciliation loop, consider sorting by application object and consolidating updates by application object.

Question: Can we use resourceRef to select application instead of matchLabels ?

Since UI topology view would have direct access of the application DC, we can use resourceRef instead of matchLabels

What are your thoughts @sbose78 @otaviof ?

Below is the SBR request created by devconsole UI internally

  const serviceBindingRequest = {
    apiVersion: 'apps.openshift.io/v1alpha1',
    kind: 'ServiceBindingRequest',
    metadata: {
      name: sbrName,
      namespace,
    },
    spec: {
      applicationSelector: {
        resourceRef: sourceName,
        group: sourceGroup[0],
        version: sourceGroup[1],
        resource: modelFor(source.kind).plural,
      },
      backingServiceSelector: {
        group: targetResourceGroup[0],
        version: targetResourceGroup[1],
        kind: targetResourceKind,
        resourceRef: targetResourceRefName,
      },
    },
  };

UI SBR PR: https://github.com/openshift/console/pull/3065/files#diff-5c764acb551787fd095fe2442835a25bR4

Feedback: Generalize BackingServiceSelector to select multiple resources including OLM-annotated CRs as well as ConfigMaps and Secrets

Backing service operators such as mongodb-enterprise-operator and strimzi-kafka-operator have distinct CRs for the service itself and its individual users. In order for an application to bind to such services as a specific user, the ServiceBindingRequest backingServiceSelector should support the selection of multiple CRs, in this case a service CR and a specific user CR. The OLM annotations associated with each of these CRs can be used to extract fields that are subsequently aggregated into a single secret.

A similar approach can be used to support backing services that do not yet have OLM-annotated descriptors. In these cases, the backingServiceSelector could be defined to select a set of ConfigMaps and/or Secrets. The service-binding-operator would aggregate the fields of the selected ConfigMaps and/or Secrets into a single secret. A list of JSON Secret Transforms defined in the ServiceBindingRequest (based on the SecretTransform type defined for ServiceBindings/AppBindings) could be used to transform the aggregated secret into a form ready for injection via the ApplicationSelector.

Support for binding to external services could also leverage a backingServiceSelector defined to select a manually-populated ConfigMap and/or Secret. Again, the aggregated, transformed secret would then presumably be ready for injection via the ApplicationSelector.

Feedback: Enhance ServiceBindingRequest to support simple secret transforms

Often, the secret generated by the service-binding-operator based on the backing service CRD annotations requires transformation before it is usable by the client application. The Service Catalog ServiceBinding (https://svc-cat.io/docs/resources/#servicebinding) and the KubeDB AppBinding (https://github.com/kmodules/custom-resources/blob/711575c0b8a9fda8169c1035048558f5bb1fe91c/apis/appcatalog/v1alpha1/appbinding_types.go#L180) support simple JSON-based transforms. Consider enhancing the ServiceBindingRequest to support these as well.

Some example JSONPath SecretTransforms are available here: https://trello.com/c/UmdhbQd8/129-catalog310-better-credentials-remapping-w-jsonpath-templates. An extract of some examples are inlined below:

  1. Add a new key like this:
secretTransform:
- addKey:
    key: "my-new-key"
    stringValue: "my-new-value"

or:

secretTransform:
- addKey:
    key: "my-new-key"
    value: base64-encoded-byte-array-value
  1. Merge an existing Secret into the credentials Secret:
secretTransform:
- addKeysFrom:
    secretRef: 
      namespace: my-namespace
      name: my-other-secret
  1. You can also create a new key by transforming existing keys (this uses a JSONPath template):
secretTransform:
- addKey:
    key: "my-new-key"
    jsonPathExpression: "key one is {.special-key-1}, key two is {.special-key-2}"
  1. Remove an entry from credentials Secret:
secretTransform:
- removeKey:
    key: "special-key-1"

`make local` fails with `resourceKind` required error

kubectl delete -f deploy/crds/apps_v1alpha1_servicebindingrequest_cr.yaml
Error from server (NotFound): error when deleting "deploy/crds/apps_v1alpha1_servicebindingrequest_cr.yaml": servicebindingrequests.apps.openshift.io "example-servicebindingrequest" not found
make: [Makefile:310: deploy-clean] Error 1 (ignored)
kubectl delete -f deploy/crds/apps_v1alpha1_servicebindingrequest_crd.yaml
customresourcedefinition.apiextensions.k8s.io "servicebindingrequests.apps.openshift.io" deleted
kubectl delete -f deploy/operator.yaml
Error from server (NotFound): error when deleting "deploy/operator.yaml": deployments.apps "service-binding-operator" not found
make: [Makefile:312: deploy-clean] Error 1 (ignored)
kubectl delete -f deploy/role_binding.yaml
rolebinding.rbac.authorization.k8s.io "service-binding-operator" deleted
kubectl delete -f deploy/role.yaml
role.rbac.authorization.k8s.io "service-binding-operator" deleted
kubectl delete -f deploy/service_account.yaml
serviceaccount "service-binding-operator" deleted
kubectl create -f deploy/service_account.yaml
serviceaccount/service-binding-operator created
kubectl create -f deploy/role.yaml
role.rbac.authorization.k8s.io/service-binding-operator created
kubectl create -f deploy/role_binding.yaml
rolebinding.rbac.authorization.k8s.io/service-binding-operator created
kubectl create -f deploy/crds/apps_v1alpha1_servicebindingrequest_crd.yaml
customresourcedefinition.apiextensions.k8s.io/servicebindingrequests.apps.openshift.io created
kubectl apply -f deploy/crds/apps_v1alpha1_servicebindingrequest_cr.yaml
The ServiceBindingRequest "example-servicebindingrequest" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"apps.openshift.io/v1alpha1", "kind":"ServiceBindingRequest", "metadata":map[string]interface {}{"annotations":map[string]interface {}{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"apps.openshift.io/v1alpha1\",\"kind\":\"ServiceBindingRequest\",\"metadata\":{\"annotations\":{},\"name\":\"example-servicebindingrequest\",\"namespace\":\"default\"},\"spec\":{\"applicationSelector\":{\"matchLabels\":{\"connects-to\":\"postgres\",\"environment\":\"production\"},\"resourceKind\":\"Deployment\"},\"backingServiceSelector\":{\"resourceName\":\"databases.example.org\",\"resourceRef\":\"pg-instance\",\"resourceVersion\":\"v1alpha1\"},\"mountPathPrefix\":\"/var/credentials\"}}\n"}, "creationTimestamp":"2019-07-30T17:45:55Z", "generation":1, "name":"example-servicebindingrequest", "namespace":"default", "uid":"e1859d07-b2f1-11e9-96c1-080027d8ec88"}, "spec":map[string]interface {}{"applicationSelector":map[string]interface {}{"matchLabels":map[string]interface {}{"connects-to":"postgres", "environment":"production"}, "resourceKind":"Deployment"}, "backingServiceSelector":map[string]interface {}{"resourceName":"databases.example.org", "resourceRef":"pg-instance", "resourceVersion":"v1alpha1"}, "mountPathPrefix":"/var/credentials"}}: validation failure list:
spec.backingServiceSelector.resourceKind in body is required
spec.envVarPrefix in body is required
make: *** [Makefile:305: deploy-cr] Error 1

Feedback: Provide more complex example w/TLS-based auth and User CRD

Backing Service operators like https://operatorhub.io/operator/strimzi-kafka-operator and https://operatorhub.io/operator/mongodb-enterprise support TLS-based authentication (SCRAM-SHA over TLS, TLS mutual authentication, etc.) and support individual User CRs (kafka.strimzi.io/v1beta1 - KafkaUser, mongodb.com/v1 - MongoDBUser) that are distinct from the Backing Service CR.

When TLS-based authentication is in use, how are the ca.crt and optional tls.crt/tls.key integrated into the client app deployment? Is the secret hosting these resources mounted as a volume?

Also when a backing service separates User CRs from the Service CR, how are servicebindingrequest annotations on the CRs defined? It is likely connection-related parameters are included in the backing service CR and credential-related parameters are included in the user CR via a secret.

A proposed Service Binding mockup for one of these types of backing services would be helpful to fully understand the feature set.

Backing service operator manages/creates credentials in Vault

Scenario
There's a PostgreSQL operator whose controller writes the database credentials in Vault.

Our service binding controller needs to be able to read the credentials from Vault and create the intermediate secret out of it. In this case, the secret gets stored in a decrypted way in etcd. The use case of not sync'ing the credentials into an intermediate secret is logged in #74

Questions

  1. How does the PostgreSQL operator connect to the Vault server? The Database CR would need the vault connection information to be able to do so.
  2. How does the servicebinding controller connect to the Vault server used by the PostgreSQL operator.

Backing Service CRD Annotation parsing error

I am running the service binding operator from master (b0c9641 on Dec 5) and using make local to run. When I try to run the example in examples/nodejs_postgresql I get the following error:

2019-12-06T11:43:38.954-0500    ERROR   controller-runtime.controller   Reconciler error        {"controller": "servicebindingrequest-controller", "request": "service-binding-demo/binding-request", "error": "should have two parts"}

from what I can see the error originates in olm.go, in the following part of the code:

func splitBindingInfo(s string) (string, string, error) {
	parts := strings.SplitN(s, "-", 2)

	if len(parts) != 2 {
		return "", "", fmt.Errorf("should have two parts")
	}

	return parts[0], parts[1], nil
}

When I added some debugging print, I noticed that some strings have the - but some don't, and that is where the issue occurs, for example:

status.dbConfigMap-db.host
status.dbConfigMap-db.user
status.dbConnectionIP. <- this where the error happens

It seems I can get past that error if I replace the line parts := strings.SplitN(s, "-", 2) with parts := strings.SplitN(s, ".", 2), however I am not clear on what what the intent for splitBindingInfo is and if this makes sense.

Binding fails when applicationSelector uses resourceRef

The following SBR exposed the issue:

apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  creationTimestamp: '2019-10-24T18:11:41Z'
  generation: 1
  name: nodejs-rest-http-crud-sbr
  namespace: div
  resourceVersion: '202896'
  selfLink: >-
    /apis/apps.openshift.io/v1alpha1/namespaces/div/servicebindingrequests/nodejs-rest-http-crud-sbr
  uid: c8e165c4-b2ca-4042-b8d3-dd651cdfa1e4
spec:
  applicationSelector:
    group: apps.openshift.io
    resource: deploymentconfigs
    resourceRef: nodejs-rest-http-crud
    version: v1
  backingServiceSelector:
    group: postgresql.baiju.dev
    kind: Database
    resourceRef: db-demo
    version: v1alpha1
status:
  bindingStatus: Fail
  secret: nodejs-rest-http-crud-sbr

The binding fails because of the following error:

{"level":"error","ts":1571942209.6852767,"msg":"On binding application.","Request.Namespace":"div","Request.Name":"binding-request","ServiceBindingRequest.Name":"binding-request","error":"\"name\" is not a known field selector: only \"metadata.name\", \"metadata.namespace\""
...
...
{"level":"error","ts":1571942209.6913586,"logger":"kubebuilder.controller","msg":"Reconciler error","controller":"servicebindingrequest-controller","request":"div/binding-request","error":"\"name\" is not a known field selector: only \"metadata.name\", \"metadata.namespace\""

Reason:

The fieldSelector should be metadata.name instead of name.

fieldName["name"] = b.sbr.Spec.ApplicationSelector.ResourceRef

Parsing error for service binding request

I am running the service binding operator from master (b0c9641 on Dec 5) and using make local to run. When I try to run the example in examples/nodejs_postgresql with the following service binding request:

apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: service-binding-demo
spec:
  applicationSelector:
    resourceRef: nodejs-rest-http-crud
    group: apps.openshift.io
    version: v1
    resource: deploymentconfigs
  backingServiceSelector:
    group: postgresql.baiju.dev
    version: v1alpha1
    kind: Database
    resourceRef: db-demo

I get the following error:

2019-12-06T12:00:31.005-0500    DEBUG   sbrcontroller.buildGVKPredicate Predicate evaluated   {"GVK": "/v1, Kind=Secret", "dataFieldsAreEqual": true}
2019-12-06T12:00:31.047-0500    ERROR   controller-runtime.controller   Reconciler error      {"controller": "servicebindingrequest-controller", "request": "service-binding-demo/binding-request", "error": "ServiceBindingRequest.apps.openshift.io \"binding-request\" is invalid: []: Invalid value: .....
....

"ServiceBindingRequest\"}: validation failure list:\nspec.customEnvVar in body must be of type array: \"null\"\nspec.applicationSelector.matchLabels in body must be of type object: \"null\""}
github.com/go-logr/zapr.(*zapLogger).Error

it looks like it is complaining that there is no label selector and no customEnvVar, but my understanding is that they should be optional ?

matchLabels in applicationSelector should not be required if using resourceRef instead

If I want to use the resourceRef instead of matchLabels in the applicationSelector which should be possible according to the operator's behavior:

If I use resourceRef instead of matchLabels I get the following errors:

  • unknown field "resourceRef" in io.openshift.apps.v1alpha1.ServiceBindingRequest.spec.applicationSelector (#187)
  • missing required field "matchLabels" in io.openshift.apps.v1alpha1.ServiceBindingRequest.spec.applicationSelector
$  cat <<EOS |kubectl apply -f -  
apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: service-binding-demo
spec:
  applicationSelector:
    group: apps.openshift.io
    version: v1
    resource: deploymentconfigs
    resourceRef: dc-app
  backingServiceSelector:
    group: postgresql.baiju.dev
    version: v1alpha1
    kind: Database
    resourceRef: db-demo
EOS
error: error validating "STDIN": error validating data: [ValidationError(ServiceBindingRequest.spec.applicationSelector): unknown field "resourceRef" in io.openshift.apps.v1alpha1.ServiceBindingRequest.spec.applicationSelector, ValidationError(ServiceBindingRequest.spec.applicationSelector): missing required field "matchLabels" in io.openshift.apps.v1alpha1.ServiceBindingRequest.spec.applicationSelector]; if you choose to ignore these errors, turn validation off with --validate=false

This affects the v0.0.20 release.

It would be more flexible (for testing, etc.) if the container image was not hardcoded

For reference - in the Service Binding Operator:

grep -r containerImage manifests
manifests/service-binding-operator.v0.0.10.clusterserviceversion.yaml:    containerImage: quay.io/redhat-developer/app-binding-operator:v0.0.10

For comparison - in the DB operator that we used for the demo:

grep -r containerImage manifests
manifests/0.0.4/postgresql-operator.v0.0.4.clusterserviceversion.yaml:    containerImage: REPLACE_IMAGE

"Error calling index" when using customEnvVar

I am running the service binding operator from master (b0c9641 on Dec 5) and using make local to run. When I try to run the example in examples/nodejs_postgresql with the following service binding request:

apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: service-binding-demo
spec:
  applicationSelector:
    matchLabels:
      connects-to: postgres
      environment: demo
    group: apps.openshift.io
    version: v1
    resource: deploymentconfigs
  backingServiceSelector:
    group: postgresql.baiju.dev
    version: v1alpha1
    kind: Database
    resourceRef: db-demo
  customEnvVar:
    - name: DB_USER
      value: '{{ index .status.dbConfigMap "db.username" }}'  

I get the following error:

{"level":"error","ts":1575664614.742795,"logger":"reconciler.bind","msg":"On saving secret data..","Request.Namespace":"service-binding-demo","Request.Name":"binding-request","ServiceBindingRequest.Name":"binding-request","error":"template: set:1:3: executing \"set\" at <index .status.dbConfigMap \"db.username\">: error calling index: index of untyped nil","stacktrace": ...

End to End test fails with error `Intermediary secret contents are invalid`

    --- FAIL: TestAddSchemesToFramework/end-to-end (150.17s)
        --- FAIL: TestAddSchemesToFramework/end-to-end/scenario-db-app-sbr (30.09s)
            servicebindingrequest_test.go:190: Creating a new test context...
            servicebindingrequest_test.go:86: Initializing cluster resources...
            servicebindingrequest_test.go:96: Using namespace 'test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7' for testing...
            servicebindingrequest_test.go:119: Cleaning namespace:
            servicebindingrequest_test.go:121: 	Databases:
            servicebindingrequest_test.go:133: 	ServiceBindingRequests:
            servicebindingrequest_test.go:145: 	Deployments:
            servicebindingrequest_test.go:157: 	ClusterServiceVersions:
            servicebindingrequest_test.go:169: 	Secrets:
            servicebindingrequest_test.go:177: 		e2e-service-binding-request...
            servicebindingrequest_test.go:310: Starting end-to-end tests for operator!
            servicebindingrequest_test.go:312: Creating ClusterServiceVersion mock object...
            client.go:57: resource type ClusterServiceVersion with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/cluster-service-version) created
            servicebindingrequest_test.go:385: Creating Database mock object...
            client.go:57: resource type Database with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-db-testing) created
            servicebindingrequest_test.go:389: Updating Database status, adding 'DBCredentials'
            servicebindingrequest_test.go:394: Creating Database credentials secret mock object...
            client.go:57: resource type Secret with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-db-credentials) created
            servicebindingrequest_test.go:405: Creating Deployment mock object...
            client.go:57: resource type Deployment with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-application) created
            servicebindingrequest_test.go:410: Waiting for application deployment reach one replica...
            wait_util.go:64: Waiting for full availability of e2e-application deployment (0/1)
            wait_util.go:64: Waiting for full availability of e2e-application deployment (0/1)
            wait_util.go:64: Waiting for full availability of e2e-application deployment (0/1)
            wait_util.go:64: Waiting for full availability of e2e-application deployment (0/1)
            wait_util.go:70: Deployment available (1/1)
            servicebindingrequest_test.go:415: Reading application deployment 'e2e-application'
            servicebindingrequest_test.go:425: Creating ServiceBindingRequest mock object...
            client.go:57: resource type  with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-service-binding-request) created
            servicebindingrequest_test.go:337: Inspecting deployment structure...
            servicebindingrequest_test.go:339: Inspecting deployment 'test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-application'
            servicebindingrequest_test.go:342: Error on inspecting deployment: '&errors.errorString{s:"can't find envFrom in first container"}'
            servicebindingrequest_test.go:339: Inspecting deployment 'test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-application'
            servicebindingrequest_test.go:346: Deployment: Result after attempts, error: '<nil>'
            require.go:794: 
                	Error Trace:	servicebindingrequest_test.go:352
                	            				servicebindingrequest_test.go:197
                	            				servicebindingrequest_test.go:64
                	Error:      	Received unexpected error:
                	            	can't find DATABASE_SECRET_USER in data
                	Test:       	TestAddSchemesToFramework/end-to-end/scenario-db-app-sbr
                	Messages:   	Intermediary secret contents are invalid: nil
            client.go:75: resource type  with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-service-binding-request) successfully deleted
            client.go:75: resource type Deployment with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-application) successfully deleted
            client.go:75: resource type Secret with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-db-credentials) successfully deleted
            client.go:75: resource type Database with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/e2e-db-testing) successfully deleted
            client.go:75: resource type ClusterServiceVersion with namespace/name (test-namespace-cfbb4589-bde1-46a4-b967-811d9c20e4b7/cluster-service-version) successfully deleted
        --- FAIL: TestAddSchemesToFramework/end-to-end/scenario-sbr-db-app (120.08s)

Backing service operator's credentials managed in vault, and intermediate secret NOT to be created

Scenario
There's a PostgreSQL operator whose controller writes the database credentials to Vault.

Our service binding controller needs to be able to bind the imported app with the credentials without writing the secret into a Kuberntes secret object ( ie, etcd ).

Possible Solution:

The service binding operator depends on the banzai/vault operator to add annotations such that the banzai/vault operator would then take care of creating init containers which in turn would inject env vars:
https://banzaicloud.com/blog/inject-secrets-into-pods-vault/

Feedback: Make ApplicationSelector optional to support Client App Operators which use ServiceBindingRequest Status Secret directly

A client app operator that implements complex secret transforms such as converting an elastic-cloud operator Elasticsearch http server pem-formatted CA into a JKS truststore or builds a strimzi-kafka-operator Kafka consumer properties file based on multiple annotated backing service properties, will likely want to directly use the ServiceBindingRequest Status Secret as its complex secret transform source. This is in lieu of having the service-binding-operator directly inject its secret into the Deployment of the client app it manages. This allows a client app operator to treat the secret created by the service-binding-operator as an "intermediate" secret which it can then further transform if needed before it itself integrates the "final transformed" secret into the client app Deployment. When a client app operator is responsible for managing the client app Deployment, it is quite unnatural for the service-binding-operator to bypass the client app operator and directly inject secrets into the client app Deployment.

ServiceBindingRequest CR requires both resourceName and resourceKind despite CRD spec

Steps to reproduce

Check the current version of the application...

$ oc get dc service-binding-demo -o jsonpath='{.status.latestVersion}'
9

... and check for the envFrom presence...

$ oc get dc service-binding-demo -o jsonpath='{.spec.template.spec.containers[0].envFrom}'

...nothing, which is expected.

So the current version is 9. Then, after applying the ServiceBindingRequest I'd expect that to be 10 (which would indicate that the DC was updated by the operator).

Now, apply ServiceBindingRequest according to the CRD (and code) - i.e. resourcenName, resourceVersion and resourceRef (Variant N)...

$ cat tmp/sbr-n.yaml
apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: service-binding-demo
spec:
  applicationSelector:
    matchLabels:
      connects-to: postgres
      environment: demo
    resourceKind: DeploymentConfig
  backingServiceSelector:
    resourceName: postgresql.baiju.dev
    resourceVersion: v1alpha1
    resourceRef: db-demo
$ oc apply -f tmp/sbr-n.yaml
The ServiceBindingRequest "binding-request" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"apps.openshift.io/v1alpha1", "kind":"ServiceBindingRequest", "metadata":map[string]interface {}{"annotations":map[string]interface {}{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"apps.openshift.io/v1alpha1\",\"kind\":\"ServiceBindingRequest\",\"metadata\":{\"annotations\":{},\"name\":\"binding-request\",\"namespace\":\"service-binding-demo\"},\"spec\":{\"applicationSelector\":{\"matchLabels\":{\"connects-to\":\"postgres\",\"environment\":\"demo\"},\"resourceKind\":\"DeploymentConfig\"},\"backingServiceSelector\":{\"resourceName\":\"postgresql.baiju.dev\",\"resourceRef\":\"db-demo\",\"resourceVersion\":\"v1alpha1\"}}}\n"}, "creationTimestamp":"2019-07-12T09:52:13Z", "generation":1, "name":"binding-request", "namespace":"service-binding-demo", "uid":"b98233f4-a48a-11e9-934e-024164363ef2"}, "spec":map[string]interface {}{"applicationSelector":map[string]interface {}{"matchLabels":map[string]interface {}{"connects-to":"postgres", "environment":"demo"}, "resourceKind":"DeploymentConfig"}, "backingServiceSelector":map[string]interface {}{"resourceName":"postgresql.baiju.dev", "resourceRef":"db-demo", "resourceVersion":"v1alpha1"}}}: validation failure list:
spec.backingServiceSelector.resourceKind in body is required

... and that caused an error complaining that resourceKind is required. So try to use the resourceKind instead (Variant K):

$ cat tmp/sbr-k.yaml 
apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: service-binding-demo
spec:
  applicationSelector:
    matchLabels:
      connects-to: postgres
      environment: demo
    resourceKind: DeploymentConfig
  backingServiceSelector:
    resourceKind: Database
    resourceVersion: v1alpha1
    resourceRef: db-demo
$ oc apply -f tmp/sbr-k.yaml
servicebindingrequest.apps.openshift.io/binding-request created

After a while check for the status of the DC...

$ oc get dc service-binding-demo -o jsonpath='{.status.latestVersion}'
9
$ oc get dc service-binding-demo -o jsonpath='{.spec.template.spec.containers[0].envFrom}'

... the version is still 9 and no envFrom was injeceted - nothing happeed so it does not work.

So try to use both resourceName and resourceKind at the same time (Variant KN):

$ oc delete servicebindingrequest binding-request
servicebindingrequest.apps.openshift.io "binding-request" deleted
$ cat tmp/sbr-kn.yaml 
apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request
  namespace: service-binding-demo
spec:
  applicationSelector:
    matchLabels:
      connects-to: postgres
      environment: demo
    resourceKind: DeploymentConfig
  backingServiceSelector:
    resourceName: postgresql.baiju.dev
    resourceKind: Database
    resourceVersion: v1alpha1
    resourceRef: db-demo

This works!

$ oc get dc service-binding-demo -o jsonpath='{.status.latestVersion}'
10

$ oc get dc service-binding-demo -o jsonpath='{.spec.template.spec.containers[0].envFrom}'
[map[secretRef:map[name:binding-request]]]

Express what is interesting for binding in the CRD (openapi validation spec?) itself

Public clone of https://jira.coreos.com/browse/APPSVC-45

"The current approach in service-binding-request is on OLM (Operator Life-cycle Manager) to recognize which fields are necessary to bind one application to another. However, we would like to support virtually all operators in Kubernetes, and therefore we would like to explore Kubernetes objects specification using OpenAPI.

Acceptance Criteria
Create spin-off document (for example this) to describe OpenAPI based approach, in order to identify what are the steps and complexity we might find. Have a look at the project source code to identify which types we need to change and how that would affect the sequence of steps that we execute to reconcile an object."

How do we watch CRs which are registered post SBR operator install?

To 'manage' the binding, we could watch changes to the "BackingService" CR. However, watches are added at the controller boot time - and you can't anticipate what is to be added.

If you did anticipate ( from the OperatorSource ) or some static list, you still wouldn't be able to do a


	gvk := schema.GroupVersionKind{
		Group:   "baiju",
		Version: "valpha1",
		Kind:    "databases",
	}

	objRuntime, _ := scheme.NewUnstructuredCreator().New(gvk)

	err = c.Watch(&source.Kind{Type: objRuntime}, handler)

This would return an error:

{"level":"error","ts":1566413911.0931442,"logger":"kubebuilder.source","msg":"if kind is a CRD, it should be installed before calling Start","kind":"databases.g1","error":"no matches for kind \"databases\" in version \"g1/valpha1\"","stacktrace":"github.com/go-logr/zapr.(*zapLogger).Error\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/vendor/github.com/go-logr/zapr/zapr.go:128\nsigs.k8s.io/controller-runtime/pkg/source.(*Kind).Start\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/vendor/sigs.k8s.io/controller-runtime/pkg/source/source.go:89\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Watch\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/vendor/sigs.k8s.io/controller-runtime/pkg/internal/controller/controller.go:122\ngithub.com/redhat-developer/service-binding-operator/pkg/controller/servicebindingrequest.add\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/pkg/controller/servicebindingrequest/controller.go:95\ngithub.com/redhat-developer/service-binding-operator/pkg/controller/servicebindingrequest.Add\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/pkg/controller/servicebindingrequest/controller.go:31\ngithub.com/redhat-developer/service-binding-operator/pkg/controller.AddToManager\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/pkg/controller/controller.go:13\nmain.main\n\t/Users/sbose/appbinding/src/github.com/redhat-developer/service-binding-operator/cmd/manager/main.go:122\nruntime.main\n\t/usr/local/Cellar/go/1.12.7/libexec/src/runtime/proc.go:200"}

Unless the CRD was installed before the ServiceBindingRequest operator is installed

Feedback: what exactly is the contract between a bindable service operator and an application?

As a first timer, I read the README.md in the root of the repository and the READMEs for the examples, but I didn't understand how exactly the application was getting bound to its service.

What I would like to have is a normative reference to the contract between the bindable service operator and the application. I'm sure that info is out there, it's just not obviously presented.

Additionally, in all four examples the order of deployment is such that the application is deployed first and is expected to operate without the services it's bound to. Is that part of the contract an application must comply with?

Proposal: Add Post-Retrieve Hooks

Add Post-Retrieve Hooks

Prepared by: Akash Shinde([email protected]), Avni Sharma([email protected])

Abstract

This proposal aims to add support for custom hooks which would get executed before storing bindable variables in Secret.

Motivation

There are use cases where we would need to update/process bindable variable and then store back to secrets.
One such a usecase we are going through is adding support of custom env variables.

Design

Architecture

Implementation

Current Retrieve() call

func (r *Retriever) Retrieve() error {
    
    ....
    
    Run []hooks
    
    return r.saveDataOnSecret()
}

Hook will accept the Retriever instance.

struct Hook interface {
    // This would run before storing secrets.
    Run(*Retriever) error
}

Then we can implement this hook to add support custom env variables.

CustomEnvHook
struct CustomEnvHook {
    Hook
}

func (c *CustomEnvHook) Run(r *Retriever) error {
 
    // TODO: Read SBR and figure out all custom env variables to bind
    // Add logic to prepare [key-value] of custom variables. Update r.Data
    // Then r.Data gets persisted.

}

Pros

  1. Keeps Retrieve modulerized-easy to use.
  2. Easy to test and maintain functional components.
  3. Create re-usable, isolated components to avoid redundant logic.

Cons

  1. Implemented in a synchronized manner which might experience some delay.

Feedback: Separate environment specific information

"Deployment Environment Configuration - How to cleanly separate environment configuration from application so that I can deploy a single container to multiple environments such as test, stage and production."

Discussion: Filter out fields from Service, Route resources for binding

This is a discussion thread to determine list of fields to extract from Service or Route yaml
for binding where binding annotations aren't available in OLM/CRD.

Related to https://jira.coreos.com/browse/APPSVC-115

Below are the sample yamls of Service and Route for reference.

1. Service yaml

spec:
  clusterIP: 172.30.131.201
  externalTrafficPolicy: Cluster
  ports:
    - name: web
      nodePort: 32595
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    name: nodejs-example
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}

Fields for binding

  • spec.clusterIP
  • spec.ports.name
  • spec.ports.port
  • spec.ports.targetPort
  • spec.ports.nodePort

2. Route yaml

spec:
  host: nodejs-example-myproject.192.168.99.102.nip.io
  to:
    kind: Service
    name: nodejs-example
    weight: 100
  wildcardPolicy: None
status:
  ingress:
    - conditions:
        - lastTransitionTime: '2019-09-25T07:08:37Z'
          status: 'True'
          type: Admitted
      host: nodejs-example-myproject.192.168.99.102.nip.io
      routerName: router
      wildcardPolicy: None

Fields for Binding

  • spec.host

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.