Giter Site home page Giter Site logo

canonical / charmcraft Goto Github PK

View Code? Open in Web Editor NEW
64.0 16.0 67.0 4.15 MB

Collaborate, build and publish charmed operators for Kubernetes, Linux and Windows.

License: Apache License 2.0

Python 96.87% Shell 0.41% Jinja 2.25% Inno Setup 0.45% PowerShell 0.02%

charmcraft's Introduction

charmcraft Tests Spread Weekly Spread

Charmcraft -- easily initialise, pack, and publish your charms

Charmcraft is a CLI tool that makes it easy and quick to initialise, package, and publish Kubernetes and machine charms. It is an official component of the Charm SDK, itself a part of the Juju universe.

Juju Learn how to quickly deploy, integrate, and manage charms on any cloud with Juju.
It's as simple as juju deploy foo, juju integrate foo bar, ..., on any cloud.
Charmhub Sample our existing charms on Charmhub.
A charm can be a cluster (OpenStack, Kubernetes), a data platform (PostgreSQL, MongoDB, etc.), an observability stack (Canonical Observability Stack), an MLOps solution (Kubeflow), and so much more.
👉 Charm SDK Write your own charm!
Juju is written in Go, but our SDK supports easy charm development in Python.

Give it a try

Let's use Charmcraft to initialise and pack a Kubernetes charm:

Set up

See Charm SDK | Set up your development environment automatically > Set up an Ubuntu charm-dev VM with Multipass.
Choose the MicroK8s track.

Initialise and pack your charm

In your Multipass VM shell, create a charm directory and use Charmcraft to initialise your charm file structure:

mkdir my-new-charm
cd my-new-charm
charmcraft init

This has created a standard charm directory structure:

$ ls -R
.:
CONTRIBUTING.md  README.md        pyproject.toml    src    tox.ini
LICENSE          charmcraft.yaml  requirements.txt  tests

./src:
charm.py

./tests:
integration  unit

./tests/integration:
test_charm.py

./tests/unit:
test_charm.py

Poke around:

Note that the charmcraft.yaml file shows that what we have is an example charm called my-new-charm, which builds on Ubuntu 22.04 and which uses an OCI image resource httpbin from kennethreitz/httpbin.

Note that the src/charm.py file contains code scaffolding featuring the Charm SDK's Ops library for writing charms.

Explore further, start editing the files, or skip ahead and pack the charm:

charmcraft pack

If you didn't take any wrong turn or simply left the charm exactly as it was, this should work and yield a file called my-new-charm_ubuntu-22.04-amd64.charm (the architecture bit may be different depending on your system's architecture). Use this name and the resource from the metadata.yaml to deploy your example charm to your local MicroK8s cloud with Juju:

juju deploy ./my-new-charm_ubuntu-22.04-amd64.charm --resource httpbin-image=kennethreitz/httpbin

Congratulations, you’ve just initialised and packed your first Kubernetes charm using Charmcraft!

But Charmcraft goes far beyond init and pack. For example, when you're ready to share your charm with the world, you can use Charmcraft to publish your charm on Charmhub. Run charmcraft help to preview more.

Clean up

See Charm SDK | Set up your development environment automatically > Clean up.

Next steps

Learn more

Read our user documentation, which also includes other guides showing Charmcraft in action

Chat with us

Read our Code of conduct and:

File an issue

Make your mark

charmcraft's People

Contributors

benhoyt avatar carlcsaposs-canonical avatar chipaca avatar cmatsuoka avatar delgod avatar dependabot[bot] avatar dstathis avatar edumucelli avatar facundobatista avatar fakela avatar fnordahl avatar jameinel avatar jbpratt avatar jdkandersson avatar jnsgruk avatar lengau avatar markshuttle avatar matuskosut avatar mr-cal avatar mthaddon avatar rbarry82 avatar renovate[bot] avatar sed-i avatar sergiusens avatar simskij avatar syu-w avatar tigarmo avatar tomwardill avatar tonyandrewmeyer avatar weiiwang01 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

