Giter Club home page Giter Club logo

cd0309-message-passing-projects-starter's Introduction

UdaConnect

Overview

Background

Conferences and conventions are hotspots for making connections. Professionals in attendance often share the same interests and can make valuable business and personal connections with one another. At the same time, these events draw a large crowd and it's often hard to make these connections in the midst of all of these events' excitement and energy. To help attendees make connections, we are building the infrastructure for a service that can inform attendees if they have attended the same booths and presentations at an event.

Goal

You work for a company that is building a app that uses location data from mobile devices. Your company has built a POC application to ingest location data named UdaTracker. This POC was built with the core functionality of ingesting location and identifying individuals who have shared a close geographic proximity.

Management loved the POC so now that there is buy-in, we want to enhance this application. You have been tasked to enhance the POC application into a MVP to handle the large volume of location data that will be ingested.

To do so, you will refactor this application into a microservice architecture using message passing techniques that you have learned in this course. It’s easy to get lost in the countless optimizations and changes that can be made: your priority should be to approach the task as an architect and refactor the application into microservices. File organization, code linting -- these are important but don’t affect the core functionality and can possibly be tagged as TODO’s for now!

Technologies

  • Flask - API webserver
  • SQLAlchemy - Database ORM
  • PostgreSQL - Relational database
  • PostGIS - Spatial plug-in for PostgreSQL enabling geographic queries]
  • Vagrant - Tool for managing virtual deployed environments
  • VirtualBox - Hypervisor allowing you to run multiple operating systems
  • K3s - Lightweight distribution of K8s to easily develop against a local cluster

Running the app

The project has been set up such that you should be able to have the project up and running with Kubernetes.

Prerequisites

We will be installing the tools that we'll need to use for getting our environment set up properly.

  1. Install Docker
  2. Set up a DockerHub account
  3. Set up kubectl
  4. Install VirtualBox with at least version 6.0
  5. Install Vagrant with at least version 2.0

Environment Setup

To run the application, you will need a K8s cluster running locally and to interface with it via kubectl. We will be using Vagrant with VirtualBox to run K3s.

Initialize K3s

In this project's root, run vagrant up.

$ vagrant up

The command will take a while and will leverage VirtualBox to load an openSUSE OS and automatically install K3s. When we are taking a break from development, we can run vagrant suspend to conserve some ouf our system's resources and vagrant resume when we want to bring our resources back up. Some useful vagrant commands can be found in this cheatsheet.

Set up kubectl

After vagrant up is done, you will SSH into the Vagrant environment and retrieve the Kubernetes config file used by kubectl. We want to copy the contents of this file into our local environment so that kubectl knows how to communicate with the K3s cluster.

$ vagrant ssh

You will now be connected inside of the virtual OS. Run sudo cat /etc/rancher/k3s/k3s.yaml to print out the contents of the file. You should see output similar to the one that I've shown below. Note that the output below is just for your reference: every configuration is unique and you should NOT copy the output I have below.

Copy the contents from the output issued from your own command into your clipboard -- we will be pasting it somewhere soon!

$ sudo cat /etc/rancher/k3s/k3s.yaml

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWekNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFU1T1RrNE9EYzFNekFlRncweU1EQTVNVE13T1RFNU1UTmFGdzB6TURBNU1URXdPVEU1TVROYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFU1T1RrNE9EYzFNekJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQk9rc2IvV1FEVVVXczJacUlJWlF4alN2MHFseE9rZXdvRWdBMGtSN2gzZHEKUzFhRjN3L3pnZ0FNNEZNOU1jbFBSMW1sNXZINUVsZUFOV0VTQWRZUnhJeWpJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSVFERjczbWZ4YXBwCmZNS2RnMTF1dCswd3BXcWQvMk5pWE9HL0RvZUo0SnpOYlFJZ1JPcnlvRXMrMnFKUkZ5WC8xQmIydnoyZXpwOHkKZ1dKMkxNYUxrMGJzNXcwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    server: https://127.0.0.1:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
  user:
    password: 485084ed2cc05d84494d5893160836c9
    username: admin

Type exit to exit the virtual OS and you will find yourself back in your computer's session. Create the file (or replace if it already exists) ~/.kube/config and paste the contents of the k3s.yaml output here.

Afterwards, you can test that kubectl works by running a command like kubectl describe services. It should not return any errors.

