Giter Club home page Giter Club logo

docker-lock's Introduction

Docker-Lock-Banner

About

ci cd-master cd-tag Go Report Card PkgGoDev

docker-lock is a cli tool that automates managing image digests by tracking them in a separate Lockfile (think package-lock.json or Pipfile.lock). With docker-lock, you can refer to images in Dockerfiles, docker-compose V3 files, and Kubernetes manifests by mutable tags (as in python:3.6) yet receive the same benefits as if you had specified immutable digests (as in python:3.6@sha256:25a189a536ae4d7c77dd5d0929da73057b85555d6b6f8a66bfbcc1a7a7de094b).

Note: If you are unsure about the differences between tags and digests, refer to this quick summary.

docker-lock ships with 3 commands that take you from development to production:

  • docker lock generate finds images in your Dockerfiles, docker-compose files, and Kubernetes manifests and generates a Lockfile containing digests that correspond to their tags.
  • docker lock verify lets you know if there are more recent digests than those last recorded in the Lockfile.
  • docker lock rewrite rewrites Dockerfiles, docker-compose files, and Kubernetes manifests to include digests.

docker-lock is most commonly used as a cli-plugin for docker so lock can be used as subcommand of docker as in docker lock. However, docker-lock does not require docker at all. Instead, it can be called manually as a standalone executable as in docker-lock lock. This is especially convenient if the proper version of docker is unavailable or you would prefer to use another container technology such as podman.

Demo

Consider a project with a multi-stage build Dockerfile at its root:

FROM ubuntu AS base
# ...
FROM mperel/log:v1
# ...
FROM python:3.6
# ...

Running docker lock generate from the root queries each images' registry to produce a Lockfile, docker-lock.json.

Generate GIF

Note that the Lockfile records image digests so you do not have to manually specify them.

Running docker lock verify ensures that the image digests are the same as those on the registry for the same tags.

Verify Success GIF

Now, assume that a change to mperel/log:v1 has been pushed to the registry.

Running docker lock verify shows that the image digest in the Lockfile is out-of-date because it differs from the newer image's digest on the registry.

Verify Fail GIF

While developing, it can be useful to generate a Lockfile, commit it to source control, and verify it periodically (for instance on PR merges). In this way, developers can be notified when images change, and if a bug related to a change in an image crops up, it will be easy to identify.

Finally, lets assume the Dockerfile is ready to be built and shared.

Running docker lock rewrite will add digests from the Lockfile to all of the images.

Rewrite GIF

At this point, the Dockerfile will contain all of the digest information from the Lockfile, so it will always maintain the same, known behavior in the future.

Install

docker-lock can be run as a

Cli-plugin

Ensure docker cli version >= 19.03 is installed by running docker --version.

Linux / Mac

$ mkdir -p "${HOME}/.docker/cli-plugins"
$ curl -fsSL "https://github.com/safe-waters/docker-lock/releases/download/v${VERSION}/docker-lock_${VERSION}_${OS}_${ARCH}.tar.gz" | tar -xz -C "${HOME}/.docker/cli-plugins" "docker-lock"
$ chmod +x "${HOME}/.docker/cli-plugins/docker-lock"

Windows

  • Create the folder %USERPROFILE%\.docker\cli-plugins
  • Download the Windows release from the releases page.
  • Unzip the release.
  • Move docker-lock.exe into %USERPROFILE%\.docker\cli-plugins

Standalone tool

  • Follow the same instructions as in the cli-plugin section except place the docker-lock executable in your PATH.
  • To use docker-lock, replace any docker command such as docker lock with the name of the executable, docker-lock, as in docker-lock lock.
  • To verify that docker-lock was installed, run:
$ docker-lock lock --help

Docker image

docker-lock can be run in a docker container, as below. If you leave off the ${VERSION} tag, you will use the latest, nightly build from the master branch.

Note: If your host machine uses a credential helper such as osxkeychain, wincred, or pass, the credentials will not be available to the container even if you pass in your docker config.

Linux / Mac

  • Without your docker config:
$ docker run -v "${PWD}":/run safewaters/docker-lock:${VERSION} [commands]
  • With your docker config:
$ docker run -v "${HOME}/.docker/config.json":/.docker/config.json:ro -v "${PWD}":/run safewaters/docker-lock:${VERSION} [commands]

Windows

  • Without your docker config:
$ docker run -v "%cd%":/run safewaters/docker-lock:${VERSION} [commands]
  • With your docker config:
$ docker run -v "%USERPROFILE%\.docker\config.json":/.docker/config.json:ro -v "%cd%":/run safewaters/docker-lock:${VERSION} [commands]

Available tags

  • By default, images are built from scratch. These images only contain the docker-lock executable and are tagged as follows:
    • safewaters/docker-lock:${VERSION}
    • safewaters/docker-lock
  • If you need a shell alongside the executable (as is required by some CI/CD providers such as Gitlab), images built from alpine are provided. They are tagged as follows:
    • safewaters/docker-lock:${VERSION}-alpine
    • safewaters/docker-lock:alpine

Use

Registries

docker-lock supports public and private registries. If necessary, login to docker before using docker-lock.

How to specify configuration options

docker-lock supports options via cli flags or a configuration file, .docker-lock.yml. The root of this repo has an example, .docker-lock.yml.example.

To see available options, run commands with --help. For instance:

$ docker lock --help
$ docker lock generate --help
$ docker lock verify --help
$ docker lock rewrite --help
$ docker lock version --help

Note: You can mix and match cli flags to get the output that you want.

Generate

Commands for Dockerfiles, docker-compose files, and Kubernetes manifests

  • docker lock generate will collect all default files (Dockerfile, compose.yml, compose.yaml, docker-compose.yaml, docker-compose.yml, pod.yml, pod.yaml, deployment.yml, deployment.yaml, job.yml, and job.yaml in the default base directory, the directory from which the command is run) and generate a Lockfile.

  • docker lock generate --lockfile-name=[file name] will generate a Lockfile with the file name as the output, instead of the default docker-lock.json.

  • docker lock generate --update-existing-digests will generate a Lockfile, querying for all digests, even those that are hardcoded in the files. Normally, if a digest is hardcoded, it would be used in the Lockfile.

  • docker lock generate --ignore-missing-digests will generate a Lockfile, recording images for which a digest could not be found as not having a digest. Normally, if a digest cannot be found, docker-lock would print an error.

  • docker lock generate --base-dir=[sub directory] will collect all default files in a sub directory and generate a Lockfile.

Commands for Dockerfiles

  • docker lock generate --dockerfiles=[file1,file2,file3] will collect all files from a comma separated list ("file1,file2,file3") as well as default docker-compose files and Kubernetes manifests and generate a Lockfile.

  • docker lock generate --exclude-all-dockerfiles will generate a Lockfile, excluding all Dockerfiles.

  • docker lock generate --dockerfile-recursive will collect all default Dockerfiles (Dockerfile) in subdirectories from the base directory as well as default docker-compose files and Kubernetes manifests in the base directory and generate a Lockfile.

  • docker lock generate --dockerfile-globs='[glob pattern]' will collect all Dockerfiles that match the glob pattern relative to the base directory as well as default docker-compose files and Kubernetes manifests in the base directory and generate a Lockfile. Use '**' to recursively search directories. Remember to quote using single quotes so that the glob is not expanded before docker-lock uses it.

Commands for docker-compose files

  • docker lock generate --composefiles=[file1,file2,file3] will collect all files from a comma separated list ("file1,file2,file3") as well as default Dockerfiles files and Kubernetes manifests and generate a Lockfile.

  • docker lock generate --exclude-all-composefiles will generate a Lockfile, excluding all docker-compose files.

  • docker lock generate --composefile-recursive will collect all default docker-compose files (compose.yml, compose.yaml, docker-compose.yaml, docker-compose.yml) in subdirectories from the base directory as well as default Dockerfiles and Kubernetes manifests in the base directory and generate a Lockfile.

  • docker lock generate --composefile-globs='[glob pattern]' will collect all docker-compose files that match the glob pattern relative to the base directory as well as default Dockerfiles and Kubernetes manifests in the base directory and generate a Lockfile. Use '**' to recursively search directories. Remember to quote using single quotes so that the glob is not expanded before docker-lock uses it.