Watchers

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

charmcraft's Issues

The build process should error out if the package has intrinsic problems

Collecting here all the checks we should do:

  • check if metadata.yaml is present and a valid YAML
  • check that the metadata.yaml has at least the following fields: name, summary, description (needed by Juju) and series (needed by the Store)
  • ensure no symlink points outside of the project
  • if opslib metadata is present, it should verify it's internal structure is ok (like the mail looks like a mail, etc, see verifications here for reference)
  • Both actions.yaml and config.yaml, if present, need to be valid YAMLs.

charmcraft should log where it's logging to, and when the logfile is removed

When running with --verbose it would be useful to see two things:

  1. an early log about where things are being logged, e.g.

    2020-06-24 10:52:17,239  charmcraft                     DEBUG    Logging to /tmp/charmcraft-log-22sbe9rb
    
  2. a towards-the-end log when and if that logfile is removed, e.g.

    2020-06-24 10:56:11,640  charmcraft                     DEBUG    Logfile removed.
    

(It was in fixing this that I noticed #48).

charmcraft doesn't support actions

See this simple hello-world charm with an action: https://github.com/AurelienLourot/charm-ops-with-action

Without charmcraft it works but with charmcraft it fails with

2020-06-16 09:57:36 ERROR juju-log Uncaught exception while in charm code:
Traceback (most recent call last):
  File "/var/lib/juju/agents/unit-ops-with-action-0/charm/hooks/install", line 38, in <module>
    main(CharmOpsWithAction)
  File "lib/ops/main.py", line 313, in main
    charm = charm_class(framework)
  File "/var/lib/juju/agents/unit-ops-with-action-0/charm/hooks/install", line 30, in __init__
    self.framework.observe(self.on.hello_action, self.on_hello_action)
AttributeError: 'CharmEvents' object has no attribute 'hello_action'
2020-06-16 09:57:36 ERROR juju.worker.uniter.operation runhook.go:132 hook "install" failed: exit status 1

This is apparently because charmcraft doesn't copy actions.yaml into the charm.

Be able to change backend Store

In the future the default will be production, but we need to support changing it by the developer.

We could support different servers through a env var, or a command line option (command line would be nice as people could put that setting in the charmcraft config file and be always pointing to that other store)

Instrument internal timing measures

We need to always be able to distinguish:

  • bootstrap time (which can be improved deferring crazy imports, etc): this is until we're ready to call the commands' .run
  • proper command time (which would need to be improved on a per command basis): this is from calling 'run to its end
  • external interaction times (to be substracted from above's times, and properly informed to external parties): so far we only have interaction with the Store, but we may have other network backends in the future, or even local ones (services in the developer machine).

Support cases of Store not respecting the API

Even when Store answers correctly, we need to support response not being correctly formed. This is not only catching a possible malformed json, but also the fields in the API being missing or misnamed.

After this, we would log in debug the problem found and present a nice message to the user (we need UnknownError for this, see #65) , instead of just crashing.

Support pep 517 for building charms

Pep 517 defines a key in pyproject.toml that allows a developer to specify how a Python project is built. Would it be possible to support that standard with charmcraft? As an example, this could be what a charm author puts into pyproject.toml:

[build-system]
requires = ["charmcraft", "wheel"]
build-backend = "charmcraft"

This would allow frictionless integration with commands such as pip wheel.

Improve error message when bad command

Currently it's redundant (should start more to the point of the problem) and it lists all available commands (which won't scale as number of commands grows).

dispatch could hook into JUJU_DEBUG_AT

One thing that stub brought up on IRC is that he was trying to debug import time problems, etc. Which meant that he couldn't run 'juju debug-code' because that only catches stuff once you get to main().

One option would be for the 'dispatch' that we create to be aware of JUJU_DEBUG_AT, and either
a) Being python code itself
b) Running the src/charm.py under pdb, eg 'python3 -m pdb ./src/charm.py'

I'm not sure the exact syntax we would want, and maybe it would be one of the breakpoints, eg:
juju debug-code --at init