Steps

  1. kubectl apply -f deployment/db-configmap.yaml - Set up environment variables for the pods
  2. kubectl apply -f deployment/db-secret.yaml - Set up secrets for the pods
  3. kubectl apply -f deployment/postgres.yaml - Set up a Postgres database running PostGIS
  4. kubectl apply -f deployment/udaconnect-api.yaml - Set up the service and deployment for the API
  5. kubectl apply -f deployment/udaconnect-app.yaml - Set up the service and deployment for the web app
  6. sh scripts/run_db_command.sh <POD_NAME> - Seed your database against the postgres pod. (kubectl get pods will give you the POD_NAME)

Manually applying each of the individual yaml files is cumbersome but going through each step provides some context on the content of the starter project. In practice, we would have reduced the number of steps by running the command against a directory to apply of the contents: kubectl apply -f deployment/.

Note: The first time you run this project, you will need to seed the database with dummy data. Use the command sh scripts/run_db_command.sh <POD_NAME> against the postgres pod. (kubectl get pods will give you the POD_NAME). Subsequent runs of kubectl apply for making changes to deployments or services shouldn't require you to seed the database again!

Verifying it Works

Once the project is up and running, you should be able to see 3 deployments and 3 services in Kubernetes: kubectl get pods and kubectl get services - should both return udaconnect-app, udaconnect-api, and postgres

These pages should also load on your web browser:

  • http://localhost:30001/ - OpenAPI Documentation
  • http://localhost:30001/api/ - Base path for API
  • http://localhost:30000/ - Frontend ReactJS Application

Deployment Note

You may notice the odd port numbers being served to localhost. By default, Kubernetes services are only exposed to one another in an internal network. This means that udaconnect-app and udaconnect-api can talk to one another. For us to connect to the cluster as an "outsider", we need to a way to expose these services to localhost.

Connections to the Kubernetes services have been set up through a NodePort. (While we would use a technology like an Ingress Controller to expose our Kubernetes services in deployment, a NodePort will suffice for development.)

Development

New Services

New services can be created inside of the modules/ subfolder. You can choose to write something new with Flask, copy and rework the modules/api service into something new, or just create a very simple Python application.

As a reminder, each module should have:

  1. Dockerfile
  2. Its own corresponding DockerHub repository
  3. requirements.txt for pip packages
  4. __init__.py

Docker Images

udaconnect-app and udaconnect-api use docker images from udacity/nd064-udaconnect-app and udacity/nd064-udaconnect-api. To make changes to the application, build your own Docker image and push it to your own DockerHub repository. Replace the existing container registry path with your own.

Configs and Secrets

In deployment/db-secret.yaml, the secret variable is d293aW1zb3NlY3VyZQ==. The value is simply encoded and not encrypted -- this is not secure! Anyone can decode it to see what it is.

# Decodes the value into plaintext
echo "d293aW1zb3NlY3VyZQ==" | base64 -d

# Encodes the value to base64 encoding. K8s expects your secrets passed in with base64
echo "hotdogsfordinner" | base64

This is okay for development against an exclusively local environment and we want to keep the setup simple so that you can focus on the project tasks. However, in practice we should not commit our code with secret values into our repository. A CI/CD pipeline can help prevent that.

PostgreSQL Database

The database uses a plug-in named PostGIS that supports geographic queries. It introduces GEOMETRY types and functions that we leverage to calculate distance between ST_POINT's which represent latitude and longitude.

You may find it helpful to be able to connect to the database. In general, most of the database complexity is abstracted from you. The Docker container in the starter should be configured with PostGIS. Seed scripts are provided to set up the database table and some rows.

Database Connection

While the Kubernetes service for postgres is running (you can use kubectl get services to check), you can expose the service to connect locally:

kubectl port-forward svc/postgres 5432:5432

This will enable you to connect to the database at localhost. You should then be able to connect to postgresql://localhost:5432/geoconnections. This is assuming you use the built-in values in the deployment config map.

Software

To manually connect to the database, you will need software compatible with PostgreSQL.

  • CLI users will find psql to be the industry standard.
  • GUI users will find pgAdmin to be a popular open-source solution.

Architecture Diagrams

Your architecture diagram should focus on the services and how they talk to one another. For our project, we want the diagram in a .png format. Some popular free software and tools to create architecture diagrams:

  1. Lucidchart
  2. Google Docs Drawings (In a Google Doc, Insert - Drawing - + New)
  3. Diagrams.net

Tips

  • We can access a running Docker container using kubectl exec -it <pod_id> sh. From there, we can curl an endpoint to debug network issues.
  • The starter project uses Python Flask. Flask doesn't work well with asyncio out-of-the-box. Consider using multiprocessing to create threads for asynchronous behavior in a standard Flask application.