Commands for Kubernetes manifests

  • docker lock generate --kubernetesfiles=[file1,file2,file3] will collect all files from a comma separated list ("file1,file2,file3") as well as default Dockerfiles files and docker-compose files and generate a Lockfile.

  • docker lock generate --exclude-all-kubernetesfiles will generate a Lockfile, excluding all Kubernetes manifests.

  • docker lock generate --kubernetesfile-recursive will collect all default Kubernetes manifests (pod.yaml, pod.yml) in subdirectories from the base directory as well as default Dockerfiles and docker-compose files in the base directory and generate a Lockfile.

  • docker lock generate --kubernetesfile-globs='[glob pattern]' will collect all Kubernetes manifests that match the glob pattern relative to the base directory as well as default Dockerfiles and docker-compose files in the base directory and generate a Lockfile. Use '**' to recursively search directories. Remember to quote using single quotes so that the glob is not expanded before docker-lock uses it.

Verify

  • docker lock verify will take an existing Lockfile, with the default name, docker-lock.json, generate a new Lockfile and report differences between the new and existing Lockfiles.

  • docker lock verify --lockfile-name=[file name] will use another file, instead of the default docker-lock.json, as the Lockfile.

  • docker lock verify --exclude-tags will check for differences between a newly generated Lockfile and the existing Lockfile, ignoring if tags are different.

  • docker lock verify --ignore-missing-digests will verify, but when generating the new Lockfile to compare against, will assume that digests that cannot be found are empty. Normally, if a digest could not be found, an error would be reported.

  • docker lock verify --update-existing-digests will verify, but when generating the new Lockfile to compare against, will query for digests even if they are hardcoded. Normally, the new Lockfile would use the hardcoded digests, instead of querying for the most recent one.

Rewrite

  • docker lock rewrite will write the image names, tags, and digests from the Lockfile into the referenced Dockerfiles, docker-compose files, and Kubernetes manifests.

  • docker lock rewrite --lockfile-name=[file name] will use another file, instead of the default docker-lock.json, as the Lockfile.

  • docker lock rewrite --exclude-tags will write image names and digests, but not the tags, from the Lockfile into the referenced Dockerfiles, docker-compose files, and Kubernetes manifests.

  • docker lock rewrite --tempdir=[directory] will create a temporary directory in the [directory] and write all files into it. Afterwards, the files are renamed to the appropriate location and the temporary directory is deleted. Normally, this occurs in the current directory. In general, this 2 step process happens to ensure that either all rewrites succeed, or none of them do. There are also other rollback measures in docker-lock to ensure this transaction happens and you are not left with some files rewritten if a failure occurs.

Suggested workflow

  • Locally run docker lock generate to create a Lockfile, docker-lock.json, and commit it.
  • Continue developing normally, as if the Lockfile does not exist.
  • When merging a code change/releasing, run docker-lock in a CI/CD pipeline. Specifically:
    • In the pipeline, run docker lock verify to make sure that the Lockfile is up-to-date. If docker lock verify fails, the developer can locally rerun docker lock generate to update the Lockfile. This has the benefit that digest changes will be explicitly tracked in git.
    • Once the docker lock verify step in the pipeline passes, the pipeline should run docker lock rewrite so all files have correct digests hardcoded in them.
    • The pipeline should run tests that use the rewritten images.
    • If the tests pass, merge the code change/push the images to the registry, etc.

Contributing

Development environment