But we should discuss what the possibilities are and how we could help people that want to debug their charms even earlier in the process.

Note that there is always 'juju debug-hooks' and then running './dispatch' by the author. which might be a better answer still for early debugging. But we should discuss it.

`charmcraft build` breaks when pip packages come from github

charmcraft build errors when pip packages located in github are included in the requirements.txt.

This issue can be reproduced if you build a charm where the requirements.txt contains pip packages that live in github.

Reproducer:

git clone https://github.com/ducks23/charm-slurmdbd -b bdx_charmcraft

cd charm-slurmdbd

charmcraft build

Here is our example of the breakage below:

$ cat requirements.txt
ops
#git+https://github.com/omnivector-solutions/interface-mysql.git@master
#git+https://github.com/omnivector-solutions/slurm-snap-manager.git@master

$ charmcraft build
Done, charm left in 'slurmdbd.charm'

$ vim requirements.txt

$ cat requirements.txt
ops
git+https://github.com/omnivector-solutions/interface-mysql.git@master
git+https://github.com/omnivector-solutions/slurm-snap-manager.git@master

$ charmcraft build
Executing ['pip3', 'install', '--target=/home/bdx/allcode/github/ducks23/charm-slurmdbd/build/venv', '--requirement=/home/bdx/allcode/github/ducks23/charm-slurmdbd/requirements.txt'] failed with return code 1
problems installing dependencies

I can install the requirements.txt by hand successfully

$ pip install -r requirements.txt
Collecting git+https://github.com/omnivector-solutions/interface-mysql.git@master (from -r requirements.txt (line 2))
  Cloning https://github.com/omnivector-solutions/interface-mysql.git (to revision master) to /tmp/pip-req-build-ztlxwhvf
  Running command git clone -q https://github.com/omnivector-solutions/interface-mysql.git /tmp/pip-req-build-ztlxwhvf
Collecting git+https://github.com/omnivector-solutions/slurm-snap-manager.git@master (from -r requirements.txt (line 3))
  Cloning https://github.com/omnivector-solutions/slurm-snap-manager.git (to revision master) to /tmp/pip-req-build-pwosnebt
  Running command git clone -q https://github.com/omnivector-solutions/slurm-snap-manager.git /tmp/pip-req-build-pwosnebt
Requirement already satisfied: ops in /home/bdx/allcode/github/venv/lib/python3.8/site-packages (from -r requirements.txt (line 1)) (0.6.1)
Requirement already satisfied: PyYAML in /home/bdx/allcode/github/venv/lib/python3.8/site-packages (from ops->-r requirements.txt (line 1)) (5.3.1)
Building wheels for collected packages: interface-mysql, slurm-snap-manager
  Building wheel for interface-mysql (setup.py) ... error
  ERROR: Command errored out with exit status 1:
   command: /home/bdx/allcode/github/venv/bin/python3 -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-req-build-ztlxwhvf/setup.py'"'"'; __file__='"'"'/tmp/pip-req-build-ztlxwhvf/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' bdist_wheel -d /tmp/pip-wheel-y8jlp8ro
       cwd: /tmp/pip-req-build-ztlxwhvf/
  Complete output (6 lines):
  usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
     or: setup.py --help [cmd1 cmd2 ...]
     or: setup.py --help-commands
     or: setup.py cmd --help

  error: invalid command 'bdist_wheel'
  ----------------------------------------
  ERROR: Failed building wheel for interface-mysql
  Running setup.py clean for interface-mysql
  Building wheel for slurm-snap-manager (setup.py) ... error
  ERROR: Command errored out with exit status 1:
   command: /home/bdx/allcode/github/venv/bin/python3 -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-req-build-pwosnebt/setup.py'"'"'; __file__='"'"'/tmp/pip-req-build-pwosnebt/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' bdist_wheel -d /tmp/pip-wheel-gd2m4kjg
       cwd: /tmp/pip-req-build-pwosnebt/
  Complete output (6 lines):
  usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
     or: setup.py --help [cmd1 cmd2 ...]
     or: setup.py --help-commands
     or: setup.py cmd --help

  error: invalid command 'bdist_wheel'
  ----------------------------------------
  ERROR: Failed building wheel for slurm-snap-manager
  Running setup.py clean for slurm-snap-manager