cd0309-message-passing-projects-starter's People

Contributors

abhiojha8 avatar douglasbergman avatar eyongkevin avatar leejustin avatar marcindulak avatar mmphego avatar ronny-udacity avatar sarah-udacity avatar sudkul avatar supirman avatar theomelo avatar udacity-content-dockerhub avatar

Stargazers

 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

cd0309-message-passing-projects-starter's Issues

Having issues setting up a vagrant virtual box in the Oracle VirtualBox

I am using macOS Big Sur version 11.6, VirtualBox 6.1.26 and Vagrant 2.2.18

I had successfully use VirtualBox 6.1.24 with Vagrant 2.2.16 and had no issues, then I upgraded my Oracle VirtualBox to the latest version 6.1.26 about two months ago and no issues. Then today, I updated my Vagrant to 2.2.18

The Oracle VirtualBox version 6.1.x or higher discontinued VMs with software virtualization. To run VMs with software virtualization you need Oracle VirtualBox version 6.0.x. Version 6.0 will remain supported until July 2020. See here https://www.virtualbox.org/wiki/Downloads "Please also use version 6.0 if you need to run VMs with software virtualization, as this has been discontinued in 6.1. Version 6.0 will remain supported until July 2020."

Therefore, I uninstall VirtualBox 6.1.26 and attempted to install all the different 6.0.x versions in my macOS because they support OpenSUSE 15 and OpenSUSE 13 / Leap 42; however, I was not able to install any of the VirtualBox 6.0.x versions in my macOS, all of the installations failed.

Therefore, I reinstall VirtualBox 6.1.26 and tried with Vagrant 2.2.16 and Vagrant 2.2.18 and I got the same results ...

So I am stuck, I am not able to execute "vagrant up" against this Vagrantfie --> https://github.com/udacity/nd064-c2-message-passing-projects-starter/blob/master/Vagrantfile

When I executed "vagrant up" I am getting the error below. It does not matter if I am using generic/opensuse42 or generic/opensuse15 or roboxes/opensuse15 or opensuse/Leap-15.2.x86_64, etc.