A development container based on ubuntu:bionic has been provided, so ensure docker is installed and the docker daemon is running.

  • Open the project in VSCode.
  • Install VSCode's Remote Development Extension - Containers.
  • In the command palette (ctrl+shift+p on Windows/Linux, command+shift+p on Mac), type "Reopen in Container".
  • In the command palette type: "Go: Install/Update Tools" and select all.
  • When all tools are finished installing, in the command palette type: "Developer: Reload Window".
  • The docker daemon is mapped from the host into the dev container, so you can use docker and docker-compose commands from within the container as if they were run on the host.

Build from source

To build and install docker-lock in docker's cli-plugins directory, from the root of the project, run:

$ make install

Code quality and correctness

To clean, format, lint, install, generate a new Lockfile, and run unit tests:

make

The CI pipeline will additionally run integration tests on pull requests.

You can run any step individually.

  • To uninstall: make clean
  • To install into docker's cli-plugins directory: make install
  • To generate a new Lockfile: make lock
  • To format Go code: make format
  • To lint all code: make lint
  • To run unit tests: make unittest

To view the coverage report after running unit tests, open coverage.html in your browser.

Note: While there exists a target in the Makefile for integration tests, these cannot run locally because they require credentials that are only available in the CI pipeline.

Tutorials

docker-lock's People

Contributors

kokes avatar margaretmeehan avatar michaelperel avatar pic123 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

docker-lock's Issues

do not include scratch images into the list

If one of the images has been built on a scratch image, the final stage is based upon the scratch image; we should not add it to the docker-lock.json file.

NOTE: we(w/@Dentrax) are willing to do this; assign it to us if you ok with this.

Suggestion: expand default glob for finding dockerfiles and compose-files

Currently it seems like docker-lock only looks for files called "Dockerfile" (in the case of dockerfiles) and files called "docker-compose.yml" (in the case of docker-compose files). Could it perhaps be a good idea to expand this to also include files that end with ".dockerfile" and ".docker-compose.yml" in cases where projects have multiple of these files in the same directory and need to differentiate them by name?

This is currently covered by the --dockerfile-globs --composefile-globs, so this is not strictly necessary. More a QoL change.

Add Support for Google Container Registry (GCR)

  • Follow the registry/acr.go code as closely as possible
  • If you have access to GCR credentials for open source, add integration tests where credentials are encrypted vars in AZDO pipeline

Multiline commands are not preserved

Given that the project leverages Moby's parser, we inherit some of its properties - e.g. the way it parses multiline commands - it eats the continuation backslashes at the parser level. When I looked at the AST, I couldn't retrieve enough information to reconstruct the original command. But I didn't spend too long on this.

Example:

If we run docker lock generate && docker lock rewrite on the following Dockerfile

FROM busybox
ENTRYPOINT echo \
    foo \
    bar

we get

-FROM busybox
-ENTRYPOINT echo \
-    foo \
-    bar
+FROM busybox:latest@sha256:9f1c79411e054199210b4d489ae600a061595967adb643cd923f8515ad8123d2
+ENTRYPOINT echo     foo     bar

docker-lock tries to read directories with matching names as dockerfiles

docker lock generate seems to want to try to parse directories as if they are dockerfiles (this does not seems to happen for docker-compose entries).

E.g

mkdir test
cd test
mkdir Dockerfile
docker lock generate

This is particularly a problem when using --dockerfile-globs, as it is not possible to specify that the glob should only match files (AFAIK)

Docker lock rewrite does not work with mandatory variables in docker-compose file

Description
Variables in docker-compose file breaks docker lock rewrite with errors: in 'docker-compose.yml', invalid interpolation format for services.hello_world.container_name: "required variable SOME_VAR is missing a value: error message". You may need to escape any $ with another $.

Repro
https://github.com/andoks/variables_in_docker-compose_breaks_docker-lock
See run_repro.bash file in repo

Version
$ docker lock version
0.8.3

Support for other tools

Currently if I want to run docker containers from make or python there is no real nice way for me to integrate with docker-lock. One option that would be nice is if I could write a docker-images.json file with a list of images and tags, and then have docker-lock output docker-images.resloved.json from a command which basically prints out the OS/Arch specific hash for all those images and tags.

so I would have