Failed to build interface-mysql slurm-snap-manager
Installing collected packages: interface-mysql, slurm-snap-manager
    Running setup.py install for interface-mysql ... done
    Running setup.py install for slurm-snap-manager ... done
Successfully installed interface-mysql-0.0.1 slurm-snap-manager-0.0.1

Running snappy-debug shows:

= AppArmor =
Time: Jun 24 10:26:01
Log: apparmor="DENIED" operation="open" profile="snap.charmcraft.charmcraft" name="/etc/" pid=3626019 comm="python3" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0
File: /etc/ (read)
Suggestions:
* adjust program to read necessary files from $SNAP, $SNAP_DATA, $SNAP_COMMON, $SNAP_USER_DATA or $SNAP_USER_COMMON
* adjust snap to use snap layouts (https://forum.snapcraft.io/t/snap-layouts/7207)

= AppArmor =
Time: Jun 24 10:28:39

leaking file descriptor returned by mkstemp

We're leaking one file descriptor with the way we call mkstemp.

As the code is only called once per invocation and not in a loop it isn't a huge issue except on windows, where the open file descriptor will prevent us from deleting the file.

charm build does not copy over 'templates' directory

In the case of a charm that needs to render configuration templates to a unit through jinja2, a folder "templates" would be created in the charm root. Charm build does not currently copy that over and causes the install to fail.

i.e
charm-example
-- src
|-- charm.py
-- templates
|-- template-example.conf.j2

initialization order means early log lines can be lost

When running with --verbose, everything that is logged before Dispatcher.run is never seen by the user. To see them they'd have to also set DEBUG in the environ.
This feels like a bug to me. Parsing of the command line should be the first thing done, or at least early enough that the debugger doesn't need to change modes after the fact.

Rename 'list' command to 'names'

This is in sync with the idea of not using "list" in general (as the tool will list ton of things, really), but using the noun in plural.

Include all project files (whatever is present there)

We're considering going the extra mile after including several well known files from the charm's project (metadata.yaml, config.yaml, etc).

We have several options:

  • include everything present in the project's directory
  • include everything except some specifics, like Juju does:
    • don't include .git, .bzr, etc... (or directly avoid including all hidden files/dirs)
    • don't include symlinks that are pointing to outside the project's directory
  • include only very well known files (metadata.yaml, etc), and have a configuration for the developer to specify other files to be included.

Last option involves having some kind of "manifest", but note that first two options also would grow some kind of "block list" for the developer to specify something to not be included.

There's a compromise between both choices: if the developer needs to specify which files to be included, they may get caught by surprise, having built and published a charm lacking a file (broken!); if the developer needs to blocklist which files to be avoided in the package, they may got caught by surprise and have published a controversial file (security!).

Poetry integration

I am trying out the new requirements.txt integration, and it is quite nice. However, for more complex dependency management, it would be handy to be able to use Poetry instead of a plain requirements.txt, as Poetry ensures that dependency installation is deterministic by generating a lock file that can be used to install dependencies.

The charm tool should offer an initial skeleton

As with many (new) frameworks that expose a lot of functionality, it's is easy to shoot oneself in the foot.
This is something that cannot be prevented, but I think that many 'beginners' mistakes could be prevented by providing some templates on how to realize certain functionality.

E.g. some helping code to create an operator framework 'project' with all links in place, using some templates for e.g. installing a snap, or a deb, or creating an interface to be reused.

Support for long help texts from each command

We need to add support for each command to produce a long help text.

Probably that would be exposed as a class attribute, or even as a return of a method, and will be exposed only when getting the help of that specific command (charmcraft foobar -h), not in the general help of charmcraft (where only the "command summary" is shown).

Verify types in the store layer for stuff returned to the commands

We could something like this (after we forget Py3.5):

import typing
from collections import namedtuple


class TypedNamedTuple:
    """A namedtuple that verifies hinted types."""

    def __new__(cls, **kwargs):
        annotations = typing.get_type_hints(cls)
        for attr, hint in annotations.items():
            assert isinstance(kwargs[attr], hint)
        nt = namedtuple(cls.__name__, list(annotations))
        return nt(**kwargs)


class User(TypedNamedTuple):
    """The authenticated user information."""
    name: str
    username: str
    userid: str

Extra benefit: we get to have docstrings for each type :)