vagrant up
Bringing machine 'master' up with 'virtualbox' provider...
==> master: Box 'opensuse/Leap-15.2.x86_64' could not be found. Attempting to find and install...
master: Box Provider: virtualbox
master: Box Version: >= 0
==> master: Loading metadata for box 'opensuse/Leap-15.2.x86_64'
master: URL: https://vagrantcloud.com/opensuse/Leap-15.2.x86_64
==> master: Adding box 'opensuse/Leap-15.2.x86_64' (v15.2.31.570) for provider: virtualbox
master: Downloading: https://vagrantcloud.com/opensuse/boxes/Leap-15.2.x86_64/versions/15.2.31.570/providers/virtualbox.box
==> master: Successfully added box 'opensuse/Leap-15.2.x86_64' (v15.2.31.570) for 'virtualbox'!
==> master: Importing base box 'opensuse/Leap-15.2.x86_64'...
==> master: Generating MAC address for NAT networking...
==> master: Checking if box 'opensuse/Leap-15.2.x86_64' version '15.2.31.570' is up to date...
==> master: Setting the name of the VM: master
==> master: Clearing any previously set network interfaces...
==> master: Preparing network interfaces based on configuration...
master: Adapter 1: nat
master: Adapter 2: intnet
==> master: Forwarding ports...
master: 22 (guest) => 2000 (host) (adapter 1)
master: 6443 (guest) => 6443 (host) (adapter 1)
master: 30000 (guest) => 30000 (host) (adapter 1)
master: 30001 (guest) => 30001 (host) (adapter 1)
master: 30002 (guest) => 30002 (host) (adapter 1)
master: 30003 (guest) => 30003 (host) (adapter 1)
master: 30004 (guest) => 30004 (host) (adapter 1)
master: 30005 (guest) => 30005 (host) (adapter 1)
master: 30006 (guest) => 30006 (host) (adapter 1)
master: 30007 (guest) => 30007 (host) (adapter 1)
master: 30008 (guest) => 30008 (host) (adapter 1)
master: 30009 (guest) => 30009 (host) (adapter 1)
master: 30010 (guest) => 30010 (host) (adapter 1)
master: 30011 (guest) => 30011 (host) (adapter 1)
master: 30012 (guest) => 30012 (host) (adapter 1)
master: 30013 (guest) => 30013 (host) (adapter 1)
master: 30014 (guest) => 30014 (host) (adapter 1)
master: 30015 (guest) => 30015 (host) (adapter 1)
master: 30016 (guest) => 30016 (host) (adapter 1)
master: 30017 (guest) => 30017 (host) (adapter 1)
master: 30018 (guest) => 30018 (host) (adapter 1)
master: 30019 (guest) => 30019 (host) (adapter 1)
master: 30020 (guest) => 30020 (host) (adapter 1)
master: 30021 (guest) => 30021 (host) (adapter 1)
master: 30022 (guest) => 30022 (host) (adapter 1)
master: 30023 (guest) => 30023 (host) (adapter 1)
master: 30024 (guest) => 30024 (host) (adapter 1)
master: 30025 (guest) => 30025 (host) (adapter 1)
master: 30026 (guest) => 30026 (host) (adapter 1)
master: 30027 (guest) => 30027 (host) (adapter 1)
master: 30028 (guest) => 30028 (host) (adapter 1)
master: 30029 (guest) => 30029 (host) (adapter 1)
master: 30030 (guest) => 30030 (host) (adapter 1)
master: 30031 (guest) => 30031 (host) (adapter 1)
master: 30032 (guest) => 30032 (host) (adapter 1)
master: 30033 (guest) => 30033 (host) (adapter 1)
master: 30034 (guest) => 30034 (host) (adapter 1)
master: 30035 (guest) => 30035 (host) (adapter 1)
master: 30036 (guest) => 30036 (host) (adapter 1)
master: 30037 (guest) => 30037 (host) (adapter 1)
master: 30038 (guest) => 30038 (host) (adapter 1)
master: 30039 (guest) => 30039 (host) (adapter 1)
master: 30040 (guest) => 30040 (host) (adapter 1)
master: 30041 (guest) => 30041 (host) (adapter 1)
master: 30042 (guest) => 30042 (host) (adapter 1)
master: 30043 (guest) => 30043 (host) (adapter 1)
master: 30044 (guest) => 30044 (host) (adapter 1)
master: 30045 (guest) => 30045 (host) (adapter 1)
master: 30046 (guest) => 30046 (host) (adapter 1)
master: 30047 (guest) => 30047 (host) (adapter 1)
master: 30048 (guest) => 30048 (host) (adapter 1)
master: 30049 (guest) => 30049 (host) (adapter 1)
master: 30050 (guest) => 30050 (host) (adapter 1)
master: 30051 (guest) => 30051 (host) (adapter 1)
master: 30052 (guest) => 30052 (host) (adapter 1)
master: 30053 (guest) => 30053 (host) (adapter 1)
master: 30054 (guest) => 30054 (host) (adapter 1)
master: 30055 (guest) => 30055 (host) (adapter 1)
master: 30056 (guest) => 30056 (host) (adapter 1)
master: 30057 (guest) => 30057 (host) (adapter 1)
master: 30058 (guest) => 30058 (host) (adapter 1)
master: 30059 (guest) => 30059 (host) (adapter 1)
master: 30060 (guest) => 30060 (host) (adapter 1)
master: 30061 (guest) => 30061 (host) (adapter 1)
master: 30062 (guest) => 30062 (host) (adapter 1)
master: 30063 (guest) => 30063 (host) (adapter 1)
master: 30064 (guest) => 30064 (host) (adapter 1)
master: 30065 (guest) => 30065 (host) (adapter 1)
master: 30066 (guest) => 30066 (host) (adapter 1)
master: 30067 (guest) => 30067 (host) (adapter 1)
master: 30068 (guest) => 30068 (host) (adapter 1)
master: 30069 (guest) => 30069 (host) (adapter 1)
master: 30070 (guest) => 30070 (host) (adapter 1)
master: 30071 (guest) => 30071 (host) (adapter 1)
master: 30072 (guest) => 30072 (host) (adapter 1)
master: 30073 (guest) => 30073 (host) (adapter 1)
master: 30074 (guest) => 30074 (host) (adapter 1)
master: 30075 (guest) => 30075 (host) (adapter 1)
master: 30076 (guest) => 30076 (host) (adapter 1)
master: 30077 (guest) => 30077 (host) (adapter 1)
master: 30078 (guest) => 30078 (host) (adapter 1)
master: 30079 (guest) => 30079 (host) (adapter 1)
master: 30080 (guest) => 30080 (host) (adapter 1)
master: 30081 (guest) => 30081 (host) (adapter 1)
master: 30082 (guest) => 30082 (host) (adapter 1)
master: 30083 (guest) => 30083 (host) (adapter 1)
master: 30084 (guest) => 30084 (host) (adapter 1)
master: 30085 (guest) => 30085 (host) (adapter 1)
master: 30086 (guest) => 30086 (host) (adapter 1)
master: 30087 (guest) => 30087 (host) (adapter 1)
master: 30088 (guest) => 30088 (host) (adapter 1)
master: 30089 (guest) => 30089 (host) (adapter 1)
master: 30090 (guest) => 30090 (host) (adapter 1)
master: 30091 (guest) => 30091 (host) (adapter 1)
master: 30092 (guest) => 30092 (host) (adapter 1)
master: 30093 (guest) => 30093 (host) (adapter 1)
master: 30094 (guest) => 30094 (host) (adapter 1)
master: 30095 (guest) => 30095 (host) (adapter 1)
master: 30096 (guest) => 30096 (host) (adapter 1)
master: 30097 (guest) => 30097 (host) (adapter 1)
master: 30098 (guest) => 30098 (host) (adapter 1)
master: 30099 (guest) => 30099 (host) (adapter 1)
master: 30100 (guest) => 30100 (host) (adapter 1)
==> master: Running 'pre-boot' VM customizations...
==> master: Booting VM...
There was an error while executing VBoxManage, a CLI used by Vagrant
for controlling VirtualBox. The command and stderr is shown below.