docker-images.json:

{
  "images": [
  {  "ref": "docker.io/python:3.7" }
  ] 
}

And then

docker-images.resolved.json:

{
  "images": [
  {  "ref": "docker.io/python:3.7", "resolved": "docker.io/python@sha256...." }
  ] 
}

And then I can query this resolved using jq, or there could even be a command docker-image resolve docker.io/python:3.7 which will return docker.io/python@sha256....

If there is interests from the maintainers for this I could potentially work on it.

Rewrite without lockfiles

The way we'd like to use a tool like docker lock is that we'd leave the digests in our Dockerfiles and upon each rewrite, we'd lookup the current digests and replace the outdated ones. We don't have a particularly good use of the lock file.

So we'd essentially do docker lock generate && docker lock rewrite && rm docker-lock.json - are we alone in this? If not, wouldn't something like docker lock rewrite --no-lock-file or something along those lines be of use? It'd do the generate step (in memory) and rewrite the Dockerfile without saving these into a lock file.

This is just a tentative proposal, maybe we're misunderstanding the intended workflow.

Idea: docker-lock migrate

I've been thinking about building something similar to this that includes the ability to copy images to an alternative registry, rather than just resolving tags to digests.

Abstractly, I'd love to have some way to map various functions over collections that contain image references.

You've already implemented support for lots of collections (Dockerfiles, docker-compose files, and kubernetes manifests) and two functions (rewite and verify). We could add a migrate function (we can bikeshed the name) that calls crane.Copy, too.

Some applications:

  1. Air-gapped/locked-down clusters that can only pull from a specific registry.
  2. Copying all your dependencies to a closer registry (for availability, latency, and rate limiting reasons).

I put together a proof of concept a while back that only worked for kubernetes manifests: ko-build/ko#11

What do you think?

cc @imjasonh

`ARG VARIANT`

ARG VARIANT

Given

ARG VARIANT
FROM python:${VARIANT}

After lock generate + rewerite, wrong

ARG VARIANT
FROM python

After lock generate + rewerite, correct. But compared to origin, wrong.

ARG VARIANT
FROM python:latest@sha256:hash

ARG VARIANT=FOOBAR

Given

ARG VARIANT="alpine"
FROM python:${VARIANT}

After lock generate + rewerite, debatable.

ARG VARIANT="alpine"
FROM python:alpine@sha256:hash

BUG: owner group changed

Given

  • user foobar have Dockerfile with owner group foobar:foobar

Problems

$ docker run -v "${PWD}":/run safewaters/docker-lock lock generate

Actual: generate docker-lock.json with owner group root:root, if there is an empty docker-lock.json, permission is OK.

Expected: generate owner group as first matched files owner group

$ docker run -v "${PWD}":/run safewaters/docker-lock lock rewrite

Actual: changed Dockerfile from foobar:foobar to root:root

Expected: owner group unchanged

errors when parsing docker-compose files with service-definitions with no image stanza

Currently docker-lock generate errors out when parsing docker-compose files which contains service-definitions with no image stanza.

I have "composition" compose-files, where we pass multiple compose files to docker-compose (e.g docker-compose --file base-system.docker-compose.yml --file host-specific-options.docker-compose.yml). The "host-specific-options.docker-compose.yml" file in this example may contain only the volumes and port bindings for all the service entries defined in "base-system.docker-compose.yml". Do note that the "addon" docker-compose.yml files may contain image stanzas for certain services (or additional services altogether).

Is it possible to have docker-lock not error out on service-definitions that does not contain an image stanza (and perhaps log or error out if the image stanza is present, but not a valid image)?