Packaging of charm and layer code

With the move to fully embracing Python for writing charms in, it seems like we can also embrace the packaging infrastructure that Python has created, and package layers as regular Python packages. This has a few advantages:

  1. Works with regular Python tooling, such as pip and virtualenv. IDEs also already know how to introspect code set up this way
  2. Python developers don't have to learn a new way of packaging/importing layers, it's done exactly as they'd expect
  3. We don't have to maintain something like https://github.com/juju/layer-index, it comes for free with the ability to publish layers to pypi.org. If we did ever want to host a pypi alternative, we could use the same code that pypi.org itself runs on.
  4. Relying on existing tooling reduces the complexity of the charm build process, such as removing the need for a separate wheelhouse.txt.
  5. We can build on top of existing dependency management tooling like pip/poetry, which already supports many different ways of grabbing dependencies, such as local paths (including from submodules), specific git commits, or pypi alternatives if we were to ever host a charming-specific pypi alternative.

This presents some issues with installing dependencies that could then be solved by packaging charm code as Python snaps. As rationale:

  1. Layers are code that's consumed by other code as libraries, whereas charms are code that's consumed more like an application
  2. Development can still use regular virtualenvs, since layers are just normal Python packages
  3. Packaging charms for the charm store wouldn't run into virtualenv issues, they're installed as "system" packages inside the snap.
  4. In both dev and production, dependencies are managed in a way that's not surprising to a Python developer
  5. Reusing the snap tooling and infrastructure would cut down on a lot of work on the charming side of things

We should log everything to a file

No matter what the log level the user choose (through -q or -v options), we need to log everything to a file (could be located inside the build dir).

If the process ended through a CommandError it should indicate that all output was left in that file.

If the process just crashed, the corresponding traceback should also be in this logfile.

We need to support config files

Multilayer: we will merge multiple configuration files, in this order (the latter prevails):

  • system: /etc/charmcraft.yaml
  • user: ($XDG_CONFIG_DIR)/charmcraft.yaml
  • local dir: charmcraft.yaml and .charmcraft.yaml
  • command-line received options

Discoverable: the same parameters used in command line (shown with --help from the tool) can be applied in the config files. In this POV, the config file is just "a way to avoid needing to pass the same option through the command line"

Format and structure: We'll use a YAML file. Internally, it will be separated by commands (as different commands may have equally named option names). General options can be used in the generic charmcraft section, but also overriden in the commands. A example valid YAML:

charmcraft:
    verbose: true

build:
    requirements:
        - reqs-legacy.txt
        - reqs-new.txt

deploy:
    verbose: 0

Explicitness: We'll log in debug which files were found and processed, and the final loaded configuration (after merging all those files and given command line modifiers).

Some details about options types:

  • if the command line option is a flag (like verbose, not expecting a value, as you just saying you want verbose by using the flag), in the YAML it's converted to a bool (valid values are 0/1, false/true, no/yes).
  • if the command line option is "multiple" (can be given multiple times), in the YAML file we will support either it being a value, or a list of values

Use hardlinks in the build directory