Command: ["startvm", "a00ac8c5-36e7-48a9-8afa-eb8751b55f59", "--type", "headless"]

Stderr: VBoxManage: error: The virtual machine 'master' has terminated unexpectedly during startup with exit code 1 (0x1)
VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component MachineWrap, interface IMachine

"generic/opensuse42" does not support letsencrypt "SRG Root X1" - switch to "generic/opensuse15"

The vagrant box "generic/opensuse42"

vagrant box list | grep "generic/opensuse"
generic/opensuse42        (virtualbox, 3.4.2)

used for in https://github.com/udacity/nd064-c2-message-passing-projects-starter/blob/b76833402deff247c29b83004ff778ff86d61eb6/Vagrantfile#L3 for this project, according to https://en.opensuse.org/Lifetime is discontinued, and results in errors when accessing certain pages securely over https, due to https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/.

The error shows up on "generic/opensuse42" as

vagrant@master:~> curl -sfL https://get.k3s.io | sh -
[INFO]  Finding release for channel stable
curl: (60) SSL certificate problem: certificate has expired
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
[INFO]  Using stable as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/stable/sha256sum-amd64.txt

As seen on "generic/opensuse15", this is due to https://update.k3s.io which uses letsencrypt, and is accessed during the execution of https://get.k3s.io

vagrant@master:> curl -sv https://update.k3s.io 2>&1 | grep -E "Server certificate|SSL certificate problem" -A 6
* Server certificate:
*  subject: CN=update.k3s.io
*  start date: Sep 14 12:45:48 2021 GMT
*  expire date: Dec 13 12:45:47 2021 GMT
*  subjectAltName: host "update.k3s.io" matched cert's "update.k3s.io"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.

Not having a k8s microservices development workflow places a high burden on students

We have hook different services up in a VM k3s and write code locally on our PC or MAC. Our Local code environment is not consistent with VM k3s environment. This creates a tedious experience for students like myself who want to focus on the topics.

@sarah-udacity

I highly recommend adding a section to show students how to properly write and deploy efficiently in a k3s/k8s microservices environment. The local PC and MAC does not have access to the other k3s services. Port forwarding is not the way. See below.

Why this is important? 80% of the time on this project would be spent on ops, not code, most students i think would give up.

Thoughts?

https://12factor.net/dev-prod-parity

https://skaffold.dev/docs/design/

https://www.youtube.com/watch?v=tTNrzEjROCo

Replace Flask with FastAPI. Replace postgreSQL with MongoDB

The ML course is using FastAPI, which auto generates Open API and Swagger. It only has one model file, leveraging Pydantic. This python app could improve readability by simplifying the number of files and amount of code it uses for models and serialization. Every object has three files that need to be changed for any object updates. It's confusing.

Replace postgreSQL with MongoDB. MongoDB supports BSON/JSON so you dont need another file to support python object serialization to postgres sql objects + writing raw sql code just to persist data. It will support JSON to JSON in a clearly written way. Since the focus isn't on database, why not simplify and detract focus away from the subject being taught.

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.