errors when parsing docker-compose files with service-definitions with no image stanza (return of #97)

#97 seems to have returned in v0.8.8. I downgraded to v0.8.5, and the exact same project now works.

Currently docker-lock generate errors out when parsing docker-compose files which contains service-definitions with no image stanza.

I have "composition" compose-files, where we pass multiple compose files to docker-compose (e.g docker-compose --file base-system.docker-compose.yml --file host-specific-options.docker-compose.yml). The "host-specific-options.docker-compose.yml" file in this example may contain only the volumes and port bindings for all the service entries defined in "base-system.docker-compose.yml". Do note that the "addon" docker-compose.yml files may contain image stanzas for certain services (or additional services altogether).

Is it possible to have docker-lock not error out on service-definitions that does not contain an image stanza (and perhaps log or error out if the image stanza is present, but not a valid image)?

Support Docker image from basic Alpine image

Problem

The current Docker image is build from scratch this unfortunately does not work in a Gitlab-ci pipeline as it requires a shell to be present. It results in a descriptive error: "Unable to upgrade connection...."

Proposal

Supply Docker images for the most popular Linux distros as well (alpine/debian etc). This way the Docker image can be reused immediately in a Gitlab pipeline as follows:

check:
  image:
    name: safewaters/docker-lock:0.8.5
    entrypoint: [ "" ]
  stage: validate
  script:
    - docker-lock lock verify 

Handle whitespace and comments

The rewriter strips all blank lines and comments from the resulting Dockerfile. These are useful for retaining clarity in a complex Dockerfile. I'll submit a draft PR shortly.

feat: when `pull_policy: never` skip query manifests

Given

Given some apps using different settings, but reference to same build local image.

version: "3.6"

networks:
  napp:

services:
  build:
    pull_policy: never
    image: private/image
    build:
      network: foobar
      context: foobar
      dockerfile: foobar.Dockerfile
      args:
        foo: bar
        bar: foo

  app1:
    depends_on:
      - build
    pull_policy: never
    image: private/image
    environment:
      - settings: A

  app2:
    depends_on:
      - build
    pull_policy: never
    image: private/image
    environment:
      - settings: B

Actual

failed to update image with err: 
failed to find digest for 'private/image' with err: 
GET https://index.docker.io/v2/library/private/image/manifests/latest: 
UNAUTHORIZED: authentication required; 
[map[Action:pull Class: Name:library/private/image Type:repository]

Expected

When pull_policy: never, skip query manifests.

Some thoughts

I think pull_policy: never can somehow indicate a local image.

  1. docker-compose pull will skip pull_policy: never section.
  2. Without pull_policy: never, docker-compose pull will fail on local images.
  3. The one who using local image, and want to use docker pull, have no choice but add pull_policy: never.

Current workaround

Move build section down to every else services.

If build sections have X lines, and being refer to Y times, becomes long long XY lines. ๐Ÿ˜ญ

Hard to know what file is causing error

I am trying to introduce docker-lock into a relatively large git repository with a lot of different docker and compose files, but when errors are discovered that works when using docker/docker-compose (and therefore has passed by unnoticed before), it is hard to know what file the error located in.

Would it be possible to print the path of the file with the error?

An example of an error I have is a compose-file which has the service.target.publish port value specified as a string instead of a integer literal ("8888" instead of just 8888), this works fine with docker-compose, but errors out with docker-lock

Bug when `services.X` and `networks.X` have same name.

Bug when services.X and networks.X have same name

in 'docker-compose.yml' '2' images rewritten, but expected to rewrite '1'
version: "3.6"

networks:
  app:

services:
  build:
    pull_policy: never
    image: private/image
    build:
      dockerfile: foobar.Dockerfile

  app:
    depends_on:
      - build
    pull_policy: never
    image: private/image

OK when services.X and networks.Y

version: "3.6"

networks:
  napp:

services:
  build:
    pull_policy: never
    image: private/image
    build:
      dockerfile: foobar.Dockerfile

  app:
    depends_on:
      - build
    pull_policy: never
    image: private/image
successfully generated lockfile!
successfully rewrote files referenced by lockfile!

Add support for multiple ACR registries

Currently, if a project relies on multiple ACR registries, only one ACR can be specified via ACR_REGISTRY_NAME.

  • make ACR_REGISTRY_NAME a slice of registry names
  • modify Prefix() to return a collection (perhaps slice or map) instead of a string

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.