Actually, we should emulate cp -al:

  • use hard links
  • preserve permissions bits, mode, attributes, flags, etc
  • preserve original symlinks (so "hooks" will look as they should, for example)
  • do not dereference (never follow symbolic links in what we're linking to)
  • copy directories recursively

As a side effect of this, we're able to allow symlinks in the zip file (not "copying contents" for everything!).

This will result in a build directory as close as possible to what would be found later when the package charm is unzipped.

Ensure credentials are stored in a safe place

We need to revisit where the credentials are stored. So far we put them in a file in user's config directory, we may move them later to a wallet or whatever.

We must do this before setting the default backend to production.

Have a "man" page

With content probably automated after all parameters (or at least a test case to verify all commands are properly reflected)

Have a help command

(Idea from here)

We should have a help command. Without options (charmcraft help) it will show the general help. Optionally it would accept a command (charmcraft help build), which breaks the rule of "arguments should not be optional", but as this is for guiding the users when they are wrong, to help them learn the tool, I think we can live with it.

Implement a backoff when polling the Store for an upload status

Currently we're polling every a fixed time, for ever.

We need to:

  • implement a slight backoff algorithm
  • implement a timeout after N retries (big one, after snapcraft experience)

Actually, it doesn't need to be a real "algorithm", it's better to manually design the user experience.

Proposal: A first test real quick, in case the Store fast approved the charm, with two more tests very fast too. Then some more tests going multi-second. Then keep trying every 3s for a total timeout of one minute:

>>> poll_delays = [.2, .3, .5, 1, 1, 1, 2] + [3] * 18
>>> sum(poll_delays)
60.0

Reflect the platform/os/arch in the name

When building the charm, the package name should include details about where it's built for:

  • platform
  • os
  • architecture

Separated by a simple dash (-). We may have also special values (like all for the architecture).

We should indicate progress on commands

Instead of showing lines and lines when command progresses, each line should replace the other.

Finally, if all went ok, only one line should remain in the output, something indicating that all ended up successfully. If it ended in error, it should keep the line of the last action, and then the error line (which refer to the logfile with all the sequence, anyway).

If the command has a previously known "length", we may include a percentage of progress, somehow.

Keep all latest logs

This are several small changes:

  • start storing the logs in user's cache directory
  • don't remove the log file when charmcraft ends
  • keep latest N log files (RotatingFileHandler)
  • have a parameter in the config about that N

Need to add an internal UnknownError exception

This is for failures that we detect in other components we call.

For example:

  • when pip fails

  • when the Store fails inexplicably (NOT when they return an error with a proper message and everything)

Event charm.py not defined

I have written a charm using charmcraft, found here:

https://github.com/juju-solutions/bundle-kubeflow/tree/admission-webhook-charm/charms/admission-webhook

I am deploying it like this:

charmcraft build
juju deploy ./admission-webhook.charm --resource oci-image=gcr.io/kubeflow-images-public/admission-webhook:vmaster-gaf96e4e3

This is with charmcraft 0.1.0 installed via pypi.org and juju 2.7.6. After deployment, the charm stays in unknown status, and I get this message from juju debug-log:

application-admission-webhook: 11:04:06 DEBUG unit.admission-webhook/6.juju-log Event charm.py not defined for <__main__.AdmissionWebhookCharm object at 0x7f5a3790ec18>.

I'm not sure how I'm getting an event of that name.

Pep 518 support for charm metadata

Related to #18. Would it be possible to support defining charm metadata in pyproject.toml as specified in pep 518?

https://www.python.org/dev/peps/pep-0518/#tool-table

As an example of what I mean, it would be nice to be able to specify my charm's metadata like this in pyproject.toml:

[tool.charmcraft.metadata]
name = "charm-name"
display-name = "Charm Name"
summary = "Charm summary"
description = "Charm description"
repo = "https://github.com/repo/name"
license = "GPLv2"
maintainers = [
  "Full Name <[email protected]>",
]
tags = [
  "tag1",
  "tag2",
]
series = [
  "kubernetes",
]

[tool.charmtools.config.example-option]
type = "string"
default = "example"
description = "example description

This would have a few advantages:

  • Unifies all of the old yaml configuration files (config.yaml, layer.yaml, metadata.yaml) as well as dependency management into a single file
  • Integrates well into the rest of the Python ecosystem by using an already defined and official standard
  • TOML is simpler and has fewer edge cases than YAML

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.