Repository: google/jsonnet Branch: master Commit: d33798d495d5 Files: 3640 Total size: 46.1 MB Directory structure: gitextract_p_3vr9vi/ ├── .bazelignore ├── .bazelversion ├── .clang-format ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── build_and_test.yml │ ├── create_archive.sh │ ├── doc_update.yml │ ├── publish-python.yml │ └── release.yml ├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── MODULE.bazel ├── PYTHON_README.md ├── README.md ├── benchmarks/ │ ├── .gitignore │ ├── bench.01.jsonnet │ ├── bench.02.jsonnet │ ├── bench.03.jsonnet │ ├── bench.04.jsonnet │ ├── bench.06.jsonnet │ ├── bench.07.jsonnet │ ├── bench.08.jsonnet │ ├── bench.09.jsonnet │ ├── gen_big_object.jsonnet │ └── regen_benchmarks.sh ├── case_studies/ │ ├── fractal/ │ │ ├── .gitignore │ │ ├── appserv/ │ │ │ ├── main.py │ │ │ ├── static/ │ │ │ │ └── style.css │ │ │ └── templates/ │ │ │ ├── base.html │ │ │ ├── error.html │ │ │ └── page.html │ │ ├── credentials.libsonnet.TEMPLATE │ │ ├── lib/ │ │ │ ├── cassandra.libsonnet │ │ │ ├── packer.libsonnet │ │ │ └── terraform.libsonnet │ │ ├── service.jsonnet │ │ └── tilegen/ │ │ ├── mandelbrot.cpp │ │ └── mandelbrot_service.py │ ├── kubernetes/ │ │ ├── README.md │ │ ├── bigquery-controller.old.yaml │ │ ├── example.jsonnet │ │ ├── kube.libsonnet │ │ ├── redis-master-service.old.yaml │ │ ├── redis-master.old.yaml │ │ ├── test_same.py │ │ └── twitter-stream.old.yaml │ ├── micro_fractal/ │ │ ├── .gitignore │ │ ├── appserv/ │ │ │ ├── main.py │ │ │ ├── static/ │ │ │ │ └── style.css │ │ │ └── templates/ │ │ │ ├── base.html │ │ │ ├── error.html │ │ │ └── page.html │ │ ├── db/ │ │ │ ├── db_export.sh │ │ │ ├── db_import.sh │ │ │ └── nodetool.sh │ │ ├── fractal.jsonnet │ │ ├── fractal_dev.jsonnet.TEMPLATE │ │ └── tilegen/ │ │ ├── mandelbrot.cpp │ │ └── mandelbrot_service.py │ └── micromanage/ │ ├── .gitignore │ ├── build_artefact.py │ ├── cmds.py │ ├── examples/ │ │ ├── hello_world_amazon.jsonnet │ │ └── hello_world_google.jsonnet │ ├── lib/ │ │ └── mmlib/ │ │ └── v0.1.2/ │ │ ├── amis/ │ │ │ ├── debian.libsonnet │ │ │ ├── ubuntu.libsonnet │ │ │ ├── ubuntu.prof │ │ │ └── ubuntu_raw.json │ │ ├── cmd/ │ │ │ ├── apt.libsonnet │ │ │ ├── cmd.libsonnet │ │ │ └── pip.libsonnet │ │ ├── db/ │ │ │ └── cassandra.libsonnet │ │ ├── service/ │ │ │ ├── amazon.libsonnet │ │ │ ├── base.libsonnet │ │ │ └── google.libsonnet │ │ └── web/ │ │ ├── nginx.libsonnet │ │ ├── solutions.libsonnet │ │ ├── uwsgi_flask.libsonnet │ │ └── web.libsonnet │ ├── micromanage │ ├── micromanage.py │ ├── packer.py │ ├── service.py │ ├── service_amazon.py │ ├── service_google.py │ ├── tests/ │ │ ├── amazon/ │ │ │ └── test_single_instance.jsonnet │ │ ├── google/ │ │ │ ├── test_empty.jsonnet │ │ │ ├── test_nested.jsonnet │ │ │ └── test_single_instance.jsonnet │ │ ├── test_really_empty.jsonnet │ │ └── testenv.libsonnet.TEMPLATE │ ├── util.py │ └── validate.py ├── cmd/ │ ├── BUILD │ ├── jsonnet.cpp │ ├── jsonnetfmt.cpp │ ├── utils.cpp │ └── utils.h ├── cpp/ │ ├── BUILD │ ├── libjsonnet++.cpp │ ├── libjsonnet++_test.cpp │ ├── libjsonnet_locale_test.cpp │ └── testdata/ │ ├── BUILD │ ├── example.jsonnet │ ├── example_golden.json │ ├── importing.jsonnet │ ├── importing_golden.json │ ├── invalid.jsonnet │ └── invalid.out ├── doc/ │ ├── .gitignore │ ├── CNAME │ ├── _config.yml │ ├── _includes/ │ │ ├── demo.inc │ │ └── examples/ │ │ ├── arith.jsonnet │ │ ├── arith.jsonnet.golden │ │ ├── cocktail-comprehensions.jsonnet │ │ ├── cocktail-comprehensions.jsonnet.golden │ │ ├── comprehensions.jsonnet │ │ ├── comprehensions.jsonnet.golden │ │ ├── computed-fields.jsonnet │ │ ├── computed-fields.jsonnet.golden │ │ ├── conditionals.jsonnet │ │ ├── conditionals.jsonnet.golden │ │ ├── error-examples.jsonnet │ │ ├── error-examples.jsonnet.golden │ │ ├── functions.jsonnet │ │ ├── functions.jsonnet.golden │ │ ├── garnish.txt │ │ ├── imports.jsonnet │ │ ├── imports.jsonnet.golden │ │ ├── inner-reference.jsonnet │ │ ├── inner-reference.jsonnet.golden │ │ ├── library-ext.libsonnet │ │ ├── library-tla.libsonnet │ │ ├── martinis.libsonnet │ │ ├── mixins.jsonnet │ │ ├── mixins.jsonnet.golden │ │ ├── negroni.jsonnet │ │ ├── negroni.jsonnet.golden │ │ ├── oo-contrived.jsonnet │ │ ├── oo-contrived.jsonnet.golden │ │ ├── references.jsonnet │ │ ├── references.jsonnet.golden │ │ ├── sours-oo.jsonnet │ │ ├── sours-oo.jsonnet.golden │ │ ├── sours.jsonnet │ │ ├── sours.jsonnet.golden │ │ ├── syntax.jsonnet │ │ ├── syntax.jsonnet.golden │ │ ├── templates.libsonnet │ │ ├── top-level-ext.jsonnet │ │ ├── top-level-ext.jsonnet.golden │ │ ├── top-level-tla.jsonnet │ │ ├── top-level-tla.jsonnet.golden │ │ ├── utils.libsonnet │ │ ├── variables.jsonnet │ │ └── variables.jsonnet.golden │ ├── _layouts/ │ │ ├── base.html │ │ ├── default.html │ │ ├── markdown.html │ │ ├── redirect.html │ │ ├── stdlib.html │ │ └── wide.html │ ├── _stdlib_gen/ │ │ ├── html.libsonnet │ │ ├── jekyll.libsonnet │ │ ├── run_tests.sh │ │ ├── stdlib-content-test.jsonnet │ │ ├── stdlib-content.jsonnet │ │ └── stdlib.jsonnet │ ├── articles/ │ │ ├── comparisons.html │ │ ├── design.html │ │ ├── fractal.1.html │ │ ├── fractal.2.html │ │ ├── fractal.3.html │ │ ├── kubernetes.html │ │ └── output-formats.html │ ├── case_studies/ │ │ └── casestudy_fractal.1.html │ ├── contributing.html │ ├── css/ │ │ ├── cellphone-small.css │ │ ├── cellphone.css │ │ ├── desktop.css │ │ └── doc.css │ ├── docs/ │ │ ├── index.html │ │ ├── stdlib.html │ │ └── tutorial.html │ ├── index.html │ ├── js/ │ │ ├── codemirror-mode-jsonnet.js │ │ ├── demo.js │ │ ├── menu.js │ │ └── wasm_exec.js │ ├── language/ │ │ └── spec.html │ ├── learning/ │ │ ├── community.html │ │ ├── getting_started.html │ │ ├── tools.html │ │ └── tutorial.html │ ├── ref/ │ │ ├── bindings.html │ │ ├── language.html.md │ │ ├── spec.html │ │ └── stdlib.html │ └── third_party/ │ ├── CodeMirror/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── addon/ │ │ │ └── edit/ │ │ │ └── matchbrackets.js │ │ ├── lib/ │ │ │ ├── codemirror.css │ │ │ └── codemirror.js │ │ └── mode/ │ │ └── yaml/ │ │ └── yaml.js │ ├── MathJax-2.7.2/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── .travis.yml │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MathJax.js │ │ ├── README.md │ │ ├── bower.json │ │ ├── composer.json │ │ ├── config/ │ │ │ ├── AM_CHTML-full.js │ │ │ ├── AM_CHTML.js │ │ │ ├── AM_HTMLorMML-full.js │ │ │ ├── AM_HTMLorMML.js │ │ │ ├── AM_SVG-full.js │ │ │ ├── AM_SVG.js │ │ │ ├── Accessible-full.js │ │ │ ├── Accessible.js │ │ │ ├── MML_CHTML-full.js │ │ │ ├── MML_CHTML.js │ │ │ ├── MML_HTMLorMML-full.js │ │ │ ├── MML_HTMLorMML.js │ │ │ ├── MML_SVG-full.js │ │ │ ├── MML_SVG.js │ │ │ ├── MMLorHTML.js │ │ │ ├── Safe.js │ │ │ ├── TeX-AMS-MML_HTMLorMML-full.js │ │ │ ├── TeX-AMS-MML_HTMLorMML.js │ │ │ ├── TeX-AMS-MML_SVG-full.js │ │ │ ├── TeX-AMS-MML_SVG.js │ │ │ ├── TeX-AMS_CHTML-full.js │ │ │ ├── TeX-AMS_CHTML.js │ │ │ ├── TeX-AMS_HTML-full.js │ │ │ ├── TeX-AMS_HTML.js │ │ │ ├── TeX-AMS_SVG-full.js │ │ │ ├── TeX-AMS_SVG.js │ │ │ ├── TeX-MML-AM_CHTML-full.js │ │ │ ├── TeX-MML-AM_CHTML.js │ │ │ ├── TeX-MML-AM_HTMLorMML-full.js │ │ │ ├── TeX-MML-AM_HTMLorMML.js │ │ │ ├── TeX-MML-AM_SVG-full.js │ │ │ ├── TeX-MML-AM_SVG.js │ │ │ ├── default.js │ │ │ └── local/ │ │ │ └── local.js │ │ ├── docs/ │ │ │ ├── README.txt │ │ │ ├── html/ │ │ │ │ └── index.html │ │ │ └── source/ │ │ │ └── README.txt │ │ ├── extensions/ │ │ │ ├── AssistiveMML.js │ │ │ ├── CHTML-preview.js │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS/ │ │ │ │ └── handle-floats.js │ │ │ ├── HelpDialog.js │ │ │ ├── MatchWebFonts.js │ │ │ ├── MathEvents.js │ │ │ ├── MathML/ │ │ │ │ ├── content-mathml.js │ │ │ │ └── mml3.js │ │ │ ├── MathMenu.js │ │ │ ├── MathZoom.js │ │ │ ├── Safe.js │ │ │ ├── TeX/ │ │ │ │ ├── AMScd.js │ │ │ │ ├── AMSmath.js │ │ │ │ ├── AMSsymbols.js │ │ │ │ ├── HTML.js │ │ │ │ ├── action.js │ │ │ │ ├── autobold.js │ │ │ │ ├── autoload-all.js │ │ │ │ ├── bbox.js │ │ │ │ ├── begingroup.js │ │ │ │ ├── boldsymbol.js │ │ │ │ ├── cancel.js │ │ │ │ ├── color.js │ │ │ │ ├── enclose.js │ │ │ │ ├── extpfeil.js │ │ │ │ ├── mathchoice.js │ │ │ │ ├── mediawiki-texvc.js │ │ │ │ ├── mhchem.js │ │ │ │ ├── mhchem3/ │ │ │ │ │ └── mhchem.js │ │ │ │ ├── newcommand.js │ │ │ │ ├── noErrors.js │ │ │ │ ├── noUndefined.js │ │ │ │ ├── unicode.js │ │ │ │ └── verb.js │ │ │ ├── a11y/ │ │ │ │ ├── accessibility-menu.js │ │ │ │ ├── auto-collapse.js │ │ │ │ ├── collapsible.js │ │ │ │ ├── explorer.js │ │ │ │ ├── invalid_keypress.ogg │ │ │ │ ├── mathjax-sre.js │ │ │ │ ├── mathmaps/ │ │ │ │ │ ├── .htaccess │ │ │ │ │ ├── functions/ │ │ │ │ │ │ ├── algebra.js │ │ │ │ │ │ ├── elementary.js │ │ │ │ │ │ ├── hyperbolic.js │ │ │ │ │ │ └── trigonometry.js │ │ │ │ │ ├── mathmaps_ie.js │ │ │ │ │ ├── symbols/ │ │ │ │ │ │ ├── greek-capital.js │ │ │ │ │ │ ├── greek-mathfonts.js │ │ │ │ │ │ ├── greek-scripts.js │ │ │ │ │ │ ├── greek-small.js │ │ │ │ │ │ ├── greek-symbols.js │ │ │ │ │ │ ├── hebrew_letters.js │ │ │ │ │ │ ├── latin-lower-double-accent.js │ │ │ │ │ │ ├── latin-lower-normal.js │ │ │ │ │ │ ├── latin-lower-phonetic.js │ │ │ │ │ │ ├── latin-lower-single-accent.js │ │ │ │ │ │ ├── latin-mathfonts.js │ │ │ │ │ │ ├── latin-rest.js │ │ │ │ │ │ ├── latin-upper-double-accent.js │ │ │ │ │ │ ├── latin-upper-normal.js │ │ │ │ │ │ ├── latin-upper-single-accent.js │ │ │ │ │ │ ├── math_angles.js │ │ │ │ │ │ ├── math_arrows.js │ │ │ │ │ │ ├── math_characters.js │ │ │ │ │ │ ├── math_delimiters.js │ │ │ │ │ │ ├── math_digits.js │ │ │ │ │ │ ├── math_geometry.js │ │ │ │ │ │ ├── math_harpoons.js │ │ │ │ │ │ ├── math_non_characters.js │ │ │ │ │ │ ├── math_symbols.js │ │ │ │ │ │ ├── math_whitespace.js │ │ │ │ │ │ └── other_stars.js │ │ │ │ │ └── units/ │ │ │ │ │ ├── energy.js │ │ │ │ │ ├── length.js │ │ │ │ │ ├── memory.js │ │ │ │ │ ├── other.js │ │ │ │ │ ├── speed.js │ │ │ │ │ ├── temperature.js │ │ │ │ │ ├── time.js │ │ │ │ │ ├── volume.js │ │ │ │ │ └── weight.js │ │ │ │ ├── semantic-enrich.js │ │ │ │ └── wgxpath.install.js │ │ │ ├── asciimath2jax.js │ │ │ ├── fast-preview.js │ │ │ ├── jsMath2jax.js │ │ │ ├── mml2jax.js │ │ │ ├── tex2jax.js │ │ │ └── toMathML.js │ │ ├── fonts/ │ │ │ └── HTML-CSS/ │ │ │ ├── Asana-Math/ │ │ │ │ ├── OFL.txt │ │ │ │ └── otf/ │ │ │ │ ├── AsanaMathJax_Alphabets-Regular.otf │ │ │ │ ├── AsanaMathJax_Arrows-Regular.otf │ │ │ │ ├── AsanaMathJax_DoubleStruck-Regular.otf │ │ │ │ ├── AsanaMathJax_Fraktur-Regular.otf │ │ │ │ ├── AsanaMathJax_Latin-Regular.otf │ │ │ │ ├── AsanaMathJax_Main-Regular.otf │ │ │ │ ├── AsanaMathJax_Marks-Regular.otf │ │ │ │ ├── AsanaMathJax_Misc-Regular.otf │ │ │ │ ├── AsanaMathJax_Monospace-Regular.otf │ │ │ │ ├── AsanaMathJax_NonUnicode-Regular.otf │ │ │ │ ├── AsanaMathJax_Normal-Regular.otf │ │ │ │ ├── AsanaMathJax_Operators-Regular.otf │ │ │ │ ├── AsanaMathJax_SansSerif-Regular.otf │ │ │ │ ├── AsanaMathJax_Script-Regular.otf │ │ │ │ ├── AsanaMathJax_Shapes-Regular.otf │ │ │ │ ├── AsanaMathJax_Size1-Regular.otf │ │ │ │ ├── AsanaMathJax_Size2-Regular.otf │ │ │ │ ├── AsanaMathJax_Size3-Regular.otf │ │ │ │ ├── AsanaMathJax_Size4-Regular.otf │ │ │ │ ├── AsanaMathJax_Size5-Regular.otf │ │ │ │ ├── AsanaMathJax_Size6-Regular.otf │ │ │ │ ├── AsanaMathJax_Symbols-Regular.otf │ │ │ │ └── AsanaMathJax_Variants-Regular.otf │ │ │ ├── Gyre-Pagella/ │ │ │ │ ├── GUST-FONT-LICENSE.txt │ │ │ │ ├── MANIFEST-GyrePagellaMathJax.txt │ │ │ │ └── otf/ │ │ │ │ ├── GyrePagellaMathJax_Alphabets-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Arrows-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_DoubleStruck-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Fraktur-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Latin-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Main-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Marks-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Misc-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Monospace-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_NonUnicode-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Normal-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Operators-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_SansSerif-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Script-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Shapes-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Size1-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Size2-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Size3-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Size4-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Size5-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Size6-Regular.otf │ │ │ │ ├── GyrePagellaMathJax_Symbols-Regular.otf │ │ │ │ └── GyrePagellaMathJax_Variants-Regular.otf │ │ │ ├── Gyre-Termes/ │ │ │ │ ├── GUST-FONT-LICENSE.txt │ │ │ │ ├── MANIFEST-GyreTermesMathJax.txt │ │ │ │ └── otf/ │ │ │ │ ├── GyreTermesMathJax_Alphabets-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Arrows-Regular.otf │ │ │ │ ├── GyreTermesMathJax_DoubleStruck-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Fraktur-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Latin-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Main-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Marks-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Misc-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Monospace-Regular.otf │ │ │ │ ├── GyreTermesMathJax_NonUnicode-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Normal-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Operators-Regular.otf │ │ │ │ ├── GyreTermesMathJax_SansSerif-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Script-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Shapes-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Size1-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Size2-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Size3-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Size4-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Size5-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Size6-Regular.otf │ │ │ │ ├── GyreTermesMathJax_Symbols-Regular.otf │ │ │ │ └── GyreTermesMathJax_Variants-Regular.otf │ │ │ ├── Latin-Modern/ │ │ │ │ ├── GUST-FONT-LICENSE.txt │ │ │ │ ├── MANIFEST-LatinModernMathJax.txt │ │ │ │ └── otf/ │ │ │ │ ├── LatinModernMathJax_Alphabets-Regular.otf │ │ │ │ ├── LatinModernMathJax_Arrows-Regular.otf │ │ │ │ ├── LatinModernMathJax_DoubleStruck-Regular.otf │ │ │ │ ├── LatinModernMathJax_Fraktur-Regular.otf │ │ │ │ ├── LatinModernMathJax_Latin-Regular.otf │ │ │ │ ├── LatinModernMathJax_Main-Regular.otf │ │ │ │ ├── LatinModernMathJax_Marks-Regular.otf │ │ │ │ ├── LatinModernMathJax_Misc-Regular.otf │ │ │ │ ├── LatinModernMathJax_Monospace-Regular.otf │ │ │ │ ├── LatinModernMathJax_NonUnicode-Regular.otf │ │ │ │ ├── LatinModernMathJax_Normal-Regular.otf │ │ │ │ ├── LatinModernMathJax_Operators-Regular.otf │ │ │ │ ├── LatinModernMathJax_SansSerif-Regular.otf │ │ │ │ ├── LatinModernMathJax_Script-Regular.otf │ │ │ │ ├── LatinModernMathJax_Shapes-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size1-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size2-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size3-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size4-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size5-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size6-Regular.otf │ │ │ │ ├── LatinModernMathJax_Size7-Regular.otf │ │ │ │ ├── LatinModernMathJax_Symbols-Regular.otf │ │ │ │ └── LatinModernMathJax_Variants-Regular.otf │ │ │ ├── Neo-Euler/ │ │ │ │ ├── OFL.txt │ │ │ │ └── otf/ │ │ │ │ ├── NeoEulerMathJax_Alphabets-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Arrows-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Fraktur-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Main-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Marks-Regular.otf │ │ │ │ ├── NeoEulerMathJax_NonUnicode-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Normal-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Operators-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Script-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Shapes-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Size1-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Size2-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Size3-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Size4-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Size5-Regular.otf │ │ │ │ ├── NeoEulerMathJax_Symbols-Regular.otf │ │ │ │ └── NeoEulerMathJax_Variants-Regular.otf │ │ │ ├── STIX-Web/ │ │ │ │ ├── OFL.txt │ │ │ │ └── otf/ │ │ │ │ ├── STIXMathJax_Alphabets-Bold.otf │ │ │ │ ├── STIXMathJax_Alphabets-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Alphabets-Italic.otf │ │ │ │ ├── STIXMathJax_Alphabets-Regular.otf │ │ │ │ ├── STIXMathJax_Arrows-Bold.otf │ │ │ │ ├── STIXMathJax_Arrows-Regular.otf │ │ │ │ ├── STIXMathJax_DoubleStruck-Bold.otf │ │ │ │ ├── STIXMathJax_DoubleStruck-BoldItalic.otf │ │ │ │ ├── STIXMathJax_DoubleStruck-Italic.otf │ │ │ │ ├── STIXMathJax_DoubleStruck-Regular.otf │ │ │ │ ├── STIXMathJax_Fraktur-Bold.otf │ │ │ │ ├── STIXMathJax_Fraktur-Regular.otf │ │ │ │ ├── STIXMathJax_Latin-Bold.otf │ │ │ │ ├── STIXMathJax_Latin-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Latin-Italic.otf │ │ │ │ ├── STIXMathJax_Latin-Regular.otf │ │ │ │ ├── STIXMathJax_Main-Bold.otf │ │ │ │ ├── STIXMathJax_Main-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Main-Italic.otf │ │ │ │ ├── STIXMathJax_Main-Regular.otf │ │ │ │ ├── STIXMathJax_Marks-Bold.otf │ │ │ │ ├── STIXMathJax_Marks-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Marks-Italic.otf │ │ │ │ ├── STIXMathJax_Marks-Regular.otf │ │ │ │ ├── STIXMathJax_Misc-Bold.otf │ │ │ │ ├── STIXMathJax_Misc-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Misc-Italic.otf │ │ │ │ ├── STIXMathJax_Misc-Regular.otf │ │ │ │ ├── STIXMathJax_Monospace-Regular.otf │ │ │ │ ├── STIXMathJax_Normal-Bold.otf │ │ │ │ ├── STIXMathJax_Normal-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Normal-Italic.otf │ │ │ │ ├── STIXMathJax_Operators-Bold.otf │ │ │ │ ├── STIXMathJax_Operators-Regular.otf │ │ │ │ ├── STIXMathJax_SansSerif-Bold.otf │ │ │ │ ├── STIXMathJax_SansSerif-BoldItalic.otf │ │ │ │ ├── STIXMathJax_SansSerif-Italic.otf │ │ │ │ ├── STIXMathJax_SansSerif-Regular.otf │ │ │ │ ├── STIXMathJax_Script-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Script-Italic.otf │ │ │ │ ├── STIXMathJax_Script-Regular.otf │ │ │ │ ├── STIXMathJax_Shapes-Bold.otf │ │ │ │ ├── STIXMathJax_Shapes-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Shapes-Regular.otf │ │ │ │ ├── STIXMathJax_Size1-Regular.otf │ │ │ │ ├── STIXMathJax_Size2-Regular.otf │ │ │ │ ├── STIXMathJax_Size3-Regular.otf │ │ │ │ ├── STIXMathJax_Size4-Regular.otf │ │ │ │ ├── STIXMathJax_Size5-Regular.otf │ │ │ │ ├── STIXMathJax_Symbols-Bold.otf │ │ │ │ ├── STIXMathJax_Symbols-Regular.otf │ │ │ │ ├── STIXMathJax_Variants-Bold.otf │ │ │ │ ├── STIXMathJax_Variants-BoldItalic.otf │ │ │ │ ├── STIXMathJax_Variants-Italic.otf │ │ │ │ └── STIXMathJax_Variants-Regular.otf │ │ │ └── TeX/ │ │ │ └── otf/ │ │ │ ├── MathJax_AMS-Regular.otf │ │ │ ├── MathJax_Caligraphic-Bold.otf │ │ │ ├── MathJax_Caligraphic-Regular.otf │ │ │ ├── MathJax_Fraktur-Bold.otf │ │ │ ├── MathJax_Fraktur-Regular.otf │ │ │ ├── MathJax_Main-Bold.otf │ │ │ ├── MathJax_Main-Italic.otf │ │ │ ├── MathJax_Main-Regular.otf │ │ │ ├── MathJax_Math-BoldItalic.otf │ │ │ ├── MathJax_Math-Italic.otf │ │ │ ├── MathJax_Math-Regular.otf │ │ │ ├── MathJax_SansSerif-Bold.otf │ │ │ ├── MathJax_SansSerif-Italic.otf │ │ │ ├── MathJax_SansSerif-Regular.otf │ │ │ ├── MathJax_Script-Regular.otf │ │ │ ├── MathJax_Size1-Regular.otf │ │ │ ├── MathJax_Size2-Regular.otf │ │ │ ├── MathJax_Size3-Regular.otf │ │ │ ├── MathJax_Size4-Regular.otf │ │ │ ├── MathJax_Typewriter-Regular.otf │ │ │ ├── MathJax_Vector-Bold.otf │ │ │ ├── MathJax_Vector-Regular.otf │ │ │ ├── MathJax_WinChrome-Regular.otf │ │ │ └── MathJax_WinIE6-Regular.otf │ │ ├── jax/ │ │ │ ├── element/ │ │ │ │ └── mml/ │ │ │ │ ├── jax.js │ │ │ │ └── optable/ │ │ │ │ ├── Arrows.js │ │ │ │ ├── BasicLatin.js │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ ├── Dingbats.js │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ ├── GeometricShapes.js │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ ├── Latin1Supplement.js │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ ├── MathOperators.js │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ ├── MiscSymbolsAndArrows.js │ │ │ │ ├── MiscTechnical.js │ │ │ │ ├── SpacingModLetters.js │ │ │ │ ├── SuppMathOperators.js │ │ │ │ ├── SupplementalArrowsA.js │ │ │ │ └── SupplementalArrowsB.js │ │ │ ├── input/ │ │ │ │ ├── AsciiMath/ │ │ │ │ │ ├── config.js │ │ │ │ │ └── jax.js │ │ │ │ ├── MathML/ │ │ │ │ │ ├── config.js │ │ │ │ │ ├── entities/ │ │ │ │ │ │ ├── a.js │ │ │ │ │ │ ├── b.js │ │ │ │ │ │ ├── c.js │ │ │ │ │ │ ├── d.js │ │ │ │ │ │ ├── e.js │ │ │ │ │ │ ├── f.js │ │ │ │ │ │ ├── fr.js │ │ │ │ │ │ ├── g.js │ │ │ │ │ │ ├── h.js │ │ │ │ │ │ ├── i.js │ │ │ │ │ │ ├── j.js │ │ │ │ │ │ ├── k.js │ │ │ │ │ │ ├── l.js │ │ │ │ │ │ ├── m.js │ │ │ │ │ │ ├── n.js │ │ │ │ │ │ ├── o.js │ │ │ │ │ │ ├── opf.js │ │ │ │ │ │ ├── p.js │ │ │ │ │ │ ├── q.js │ │ │ │ │ │ ├── r.js │ │ │ │ │ │ ├── s.js │ │ │ │ │ │ ├── scr.js │ │ │ │ │ │ ├── t.js │ │ │ │ │ │ ├── u.js │ │ │ │ │ │ ├── v.js │ │ │ │ │ │ ├── w.js │ │ │ │ │ │ ├── x.js │ │ │ │ │ │ ├── y.js │ │ │ │ │ │ └── z.js │ │ │ │ │ └── jax.js │ │ │ │ └── TeX/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ └── output/ │ │ │ ├── CommonHTML/ │ │ │ │ ├── autoload/ │ │ │ │ │ ├── annotation-xml.js │ │ │ │ │ ├── maction.js │ │ │ │ │ ├── menclose.js │ │ │ │ │ ├── mglyph.js │ │ │ │ │ ├── mmultiscripts.js │ │ │ │ │ ├── ms.js │ │ │ │ │ ├── mtable.js │ │ │ │ │ └── multiline.js │ │ │ │ ├── config.js │ │ │ │ ├── fonts/ │ │ │ │ │ └── TeX/ │ │ │ │ │ ├── AMS-Regular.js │ │ │ │ │ ├── Caligraphic-Bold.js │ │ │ │ │ ├── Fraktur-Bold.js │ │ │ │ │ ├── Fraktur-Regular.js │ │ │ │ │ ├── Main-Bold.js │ │ │ │ │ ├── Math-BoldItalic.js │ │ │ │ │ ├── SansSerif-Bold.js │ │ │ │ │ ├── SansSerif-Italic.js │ │ │ │ │ ├── SansSerif-Regular.js │ │ │ │ │ ├── Script-Regular.js │ │ │ │ │ ├── Typewriter-Regular.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ └── jax.js │ │ │ ├── HTML-CSS/ │ │ │ │ ├── autoload/ │ │ │ │ │ ├── annotation-xml.js │ │ │ │ │ ├── maction.js │ │ │ │ │ ├── menclose.js │ │ │ │ │ ├── mglyph.js │ │ │ │ │ ├── mmultiscripts.js │ │ │ │ │ ├── ms.js │ │ │ │ │ ├── mtable.js │ │ │ │ │ └── multiline.js │ │ │ │ ├── config.js │ │ │ │ ├── fonts/ │ │ │ │ │ ├── Asana-Math/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Gyre-Pagella/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Gyre-Termes/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Latin-Modern/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size7/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Neo-Euler/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── STIX/ │ │ │ │ │ │ ├── General/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ │ │ ├── BBBold.js │ │ │ │ │ │ │ │ ├── BoldFraktur.js │ │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ │ ├── GreekBold.js │ │ │ │ │ │ │ │ ├── GreekSSBold.js │ │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ │ ├── LatinExtendedD.js │ │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ ├── MathBold.js │ │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ │ ├── MathSSBold.js │ │ │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ │ │ ├── NumberForms.js │ │ │ │ │ │ │ │ ├── PhoneticExtensions.js │ │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ │ ├── SuperAndSubscripts.js │ │ │ │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ │ ├── GreekBoldItalic.js │ │ │ │ │ │ │ │ ├── GreekSSBoldItalic.js │ │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ ├── MathBoldItalic.js │ │ │ │ │ │ │ │ ├── MathBoldScript.js │ │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ │ ├── MathSSItalicBold.js │ │ │ │ │ │ │ │ └── SpacingModLetters.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ │ ├── GreekItalic.js │ │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ ├── MathItalic.js │ │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ │ ├── MathSSItalic.js │ │ │ │ │ │ │ │ ├── MathScript.js │ │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ │ └── ij.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ │ ├── BBBold.js │ │ │ │ │ │ │ ├── BlockElements.js │ │ │ │ │ │ │ ├── BoldFraktur.js │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ ├── CJK.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ ├── Dingbats.js │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ ├── Fraktur.js │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ ├── GreekBold.js │ │ │ │ │ │ │ ├── GreekBoldItalic.js │ │ │ │ │ │ │ ├── GreekItalic.js │ │ │ │ │ │ │ ├── GreekSSBold.js │ │ │ │ │ │ │ ├── GreekSSBoldItalic.js │ │ │ │ │ │ │ ├── Hiragana.js │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ ├── LatinExtendedD.js │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ ├── MathBold.js │ │ │ │ │ │ │ ├── MathBoldItalic.js │ │ │ │ │ │ │ ├── MathBoldScript.js │ │ │ │ │ │ │ ├── MathItalic.js │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ ├── MathSS.js │ │ │ │ │ │ │ ├── MathSSBold.js │ │ │ │ │ │ │ ├── MathSSItalic.js │ │ │ │ │ │ │ ├── MathSSItalicBold.js │ │ │ │ │ │ │ ├── MathScript.js │ │ │ │ │ │ │ ├── MathTT.js │ │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ │ ├── MiscSymbolsAndArrows.js │ │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ │ ├── NumberForms.js │ │ │ │ │ │ │ ├── PhoneticExtensions.js │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ ├── Specials.js │ │ │ │ │ │ │ ├── SuperAndSubscripts.js │ │ │ │ │ │ │ ├── SuppMathOperators.js │ │ │ │ │ │ │ ├── SupplementalArrowsA.js │ │ │ │ │ │ │ ├── SupplementalArrowsB.js │ │ │ │ │ │ │ └── ij.js │ │ │ │ │ │ ├── IntegralsD/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsSm/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsUp/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsUpD/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsUpSm/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ ├── SizeFiveSym/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeFourSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeOneSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeThreeSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeTwoSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-1.0.js │ │ │ │ │ │ ├── fontdata-beta.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── STIX-Web/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ └── TeX/ │ │ │ │ │ ├── AMS/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ ├── BBBold.js │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Dingbats.js │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ ├── PUA.js │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ │ ├── Caligraphic/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ ├── Other.js │ │ │ │ │ │ │ └── PUA.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── Other.js │ │ │ │ │ │ └── PUA.js │ │ │ │ │ ├── Greek/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ ├── SuppMathOperators.js │ │ │ │ │ │ │ └── SupplementalArrowsA.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ └── SpacingModLetters.js │ │ │ │ │ ├── Math/ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Italic/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ └── Other.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ └── Other.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Typewriter/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── WinChrome/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── WinIE6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── AMS.js │ │ │ │ │ │ ├── Bold.js │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── imageFonts.js │ │ │ │ └── jax.js │ │ │ ├── NativeMML/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ ├── PlainSource/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ ├── PreviewHTML/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ └── SVG/ │ │ │ ├── autoload/ │ │ │ │ ├── annotation-xml.js │ │ │ │ ├── maction.js │ │ │ │ ├── menclose.js │ │ │ │ ├── mglyph.js │ │ │ │ ├── mmultiscripts.js │ │ │ │ ├── ms.js │ │ │ │ ├── mtable.js │ │ │ │ └── multiline.js │ │ │ ├── config.js │ │ │ ├── fonts/ │ │ │ │ ├── Asana-Math/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Gyre-Pagella/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Gyre-Termes/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Latin-Modern/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size7/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Neo-Euler/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── STIX-Web/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Italic/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ └── TeX/ │ │ │ │ ├── AMS/ │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── Dingbats.js │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ ├── Main.js │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ ├── PUA.js │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ ├── Caligraphic/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Fraktur/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── Other.js │ │ │ │ │ │ └── PUA.js │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── Main.js │ │ │ │ │ ├── Other.js │ │ │ │ │ └── PUA.js │ │ │ │ ├── Main/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ ├── SuppMathOperators.js │ │ │ │ │ │ └── SupplementalArrowsA.js │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── MathOperators.js │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ ├── Main.js │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ ├── Math/ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ └── Italic/ │ │ │ │ │ └── Main.js │ │ │ │ ├── SansSerif/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── Main.js │ │ │ │ │ └── Other.js │ │ │ │ ├── Script/ │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ └── Main.js │ │ │ │ ├── Size1/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Size2/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Size3/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Size4/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Typewriter/ │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── Main.js │ │ │ │ │ └── Other.js │ │ │ │ ├── fontdata-extra.js │ │ │ │ └── fontdata.js │ │ │ └── jax.js │ │ ├── latest.js │ │ ├── localization/ │ │ │ ├── ar/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ar.js │ │ │ ├── ast/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ast.js │ │ │ ├── bcc/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── bcc.js │ │ │ ├── bg/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── bg.js │ │ │ ├── br/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── br.js │ │ │ ├── ca/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ca.js │ │ │ ├── cdo/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── cdo.js │ │ │ ├── ce/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ce.js │ │ │ ├── cs/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── cs.js │ │ │ ├── cy/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── cy.js │ │ │ ├── da/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── da.js │ │ │ ├── de/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── de.js │ │ │ ├── diq/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── diq.js │ │ │ ├── en/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── en.js │ │ │ ├── eo/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── eo.js │ │ │ ├── es/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── es.js │ │ │ ├── fa/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── fa.js │ │ │ ├── fi/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── fi.js │ │ │ ├── fr/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── fr.js │ │ │ ├── gl/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── gl.js │ │ │ ├── he/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── he.js │ │ │ ├── ia/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ia.js │ │ │ ├── it/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── it.js │ │ │ ├── ja/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ja.js │ │ │ ├── kn/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── kn.js │ │ │ ├── ko/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ko.js │ │ │ ├── lb/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── lb.js │ │ │ ├── lki/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── lki.js │ │ │ ├── lt/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── lt.js │ │ │ ├── mk/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── mk.js │ │ │ ├── nl/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── nl.js │ │ │ ├── oc/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── oc.js │ │ │ ├── pl/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── pl.js │ │ │ ├── pt/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── pt.js │ │ │ ├── pt-br/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── pt-br.js │ │ │ ├── qqq/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── qqq.js │ │ │ ├── ru/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── ru.js │ │ │ ├── scn/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── scn.js │ │ │ ├── sco/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── sco.js │ │ │ ├── sk/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── sk.js │ │ │ ├── sl/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── sl.js │ │ │ ├── sv/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── sv.js │ │ │ ├── th/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── th.js │ │ │ ├── tr/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── tr.js │ │ │ ├── uk/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── uk.js │ │ │ ├── vi/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── vi.js │ │ │ ├── zh-hans/ │ │ │ │ ├── FontWarnings.js │ │ │ │ ├── HTML-CSS.js │ │ │ │ ├── HelpDialog.js │ │ │ │ ├── MathML.js │ │ │ │ ├── MathMenu.js │ │ │ │ ├── TeX.js │ │ │ │ └── zh-hans.js │ │ │ └── zh-hant/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── zh-hant.js │ │ ├── package.json │ │ ├── test/ │ │ │ ├── examples.html │ │ │ ├── index-images.html │ │ │ ├── index.html │ │ │ ├── localization.html │ │ │ ├── sample-all-at-once.html │ │ │ ├── sample-asciimath.html │ │ │ ├── sample-autoload.html │ │ │ ├── sample-dynamic-2.html │ │ │ ├── sample-dynamic-steps.html │ │ │ ├── sample-dynamic.html │ │ │ ├── sample-eqnum.html │ │ │ ├── sample-eqrefs.html │ │ │ ├── sample-loader-config.html │ │ │ ├── sample-loader.html │ │ │ ├── sample-macros.html │ │ │ ├── sample-mediawiki-texvc.html │ │ │ ├── sample-mml.html │ │ │ ├── sample-signals.html │ │ │ ├── sample-tex.html │ │ │ └── sample.html │ │ └── unpacked/ │ │ ├── MathJax.js │ │ ├── config/ │ │ │ ├── AM_CHTML-full.js │ │ │ ├── AM_CHTML.js │ │ │ ├── AM_HTMLorMML-full.js │ │ │ ├── AM_HTMLorMML.js │ │ │ ├── AM_SVG-full.js │ │ │ ├── AM_SVG.js │ │ │ ├── Accessible-full.js │ │ │ ├── Accessible.js │ │ │ ├── MML_CHTML-full.js │ │ │ ├── MML_CHTML.js │ │ │ ├── MML_HTMLorMML-full.js │ │ │ ├── MML_HTMLorMML.js │ │ │ ├── MML_SVG-full.js │ │ │ ├── MML_SVG.js │ │ │ ├── MMLorHTML.js │ │ │ ├── Safe.js │ │ │ ├── TeX-AMS-MML_HTMLorMML-full.js │ │ │ ├── TeX-AMS-MML_HTMLorMML.js │ │ │ ├── TeX-AMS-MML_SVG-full.js │ │ │ ├── TeX-AMS-MML_SVG.js │ │ │ ├── TeX-AMS_CHTML-full.js │ │ │ ├── TeX-AMS_CHTML.js │ │ │ ├── TeX-AMS_HTML-full.js │ │ │ ├── TeX-AMS_HTML.js │ │ │ ├── TeX-AMS_SVG-full.js │ │ │ ├── TeX-AMS_SVG.js │ │ │ ├── TeX-MML-AM_CHTML-full.js │ │ │ ├── TeX-MML-AM_CHTML.js │ │ │ ├── TeX-MML-AM_HTMLorMML-full.js │ │ │ ├── TeX-MML-AM_HTMLorMML.js │ │ │ ├── TeX-MML-AM_SVG-full.js │ │ │ ├── TeX-MML-AM_SVG.js │ │ │ ├── default.js │ │ │ └── local/ │ │ │ └── local.js │ │ ├── extensions/ │ │ │ ├── AssistiveMML.js │ │ │ ├── CHTML-preview.js │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS/ │ │ │ │ └── handle-floats.js │ │ │ ├── HelpDialog.js │ │ │ ├── MatchWebFonts.js │ │ │ ├── MathEvents.js │ │ │ ├── MathML/ │ │ │ │ ├── content-mathml.js │ │ │ │ └── mml3.js │ │ │ ├── MathMenu.js │ │ │ ├── MathZoom.js │ │ │ ├── Safe.js │ │ │ ├── TeX/ │ │ │ │ ├── AMScd.js │ │ │ │ ├── AMSmath.js │ │ │ │ ├── AMSsymbols.js │ │ │ │ ├── HTML.js │ │ │ │ ├── action.js │ │ │ │ ├── autobold.js │ │ │ │ ├── autoload-all.js │ │ │ │ ├── bbox.js │ │ │ │ ├── begingroup.js │ │ │ │ ├── boldsymbol.js │ │ │ │ ├── cancel.js │ │ │ │ ├── color.js │ │ │ │ ├── enclose.js │ │ │ │ ├── extpfeil.js │ │ │ │ ├── mathchoice.js │ │ │ │ ├── mediawiki-texvc.js │ │ │ │ ├── mhchem.js │ │ │ │ ├── mhchem3/ │ │ │ │ │ └── mhchem.js │ │ │ │ ├── newcommand.js │ │ │ │ ├── noErrors.js │ │ │ │ ├── noUndefined.js │ │ │ │ ├── unicode.js │ │ │ │ └── verb.js │ │ │ ├── a11y/ │ │ │ │ ├── accessibility-menu.js │ │ │ │ ├── auto-collapse.js │ │ │ │ ├── collapsible.js │ │ │ │ ├── explorer.js │ │ │ │ ├── invalid_keypress.ogg │ │ │ │ ├── mathjax-sre.js │ │ │ │ ├── mathmaps/ │ │ │ │ │ ├── .htaccess │ │ │ │ │ ├── functions/ │ │ │ │ │ │ ├── algebra.js │ │ │ │ │ │ ├── elementary.js │ │ │ │ │ │ ├── hyperbolic.js │ │ │ │ │ │ └── trigonometry.js │ │ │ │ │ ├── mathmaps_ie.js │ │ │ │ │ ├── symbols/ │ │ │ │ │ │ ├── greek-capital.js │ │ │ │ │ │ ├── greek-mathfonts.js │ │ │ │ │ │ ├── greek-scripts.js │ │ │ │ │ │ ├── greek-small.js │ │ │ │ │ │ ├── greek-symbols.js │ │ │ │ │ │ ├── hebrew_letters.js │ │ │ │ │ │ ├── latin-lower-double-accent.js │ │ │ │ │ │ ├── latin-lower-normal.js │ │ │ │ │ │ ├── latin-lower-phonetic.js │ │ │ │ │ │ ├── latin-lower-single-accent.js │ │ │ │ │ │ ├── latin-mathfonts.js │ │ │ │ │ │ ├── latin-rest.js │ │ │ │ │ │ ├── latin-upper-double-accent.js │ │ │ │ │ │ ├── latin-upper-normal.js │ │ │ │ │ │ ├── latin-upper-single-accent.js │ │ │ │ │ │ ├── math_angles.js │ │ │ │ │ │ ├── math_arrows.js │ │ │ │ │ │ ├── math_characters.js │ │ │ │ │ │ ├── math_delimiters.js │ │ │ │ │ │ ├── math_digits.js │ │ │ │ │ │ ├── math_geometry.js │ │ │ │ │ │ ├── math_harpoons.js │ │ │ │ │ │ ├── math_non_characters.js │ │ │ │ │ │ ├── math_symbols.js │ │ │ │ │ │ ├── math_whitespace.js │ │ │ │ │ │ └── other_stars.js │ │ │ │ │ └── units/ │ │ │ │ │ ├── energy.js │ │ │ │ │ ├── length.js │ │ │ │ │ ├── memory.js │ │ │ │ │ ├── other.js │ │ │ │ │ ├── speed.js │ │ │ │ │ ├── temperature.js │ │ │ │ │ ├── time.js │ │ │ │ │ ├── volume.js │ │ │ │ │ └── weight.js │ │ │ │ ├── semantic-enrich.js │ │ │ │ └── wgxpath.install.js │ │ │ ├── asciimath2jax.js │ │ │ ├── fast-preview.js │ │ │ ├── jsMath2jax.js │ │ │ ├── mml2jax.js │ │ │ ├── tex2jax.js │ │ │ └── toMathML.js │ │ ├── jax/ │ │ │ ├── element/ │ │ │ │ └── mml/ │ │ │ │ ├── jax.js │ │ │ │ └── optable/ │ │ │ │ ├── Arrows.js │ │ │ │ ├── BasicLatin.js │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ ├── Dingbats.js │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ ├── GeometricShapes.js │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ ├── Latin1Supplement.js │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ ├── MathOperators.js │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ ├── MiscSymbolsAndArrows.js │ │ │ │ ├── MiscTechnical.js │ │ │ │ ├── SpacingModLetters.js │ │ │ │ ├── SuppMathOperators.js │ │ │ │ ├── SupplementalArrowsA.js │ │ │ │ └── SupplementalArrowsB.js │ │ │ ├── input/ │ │ │ │ ├── AsciiMath/ │ │ │ │ │ ├── config.js │ │ │ │ │ └── jax.js │ │ │ │ ├── MathML/ │ │ │ │ │ ├── config.js │ │ │ │ │ ├── entities/ │ │ │ │ │ │ ├── a.js │ │ │ │ │ │ ├── b.js │ │ │ │ │ │ ├── c.js │ │ │ │ │ │ ├── d.js │ │ │ │ │ │ ├── e.js │ │ │ │ │ │ ├── f.js │ │ │ │ │ │ ├── fr.js │ │ │ │ │ │ ├── g.js │ │ │ │ │ │ ├── h.js │ │ │ │ │ │ ├── i.js │ │ │ │ │ │ ├── j.js │ │ │ │ │ │ ├── k.js │ │ │ │ │ │ ├── l.js │ │ │ │ │ │ ├── m.js │ │ │ │ │ │ ├── n.js │ │ │ │ │ │ ├── o.js │ │ │ │ │ │ ├── opf.js │ │ │ │ │ │ ├── p.js │ │ │ │ │ │ ├── q.js │ │ │ │ │ │ ├── r.js │ │ │ │ │ │ ├── s.js │ │ │ │ │ │ ├── scr.js │ │ │ │ │ │ ├── t.js │ │ │ │ │ │ ├── u.js │ │ │ │ │ │ ├── v.js │ │ │ │ │ │ ├── w.js │ │ │ │ │ │ ├── x.js │ │ │ │ │ │ ├── y.js │ │ │ │ │ │ └── z.js │ │ │ │ │ └── jax.js │ │ │ │ └── TeX/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ └── output/ │ │ │ ├── CommonHTML/ │ │ │ │ ├── autoload/ │ │ │ │ │ ├── annotation-xml.js │ │ │ │ │ ├── maction.js │ │ │ │ │ ├── menclose.js │ │ │ │ │ ├── mglyph.js │ │ │ │ │ ├── mmultiscripts.js │ │ │ │ │ ├── ms.js │ │ │ │ │ ├── mtable.js │ │ │ │ │ └── multiline.js │ │ │ │ ├── config.js │ │ │ │ ├── fonts/ │ │ │ │ │ └── TeX/ │ │ │ │ │ ├── AMS-Regular.js │ │ │ │ │ ├── Caligraphic-Bold.js │ │ │ │ │ ├── Fraktur-Bold.js │ │ │ │ │ ├── Fraktur-Regular.js │ │ │ │ │ ├── Main-Bold.js │ │ │ │ │ ├── Math-BoldItalic.js │ │ │ │ │ ├── SansSerif-Bold.js │ │ │ │ │ ├── SansSerif-Italic.js │ │ │ │ │ ├── SansSerif-Regular.js │ │ │ │ │ ├── Script-Regular.js │ │ │ │ │ ├── Typewriter-Regular.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ └── jax.js │ │ │ ├── HTML-CSS/ │ │ │ │ ├── autoload/ │ │ │ │ │ ├── annotation-xml.js │ │ │ │ │ ├── maction.js │ │ │ │ │ ├── menclose.js │ │ │ │ │ ├── mglyph.js │ │ │ │ │ ├── mmultiscripts.js │ │ │ │ │ ├── ms.js │ │ │ │ │ ├── mtable.js │ │ │ │ │ └── multiline.js │ │ │ │ ├── config.js │ │ │ │ ├── fonts/ │ │ │ │ │ ├── Asana-Math/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Gyre-Pagella/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Gyre-Termes/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Latin-Modern/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size7/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── Neo-Euler/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── STIX/ │ │ │ │ │ │ ├── General/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ │ │ ├── BBBold.js │ │ │ │ │ │ │ │ ├── BoldFraktur.js │ │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ │ ├── GreekBold.js │ │ │ │ │ │ │ │ ├── GreekSSBold.js │ │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ │ ├── LatinExtendedD.js │ │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ ├── MathBold.js │ │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ │ ├── MathSSBold.js │ │ │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ │ │ ├── NumberForms.js │ │ │ │ │ │ │ │ ├── PhoneticExtensions.js │ │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ │ ├── SuperAndSubscripts.js │ │ │ │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ │ ├── GreekBoldItalic.js │ │ │ │ │ │ │ │ ├── GreekSSBoldItalic.js │ │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ ├── MathBoldItalic.js │ │ │ │ │ │ │ │ ├── MathBoldScript.js │ │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ │ ├── MathSSItalicBold.js │ │ │ │ │ │ │ │ └── SpacingModLetters.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ │ ├── GreekItalic.js │ │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ ├── MathItalic.js │ │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ │ ├── MathSSItalic.js │ │ │ │ │ │ │ │ ├── MathScript.js │ │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ │ └── ij.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── AlphaPresentForms.js │ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ │ ├── BBBold.js │ │ │ │ │ │ │ ├── BlockElements.js │ │ │ │ │ │ │ ├── BoldFraktur.js │ │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ │ ├── CJK.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ ├── ControlPictures.js │ │ │ │ │ │ │ ├── CurrencySymbols.js │ │ │ │ │ │ │ ├── Cyrillic.js │ │ │ │ │ │ │ ├── Dingbats.js │ │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ │ ├── Fraktur.js │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ │ ├── GreekBold.js │ │ │ │ │ │ │ ├── GreekBoldItalic.js │ │ │ │ │ │ │ ├── GreekItalic.js │ │ │ │ │ │ │ ├── GreekSSBold.js │ │ │ │ │ │ │ ├── GreekSSBoldItalic.js │ │ │ │ │ │ │ ├── Hiragana.js │ │ │ │ │ │ │ ├── IPAExtensions.js │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ ├── LatinExtendedAdditional.js │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ ├── LatinExtendedD.js │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ ├── MathBold.js │ │ │ │ │ │ │ ├── MathBoldItalic.js │ │ │ │ │ │ │ ├── MathBoldScript.js │ │ │ │ │ │ │ ├── MathItalic.js │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ ├── MathSS.js │ │ │ │ │ │ │ ├── MathSSBold.js │ │ │ │ │ │ │ ├── MathSSItalic.js │ │ │ │ │ │ │ ├── MathSSItalicBold.js │ │ │ │ │ │ │ ├── MathScript.js │ │ │ │ │ │ │ ├── MathTT.js │ │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ │ ├── MiscSymbolsAndArrows.js │ │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ │ ├── NumberForms.js │ │ │ │ │ │ │ ├── PhoneticExtensions.js │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ ├── Specials.js │ │ │ │ │ │ │ ├── SuperAndSubscripts.js │ │ │ │ │ │ │ ├── SuppMathOperators.js │ │ │ │ │ │ │ ├── SupplementalArrowsA.js │ │ │ │ │ │ │ ├── SupplementalArrowsB.js │ │ │ │ │ │ │ └── ij.js │ │ │ │ │ │ ├── IntegralsD/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsSm/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsUp/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsUpD/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── IntegralsUpSm/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── All.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ └── PrivateUse.js │ │ │ │ │ │ ├── SizeFiveSym/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeFourSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeOneSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeThreeSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SizeTwoSym/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ ├── All.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-1.0.js │ │ │ │ │ │ ├── fontdata-beta.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ ├── STIX-Web/ │ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Main/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Script/ │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ │ └── fontdata.js │ │ │ │ │ └── TeX/ │ │ │ │ │ ├── AMS/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ ├── BBBold.js │ │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Dingbats.js │ │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ ├── PUA.js │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ │ ├── Caligraphic/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ ├── Other.js │ │ │ │ │ │ │ └── PUA.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── Other.js │ │ │ │ │ │ └── PUA.js │ │ │ │ │ ├── Greek/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ │ ├── SuppMathOperators.js │ │ │ │ │ │ │ └── SupplementalArrowsA.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ └── SpacingModLetters.js │ │ │ │ │ ├── Math/ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Italic/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ └── Other.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ │ └── Other.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Typewriter/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── WinChrome/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── WinIE6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ ├── AMS.js │ │ │ │ │ │ ├── Bold.js │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── imageFonts.js │ │ │ │ └── jax.js │ │ │ ├── NativeMML/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ ├── PlainSource/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ ├── PreviewHTML/ │ │ │ │ ├── config.js │ │ │ │ └── jax.js │ │ │ └── SVG/ │ │ │ ├── autoload/ │ │ │ │ ├── annotation-xml.js │ │ │ │ ├── maction.js │ │ │ │ ├── menclose.js │ │ │ │ ├── mglyph.js │ │ │ │ ├── mmultiscripts.js │ │ │ │ ├── ms.js │ │ │ │ ├── mtable.js │ │ │ │ └── multiline.js │ │ │ ├── config.js │ │ │ ├── fonts/ │ │ │ │ ├── Asana-Math/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Gyre-Pagella/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Gyre-Termes/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Latin-Modern/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size6/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size7/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── Neo-Euler/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── NonUnicode/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ ├── STIX-Web/ │ │ │ │ │ ├── Alphabets/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Arrows/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── DoubleStruck/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Fraktur/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Latin/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Main/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Marks/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Misc/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Monospace/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Normal/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Italic/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Operators/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── SansSerif/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Script/ │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Shapes/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size1/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size2/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size3/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size4/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Size5/ │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Symbols/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── Variants/ │ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ │ └── Main.js │ │ │ │ │ │ └── Regular/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ ├── fontdata-extra.js │ │ │ │ │ └── fontdata.js │ │ │ │ └── TeX/ │ │ │ │ ├── AMS/ │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ ├── BoxDrawing.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── Dingbats.js │ │ │ │ │ ├── EnclosedAlphanum.js │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ ├── Main.js │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ ├── MiscMathSymbolsB.js │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ ├── PUA.js │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ ├── Caligraphic/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Fraktur/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── Other.js │ │ │ │ │ │ └── PUA.js │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── Main.js │ │ │ │ │ ├── Other.js │ │ │ │ │ └── PUA.js │ │ │ │ ├── Main/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ ├── Arrows.js │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── CombDiactForSymbols.js │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ ├── Latin1Supplement.js │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ ├── MathOperators.js │ │ │ │ │ │ ├── MiscMathSymbolsA.js │ │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ │ ├── MiscTechnical.js │ │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ │ ├── SuppMathOperators.js │ │ │ │ │ │ └── SupplementalArrowsA.js │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── GeneralPunctuation.js │ │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ │ └── Main.js │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── GeometricShapes.js │ │ │ │ │ ├── GreekAndCoptic.js │ │ │ │ │ ├── LatinExtendedA.js │ │ │ │ │ ├── LatinExtendedB.js │ │ │ │ │ ├── LetterlikeSymbols.js │ │ │ │ │ ├── Main.js │ │ │ │ │ ├── MiscSymbols.js │ │ │ │ │ ├── SpacingModLetters.js │ │ │ │ │ └── SuppMathOperators.js │ │ │ │ ├── Math/ │ │ │ │ │ ├── BoldItalic/ │ │ │ │ │ │ └── Main.js │ │ │ │ │ └── Italic/ │ │ │ │ │ └── Main.js │ │ │ │ ├── SansSerif/ │ │ │ │ │ ├── Bold/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ ├── Italic/ │ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ │ ├── Main.js │ │ │ │ │ │ └── Other.js │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── Main.js │ │ │ │ │ └── Other.js │ │ │ │ ├── Script/ │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ └── Main.js │ │ │ │ ├── Size1/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Size2/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Size3/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Size4/ │ │ │ │ │ └── Regular/ │ │ │ │ │ └── Main.js │ │ │ │ ├── Typewriter/ │ │ │ │ │ └── Regular/ │ │ │ │ │ ├── BasicLatin.js │ │ │ │ │ ├── CombDiacritMarks.js │ │ │ │ │ ├── Main.js │ │ │ │ │ └── Other.js │ │ │ │ ├── fontdata-extra.js │ │ │ │ └── fontdata.js │ │ │ └── jax.js │ │ ├── latest.js │ │ └── localization/ │ │ ├── ar/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ar.js │ │ ├── ast/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ast.js │ │ ├── bcc/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── bcc.js │ │ ├── bg/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── bg.js │ │ ├── br/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── br.js │ │ ├── ca/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ca.js │ │ ├── cdo/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── cdo.js │ │ ├── ce/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ce.js │ │ ├── cs/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── cs.js │ │ ├── cy/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── cy.js │ │ ├── da/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── da.js │ │ ├── de/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── de.js │ │ ├── diq/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── diq.js │ │ ├── en/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── en.js │ │ ├── eo/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── eo.js │ │ ├── es/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── es.js │ │ ├── fa/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── fa.js │ │ ├── fi/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── fi.js │ │ ├── fr/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── fr.js │ │ ├── gl/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── gl.js │ │ ├── he/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── he.js │ │ ├── ia/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ia.js │ │ ├── it/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── it.js │ │ ├── ja/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ja.js │ │ ├── kn/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── kn.js │ │ ├── ko/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ko.js │ │ ├── lb/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── lb.js │ │ ├── lki/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── lki.js │ │ ├── lt/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── lt.js │ │ ├── mk/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── mk.js │ │ ├── nl/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── nl.js │ │ ├── oc/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── oc.js │ │ ├── pl/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── pl.js │ │ ├── pt/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── pt.js │ │ ├── pt-br/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── pt-br.js │ │ ├── qqq/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── qqq.js │ │ ├── ru/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── ru.js │ │ ├── scn/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── scn.js │ │ ├── sco/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── sco.js │ │ ├── sk/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── sk.js │ │ ├── sl/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── sl.js │ │ ├── sv/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── sv.js │ │ ├── th/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── th.js │ │ ├── tr/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── tr.js │ │ ├── uk/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── uk.js │ │ ├── vi/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── vi.js │ │ ├── zh-hans/ │ │ │ ├── FontWarnings.js │ │ │ ├── HTML-CSS.js │ │ │ ├── HelpDialog.js │ │ │ ├── MathML.js │ │ │ ├── MathMenu.js │ │ │ ├── TeX.js │ │ │ └── zh-hans.js │ │ └── zh-hant/ │ │ ├── FontWarnings.js │ │ ├── HTML-CSS.js │ │ ├── HelpDialog.js │ │ ├── MathML.js │ │ ├── MathMenu.js │ │ ├── TeX.js │ │ └── zh-hant.js │ └── js-yaml/ │ ├── LICENSE │ └── README.md ├── editors/ │ ├── atom/ │ │ └── README.md │ └── vim/ │ └── README.md ├── examples/ │ ├── arith.jsonnet │ ├── arith.jsonnet.golden │ ├── bazel/ │ │ ├── .bazelrc │ │ ├── .gitignore │ │ ├── BUILD │ │ ├── MODULE.bazel │ │ ├── example.jsonnet │ │ ├── expect.out │ │ ├── use_c_lib.c │ │ ├── use_cpp_lib.cc │ │ └── use_py_module.py │ ├── build_example.jsonnet │ ├── build_example.jsonnet.golden │ ├── check.sh │ ├── cmake/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── cocktail-comprehensions.jsonnet │ ├── cocktail-comprehensions.jsonnet.golden │ ├── comprehensions.jsonnet │ ├── comprehensions.jsonnet.golden │ ├── computed-fields.jsonnet │ ├── computed-fields.jsonnet.golden │ ├── conditionals.jsonnet │ ├── conditionals.jsonnet.golden │ ├── error-examples.jsonnet │ ├── error-examples.jsonnet.golden │ ├── functions.jsonnet │ ├── functions.jsonnet.golden │ ├── garnish.txt │ ├── imports.jsonnet │ ├── imports.jsonnet.golden │ ├── inner-reference.jsonnet │ ├── inner-reference.jsonnet.golden │ ├── landingpage.jsonnet │ ├── landingpage.jsonnet.golden │ ├── library-ext.libsonnet │ ├── library-tla.libsonnet │ ├── martinis.libsonnet │ ├── mixins.jsonnet │ ├── mixins.jsonnet.golden │ ├── negroni.jsonnet │ ├── negroni.jsonnet.golden │ ├── oo-contrived.jsonnet │ ├── oo-contrived.jsonnet.fmt.golden │ ├── oo-contrived.jsonnet.golden │ ├── references.jsonnet │ ├── references.jsonnet.golden │ ├── sours-oo.jsonnet │ ├── sours-oo.jsonnet.golden │ ├── sours.jsonnet │ ├── sours.jsonnet.golden │ ├── syntax.jsonnet │ ├── syntax.jsonnet.fmt.golden │ ├── syntax.jsonnet.golden │ ├── templates.libsonnet │ ├── terraform/ │ │ ├── aws-count.jsonnet │ │ ├── aws-count.jsonnet.golden │ │ ├── aws-two-tier.jsonnet │ │ ├── aws-two-tier.jsonnet.golden │ │ ├── check.sh │ │ ├── consul.jsonnet │ │ ├── consul.jsonnet.golden │ │ ├── cross-provider.jsonnet │ │ └── cross-provider.jsonnet.golden │ ├── top-level-ext.jsonnet │ ├── top-level-ext.jsonnet.golden │ ├── top-level-tla.jsonnet │ ├── top-level-tla.jsonnet.golden │ ├── utils.libsonnet │ ├── variables.jsonnet │ └── variables.jsonnet.golden ├── gc_stress/ │ └── long_chains.jsonnet ├── include/ │ ├── BUILD │ ├── CMakeLists.txt │ ├── libjsonnet++.h │ ├── libjsonnet.h │ └── libjsonnet_fmt.h ├── java_comparison/ │ ├── .gitignore │ ├── BaseTemplate.java │ ├── JsonnetObject.java │ ├── JsonnetValue.java │ ├── README.md │ ├── SubTemplate.java │ ├── Test.java │ ├── base-template.libsonnet │ ├── sub-template.json │ └── sub-template.jsonnet ├── perf_tests/ │ ├── large_string_join.jsonnet │ ├── large_string_template.jsonnet │ ├── realistic1.jsonnet │ └── realistic2.jsonnet ├── platform_defs/ │ └── BUILD ├── pyproject.toml ├── python/ │ ├── BUILD │ ├── __init__.py │ ├── _jsonnet.c │ ├── _jsonnet_test.py │ └── testdata/ │ ├── basic_check.jsonnet │ ├── trivial.jsonnet │ └── trivial_no_eol.jsonnet ├── release_checklist.md ├── setup.py ├── stdlib/ │ ├── BUILD │ ├── CMakeLists.txt │ ├── std.jsonnet │ └── to_c_array.cpp ├── test_cmd/ │ ├── .gitignore │ ├── BUILD │ ├── bad_out.golden.stderr │ ├── cmd_tests.source │ ├── double_dash.golden.stdout │ ├── exec1.golden.stdout │ ├── exec_out.golden.custom_output │ ├── ext1.golden.stdout │ ├── ext2.golden.stdout │ ├── ext3.golden.stderr │ ├── ext4.golden.stdout │ ├── ext5.golden.stdout │ ├── ext6.golden.stdout │ ├── ext7.golden.stdout │ ├── fmt1.golden.stdout │ ├── fmt_bad_out.golden.stderr │ ├── fmt_double_dash.golden.stdout │ ├── fmt_help.golden.stdout │ ├── fmt_inplace.golden.stdout │ ├── fmt_no_args.golden.stderr │ ├── fmt_out.golden.custom_output │ ├── fmt_simple1.golden.stdout │ ├── fmt_simple3.golden.stdout │ ├── fmt_simple3.stdin │ ├── fmt_simple4.golden.stderr │ ├── fmt_simple5.golden.stderr │ ├── fmt_simple_out.golden.custom_output │ ├── fmt_simple_test1.golden.stdout │ ├── fmt_simple_test2.golden.stdout │ ├── fmt_simple_test3.golden.stdout │ ├── fmt_simple_test4.golden.stdout │ ├── fmt_simple_test4.stdin │ ├── fmt_simple_test5.golden.stdout │ ├── fmt_simple_test5.stdin │ ├── fmt_simple_test6.golden.stdout │ ├── fmt_version1.golden.stdout │ ├── fmt_version2.golden.stdout │ ├── help.golden.stdout │ ├── jpath1.golden.stdout │ ├── jpath10.golden.stdout │ ├── jpath2.golden.stdout │ ├── jpath3.golden.stdout │ ├── jpath4.golden.stdout │ ├── jpath5.golden.stdout │ ├── jpath6.golden.stdout │ ├── jpath7.golden.stdout │ ├── jpath8.golden.stderr │ ├── jpath9.golden.stdout │ ├── jsonnet_path1.golden.stdout │ ├── jsonnet_path2.golden.stdout │ ├── lib1/ │ │ ├── lib1_test.jsonnet │ │ ├── lib3/ │ │ │ └── lib3_test.jsonnet │ │ ├── lib3_test.jsonnet │ │ └── shared.txt │ ├── lib2/ │ │ ├── lib2_test.jsonnet │ │ └── shared.txt │ ├── max_stack1.golden.stderr │ ├── max_stack2.golden.stderr │ ├── max_stack3.golden.stdout │ ├── max_stack4.golden.stderr │ ├── max_stack4.golden.stdout │ ├── max_stack5.golden.stderr │ ├── max_stack6.golden.stderr │ ├── max_trace1.golden.stderr │ ├── max_trace2.golden.stderr │ ├── max_trace3.golden.stderr │ ├── max_trace4.golden.stderr │ ├── max_trace5.golden.stderr │ ├── multi1.golden.file1 │ ├── multi1.golden.file2 │ ├── multi1.golden.stdout │ ├── multi2.golden.file1 │ ├── multi2.golden.file2 │ ├── multi2.golden.stdout │ ├── multi3.golden.file1 │ ├── multi3.golden.file2 │ ├── multi3.golden.list │ ├── multi4.golden.stderr │ ├── no_args.golden.stderr │ ├── nonewline1.golden.stdout │ ├── nonewline2.golden.stdout │ ├── nonewline3.golden.stdout │ ├── nonewline_multi1.golden.file1 │ ├── nonewline_multi1.golden.file2 │ ├── nonewline_multi1.golden.stdout │ ├── nonewline_multi2.golden.file1 │ ├── nonewline_multi2.golden.file2 │ ├── nonewline_multi2.golden.stdout │ ├── nonewline_yaml1.golden.stderr │ ├── run_cmd_tests.sh │ ├── simple1.golden.stdout │ ├── simple2.golden.stdout │ ├── simple3.golden.stdout │ ├── simple3.stdin │ ├── simple4.golden.stderr │ ├── simple5.golden.stderr │ ├── simple_out.golden.custom_output │ ├── string1.golden.stdout │ ├── string2.golden.stderr │ ├── test.jsonnet │ ├── test.txt │ ├── test_badfmt.jsonnet │ ├── tla1.golden.stdout │ ├── tla10.golden.stdout │ ├── tla2.golden.stdout │ ├── tla3.golden.stderr │ ├── tla4.golden.stdout │ ├── tla5.golden.stdout │ ├── tla6.golden.stdout │ ├── tla7.golden.stderr │ ├── tla8.golden.stdout │ ├── tla9.golden.stdout │ ├── version1.golden.stdout │ ├── version2.golden.stdout │ ├── yaml1.golden.stdout │ ├── yaml2.golden.stderr │ └── yaml3.golden.stream ├── test_suite/ │ ├── BUILD │ ├── CMakeLists.txt │ ├── README.md │ ├── arith_bool.jsonnet │ ├── arith_float.jsonnet │ ├── arith_float.jsonnet.fmt.golden │ ├── arith_string.jsonnet │ ├── array.jsonnet │ ├── array.jsonnet.fmt.golden │ ├── array_comparison.jsonnet │ ├── array_comparison.jsonnet.golden │ ├── array_comparison2.jsonnet │ ├── array_comparison2.jsonnet.golden │ ├── assert.jsonnet │ ├── binary.jsonnet │ ├── binary.jsonnet.fmt.golden │ ├── comments.jsonnet │ ├── comments.jsonnet.fmt.golden │ ├── condition.jsonnet │ ├── count_tests.sh │ ├── digitsep.jsonnet │ ├── digitsep.jsonnet.golden │ ├── dos_line_endings.jsonnet │ ├── dos_line_endings.jsonnet.fmt.golden │ ├── dos_line_endings.jsonnet.golden │ ├── error.01.jsonnet │ ├── error.01.jsonnet.golden │ ├── error.02.jsonnet │ ├── error.02.jsonnet.golden │ ├── error.03.jsonnet │ ├── error.03.jsonnet.golden │ ├── error.04.jsonnet │ ├── error.04.jsonnet.golden │ ├── error.05.jsonnet │ ├── error.05.jsonnet.golden │ ├── error.06.jsonnet │ ├── error.06.jsonnet.golden │ ├── error.07.jsonnet │ ├── error.07.jsonnet.golden │ ├── error.08.jsonnet │ ├── error.08.jsonnet.golden │ ├── error.args_commafodder.jsonnet │ ├── error.args_commafodder.jsonnet.golden │ ├── error.array_fractional_index.jsonnet │ ├── error.array_fractional_index.jsonnet.golden │ ├── error.array_index_string.jsonnet │ ├── error.array_index_string.jsonnet.golden │ ├── error.array_large_index.jsonnet │ ├── error.array_large_index.jsonnet.golden │ ├── error.array_recursive_manifest.jsonnet │ ├── error.array_recursive_manifest.jsonnet.golden │ ├── error.assert.fail1.jsonnet │ ├── error.assert.fail1.jsonnet.golden │ ├── error.assert.fail2.jsonnet │ ├── error.assert.fail2.jsonnet.golden │ ├── error.assert_equal_obj.jsonnet │ ├── error.assert_equal_obj.jsonnet.golden │ ├── error.assert_equal_str.jsonnet │ ├── error.assert_equal_str.jsonnet.golden │ ├── error.comprehension_spec_object.jsonnet │ ├── error.comprehension_spec_object.jsonnet.golden │ ├── error.comprehension_spec_object2.jsonnet │ ├── error.comprehension_spec_object2.jsonnet.golden │ ├── error.computed_field_scope.jsonnet │ ├── error.computed_field_scope.jsonnet.golden │ ├── error.decodeUTF8_float.jsonnet │ ├── error.decodeUTF8_float.jsonnet.golden │ ├── error.decodeUTF8_nan.jsonnet │ ├── error.decodeUTF8_nan.jsonnet.golden │ ├── error.divide_zero.jsonnet │ ├── error.divide_zero.jsonnet.golden │ ├── error.equality_function.jsonnet │ ├── error.equality_function.jsonnet.golden │ ├── error.field_not_exist.jsonnet │ ├── error.field_not_exist.jsonnet.golden │ ├── error.flatMap_array_typecheck.jsonnet │ ├── error.flatMap_array_typecheck.jsonnet.golden │ ├── error.flatMap_seq_typecheck.jsonnet │ ├── error.flatMap_seq_typecheck.jsonnet.golden │ ├── error.flatMap_string_typecheck.jsonnet │ ├── error.flatMap_string_typecheck.jsonnet.golden │ ├── error.format.too_few_values.jsonnet │ ├── error.format.too_few_values.jsonnet.fmt.golden │ ├── error.format.too_few_values.jsonnet.golden │ ├── error.function_duplicate_arg.jsonnet │ ├── error.function_duplicate_arg.jsonnet.golden │ ├── error.function_duplicate_param.jsonnet │ ├── error.function_duplicate_param.jsonnet.golden │ ├── error.function_infinite_default.jsonnet │ ├── error.function_infinite_default.jsonnet.golden │ ├── error.function_no_default_arg.jsonnet │ ├── error.function_no_default_arg.jsonnet.golden │ ├── error.function_too_many_args.jsonnet │ ├── error.function_too_many_args.jsonnet.golden │ ├── error.import_empty.jsonnet │ ├── error.import_empty.jsonnet.golden │ ├── error.import_static-check-failure.jsonnet │ ├── error.import_static-check-failure.jsonnet.golden │ ├── error.import_syntax-error.jsonnet │ ├── error.import_syntax-error.jsonnet.golden │ ├── error.inside_equals_array.jsonnet │ ├── error.inside_equals_array.jsonnet.golden │ ├── error.inside_equals_object.jsonnet │ ├── error.inside_equals_object.jsonnet.golden │ ├── error.inside_tostring_array.jsonnet │ ├── error.inside_tostring_array.jsonnet.golden │ ├── error.inside_tostring_object.jsonnet │ ├── error.inside_tostring_object.jsonnet.golden │ ├── error.integer_conversion.jsonnet │ ├── error.integer_conversion.jsonnet.golden │ ├── error.integer_left_shift.jsonnet │ ├── error.integer_left_shift.jsonnet.golden │ ├── error.integer_left_shift_runtime.jsonnet │ ├── error.integer_left_shift_runtime.jsonnet.golden │ ├── error.invariant.avoid_output_change.jsonnet │ ├── error.invariant.avoid_output_change.jsonnet.golden │ ├── error.invariant.equality.jsonnet │ ├── error.invariant.equality.jsonnet.golden │ ├── error.invariant.option.jsonnet │ ├── error.invariant.option.jsonnet.golden │ ├── error.invariant.simple.jsonnet │ ├── error.invariant.simple.jsonnet.golden │ ├── error.invariant.simple2.jsonnet │ ├── error.invariant.simple2.jsonnet.golden │ ├── error.invariant.simple3.jsonnet │ ├── error.invariant.simple3.jsonnet.golden │ ├── error.manifest_toml_null_value.jsonnet │ ├── error.manifest_toml_null_value.jsonnet.golden │ ├── error.manifest_toml_wrong_type.jsonnet │ ├── error.manifest_toml_wrong_type.jsonnet.golden │ ├── error.negative_shfit.jsonnet │ ├── error.negative_shfit.jsonnet.golden │ ├── error.obj_assert.fail1.jsonnet │ ├── error.obj_assert.fail1.jsonnet.golden │ ├── error.obj_assert.fail2.jsonnet │ ├── error.obj_assert.fail2.jsonnet.golden │ ├── error.obj_recursive.jsonnet │ ├── error.obj_recursive.jsonnet.golden │ ├── error.obj_recursive_manifest.jsonnet │ ├── error.obj_recursive_manifest.jsonnet.golden │ ├── error.overflow.jsonnet │ ├── error.overflow.jsonnet.golden │ ├── error.overflow2.jsonnet │ ├── error.overflow2.jsonnet.golden │ ├── error.overflow3.jsonnet │ ├── error.overflow3.jsonnet.golden │ ├── error.parse.array_comma.jsonnet │ ├── error.parse.array_comma.jsonnet.golden │ ├── error.parse.deep_array_nesting.jsonnet │ ├── error.parse.deep_array_nesting.jsonnet.golden │ ├── error.parse.function_arg_positional_after_named.jsonnet │ ├── error.parse.function_arg_positional_after_named.jsonnet.golden │ ├── error.parse.import_not_literal.jsonnet │ ├── error.parse.import_not_literal.jsonnet.golden │ ├── error.parse.import_text_block.jsonnet │ ├── error.parse.import_text_block.jsonnet.golden │ ├── error.parse.index_unterminated.jsonnet │ ├── error.parse.index_unterminated.jsonnet.golden │ ├── error.parse.method_plus.jsonnet │ ├── error.parse.method_plus.jsonnet.golden │ ├── error.parse.object_comma.jsonnet │ ├── error.parse.object_comma.jsonnet.golden │ ├── error.parse.object_comprehension_local_clash.jsonnet │ ├── error.parse.object_comprehension_local_clash.jsonnet.golden │ ├── error.parse.object_local_clash.jsonnet │ ├── error.parse.object_local_clash.jsonnet.golden │ ├── error.parse.self_in_computed_field.jsonnet │ ├── error.parse.self_in_computed_field.jsonnet.golden │ ├── error.parse.static_error_bad_number.jsonnet │ ├── error.parse.static_error_bad_number.jsonnet.golden │ ├── error.parse.string.invalid_escape.jsonnet │ ├── error.parse.string.invalid_escape.jsonnet.golden │ ├── error.parse.string.invalid_escape_unicode_non_hex.jsonnet │ ├── error.parse.string.invalid_escape_unicode_non_hex.jsonnet.golden │ ├── error.parse.string.invalid_escape_unicode_short.jsonnet │ ├── error.parse.string.invalid_escape_unicode_short.jsonnet.golden │ ├── error.parse.string.invalid_escape_unicode_short2.jsonnet │ ├── error.parse.string.invalid_escape_unicode_short2.jsonnet.golden │ ├── error.parse.string.invalid_escape_unicode_short3.jsonnet │ ├── error.parse.string.invalid_escape_unicode_short3.jsonnet.golden │ ├── error.parse.string.unfinished.jsonnet │ ├── error.parse.string.unfinished.jsonnet.golden │ ├── error.parse.string.unfinished2.jsonnet │ ├── error.parse.string.unfinished2.jsonnet.golden │ ├── error.parse.string_multi_no_newline.jsonnet │ ├── error.parse.string_multi_no_newline.jsonnet.golden │ ├── error.parse.text_block_bad_whitespace.jsonnet │ ├── error.parse.text_block_bad_whitespace.jsonnet.golden │ ├── error.parse.text_block_eof.jsonnet │ ├── error.parse.text_block_eof.jsonnet.golden │ ├── error.parse.text_block_indent_spaces.jsonnet │ ├── error.parse.text_block_indent_spaces.jsonnet.golden │ ├── error.parse.text_block_not_terminated.jsonnet │ ├── error.parse.text_block_not_terminated.jsonnet.golden │ ├── error.parse_json.jsonnet │ ├── error.parse_json.jsonnet.golden │ ├── error.recursive_function_nonterm.jsonnet │ ├── error.recursive_function_nonterm.jsonnet.golden │ ├── error.recursive_import.jsonnet │ ├── error.recursive_import.jsonnet.golden │ ├── error.recursive_object_non_term.jsonnet │ ├── error.recursive_object_non_term.jsonnet.golden │ ├── error.sanity.jsonnet │ ├── error.sanity.jsonnet.golden │ ├── error.slice_out_of_bounds.sugar.jsonnet.golden │ ├── error.static_error_self.jsonnet │ ├── error.static_error_self.jsonnet.golden │ ├── error.static_error_super.jsonnet │ ├── error.static_error_super.jsonnet.golden │ ├── error.static_error_var_not_exist.jsonnet │ ├── error.static_error_var_not_exist.jsonnet.golden │ ├── error.std_join_types1.jsonnet │ ├── error.std_join_types1.jsonnet.golden │ ├── error.std_join_types2.jsonnet │ ├── error.std_join_types2.jsonnet.golden │ ├── error.std_makeArray_negative.jsonnet │ ├── error.std_makeArray_negative.jsonnet.golden │ ├── error.std_maxArray.jsonnet │ ├── error.std_maxArray.jsonnet.golden │ ├── error.std_minArray.jsonnet │ ├── error.std_minArray.jsonnet.golden │ ├── error.std_parseJson.nodigitsep.jsonnet │ ├── error.std_parseJson.nodigitsep.jsonnet.golden │ ├── error.std_parseYaml1.jsonnet │ ├── error.std_parseYaml1.jsonnet.golden │ ├── error.string.invalid_escape_unicode_ascii.jsonnet.golden │ ├── error.top_level_func.jsonnet │ ├── error.top_level_func.jsonnet.golden │ ├── error.trace_one_param.jsonnet │ ├── error.trace_one_param.jsonnet.golden │ ├── error.trace_three_param.jsonnet │ ├── error.trace_three_param.jsonnet.golden │ ├── error.trace_two_param.jsonnet │ ├── error.trace_two_param.jsonnet.golden │ ├── error.trace_zero_param.jsonnet │ ├── error.trace_zero_param.jsonnet.golden │ ├── error.verbatim_import.jsonnet │ ├── error.verbatim_import.jsonnet.golden │ ├── error.wrong_type.jsonnet │ ├── error.wrong_type.jsonnet.golden │ ├── fmt_idempotence_issue.jsonnet │ ├── fmt_idempotence_issue.jsonnet.fmt.golden │ ├── fmt_idempotence_issue.jsonnet.golden │ ├── fmt_no_trailing_newline.jsonnet │ ├── fmt_no_trailing_newline.jsonnet.fmt.golden │ ├── fmt_no_trailing_newline.jsonnet.golden │ ├── fmt_trailing_c_style.jsonnet │ ├── fmt_trailing_c_style.jsonnet.fmt.golden │ ├── fmt_trailing_c_style.jsonnet.golden │ ├── fmt_trailing_multiple_comments.jsonnet │ ├── fmt_trailing_multiple_comments.jsonnet.fmt.golden │ ├── fmt_trailing_multiple_comments.jsonnet.golden │ ├── fmt_trailing_newlines.jsonnet │ ├── fmt_trailing_newlines.jsonnet.fmt.golden │ ├── fmt_trailing_newlines.jsonnet.golden │ ├── fmt_trailing_newlines2.jsonnet │ ├── fmt_trailing_newlines2.jsonnet.fmt.golden │ ├── fmt_trailing_newlines2.jsonnet.golden │ ├── fmt_trailing_same_line_comment.jsonnet │ ├── fmt_trailing_same_line_comment.jsonnet.fmt.golden │ ├── fmt_trailing_same_line_comment.jsonnet.golden │ ├── format.jsonnet │ ├── formatter.jsonnet │ ├── formatter.jsonnet.fmt.golden │ ├── formatter.jsonnet.golden │ ├── formatting_braces.jsonnet │ ├── formatting_braces.jsonnet.fmt.golden │ ├── formatting_braces.jsonnet.golden │ ├── formatting_braces2.jsonnet │ ├── formatting_braces2.jsonnet.fmt.golden │ ├── formatting_braces2.jsonnet.golden │ ├── formatting_braces3.jsonnet │ ├── formatting_braces3.jsonnet.fmt.golden │ ├── functions.jsonnet │ ├── import.jsonnet │ ├── import_sorting.jsonnet │ ├── import_sorting.jsonnet.fmt.golden │ ├── import_sorting_by_filename.jsonnet │ ├── import_sorting_by_filename.jsonnet.fmt.golden │ ├── import_sorting_crazy.jsonnet │ ├── import_sorting_crazy.jsonnet.fmt.golden │ ├── import_sorting_function_sugar.jsonnet │ ├── import_sorting_function_sugar.jsonnet.fmt.golden │ ├── import_sorting_group_ends.jsonnet │ ├── import_sorting_group_ends.jsonnet.fmt.golden │ ├── import_sorting_groups.jsonnet │ ├── import_sorting_groups.jsonnet.fmt.golden │ ├── import_sorting_multiple_binds_and_comments.jsonnet │ ├── import_sorting_multiple_binds_and_comments.jsonnet.fmt.golden │ ├── import_sorting_multiple_in_local.jsonnet │ ├── import_sorting_multiple_in_local.jsonnet.fmt.golden │ ├── import_sorting_unicode.jsonnet │ ├── import_sorting_unicode.jsonnet.fmt.golden │ ├── import_sorting_with_license.jsonnet │ ├── import_sorting_with_license.jsonnet.fmt.golden │ ├── invariant.jsonnet │ ├── invariant_manifest.jsonnet │ ├── invariant_manifest.jsonnet.golden │ ├── lib/ │ │ ├── A_20.libsonnet │ │ ├── A_20_func.libsonnet │ │ ├── capture_std.libsonnet │ │ ├── capture_std_func.libsonnet │ │ ├── rel_path.libsonnet │ │ ├── rel_path2.libsonnet │ │ ├── rel_path3.libsonnet │ │ ├── rel_path4.libsonnet │ │ ├── some_file.txt │ │ ├── static_check_failure.jsonnet │ │ └── syntax_error.jsonnet │ ├── local.jsonnet │ ├── merge.jsonnet │ ├── native_not_found.jsonnet │ ├── native_not_found.jsonnet.golden │ ├── null.jsonnet │ ├── object.jsonnet │ ├── object.jsonnet.fmt.golden │ ├── oop.jsonnet │ ├── oop_extra.jsonnet │ ├── parseJson_long_array_gc_test.jsonnet │ ├── parseJson_long_array_gc_test.jsonnet.golden │ ├── parsing_edge_cases.jsonnet │ ├── parsing_error.jsonnet │ ├── precedence.jsonnet │ ├── recursive_function.jsonnet │ ├── recursive_import_ok.jsonnet │ ├── recursive_object.jsonnet │ ├── refresh_fmt_golden.sh │ ├── refresh_golden.sh │ ├── run_fmt_idempotence_tests.sh │ ├── run_fmt_tests.sh │ ├── run_tests.sh │ ├── safe_integer_conversion.jsonnet │ ├── safe_integer_conversion.jsonnet.fmt.golden │ ├── safe_integer_conversion.jsonnet.golden │ ├── sanity.jsonnet │ ├── sanity.jsonnet.golden │ ├── sanity2.jsonnet │ ├── sanity2.jsonnet.golden │ ├── shebang.jsonnet │ ├── slice.sugar.jsonnet │ ├── slice.sugar.jsonnet.fmt.golden │ ├── std_all_hidden.jsonnet │ ├── stdlib.jsonnet │ ├── stdlib.jsonnet.golden │ ├── tests.source │ ├── text_block.jsonnet │ ├── text_block.jsonnet.fmt.golden │ ├── this_file/ │ │ ├── a.libsonnet │ │ └── b.libsonnet │ ├── tla.simple.jsonnet │ ├── trace.jsonnet │ ├── trace.jsonnet.golden │ ├── unicode.jsonnet │ ├── unicode_bmp.jsonnet │ ├── unicode_bmp.jsonnet.golden │ ├── unicode_bmp1.jsonnet.in │ ├── unicode_bmp2.jsonnet.in │ ├── unix_line_endings.jsonnet │ ├── unix_line_endings.jsonnet.golden │ ├── unparse.jsonnet │ ├── unparse.jsonnet.fmt.golden │ ├── unparse.jsonnet.golden │ └── verbatim_strings.jsonnet ├── tests.sh ├── third_party/ │ ├── json/ │ │ ├── BUILD │ │ ├── LICENSE │ │ └── nlohmann/ │ │ └── json.hpp │ ├── md5/ │ │ ├── BUILD │ │ ├── LICENSE │ │ ├── md5.cpp │ │ └── md5.h │ └── rapidyaml/ │ ├── BUILD │ ├── README.md │ ├── rapidyaml-0.10.0.hpp │ └── rapidyaml.cpp └── tools/ ├── build_defs/ │ ├── BUILD │ └── golden_test.bzl └── scripts/ ├── push_docs.sh ├── replace_test_cmd_version.sh └── update_web_content.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bazelignore ================================================ # The bazel example is an entirely separate bazel module and should # not be built as part of the main library module. examples/bazel # The README instructions for building with CMake involve initialising the # CMake build directory as a subdirectory called build, that directory # ends up containing other bazel BUILD files, which then interfere with a # bazel build. Therefore, exclude it. build ================================================ FILE: .bazelversion ================================================ 8.5.1 ================================================ FILE: .clang-format ================================================ --- Language: Cpp BasedOnStyle: Google AccessModifierOffset: -1 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: true BreakBeforeBinaryOperators: None BreakBeforeBraces: WebKit BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^[*]* [@\\]' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: true DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true IncludeCategories: - Regex: '^<.*\.h>' Priority: 1 - Regex: '^<.*' Priority: 2 - Regex: '.*' Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: true IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '^TRY$' MacroBlockEnd: '^CATCH$' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 8 UseTab: Never ... ================================================ FILE: .gitattributes ================================================ * text=auto *.py text *.cpp text *.c text *.h text *.jsonnet text *.jinja text *.html text README text *.TEMPLATE text *.css text Makefile text # These tests are explicitly given specific line endings test_suite/unix_line_endings.jsonnet text eol=lf test_suite/dos_line_endings.jsonnet text eol=crlf ================================================ FILE: .github/workflows/build_and_test.yml ================================================ name: Build and Test on: pull_request: types: [opened, reopened, synchronize, ready_for_review, review_requested] branches-ignore: - "gh-pages" push: branches: - "master" - "prepare-release" tags: - v* workflow_dispatch: permissions: contents: read jobs: check_code_format: name: Check code formatting runs-on: "ubuntu-22.04" # The code currently doesn't pass formatting. # Enable this workflow job once it does, to detect regressions. if: false steps: - uses: actions/checkout@v4 - name: Check clang-format (make test-formatting) run: | make test-formatting plain_makefile: name: Build and test using plain Makefile runs-on: "ubuntu-22.04" steps: - uses: actions/checkout@v4 - name: Add system tools run: | sudo apt-get update && sudo apt-get install libgtest-dev help2man - name: Build (make all) run: | make all - name: Test (make test) run: | make test python_module: name: Build and test Python module runs-on: "ubuntu-22.04" strategy: fail-fast: false matrix: # Python 3.6 isn't available on the ubuntu-22.04 GitHub runner # (which is the oldest Ubuntu runner still available) # Python 3.7 is too old for the version of setuptools that we request. # (and we want that setuptools to be able to set certain compiler options correctly) # Python 3.8 is the oldest we can easily support, I think. # Note Python 3.8 and 3.9 are already _past_ end of life: https://devguide.python.org/versions/ # 3.8 EOL was 07 Oct 2024 # 3.9 EOL was 31 Oct 2025 python_version: ["3.8", "3.10", "3.14"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "${{ matrix.python_version }}" - name: Build run: | pip install build python -m build --sdist --wheel - name: Test run: | python -m venv --clear trial && cd trial && source bin/activate && pip install ../dist/jsonnet-*.whl && python3 ../python/_jsonnet_test.py cmake_build: name: Build and test using CMake runs-on: "ubuntu-22.04" steps: - uses: actions/checkout@v4 - name: Configure (cmake) run: | cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B "${{github.workspace}}/build" - name: Build (cmake --build) working-directory: ${{github.workspace}}/build run: | cmake --build . - name: Test (ctest)) working-directory: ${{github.workspace}}/build run: | ctest --output-on-failure bazel_build: name: Build and test using Bazel runs-on: "ubuntu-22.04" steps: - uses: actions/checkout@v4 - name: Build run: | bazelisk build --lockfile_mode=error -c opt //cmd:all - name: Test run: | bazelisk test --lockfile_mode=error //... bazel_use_module: name: Build Bazel example runs-on: "ubuntu-22.04" strategy: fail-fast: false matrix: bazel_version: ["7.*", "8.*", "9.*"] steps: - uses: actions/checkout@v4 - name: Build env: USE_BAZEL_VERSION: ${{ matrix.bazel_version }} run: | cd examples/bazel bazelisk build --lockfile_mode=off //... cmake_example: name: Build CMake example runs-on: "ubuntu-22.04" steps: - uses: actions/checkout@v4 - name: Build run: | cd examples/cmake BUILD_DIR="$(mktemp -d)" cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -S . -B "${BUILD_DIR}" && \ cmake --build "${BUILD_DIR}" -- -v && \ ${BUILD_DIR}/jsonnet_cmake_example ================================================ FILE: .github/workflows/create_archive.sh ================================================ #!/usr/bin/env bash # Copyright 2023 The Bazel Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail JSONNET_VERSION="$(grep -Ee '^\s*#\s*define\s+LIB_JSONNET_VERSION\s+"[^"]+"\s*$' include/libjsonnet.h | sed -E -e 's/[^"]+"([^"]+)".*/\1/')" # GITHUB_REF is set by GH actions, see # https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables VERSION_SUFFIX= if [[ ( "${GITHUB_REF_TYPE}" != 'branch' || "${GITHUB_REF_NAME}" != "prepare-release" ) && ( "${GITHUB_REF_TYPE}" != 'tag' || "${GITHUB_REF_NAME}" != "${JSONNET_VERSION}" ) ]]; then >&2 echo 'WARNING: Jsonnet library version in header does not match release ref. Adding commit suffix.' VERSION_SUFFIX="-${GITHUB_SHA:0:9}" fi # A prefix is added to better match the GitHub generated archives. PREFIX="jsonnet-${JSONNET_VERSION}${VERSION_SUFFIX}" ARCHIVE="jsonnet-${JSONNET_VERSION}${VERSION_SUFFIX}.tar.gz" git archive --format=tar --prefix=${PREFIX}/ "${GITHUB_SHA}" | gzip > "$ARCHIVE" ARCHIVE_SHA=$(shasum -a 256 "$ARCHIVE" | awk '{print $1}') echo "archive_sha256=${ARCHIVE_SHA}" >> "$GITHUB_OUTPUT" echo "jsonnet_version=${JSONNET_VERSION}" >> "$GITHUB_OUTPUT" echo "jsonnet_version_permanent=${JSONNET_VERSION}${VERSION_SUFFIX}" >> "$GITHUB_OUTPUT" ================================================ FILE: .github/workflows/doc_update.yml ================================================ name: Rebuild Docs on: pull_request: types: [opened, reopened, synchronize, ready_for_review, review_requested] branches-ignore: - "gh-pages" push: branches: - "master" workflow_dispatch: inputs: build_wasm: description: "Rebuild libjsonnet.wasm from source" default: false type: boolean required: false build_wasm_from_branch: description: "go-jsonnet branch, tag or SHA to build from" default: "master" type: string required: false permissions: contents: read jobs: build_libjsonnet_wasm: name: Build libjsonnet.wasm for website if: ${{ inputs.build_wasm }} runs-on: "ubuntu-24.04" outputs: libjsonnet_wasm_sha256: ${{ steps.build.outputs.libjsonnet_wasm_sha256 }} libjsonnet_wasm_artifact_id: ${{ steps.upload-wasm.outputs.artifact_id }} steps: - uses: actions/checkout@v6 with: repository: google/go-jsonnet ref: ${{ inputs.build_wasm_from_branch }} submodules: false - uses: actions/cache@v5 with: path: | ~/.cache/bazel ~/.cache/bazelisk key: ${{ runner.os }}-bazel-cache - id: build run: | bazelisk test --lockfile_mode=error //... bazelisk build --lockfile_mode=error //cmd/wasm:libjsonnet.wasm WHERE="$(bazelisk cquery //cmd/wasm:libjsonnet.wasm --output files)" cp "${WHERE}" ./ { printf 'libjsonnet_wasm_sha256='; sha256sum --binary ./libjsonnet.wasm; } >> "$GITHUB_OUTPUT" - name: Upload libjsonnet.wasm id: upload-wasm uses: actions/upload-artifact@v4 with: path: ./libjsonnet.wasm name: libjsonnet-wasm if-no-files-found: "error" retention-days: 4 include-hidden-files: false build_website: name: Build website # Ordering dependency - this must run after building libjsonnet.wasm. needs: build_libjsonnet_wasm # Don't run if libjsonnet.wasm build _failed_ or the workflow cancelled, but run if it was skipped. if: always() && !failure() && !cancelled() runs-on: "ubuntu-24.04" outputs: diff_status: ${{ steps.diff-generated.outputs.diff_status }} # Note upload-pages-artifact output name is artifact_id (with underscore). pages_artifact_id: ${{ steps.upload-pages-package.outputs.artifact_id }} # Note upload-artifact output name is artifact-id (with dash). branch_content_artifact_id: ${{ steps.upload-full-archive.outputs.artifact-id }} git_tree_hash: ${{ steps.diff-generated.outputs.git_tree_hash }} steps: - uses: actions/checkout@v6 - name: Setup Pages if: ${{ github.repository == 'google/jsonnet' }} uses: actions/configure-pages@v5 - name: Create Gemfile run: | echo $'source "https://rubygems.org"\n\ngem "jekyll"\n' > Gemfile - name: Setup Ruby and Jekyll uses: ruby/setup-ruby@8d27f39a5e7ad39aebbcbd1324f7af020229645c # v1.287.0 with: ruby-version: "4.0.1" bundler-cache: true - name: Cache jsonnet binary id: cache-jsonnet-binary uses: actions/cache@v5 with: key: jsonnet-bin-${{ hashFiles('include/*', 'core/*', 'cmd/*', 'cpp/*', 'third_party/**/*', 'stdlib/std.jsonnet') }} path: ./jsonnet - name: Build jsonnet binary if: ${{ steps.cache-jsonnet-binary.outputs.cache-hit != 'true' }} run: | make jsonnet - id: get_built_libjsonnet_wasm continue-on-error: true uses: actions/download-artifact@v7 with: artifact-ids: ${{ needs.build_libjsonnet_wasm.outputs.libjsonnet_wasm_artifact_id }} path: built_libjsonnet_wasm - name: Set up output dir id: make-output-dir run: | WORKDIR="$(mktemp -d)" # If the workflow runs on a fork that doesn't have a gh-pages branch # (which should be most forks) then we still want to be able to build # the docs (so the repo owner can examine them), but we have no baseline. # So we just build as though this is the first time. if git fetch origin gh-pages; then git worktree add --no-checkout --detach "$WORKDIR"/work FETCH_HEAD else echo "Repo has no gh-pages! Creating new orphan branch instead." git worktree add --orphan -b gh-pages "$WORKDIR"/work fi echo "doc_output_dir=$WORKDIR" >> "$GITHUB_OUTPUT" - name: Build website env: WORKDIR: ${{ steps.make-output-dir.outputs.doc_output_dir }} WANT_BUILT_LIBJSONNET_WASM_PATH: ${{ steps.get_built_libjsonnet_wasm.outputs.download-path }} WANT_BUILT_LIBJSONNET_WASM_SHA256: ${{ needs.build_libjsonnet_wasm.outputs.libjsonnet_wasm_sha256 }} run: | tools/scripts/update_web_content.sh bundler exec jekyll build -s doc -d "$WORKDIR"/work cd "$WORKDIR"/work if [[ -z "${WANT_BUILT_LIBJSONNET_WASM_SHA256}" ]]; then # Keep existing libjsonnet.wasm. git checkout HEAD -- js/libjsonnet.wasm || echo "Could not find existing js/libjsonnet.wasm" else # Use a newly build libjsonnet.wasm. cp "${WANT_BUILT_LIBJSONNET_WASM_PATH}"/libjsonnet.wasm ./js/libjsonnet.wasm # Safety check - check that we transferred the file correctly. echo "${WANT_BUILT_LIBJSONNET_WASM_SHA256}" | (cd ./js && sha256sum --check --strict) fi # Want a .nojekyll file while we're still publishing by pushing to gh-pages branch. touch .nojekyll git add . - name: Diff docs id: diff-generated env: WORKDIR: ${{ steps.make-output-dir.outputs.doc_output_dir }} run: | cd "$WORKDIR"/work if git rev-parse --quiet --verify HEAD > /dev/null; then git diff --cached --stat # Only show the first 5000 lines of the diff output, for sanity. git diff --cached | head -n5000 if git diff-index --quiet HEAD; then echo "Generated docs are identical to existing gh-pages branch." echo "diff_status=clean" >> "$GITHUB_OUTPUT" else echo "Generated docs differ from existing gh-pages branch." echo "diff_status=changed" >> "$GITHUB_OUTPUT" fi else echo "No HEAD revision; probably on an orphan branch. Skipping diff step." echo "diff_status=changed" >> "$GITHUB_OUTPUT" fi TREE_SHA1=$(git write-tree) echo "Computed sha1 for new gh-pages tree: $TREE_SHA1" echo "git_tree_hash=$TREE_SHA1" >> "$GITHUB_OUTPUT" git archive --format=tar -o ../gh-pages-content.tar "$TREE_SHA1" - name: Upload full archive id: upload-full-archive if: ${{ steps.diff-generated.outputs.diff_status == 'changed' }} uses: actions/upload-artifact@v4 with: path: ${{ steps.make-output-dir.outputs.doc_output_dir }}/gh-pages-content.tar name: full-gh-pages-content if-no-files-found: "error" retention-days: 4 include-hidden-files: true - name: Upload Pages package id: upload-pages-package if: ${{ github.repository == 'google/jsonnet' && steps.diff-generated.outputs.diff_status == 'changed' }} uses: actions/upload-pages-artifact@v4 with: path: ${{ steps.make-output-dir.outputs.doc_output_dir }}/work/ push_pages_branch: name: Update gh-pages branch needs: build_website if: always() && github.repository == 'google/jsonnet' && contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) && needs.build_website.result == 'success' && needs.build_website.outputs.diff_status == 'changed' permissions: contents: write environment: name: pages-via-branch-push runs-on: "ubuntu-24.04" steps: - uses: actions/checkout@v6 with: ref: gh-pages path: repo - uses: actions/download-artifact@v7 with: artifact-ids: ${{ needs.build_website.outputs.branch_content_artifact_id }} path: built - name: Extract and push env: WANT_TREE_SHA1: ${{ needs.build_website.outputs.git_tree_hash }} GIT_AUTHOR_NAME: "github-actions[bot]" GIT_AUTHOR_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com" GIT_COMMITTER_NAME: "github-actions[bot]" GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com" run: | [[ ! -z "$WANT_TREE_SHA1" ]] || { >&2 echo "did not get valid sha1 hash for expected git tree from builder job" exit 1 } cd repo rm --one-file-system -r -- $(git ls-tree --name-only HEAD) echo "Cleared working dir. Content is now:" ls -R "$GITHUB_WORKSPACE" echo "Unpacking artifact from builder job." tar -xf ../built/gh-pages-content.tar git add . git diff --cached --stat UNPACKED_TREE_SHA1=$(git write-tree) echo "Expect tree SHA1: $WANT_TREE_SHA1 (computed in build step)" echo "Unpacked tree SHA1: $UNPACKED_TREE_SHA1" [[ "$WANT_TREE_SHA1" = "$UNPACKED_TREE_SHA1" ]] || { >&2 echo "builder-produced tree differs from extracted tree" exit 1 } git commit -m 'Update docs.' git log -n1 git push publish_pages: name: Direct publish pages needs: build_website if: false && github.repository == 'google/jsonnet' && contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) && needs.build_website.result == 'success' && needs.build_website.outputs.diff_status == 'changed' permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: "ubuntu-24.04" steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/publish-python.yml ================================================ name: Build and Publish Python Package # Be very careful granting extra permissions here, as this workflow uses third party actions. # Prefer to pin to exact commits for third-party actions. I'm making an exception for actions # maintained by GitHub itself. # For now, just trigger this workflow manually. on: workflow_dispatch: inputs: upload_to_pypi: description: "Upload generate package files to PyPI" required: true type: boolean pypi_environment: description: "Which PyPI instance to publish to" required: true type: choice options: - testpypi - pypi jobs: build_sdist: name: Build Python sdist runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.8" cache: "pip" - run: pip install 'build==1.2.2' - name: Build sdist run: python3 -m build --sdist - uses: actions/upload-artifact@v4 with: name: sdist path: ./dist/*.gz if-no-files-found: error build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-22.04, windows-latest, macos-14, macos-latest] permissions: contents: read steps: - uses: actions/checkout@v4 - name: Build wheels uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 env: # CIBuildWheel itself no longer supports earlier than Python 3.8, which is # the earliest we try to support here. Also likely cibuildwheel will soon # drop support for 3.8, see https://github.com/pypa/cibuildwheel/issues/2685 CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_i686" CIBW_TEST_COMMAND: > python {package}/python/_jsonnet_test.py - uses: actions/upload-artifact@v4 with: name: cibw-wheels-${{ matrix.os }} path: ./wheelhouse/*.whl if-no-files-found: error upload_to_pypi: name: "Upload packages to PyPI" needs: [build_sdist, build_wheels] runs-on: ubuntu-24.04 if: "${{ inputs.upload_to_pypi }}" permissions: contents: read id-token: write # Needed for PyPI Trusted Publishing environment: name: "${{ inputs.pypi_environment }}" url: "${{ inputs.pypi_environment == 'pypi' && 'https://pypi.org/p/jsonnet' || 'https://test.pypi.org/p/jsonnet' }}" steps: - uses: actions/download-artifact@v4 with: path: cibw-wheels pattern: cibw-wheels-* - uses: actions/download-artifact@v4 with: path: sdist name: sdist - name: Flatten wheels to one directory run: | mkdir dist find cibw-wheels/ -type f -name '*.whl' -exec mv '{}' ./dist/ ';' find sdist/ -type f -name '*.gz' -exec mv '{}' ./dist/ ';' - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: verbose: true print-hash: true repository-url: "${{ inputs.pypi_environment == 'testpypi' && 'https://test.pypi.org/legacy/' || '' }}" ================================================ FILE: .github/workflows/release.yml ================================================ # Copyright 2023 The Bazel Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Cut a release whenever a new tag is pushed to the repo. name: Release on: workflow_dispatch: inputs: prerelease: description: If set, publish this as a release candidate / prerelease version. type: boolean required: true jobs: build: permissions: contents: write runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Create release archive id: create_archive run: .github/workflows/create_archive.sh - name: Release uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 with: name: ${{ steps.create_archive.outputs.jsonnet_version }} tag_name: ${{ steps.create_archive.outputs.jsonnet_version }} prerelease: ${{ inputs.prerelease }} draft: true fail_on_unmatched_files: true files: jsonnet-*.tar.gz ================================================ FILE: .gitignore ================================================ **/core !core/ **/*~ **/*.pyc **/.*.swp **/.*.swo **/*.o **/*.a **/*.dylib *.dSYM .makebuild/ jsonnet jsonnetfmt _jsonnet.so libjsonnet.so libjsonnet.so.* libjsonnet++.so libjsonnet++.so.* **/core.* **/vgcore **/vgcore.* core/std.jsonnet.h stdlib/to_c_array Makefile.depend Makefile.depend.bak bazel-* libjsonnet_file_test libjsonnet_native_callbacks_test libjsonnet_locale_test libjsonnet_test libjsonnet++_test unicode_test lexer_test parser_test **/*.tfstate **/*.tfstate.backup /build/ /external/ /dist/ /jsonnet.egg-info/ /python/jsonnet.egg-info/ /doc/.jekyll-cache/ /production/ /doc/production/ /doc/js/libjsonnet.wasm /man # CMake **/CMakeCache.txt **/CMakeFiles **/cmake_install.cmake **/CTestTestfile.cmake tags bin/ Testing/ cmake_test_discovery_*.json # Ignore auto-generated makefiles from CMake. **/Makefile ^Makefile/ **/.DS_Store .vscode ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15...4.1) #### Check important configuration pre-requisites. # # Discourage in-source builds; they overwrite the hand-written Makefile, and generally cause confusion. # Use `cmake . -B` or the CMake GUI to do an out-of-source build. if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) message(FATAL_ERROR "Out-of-source builds are recommended for this project.") endif() #### Extract version from include/libjsonnet.h so it can be put in the project version. # # Extract release version number from include/libjsonnet.h. # We do this in various other (non-CMake-build) places too, e.g., the Python wheel build. set(JSONNET_HEADER_VERSION_REGEX [=[^[ \t]*#[ \t]*define[ \t]+LIB_JSONNET_VERSION[ \t]+\"v([0-9.]+(-?[a-z][a-z0-9]*)?)\"[ \t]*$]=]) file( STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/include/libjsonnet.h JSONNET_HEADER_VERSION LIMIT_COUNT 1 REGEX ${JSONNET_HEADER_VERSION_REGEX}) string(REGEX REPLACE ${JSONNET_HEADER_VERSION_REGEX} "\\1" JSONNET_HEADER_VERSION "${JSONNET_HEADER_VERSION}") message(VERBOSE "Extracted version from include/libjsonnet.h: ${JSONNET_HEADER_VERSION}") #### Set CMake project. # project( jsonnet HOMEPAGE_URL "https://jsonnet.org/" LANGUAGES C CXX) #### Unconditional CMake module includes. # include(GNUInstallDirs) #### Set up cacheable/user-configurable state. # # User-configurable options. option(BUILD_JSONNET "Build jsonnet command-line tool." ON) option(BUILD_JSONNETFMT "Build jsonnetfmt command-line tool." ON) option(BUILD_TESTS "Build and run jsonnet tests." ON) option(BUILD_STATIC_LIBS "Build a static libjsonnet." ON) option(BUILD_SHARED_LIBS "Build shared libjsonnet." ON) option(BUILD_SHARED_BINARIES "Link binaries to the shared libjsonnet instead of the static one." OFF) option(BUILD_MAN_PAGES "Build manpages." ON) option(USE_SYSTEM_GTEST "Use system-provided gtest library" OFF) option(USE_SYSTEM_JSON "Use the system-provided json library" OFF) option(USE_SYSTEM_RAPIDYAML "Use the system-provided rapidyaml library" OFF) #### Compute derived values from user-configurable state. # # Helper variable set to ON if there is any reason we need to build the jsonnet command binaries. if(BUILD_TESTS OR BUILD_JSONNET OR BUILD_JSONNETFMT) set(BUILD_SOME_BINARIES ON) else() set(BUILD_SOME_BINARIES OFF) endif() #### System package searches. # if(USE_SYSTEM_JSON) find_package(nlohmann_json 3.6.1 REQUIRED) endif() if(USE_SYSTEM_RAPIDYAML) find_package(ryml REQUIRED VERSION 0.10.0) endif() #### Utility function to configure a target with our preferred C and C++ compilation flags. # function(configure_target_cc_props LIB_TARGET_NAME) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY CXX_STANDARD 17) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY CXX_EXTENSIONS OFF) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY C_STANDARD 99) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY C_EXTENSIONS OFF) set_property(TARGET ${LIB_TARGET_NAME} PROPERTY C_STANDARD_REQUIRED ON) if (NOT MSVC) target_compile_options(${LIB_TARGET_NAME} PRIVATE -Wall -Wextra -Wimplicit-fallthrough -pedantic $<$:-Woverloaded-virtual> ) endif() endfunction() #### Sub-directory CMakeLists includes. # add_subdirectory(stdlib) #### Utility function to set up all the properties for an OBJECT library for the jsonnet core. # # This is done as a function because there separate object libraries for shared vs. static. # Shared has slightly different compilation flags (it needs -fPIC). # function(configure_jsonnet_obj_target LIB_TARGET_NAME) configure_target_cc_props(${LIB_TARGET_NAME}) target_sources(${LIB_TARGET_NAME} PRIVATE third_party/md5/md5.h third_party/md5/md5.cpp core/static_error.h core/desugarer.h core/unicode.h core/vm.h core/static_analysis.h core/path_utils.h core/state.h core/pass.h core/ast.h core/json.h core/string_utils.h core/formatter.h core/parser.h core/lexer.h core/desugarer.cpp core/formatter.cpp core/lexer.cpp core/libjsonnet.cpp core/parser.cpp core/pass.cpp core/path_utils.cpp core/static_analysis.cpp core/string_utils.cpp core/vm.cpp PUBLIC include/libjsonnet.h include/libjsonnet_fmt.h ) target_link_libraries(${LIB_TARGET_NAME} PRIVATE stdlib_h) if(USE_SYSTEM_JSON) target_link_libraries(${LIB_TARGET_NAME} PRIVATE nlohmann_json::nlohmann_json) else() target_sources(${LIB_TARGET_NAME} PRIVATE third_party/json/nlohmann/json.hpp ) target_include_directories(${LIB_TARGET_NAME} SYSTEM PRIVATE third_party/json) endif() if(USE_SYSTEM_RAPIDYAML) target_link_libraries(${LIB_TARGET_NAME} PRIVATE ryml::ryml) target_compile_definitions(${LIB_TARGET_NAME} PRIVATE USE_SYSTEM_RAPIDYAML) else() target_sources(${LIB_TARGET_NAME} PRIVATE third_party/rapidyaml/rapidyaml-0.10.0.hpp third_party/rapidyaml/rapidyaml.cpp ) endif() target_include_directories(${LIB_TARGET_NAME} PUBLIC $ $ PRIVATE third_party/md5 $<$>:${CMAKE_CURRENT_SOURCE_DIR}/third_party/rapidyaml> ) endfunction() #### Utility function to set up library target properties for the C lib. # function(configure_jsonnet_lib_target LIB_TARGET_NAME OBJ_TARGET_NAME) configure_target_cc_props(${LIB_TARGET_NAME}) target_link_libraries(${LIB_TARGET_NAME} PUBLIC ${OBJ_TARGET_NAME}) set(JSONNET_PUBLIC_HEADERS include/libjsonnet.h include/libjsonnet_fmt.h) set_target_properties(${LIB_TARGET_NAME} PROPERTIES OUTPUT_NAME jsonnet PUBLIC_HEADER "${JSONNET_PUBLIC_HEADERS}") endfunction() #### Utility function to set up library target properties for the C++ lib. # function(configure_jsonnet_cpp_lib_target LIB_TARGET_NAME OBJ_TARGET_NAME) configure_target_cc_props(${LIB_TARGET_NAME}) target_sources(${LIB_TARGET_NAME} PRIVATE cpp/libjsonnet++.cpp include/libjsonnet++.h ) target_link_libraries(${LIB_TARGET_NAME} PUBLIC ${OBJ_TARGET_NAME}) set(JSONNET_PUBLIC_HEADERS include/libjsonnet.h include/libjsonnet++.h) set_target_properties(${LIB_TARGET_NAME} PROPERTIES OUTPUT_NAME jsonnet++ PUBLIC_HEADER "${JSONNET_PUBLIC_HEADERS}") endfunction() #### Static library target setup. # if(BUILD_STATIC_LIBS OR (BUILD_SOME_BINARIES AND NOT BUILD_SHARED_BINARIES)) add_library(jsonnet_obj_static OBJECT) configure_jsonnet_obj_target(jsonnet_obj_static) add_library(jsonnet_static STATIC) configure_jsonnet_lib_target(jsonnet_static jsonnet_obj_static) add_library(jsonnet_cpp_static STATIC) configure_jsonnet_cpp_lib_target(jsonnet_cpp_static jsonnet_obj_static) if(BUILD_STATIC_LIBS) install(TARGETS jsonnet_static jsonnet_cpp_static) endif() endif() #### Shared library target setup. # if(BUILD_SHARED_LIBS OR (BUILD_SOME_BINARIES AND BUILD_SHARED_BINARIES)) add_library(jsonnet_obj_shared OBJECT) set_property(TARGET jsonnet_obj_shared PROPERTY POSITION_INDEPENDENT_CODE ON) configure_jsonnet_obj_target(jsonnet_obj_shared) add_library(jsonnet_shared SHARED) configure_jsonnet_lib_target(jsonnet_shared jsonnet_obj_shared) set_target_properties(jsonnet_shared PROPERTIES VERSION "${JSONNET_HEADER_VERSION}" SOVERSION "0") add_library(jsonnet_cpp_shared SHARED) configure_jsonnet_cpp_lib_target(jsonnet_cpp_shared jsonnet_obj_shared) set_target_properties(jsonnet_cpp_shared PROPERTIES VERSION "${JSONNET_HEADER_VERSION}" SOVERSION "0") if(BUILD_SHARED_LIBS) install(TARGETS jsonnet_shared jsonnet_cpp_shared) endif() endif() #### Alias the library to use for binaries. # if(BUILD_SHARED_BINARIES) add_library(jsonnet_lib_for_binaries ALIAS jsonnet_shared) add_library(jsonnet_cpp_lib_for_binaries ALIAS jsonnet_cpp_shared) else() add_library(jsonnet_lib_for_binaries ALIAS jsonnet_static) add_library(jsonnet_cpp_lib_for_binaries ALIAS jsonnet_cpp_static) endif() #### An object library for the utils.cpp code which is shared by both CLI binaries. # if(BUILD_SOME_BINARIES) add_library(jsonnet_cmd_utils OBJECT cmd/utils.cpp cmd/utils.h ) endif() #### CLI (jsonnet and jsonnetfmt) binary targets. # if(BUILD_JSONNET OR BUILD_TESTS) add_executable(jsonnet cmd/jsonnet.cpp $ ) target_link_libraries(jsonnet PRIVATE jsonnet_lib_for_binaries) if(BUILD_JSONNET) install(TARGETS jsonnet) endif() endif() if(BUILD_JSONNETFMT OR BUILD_TESTS) add_executable(jsonnetfmt cmd/jsonnetfmt.cpp $ ) target_link_libraries(jsonnetfmt PRIVATE jsonnet_lib_for_binaries) if(BUILD_JSONNETFMT) install(TARGETS jsonnetfmt) endif() endif() #### GoogleTest based tests. # if(BUILD_TESTS) enable_testing() include(GoogleTest) if(USE_SYSTEM_GTEST) find_package(GTest REQUIRED) else() include(FetchContent) FetchContent_Declare(googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.17.0 ) # For Windows: Prevent overriding the parent project's compiler/linker settings. set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent including GoogleTest in the generated install rules. set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) endif() set(JSONNET_TEST_TARGETS) function(jsonnet_add_gtest TESTNAME JSONNET_LIB_TARGET) set(JSONNET_TEST_TARGETS ${JSONNET_TEST_TARGETS} ${TESTNAME} PARENT_SCOPE) add_executable(${TESTNAME} ${ARGN}) target_link_libraries(${TESTNAME} ${JSONNET_LIB_TARGET} gtest gmock gtest_main) gtest_discover_tests(${TESTNAME} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" ENVIRONMENT "JSONNET_SOURCE_BASE=${PROJECT_SOURCE_DIR}" ) set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) endfunction() function(jsonnet_add_raw_test TESTNAME JSONNET_LIB_TARGET) set(JSONNET_TEST_TARGETS ${JSONNET_TEST_TARGETS} ${TESTNAME} PARENT_SCOPE) add_executable(${TESTNAME} ${ARGN}) target_link_libraries(${TESTNAME} ${JSONNET_LIB_TARGET}) set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) add_test(NAME ${TESTNAME} COMMAND ${TESTNAME}) endfunction() jsonnet_add_gtest(test_core_unicode jsonnet_lib_for_binaries core/unicode_test.cpp) jsonnet_add_gtest(test_core_lexer jsonnet_lib_for_binaries core/lexer_test.cpp) jsonnet_add_gtest(test_core_parser jsonnet_lib_for_binaries core/parser_test.cpp) jsonnet_add_gtest(test_core_libjsonnet jsonnet_lib_for_binaries core/libjsonnet_test.cpp) jsonnet_add_gtest(test_cpp_libjsonnet jsonnet_cpp_lib_for_binaries cpp/libjsonnet++_test.cpp) jsonnet_add_gtest(test_cpp_libjsonnet_locale jsonnet_cpp_lib_for_binaries cpp/libjsonnet_locale_test.cpp) jsonnet_add_raw_test(test_core_libjsonnet_native_callbacks jsonnet_lib_for_binaries core/libjsonnet_native_callbacks_test.c) # core/libjsonnet_file_test needs an input file add_executable(test_libjsonnet_file core/libjsonnet_file_test.c) target_link_libraries(test_libjsonnet_file jsonnet_lib_for_binaries) set_target_properties(test_libjsonnet_file PROPERTIES FOLDER tests) add_test( NAME test_libjsonnet_file COMMAND test_libjsonnet_file "test_suite/object.jsonnet" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) add_subdirectory(test_suite) # `run_tests` target builds and runs all tests add_custom_target(run_tests COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS jsonnet jsonnetfmt test_libjsonnet_file ${JSONNET_TEST_TARGETS} ) endif() # if(BUILD_TESTS) #### Man pages # if(BUILD_MAN_PAGES AND (BUILD_JSONNET OR BUILD_JSONNETFMT)) find_program(HELP2MAN_BINARY NAMES help2man) if (HELP2MAN_BINARY) message(STATUS "help2man found, man pages will be generated.") file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/man/man1") if(BUILD_JSONNET) add_custom_command( OUTPUT "${PROJECT_BINARY_DIR}/man/man1/jsonnet.1" COMMAND "${HELP2MAN_BINARY}" ARGS --section=1 --no-info --name="Jsonnet data templating language interpreter" --output="${PROJECT_BINARY_DIR}/man/man1/jsonnet.1" "$" DEPENDS jsonnet ) endif() if(BUILD_JSONNETFMT) add_custom_command( OUTPUT "${PROJECT_BINARY_DIR}/man/man1/jsonnetfmt.1" COMMAND "${HELP2MAN_BINARY}" ARGS --section=1 --no-info --name="Jsonnet data templating language auto-formatter" --output="${PROJECT_BINARY_DIR}/man/man1/jsonnetfmt.1" "$" DEPENDS jsonnetfmt ) endif() add_custom_target(jsonnet-man ALL DEPENDS "${PROJECT_BINARY_DIR}/man/man1/jsonnet.1") add_custom_target(jsonnetfmt-man ALL DEPENDS "${PROJECT_BINARY_DIR}/man/man1/jsonnetfmt.1") install(FILES ${PROJECT_BINARY_DIR}/man/man1/jsonnet.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") install(FILES ${PROJECT_BINARY_DIR}/man/man1/jsonnetfmt.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") else() message(STATUS "help2man not found, man pages will not be generated.") endif() endif() # if(BUILD_MAN_PAGES) #### CMake debugging outputs # # Note cmake_language GET_MESSAGE_LOG_LEVEL exists only in 3.25 and above. if(CMAKE_VERSION VERSION_GREATER "3.25") include(CMakePrintHelpers) cmake_language(GET_MESSAGE_LOG_LEVEL CURRENT_CMAKE_LOG_LEVEL) if(CURRENT_CMAKE_LOG_LEVEL MATCHES "TRACE") # Print specific properties for specific targets cmake_print_properties( TARGETS to_c_array stdlib_h jsonnet_obj_static jsonnet_obj_shared jsonnet_static jsonnet_shared jsonnet_cpp_static jsonnet_cpp_shared jsonnet_cmd_utils jsonnet jsonnetfmt jsonnet-man jsonnetfmt-man PROPERTIES TYPE C_STANDARD CXX_STANDARD POSITION_INDEPENDENT_CODE PUBLIC_LINK_DEPENDS INTERFACE_LINK_DEPENDS PUBLIC_LINK_LIBRARIES INTERFACE_LINK_LIBRARIES PUBLIC_INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES OUTPUT_NAME PUBLIC_HEADER RUNTIME_OUTPUT_DIRECTORY ) endif() endif() ================================================ FILE: CONTRIBUTING ================================================ Before we can merge your pull request, we need you to sign either the Google individual or corporate contributor license agreement (CLA), unless you are a Google employee, intern, or contractor. Please see https://jsonnet.org/learning/community.html#license for more information. ================================================ FILE: Dockerfile ================================================ FROM alpine:latest AS builder RUN apk -U add build-base WORKDIR /opt COPY . /opt/jsonnet RUN cd jsonnet && \ make FROM alpine:latest RUN apk add --no-cache libstdc++ COPY --from=builder /opt/jsonnet/jsonnet /usr/local/bin COPY --from=builder /opt/jsonnet/jsonnetfmt /usr/local/bin ENTRYPOINT ["/usr/local/bin/jsonnet"] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MANIFEST.in ================================================ include LICENSE include/*.h core/*.cpp core/*.h python/*.c stdlib/std.jsonnet Makefile recursive-include third_party/json *.hpp include third_party/md5/*.cpp third_party/md5/*.h include third_party/rapidyaml/*.cpp third_party/rapidyaml/*.hpp #recursive-include test_suite examples gc_stress benchmarks editors ================================================ FILE: MODULE.bazel ================================================ module(name = "jsonnet") bazel_dep(name = "googletest", version = "1.16.0") bazel_dep(name = "rules_python", version = "1.2.0") bazel_dep(name = "rules_cc", version = "0.2.16") bazel_dep(name = "rules_shell", version = "0.6.1") ================================================ FILE: PYTHON_README.md ================================================ # Jsonnet - The data templating language For an introduction to Jsonnet and documentation, [visit our website](https://jsonnet.org). This is the Python module for the original C++ implementation of Jsonnet. **Security notes:** The C++ implementation is not suitable for processing *untrusted inputs* without significant external effort to sandbox it. It is not hardened and may have unknown exploitable bugs. It is intended for use to evaluate Jsonnet code that you trust not to be malicious (e.g., code written by you/your organisation). Even ignoring the risk of exploitable bugs in the implementation, the `import`, `importstr`, and `importbin` language constructs can potentially be used to exfiltrate sensitive data unless you take extra steps to restrict these or sandbox the jsonnet interpreter. By default, these constructs can import from any path accessible to the interpreter process. ## Using the Jsonnet Python module You can install from PyPI with `uv add jsonnet` or with your preferred Python package management tool. The two main functions in the `_jsonnet` package are `evaluate_file` and `evaluate_snippet`: - `evaluate_file(filename, ...)` reads and evalutes the file at the given path, returning an output as a JSON string. - `evaluate_snippet(filename, src, ...)` evaluates the given source code (`src`), returning an output as a JSON string. The provided filename is used in error messages. The functions support keyword arguments: - `jpathdir`: A string (path to a directory) or list of strings (list of directories) to be added to the import search path. - `ext_vars`, `ext_codes`: Dictionaries of variables to set; these can be used from within the evaluated code through the `std.extVar` Jsonnet function. `ext_codes` values are Jsonnet expressions, `ext_vars` values are provided to Jsonnet as plain strings. - `tla_vars`, `tla_codes`: Dictionaries of Top-Level arguments. If the provided Jsonnet code to evaluate represents a function, that function is called with these values as (named) arguments. - `import_callback`: A function which will be called when `import` (or `importstr`, `importbin`) expressions are evaluated. See below. - `native_callbacks`: A dictionary of functions which can be called from Jsonnet using the Jsonnet `std.native` function. See below. And some configuration for internal implementation details: - `max_stack` - `max_trace` - `gc_min_objects` - `gc_growth_trigger` ### `import_callback` Usage example: ```python import _jsonnet FILES = { "the_message.txt": b"hello, world", } def import_callback_memfile(dir, rel): """Custom import function which only returns files from some in-memory storage. Args: dir: The directory part of the file in which the `import` occurred. rel: The path string passed to the `import` expression. """ if rel in FILES: # Note that the returned file _content_ should be a bytes value, not a string. return rel, FILES[rel] raise RuntimeError('File not found') result = _jsonnet.evaluate_snippet( 'example', 'local the_message = importstr "the_message.txt"; ' + '{ msg: the_message }', import_callback=import_callback_memfile) assert result == '{\n "msg": "hello, world"\n}\n'; ``` ### `native_callbacks` Usage example: ```python import _jsonnet def concat(a, b): return a + b native_callbacks = { 'concat': (('a', 'b'), concat), } result = _jsonnet.evaluate_snippet( 'example', 'local concat = std.native("concat"); ' + 'concat("hello, ", "world")', native_callbacks=native_callbacks) assert result == '"hello, world"\n'; ``` ================================================ FILE: README.md ================================================ # Jsonnet - The data templating language ![master branch build status badge](https://github.com/google/jsonnet/actions/workflows/build_and_test.yml/badge.svg?event=push&branch=master) For an introduction to Jsonnet and documentation, [visit our website](https://jsonnet.org). This repository contains the original implementation. You can also try [go-jsonnet](https://github.com/google/go-jsonnet), a newer implementation which in some cases is orders of magnitude faster, and is recommended in preference to the C++ version. Visit our [discussion forum](https://groups.google.com/g/jsonnet). **Security notes:** If you need to process *untrusted inputs* (untrusted Jsonnet code), it is best not to use the C++ implementation, as it is not hardened for that use-case. The expected use-case is for evaluating Jsonnet code that you / your organisation has written and trusts not to be malicious. Even ignoring the risk of exploitable bugs in the implementation, the `import`, `importstr`, and `importbin` language constructs can potentially be used to exfiltrate sensitive data unless you take extra steps to restrict these or sandbox the jsonnet interpreter. By default, these constructs can import from any path accessible to the interpreter process. ## Packages Jsonnet is available on Homebrew: ``` brew install jsonnet ``` [Jsonnet](https://packages.msys2.org/base/mingw-w64-jsonnet) is available on [MSYS2](https://www.msys2.org/): ``` pacman -S mingw-w64-clang-i686-jsonnet ``` ``` pacman -S mingw-w64-clang-x86_64-jsonnet ``` ``` pacman -S mingw-w64-i686-jsonnet ``` ``` pacman -S mingw-w64-x86_64-jsonnet ``` ``` pacman -S mingw-w64-ucrt-x86_64-jsonnet ``` The Python binding is on pypi: ``` pip install jsonnet ``` You can also download and install Jsonnet using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: ``` git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install vcpkg install jsonnet ``` The Jsonnet port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. ## Building Jsonnet You can use either GCC or Clang to build Jsonnet. Note that on recent versions of macOS, `/usr/bin/gcc` and `/usr/bin/g++` are actually Clang, so there is no difference. ### Makefile To build Jsonnet with GCC, run: ``` make ``` To build Jsonnet with Clang, run: ``` make CC=clang CXX=clang++ ``` To run the output binary, run: ``` ./jsonnet ``` To run the reformatter, run: ``` ./jsonnetfmt ``` ### Bazel Bazel builds are also supported. Install [Bazel](https://www.bazel.io/versions/master/docs/install.html) if it is not installed already. Then, run the following command to build with GCC: ``` bazel build -c opt //cmd:all ``` To build with Clang, use one of these two options: ``` env CC=clang CXX=clang++ bazel build -c opt //cmd:all # OR bazel build -c opt --action_env=CC=clang --action_env=CXX=clang++ //cmd:all ``` This builds the `jsonnet` and `jsonnetfmt` targets defined in [`cmd/BUILD`](./cmd/BUILD). To launch the output binaries, run: ``` bazel-bin/cmd/jsonnet bazel-bin/cmd/jsonnetfmt ``` ### CMake ``` cmake . -Bbuild ``` ``` cmake --build build --target run_tests ``` ## Contributing See the [contributing page](https://jsonnet.org/learning/community.html#license) on our website. ## Developing Jsonnet ### Running tests To run the comprehensive suite: ``` make test ``` ### Locally serving the website You need a `doc/js/libjsonnet.wasm` which can either be downloaded from the production website: ``` wget https://jsonnet.org/js/libjsonnet.wasm -O doc/js/libjsonnet.wasm ``` Or you can build it yourself, which requires checking out [go-jsonnet](https://github.com/google/go-jsonnet). See the README.md in that repo for instructions. The standard library is documented in a structured format in `doc/_stdlib_gen/stdlib-content.jsonnet`. The HTML (input for Jekyll) is regenerated using the following command: ``` tools/scripts/update_web_content.sh ``` Then, from the root of the repository you can generate and serve the website using [Jekyll](https://jekyllrb.com/) (you need version 4.3.0 or later): ``` jekyll serve -s doc/ ``` This should build and serve the website locally, and automatically rebuild when you change any underlying files. ================================================ FILE: benchmarks/.gitignore ================================================ *.gen.jsonnet ================================================ FILE: benchmarks/bench.01.jsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ local sum(x) = if x == 0 then 0 else x + sum(x - 1); sum(300) ================================================ FILE: benchmarks/bench.02.jsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ local Fib = { n: 1, local outer = self, r: if self.n <= 1 then 1 else (Fib { n: outer.n - 1 }).r + (Fib { n: outer.n - 2 }).r, }; (Fib { n: 25 }).r ================================================ FILE: benchmarks/bench.03.jsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ local fibonacci(n) = if n <= 1 then 1 else fibonacci(n - 1) + fibonacci(n - 2); fibonacci(25) ================================================ FILE: benchmarks/bench.04.jsonnet ================================================ std.foldl(function(e, res) e + res, std.makeArray(20000, function(i) 'aaaaa'), '') ================================================ FILE: benchmarks/bench.06.jsonnet ================================================ // A benchmark for builtin sort local reverse = std.reverse; local sort = std.sort; true && std.assertEqual(std.range(1, 500), sort(std.range(1, 500))) && std.assertEqual(std.range(1, 1000), sort(std.range(1, 1000))) && std.assertEqual(reverse(std.range(1, 1000)), sort(std.range(1, 1000), keyF=function(x) -x)) && std.assertEqual(std.range(1, 1000), sort(reverse(std.range(1, 1000)))) && std.assertEqual(std.makeArray(2000, function(i) std.floor((i + 2) / 2)), sort(std.range(1, 1000) + reverse(std.range(1, 1000)))) ================================================ FILE: benchmarks/bench.07.jsonnet ================================================ local f2(f) = function(x) f(f(x)); local id(x) = x; local slowId = std.makeArray(20, function(i) if i == 0 then id else f2(slowId[i - 1])); slowId[15](42) ================================================ FILE: benchmarks/bench.08.jsonnet ================================================ local fibnext = { a: super.a + super.b, b: super.a, }; local fib(n) = if n == 0 then { a: 1, b: 1 } else fib(n - 1) + fibnext; fib(25) ================================================ FILE: benchmarks/bench.09.jsonnet ================================================ // This string must be longer than max stack frames local veryLongString = std.join('', std.repeat(['e'], 510)); std.assertEqual(std.stripChars(veryLongString + 'ok' + veryLongString, 'e'), 'ok') && std.assertEqual(std.lstripChars(veryLongString + 'ok', 'e'), 'ok') && std.assertEqual(std.rstripChars('ok' + veryLongString, 'e'), 'ok') && true ================================================ FILE: benchmarks/gen_big_object.jsonnet ================================================ local n = 2000; local objLocal(name, body) = 'local ' + name + ' = ' + body + ','; local objField(name, body) = name + ': ' + body + ','; local allLocals = std.makeArray(n, function(i) objLocal('l' + i, '1')); local allFields = std.makeArray(n, function(i) objField('f' + i, '2')); local indent = ' '; local indentAndSeparate(s) = indent + s + '\n'; local objContents = std.map(indentAndSeparate, allLocals + allFields); local objectBody = std.join('', objContents); '{\n' + objectBody + '}\n' ================================================ FILE: benchmarks/regen_benchmarks.sh ================================================ #!/usr/bin/env bash set -e set -x ../jsonnet -S gen_big_object.jsonnet > bench.05.gen.jsonnet for i in *.gen.jsonnet; do ../jsonnet fmt -i "$i" done ================================================ FILE: case_studies/fractal/.gitignore ================================================ *.packer.log *.packer.done *.packer.json terraform.done terraform.plan service.list *.tf service_account_key.json tilegen/mandelbrot credentials.libsonnet ================================================ FILE: case_studies/fractal/appserv/main.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Frontend for Fractal demo. Application server for a 3 tier web app. Submits fractal image generation requests. Manages metadata database. Renders pages with Jinja2. """ import datetime import json import os import sys import time import uuid import flask import httplib2 import jinja2 from cassandra.auth import PlainTextAuthProvider from cassandra.cluster import Cluster from cassandra.cluster import NoHostAvailable CONF = None for i in xrange(1, 60): try: with open('conf.json') as conf_file: CONF = json.load(conf_file) except IOError: time.sleep(1) if CONF == None: sys.stderr.write('ERROR: Could not open conf.json.') sys.exit(1) class DbError(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg session = None app = flask.Flask(__name__) app.config['PROPAGATE_EXCEPTIONS'] = True JINJA_ENVIRONMENT = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), extensions=['jinja2.ext.autoescape'], autoescape=True) def render_error(code, msg): return flask.make_response(my_render_template('error.html', code=code, msg=msg), code) def db_execute(s, t=()): global session if session == None: try: ap = PlainTextAuthProvider(username=CONF['database_user'], password=CONF['database_pass']) cluster = Cluster(CONF['db_endpoints'], auth_provider=ap) session = cluster.connect() session.set_keyspace(CONF['database_name']) return session.execute(s, t) except NoHostAvailable: session = None raise DbError("Unable to contact database backend.") else: try: return session.execute(s, t) except NoHostAvailable: session = None return db_execute(s, t) def my_render_template(filename, **context): context.update(CONF) return flask.render_template(filename, **context) def db_clear(): db_execute('TRUNCATE discoveries;') def db_partition(dt): #return "%04d-%02d-%02d" % (dt.year, dt.month, dt.day) return "FIXED" def db_remember(text, x, y, level): u = uuid.uuid1() dt = datetime.datetime.fromtimestamp((u.time - 0x01b21dd213814000L)*100/1e9) db_execute("INSERT INTO discoveries (Date, TimeId, X, Y, L, Text)" + " VALUES (%s, %s, %s, %s, %s, %s)", (db_partition(dt), u, x, y, level, text)) def db_list(): latest = [] # Keep executing calls until we find 10 or we reach 2013 day_counter = datetime.datetime.now() last_partition = None while len(latest) < 10 and day_counter >= datetime.datetime(2013, 1, 1): curr_partition = db_partition(day_counter) if last_partition != curr_partition: last_partition = curr_partition q = "SELECT * FROM discoveries WHERE Date = '%s' ORDER BY TimeId DESC limit 10;" rows = db_execute(q % curr_partition) for row in rows: u = row[1] dt = datetime.datetime.fromtimestamp((u.time - 0x01b21dd213814000L)*100/1e9) latest += [ {'text': row[3], 'x': row[4], 'y': row[5], 'l': row[2], 'ts': dt.isoformat()} ] day_counter -= datetime.timedelta(1) return latest @app.route("/") def default_handler(): return my_render_template('page.html') @app.route("/clear_db") def cleardb_handler(): try: db_clear() return 'Done' except DbError as dbe: return render_error(500, "Internal error: " + dbe.msg) @app.route("/remember", methods=['POST']) def remember_handler(): try: j = flask.request.json x = j['x'] y = j['y'] level = j['l'] text = j['text'][:50] if x < -2: x = -2 if x > 2: x = 2 if y < -2: y = -2 if y > 2: y = 2 if level < 0: level = 0 if level > 50: level = 50 db_remember(text, x, y, level) return 'Done' except DbError as dbe: return render_error(500, "Internal error: " + dbe.msg) @app.route("/remembered_list") def remembered_list_handler(): try: latest = db_list() return flask.jsonify(latest=latest) except DbError as dbe: return render_error(500, "Internal error: " + dbe.msg) @app.errorhandler(404) def error_404(error): return render_error(404, 'Nothing at this URL.') @app.errorhandler(500) def error_500(error): return render_error(500, 'Internal error') if __name__ == "__main__": app.run(host="0.0.0.0", debug=True) ================================================ FILE: case_studies/fractal/appserv/static/style.css ================================================ html { height: 100%; } body { font-family: sans-serif; height: 100%; margin: 0; padding: 0; background-color: #112; color: #ccc; font-family: Arial, Helvetica, sans-serif; } a { color: #fff; text-decoration: none; } a:hover { text-decoration: underline; } a:visited { text-decoration: none; } div#left { float: left; width: 350px; height: 100%; margin: 0; padding: 0; overflow: hidden; } p#banner { padding: 24px 30px 0px 30px; font-size: 36pt; font-weight: bold; margin: 0; text-align: center; } p#instructions { margin: 0; padding: 12px 16px 0 16px; text-align: justify; } div#discoveries { height: 100%; margin: 0; padding: 16px; } div#discoveries h1 { margin: 12px 0 0 0; font-size: 1.5em; } ul#remembered_list { list-style-type:none; padding: 0; margin: 0; } a.remembered { text-decoration: none; } li.remembered { overflow: hidden; margin: 4px; } img.remembered { float: left; border-width: 1px; border-style: solid; border-color: #444; margin-right: 16px; } p.remembered { margin: 0; padding: 8px; font-size: 11pt; word-wrap:break-word; width: 210px; float: left; } div.remembered_date { position: relative; } span.remembered_date { float: right; color: #444; font-size: 10pt; position: absolute; right: 0; top: 44px; } div#remember_plus { margin: 8px 4px; overflow: hidden; } div#remember_plus_text { float: left; border-style: solid; border-width: 1px; border-color: #444; margin-right: 16px; } p#remember_plus { margin: 8px; font-size: 32pt; text-align: center; } textarea#remember_plus_input { padding: 8px; font-size: 11pt; resize: none; background-color: inherit; color: #fff; border: none; font-family: inherit; width: 210px; } div#right { background-color: #002; height: 100%; max-height: 100%; margin: 0; padding: 0; overflow: hidden; } div#fimg_inside { padding: 0; margin: 0; overflow: hidden; position: relative; } img.fimg { float: left; padding: 0; margin: 0; /* outline: 1px solid #f00; outline-offset: -1px; */ } div#err_box { padding: 16px; } ================================================ FILE: case_studies/fractal/appserv/templates/base.html ================================================ Mandelbrot Viewer

{% block instructions %} {% endblock %}

{% block discoveries %} {% endblock %}
================================================ FILE: case_studies/fractal/appserv/templates/error.html ================================================ {% extends "base.html" %} {% block page %}

ERROR: {{ code }}

{{ msg }}

{% endblock %} ================================================ FILE: case_studies/fractal/appserv/templates/page.html ================================================ {% extends 'base.html' %} {% block instructions %} The Mandelbrot set is a beautiful fractal popularized by Benoit Mandelbrot. Explore it by clicking to zoom in, and shift+clicking to zoom out. Take a look at other peoples' discoveries below, or add your own! {% endblock %} {% block discoveries %}

Recent Discoveries

{% endblock %} {% block page %}
{% endblock %} ================================================ FILE: case_studies/fractal/credentials.libsonnet.TEMPLATE ================================================ { project: "XXXXXXXX", // GCP project name (e.g. verbing-noun-123) cassandraUserPass: "XXXXXXXX", // Any valid Cassandra password (e.g. numbers, letters, capitals) cassandraRootPass: "XXXXXXXX", // Any other valid Cassandra password (e.g. numbers, letters, capitals) dnsPrefix: "", // Change to differentiate one deployment of Fractal from another. } ================================================ FILE: case_studies/fractal/lib/cassandra.libsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ local packer = import "packer.libsonnet"; { local cassandra = self, // The default Cassandra configuration. Override what you want to change. conf:: { authenticator: "AllowAllAuthenticator", authorizer: "AllowAllAuthorizer", auto_snapshot: true, batch_size_warn_threshold_in_kb: 5, batchlog_replay_throttle_in_kb: 1024, cas_contention_timeout_in_ms: 1000, client_encryption_options: { enabled: false, keystore: "conf/.keystore", keystore_password: "cassandra", }, cluster_name: "Unnamed Cluster", column_index_size_in_kb: 64, commit_failure_policy: "stop", commitlog_directory: "/var/lib/cassandra/commitlog", commitlog_segment_size_in_mb: 32, commitlog_sync: "periodic", commitlog_sync_period_in_ms: 10000, compaction_throughput_mb_per_sec: 16, concurrent_counter_writes: 32, concurrent_reads: 32, concurrent_writes: 32, counter_cache_save_period: 7200, counter_cache_size_in_mb: null, counter_write_request_timeout_in_ms: 5000, cross_node_timeout: false, data_file_directories: ["/var/lib/cassandra/data"], disk_failure_policy: "stop", dynamic_snitch_badness_threshold: 0.1, dynamic_snitch_reset_interval_in_ms: 600000, dynamic_snitch_update_interval_in_ms: 100, endpoint_snitch: "SimpleSnitch", hinted_handoff_enabled: true, hinted_handoff_throttle_in_kb: 1024, incremental_backups: false, index_summary_capacity_in_mb: null, index_summary_resize_interval_in_minutes: 60, inter_dc_tcp_nodelay: false, internode_compression: "all", key_cache_save_period: 14400, key_cache_size_in_mb: null, listen_address: "localhost", max_hint_window_in_ms: 10800000, max_hints_delivery_threads: 2, memtable_allocation_type: "heap_buffers", native_transport_port: 9042, num_tokens: 256, partitioner: "org.apache.cassandra.dht.Murmur3Partitioner", permissions_validity_in_ms: 2000, range_request_timeout_in_ms: 10000, read_request_timeout_in_ms: 5000, request_scheduler: "org.apache.cassandra.scheduler.NoScheduler", request_timeout_in_ms: 10000, row_cache_save_period: 0, row_cache_size_in_mb: 0, rpc_address: "localhost", rpc_keepalive: true, rpc_port: 9160, rpc_server_type: "sync", saved_caches_directory: "/var/lib/cassandra/saved_caches", seed_provider: [ { class_name: "org.apache.cassandra.locator.SimpleSeedProvider", parameters: [{ seeds: "127.0.0.1" }], }, ], server_encryption_options: { internode_encryption: "none", keystore: "conf/.keystore", keystore_password: "cassandra", truststore: "conf/.truststore", truststore_password: "cassandra", }, snapshot_before_compaction: false, ssl_storage_port: 7001, sstable_preemptive_open_interval_in_mb: 50, start_native_transport: true, start_rpc: true, storage_port: 7000, thrift_framed_transport_size_in_mb: 15, tombstone_failure_threshold: 100000, tombstone_warn_threshold: 1000, trickle_fsync: false, trickle_fsync_interval_in_kb: 10240, truncate_request_timeout_in_ms: 60000, write_request_timeout_in_ms: 2000, }, // Some firewall resources to open up Cassandra ports. GcpFirewall:: { cassandraTag:: "cassandra-server", source_ranges: ["0.0.0.0/0"], network: error "cassandra.GcpFirewall must have field: network", allow: { protocol: "tcp", ports: ["9042", "9160"] }, // From the Internet to these ports. target_tags: [self.cassandraTag], }, GcpFirewallGossip:: { cassandraTag:: "cassandra-server", source_ranges: ["0.0.0.0/0"], network: error "cassandra.GcpFirewallGossip must have field: network", allow: { protocol: "tcp", ports: ["7000", "7001", "7199"] }, // From these machines amongst themselves. source_tags: [self.cassandraTag], target_tags: [self.cassandraTag], }, // Sets the root password to something, while the server is listening only on localhost. GcpDebianImage: packer.GcpDebianImage { local image = self, rootPassword:: error "GcpDebianCassandraPrimedImage: must have field: rootPassword", clusterName:: error "GcpDebianCassandraPrimedImage: must have field: clusterName", aptKeyUrls+: ["https://www.apache.org/dist/cassandra/KEYS"], aptRepoLines+: { cassandra: "deb https://www.apache.org/dist/cassandra/debian 21x main", }, aptPackages+: ["cassandra"], conf:: cassandra.conf { authenticator: "PasswordAuthenticator", cluster_name: image.clusterName, }, provisioners+: [ packer.RootShell { inline: [ // Shut it down "/etc/init.d/cassandra stop", // Remove junk from unconfigured startup "rm -rfv /var/lib/cassandra/*", // Enable authentication local dest = "/etc/cassandra/cassandra.yaml"; "echo %s > %s" % [std.escapeStringBash("" + image.conf), dest], // Start it up again "/etc/init.d/cassandra start", // Wait for it to be ready cassandra.waitForCqlsh("cassandra", "cassandra", "localhost"), // Set root password local cql = "ALTER USER cassandra WITH PASSWORD '%s';" % image.rootPassword; "echo %s | cqlsh -u cassandra -p cassandra" % std.escapeStringBash(cql), ], }, ], }, // A line of bash that will wait for a Cassandra service to be "up". waitForCqlsh(user, pass, host):: "while ! echo show version | cqlsh -u %s -p %s %s ; do sleep 1; done" % [user, pass, host], // A line of bash that will wait for the given port to accept connections. waitForSeed(host, port):: "while ! nc %s %d < /dev/null; do sleep 1; done" % [host, port], // This mixin is intended to be used on a Google Cloud Platform Instance based on the above // GcpDebianImage. It adds commands to the startup script that bootstrap Cassandra using a // given CQL script and configuration. GcpStarterMixin: { startup_script+: [ // Wait for the misconfigured cassandra to start up. cassandra.waitForCqlsh("cassandra", self.rootPass, "localhost"), // Set up system_auth replication level "echo %s | cqlsh -u cassandra -p %s localhost" % [std.escapeStringBash("ALTER KEYSPACE system_auth WITH REPLICATION = %s;" % self.authReplication), self.rootPass], // Drop in the correct configuration. "echo %s > %s" % [std.escapeStringBash("" + self.conf), "/etc/cassandra/cassandra.yaml"], // Restart with new configuration. "/etc/init.d/cassandra restart", // Wait for the properly configured cassandra to start up and reach quorum. cassandra.waitForCqlsh("cassandra", self.rootPass, "$HOSTNAME"), // Set up users, empty tables, etc. "echo %s | cqlsh -u cassandra -p %s $HOSTNAME" % [std.escapeStringBash(std.lines(self.initCql)), self.rootPass], ], }, // This mixin is intended to be used on a Google Cloud Platform Instance based on the above // GcpDebianImage. It adds commands to the startup script that bootstrap Cassandra by wiping // its slate clean and allowing it to join an existing cluster. GcpTopUpMixin: { startup_script+: [ // Wait for the misconfigured cassandra to start up. cassandra.waitForCqlsh("cassandra", self.rootPass, "localhost"), // Kill it. "/etc/init.d/cassandra stop", // Clean up the mess it caused due to being misconfigured. "rm -rf /var/lib/cassandra/*", // Drop in the correct configuration. "echo %s > %s" % [std.escapeStringBash("" + self.conf), "/etc/cassandra/cassandra.yaml"], // Start it up again. "/etc/init.d/cassandra start", ], }, } ================================================ FILE: case_studies/fractal/lib/packer.libsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ { local packer = self, // A Packer shell provisioner to run something as root. RootShell:: { type: "shell", execute_command: "{{ .Vars }} sudo -E /bin/bash '{{ .Path }}'", }, // A Packer shell provisioner to run something as the current user. Shell:: { type: "shell", }, // A Packer file provisioner. File:: { type: "file", destination: error "File requires field: destination", source: error "File requires field: source", }, // A Packer provisioner that creates a file from inline content, with the given permissions. EnsureFile:: packer.RootShell { content:: error "EnsureFile provisioner must have field content", destination:: error "EnsureFile provisioner must have field destination", permissions:: "644", user:: "root", group:: self.user, environment_vars: [ // TODO(dcunnin): escaping bash is a workaround for Packer #1733 "PACKER_EXPLICIT_FILE_CONTENT='%s'" % std.escapeStringBash(self.content), "PACKER_EXPLICIT_FILE=" + self.destination, ], inline: [ "mkdir -v -p \"$(dirname \"$PACKER_EXPLICIT_FILE\")\"", "echo \"Creating file: $PACKER_EXPLICIT_FILE\"", "echo -n \"$PACKER_EXPLICIT_FILE_CONTENT\" > \"$PACKER_EXPLICIT_FILE\"", "chmod -v %s \"$PACKER_EXPLICIT_FILE\"" % [self.permissions], "chown -v %s.%s \"$PACKER_EXPLICIT_FILE\"" % [self.user, self.group], ], }, // A Packer provisioner that creates a directory with given permissions. EnsureDir:: packer.RootShell { dir:: error "EnsureDir provisioner must have field dir", permissions:: "775", user:: "root", group:: self.user, environment_vars: [ "PACKER_DIR=" + self.dir, ], inline: [ "mkdir -pv \"$PACKER_DIR\"", "chmod -v %s \"$PACKER_DIR\"" % [self.permissions], "chown -v %s.%s \"$PACKER_DIR\"" % [self.user, self.group], ], }, // A Packer provisioner that creates a symlink. EnsureSymLink:: packer.RootShell { from:: error "EnsureSymLink provisioner must have field: from", to:: error "EnsureSymLink provisioner must have field: to", environment_vars: [ "PACKER_SYM_FROM=" + self.from, "PACKER_SYM_TO=" + self.to, ], inline: [ "ln -sfv \"$PACKER_SYM_FROM\" \"$PACKER_SYM_TO\"", ], }, // A Packer provisioner to install Python packages via Pip. Pip must already be installed. The // packages are given as an array of strings. Pip:: packer.RootShell { packages:: error "Pip provisioner must have field: packages", inline: ["pip install " + std.join(" ", self.packages)], }, // A Packer provisioner to install Apt packages. This provisioner can be configured with // additional keys and repositories. The packages are given as an array of strings. Apt:: packer.RootShell { packages:: error "Apt provisioner must have field: packages", keyUrls:: [], // { foo: "..." } will add a foo.list containing the given content. repoLines:: {}, // { foo: "..." } will add a foo.list fetched from the given URL. repoUrls:: {}, keyCommands:: ["curl --silent %s | apt-key add -" % [url] for url in self.keyUrls], local dir = "/etc/apt/sources.list.d", local repoLineCommands = ["echo \"%s\" > %s/%s.list" % [self.repoLines[k], dir, k] for k in std.objectFields(self.repoLines)], local repoUrlCommands = ["curl -o %s/%s.list %s" % [dir, k, self.repoUrls[k]] for k in std.objectFields(self.repoUrls)], repoCommands:: repoLineCommands + repoUrlCommands, local opts = "-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold", installCommands:: ["apt-get -qq -y %s install %s" % [opts, std.join(" ", self.packages)]], environment_vars: ["DEBIAN_FRONTEND=noninteractive"], inline: self.repoCommands + self.keyCommands + ["apt-get -qq -y update"] + self.installCommands, }, // TODO(dcunnin): yum /* # RHEL and CentOS. rpm -Uvh --quiet "RPM_URL" yum makecache yum -q -y install package1 package2 ... */ // TODO(dcunnin): zypper /* zypper refresh zypper --non-interactive --quiet install package1 package2 ... */ // A template for building an image on Google Cloud Platform. This brings out build attributes // to the top level and provides defaults for others. GcpImage:: { // Override these name:: error "GcpImage must have field: name", description: "Packer GcpImage: " + self.name, source_image:: error "GcpImage must have field: name", project_id:: error "GcpImage must have field: project_id", account_file:: error "GcpImage must have field: account_file", sshUsername:: error "GcpImage must have field: sshUsername", local img = self, builder:: { type: "googlecompute", name: img.name, image_name: "%s" % [self.name], image_description: "GCP builder for " + self.name, // Project & authentication project_id: img.project_id, account_file: img.account_file, // Instance mechanics machine_type: "n1-standard-1", // Multicore probably doesn't provide any benefit source_image: img.source_image, instance_name: "packer-" + self.name, zone: "us-central1-a", ssh_username: img.sshUsername, [if std.objectHas(img, "disk_size") then "disk_size"]: img.disk_size, }, builders: [self.builder], provisioners: [], }, // A template for building a Debian image on Google Cloud Platform. This allows specifying apt // and pip attributes at the top-level and automatically provisions the desired packages. GcpDebianImage:: packer.GcpImage { local image = self, source_image: "backports-debian-7-wheezy-v20141017", local pip_pkgs = self.aptPackages + if std.length(self.pipPackages) == 0 then [] else ["python-pip"], aptPackages:: [], aptKeyUrls:: [], aptRepoLines:: {}, aptRepoUrls:: {}, pipPackages:: [], local apt_provisioners = if std.length(pip_pkgs) == 0 && std.length(self.aptKeyUrls) == 0 && std.length(self.aptRepos) == 0 then [ ] else [ packer.Apt { packages: pip_pkgs, keyUrls: image.aptKeyUrls, repoLines: image.aptRepoLines, repoUrls: image.aptRepoUrls, }, ], local pip_provisioners = if std.length(self.pipPackages) == 0 then [ ] else [ packer.Pip { packages: image.pipPackages }, ], provisioners+: apt_provisioners + pip_provisioners, }, // A template for building Nginx/uwsgi/flask based application servers. The uwsgi configuration // is provided at the top level and is automatically compiler to INI format. A simple cron line // is used to start the uwsgi emperor at boot. The given Flask module must exist in /var/www, // so extend this template with additional provisioners to create that content. GcpDebianNginxUwsgiFlaskImage:: packer.GcpDebianImage { local image = self, module:: error "NginxUwsgiFlaskImage must have field: module", application:: "app", port:: 80, uwsgiSocket:: "/var/www/uwsgi.sock", aptPackages+: ["nginx", "python-dev"], pipPackages+: ["flask", "uwsgi"], uwsgiConf:: { chdir: "/var/www", base: "/var/www", module: image.module, pythonpath: "/var/www", socket: image.uwsgiSocket, "chmod-socket": "644", callable: image.application, logto: "/var/log/uwsgi/uwsgi.log", }, nginxConf:: [ "server {", " listen %d;" % image.port, " server_name localhost;", " charset utf-8;", " client_max_body_size 75M;", " location / { try_files $uri @yourapplication; }", " location @yourapplication {", " include uwsgi_params;", " uwsgi_pass unix:%s;" % image.uwsgiSocket, " }", "}", ], provisioners+: [ packer.RootShell { inline: ["rm /etc/nginx/sites-enabled/default"] }, packer.EnsureFile { destination: "/etc/nginx/conf.d/frontend_nginx.conf", content: std.lines(image.nginxConf), }, packer.EnsureFile { destination: "/etc/uwsgi/vassals/uwsgi.ini", content: std.manifestIni({ sections: { uwsgi: image.uwsgiConf, }, }), }, packer.EnsureFile { destination: "/etc/cron.d/emperor", content: "@reboot root /usr/local/bin/uwsgi --master --emperor /etc/uwsgi/vassals " + "--daemonize /var/log/uwsgi/emperor.log --pidfile /var/run/uwsgi.pid " + "--die-on-term --uid www-data --gid www-data\n", }, packer.EnsureDir { dir: "/var/log/uwsgi", user: "www-data" }, packer.EnsureDir { dir: "/var/www", user: "www-data" }, ], }, // A template to help build PostgreSQL images (experimental). GcpDebianPostgresqlImage: packer.GcpDebianImage { local image = self, rootPassword:: error "GcpDebianPostgresqlImage: must have field: rootPassword", aptPackages+: ["postgresql", "postgresql-contrib"], initSql:: [ "ALTER USER POSTGRES WITH PASSWORD '%s';" % image.rootPassword, ], provisioners+: [ packer.RootShell { inline: [ "echo %s | sudo -u postgres psql" % std.escapeStringBash(std.lines(image.initSql)), ] }, packer.EnsureFile { destination: "/etc/postgresql/9.1/main/postgresql.conf", content: std.lines([ "data_directory = '/var/lib/postgresql/9.1/main'", "hba_file = '/etc/postgresql/9.1/main/pg_hba.conf'", "ident_file = '/etc/postgresql/9.1/main/pg_ident.conf'", "external_pid_file = '/var/run/postgresql/9.1-main.pid'", "listen_addresses = '*'", "port = 5432", "max_connections = 100", "unix_socket_directory = '/var/run/postgresql'", "ssl = true", "shared_buffers = 32MB", "log_line_prefix = '%t '", "datestyle = 'iso, mdy'", "lc_messages = 'en_US.UTF-8'", "lc_monetary = 'en_US.UTF-8'", "lc_numeric = 'en_US.UTF-8'", "lc_time = 'en_US.UTF-8'", "default_text_search_config = 'pg_catalog.english'", ]), }, packer.EnsureFile { destination: "/etc/postgresql/9.1/main/pg_hba.conf", content: std.lines([ "local all all md5", "host all all 255.255.255.255/0 md5", "host all all ::1/128 md5", ]), }, ], }, // A template to help build MySQL images (experimental). GcpDebianMysqlImage: packer.GcpDebianImage { local image = self, rootPassword:: error "GcpDebianMysqlImage: must have field: rootPassword", aptPackages+: ["mysql-server"], initSql:: [], provisioners+: [ packer.RootShell { inline: [ "mysqladmin -u root password '%s'" % std.escapeStringBash(image.rootPassword), "echo %s | mysql -u root --password=%s" % [std.escapeStringBash(std.lines(image.initSql)), std.escapeStringBash(image.rootPassword)], ] }, packer.EnsureFile { destination: "/etc/mysql/conf.d/local.cnf", content: std.manifestIni({ sections: { mysqld: { "bind-address": "0.0.0.0", }, }, }), }, ], }, } ================================================ FILE: case_studies/fractal/lib/terraform.libsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ { local terraform = self, // Basic template for a GCP instance, adds default service account scopes, and factors out a few // things into top-level fields. GcpInstance:: { local instance = self, name: error "Instance must have field: name", image:: error "Instance must have field: image", address:: null, scopes:: ["devstorage.read_only", "logging.write"], service_account: [ { scopes: ["https://www.googleapis.com/auth/" + s for s in instance.scopes], }, ], machine_type: "f1-micro", zone: "us-central1-f", tags: ["terraform"], disk: { image: instance.image, }, startup_script:: [], addFile(v, dest):: "echo %s > %s" % [std.escapeStringBash(v), std.escapeStringBash(dest)], metadata: { "startup-script": std.lines(instance.startup_script), }, network_interface: [{ network: "default", access_config: if instance.address != null then [ { nat_ip: instance.address }, ] else [ {}, ], }], }, // Allow http for servers tagged with "http-server" on the given network. GcpFirewallHttp:: { local fw = self, source_ranges: ["0.0.0.0/0"], port:: 80, network: error "GcpFirewallHttp must have field: network", allow: [{ protocol: "tcp", ports: [std.toString(fw.port)] }], target_tags: ["http-server"], }, // Allow ssh for all servers on the given network. GcpFirewallSsh:: { source_ranges: ["0.0.0.0/0"], network: error "GcpFirewallSsh must have field: network", allow: [{ protocol: "tcp", ports: ["22"] }], }, } ================================================ FILE: case_studies/fractal/service.jsonnet ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Import some libraries local packer = import "packer.libsonnet"; local terraform = import "terraform.libsonnet"; local cassandra = import "cassandra.libsonnet"; // Credentials file (don't commit this!) local credentials = import "credentials.libsonnet"; function(ssh_username) { /////////////////////////// // GENERAL CONFIGURATION // /////////////////////////// local tilegenPort = 8080, local cassandraUser = "fractal", // Password in credentials import. local cassandraKeyspace = "fractal", local cassandraReplication = "{ 'class' : 'SimpleStrategy', 'replication_factor' : 2 }", // These are the nodes the application server attempts to use (client-side load balancing). local cassandraNodes = ["db1", "db2", "db3", "db4", "db5"], // The Cassandra configuration file we use for this service. local cassandraConf = cassandra.conf { cluster_name: "Fractal Cluster", rpc_address:: null, // Unset by making it hidden (::). listen_address:: null, // Unset by making it hidden (::). authenticator: "PasswordAuthenticator", seed_provider: [ { class_name: "org.apache.cassandra.locator.SimpleSeedProvider", parameters: [{ seeds: std.join(", ", cassandraNodes) }], }, ], }, // Create an initial (empty) database for storing 'discovered' fractal coordinates. local cql_insert(uuid, x, y, l, n) = "INSERT INTO discoveries (Date, TimeId, X, Y, L, Text) " + ("VALUES ('FIXED', %s, %s, %s, %s, '%s');" % [uuid, x, y, l, n]), local cassandraInitCql = [ "CREATE USER %s WITH PASSWORD '%s';" % [cassandraUser, credentials.cassandraUserPass], "CREATE KEYSPACE %s WITH REPLICATION = %s;" % [cassandraKeyspace, cassandraReplication], "USE %s;" % cassandraKeyspace, "CREATE TABLE discoveries(" + "Date TEXT, TimeId TIMEUUID, Text TEXT, X FLOAT, Y FLOAT, L INT, " + "PRIMARY KEY(Date, TimeId));", cql_insert("18063880-5a4d-11e4-ada4-247703d0f194", "0", "0", "0", "Zoomed Out"), cql_insert("66b6d100-5a53-11e4-aa05-247703d0f194", "-1.21142578125", "0.3212890625", "4", "Lightning"), cql_insert("77ffdd80-5a53-11e4-8ccf-247703d0f194", "-1.7568359375", "-0.0009765625", "5", "Self-similarity"), cql_insert("7fbf8200-5a53-11e4-804a-247703d0f194", "0.342529296875", "0.419189453125", "5", "Windmills"), cql_insert("9ae7bd00-5a66-11e4-9c66-247703d0f194", "-1.48309979046093", "0.00310595797955671", "39", "Star"), cql_insert("75fe4480-5a7c-11e4-a747-247703d0f194", "-0.244976043701172", "0.716987609863281", "10", "Baroque"), cql_insert("abf70380-5b24-11e4-8a46-247703d0f194", "-1.74749755859375", "0.009002685546875", "9", "Hairy windmills"), ], // Configuration shared by the application server and tile generation nodes. local ApplicationConf = { width: 256, height: 256, thumb_width: 64, thumb_height: 64, iters: 200, database: cassandraKeyspace, tilegen: "${google_compute_address.tilegen.address}:%d" % tilegenPort, db_endpoints: cassandraNodes, }, /////////////////////////// // PACKER CONFIGURATIONS // /////////////////////////// // Some config used in every Packer image we create. local ImageMixin = { project_id: credentials.project, account_file: "service_account_key.json", // For debugging: local network_debug = ["traceroute", "lsof", "iptraf", "tcpdump", "host", "dnsutils"], aptPackages+: ["vim", "git", "psmisc", "screen", "strace"] + network_debug, sshUsername: ssh_username, }, // Frontend image. "appserv.packer.json": packer.GcpDebianNginxUwsgiFlaskImage + ImageMixin { name: "appserv-v20150430-2145", module: "main", // Entrypoint in the Python code. pipPackages+: ["httplib2", "cassandra-driver", "blist"], uwsgiConf+: { lazy: "true" }, // cassandra-driver does not survive fork() // Copy website content and code. provisioners+: [ packer.File { source: "appserv", destination: "/tmp/", }, packer.RootShell { inline: [ "mv /tmp/appserv/* /var/www/", "chown -R www-data.www-data /var/www/*", ] }, ], }, // The Cassandra image is basic, but more configuration is done at deployment time. "cassandra.packer.json": cassandra.GcpDebianImage + ImageMixin { name: "cassandra-v20150430-2145", rootPassword: credentials.cassandraRootPass, clusterName: cassandraConf.cluster_name, }, // Tile Generation node runs a C++ program to generate PNG tiles for the fractal. "tilegen.packer.json": packer.GcpDebianNginxUwsgiFlaskImage + ImageMixin { name: "tilegen-v20150430-2145", module: "mandelbrot_service", aptPackages+: ["g++", "libpng-dev"], port: tilegenPort, // Copy the flask handlers and also build the C++ executable. provisioners+: [ packer.File { source: "tilegen", destination: "/tmp/", }, packer.RootShell { inline: [ "mv /tmp/tilegen/* /var/www/", "chown -R www-data.www-data /var/www/*", ] }, packer.RootShell { inline: [ "g++ -Wall -Wextra -ansi -pedantic -O3 -ffast-math -g " + "/var/www/mandelbrot.cpp -lpng -o /var/www/mandelbrot", ] }, ], }, ///////////////////////////// // TERRAFORM CONFIGURATION // ///////////////////////////// "terraform.tf": { // How to contact the Google Cloud Platform APIs. provider: { google: { account_file: "service_account_key.json", project: credentials.project, region: "us-central1", }, }, // The deployed resources. resource: { // Instances are assigned zones on a round robin scheme. local zone(hash) = local arr = [ "us-central1-c", "us-central1-b", "us-central1-f", ]; arr[hash % std.length(arr)], // The internal subnet. google_compute_network: { fractal: { name: "fractal", ipv4_range: "10.0.0.0/16", }, }, // Publicly visible static ip addresses. google_compute_address: { appserv: { name: "appserv" }, tilegen: { name: "tilegen" }, }, // The next 3 resource types configure load balancing for the appserv and tilegen. google_compute_http_health_check: { appserv: { name: "appserv", port: 80, }, tilegen: { name: "tilegen", port: tilegenPort, }, }, google_compute_target_pool: { appserv: { name: "appserv", health_checks: ["${google_compute_http_health_check.appserv.name}"], instances: ["%s/appserv%d" % [zone(k), k] for k in [1, 2, 3]], }, tilegen: { name: "tilegen", health_checks: ["${google_compute_http_health_check.tilegen.name}"], instances: ["%s/tilegen%d" % [zone(k), k] for k in [1, 2, 3, 4, 5]], }, }, google_compute_forwarding_rule: { appserv: { ip_address: "${google_compute_address.appserv.address}", name: "appserv", target: "${google_compute_target_pool.appserv.self_link}", port_range: "80", }, tilegen: { ip_address: "${google_compute_address.tilegen.address}", name: "tilegen", target: "${google_compute_target_pool.tilegen.self_link}", port_range: tilegenPort, }, }, // Open ports for the various services, to instances (identified by tags) google_compute_firewall: { local NetworkMixin = { network: "${google_compute_network.fractal.name}" }, ssh: terraform.GcpFirewallSsh + NetworkMixin { name: "ssh" }, appserv: terraform.GcpFirewallHttp + NetworkMixin { name: "appserv" }, tilegen: terraform.GcpFirewallHttp + NetworkMixin { name: "tilegen", port: tilegenPort }, cassandra: cassandra.GcpFirewall + NetworkMixin { name: "cassandra" }, gossip: cassandra.GcpFirewallGossip + NetworkMixin { name: "gossip" }, }, // All our instances share this configuration. local FractalInstance(zone_hash) = terraform.GcpInstance { network_interface: [super.network_interface[0] { network: "${google_compute_network.fractal.name}" }], tags+: ["fractal"], zone: zone(zone_hash), scopes+: ["devstorage.full_control"], }, // The various kinds of Cassandra instances all share this basic configuration. local CassandraInstance(zone_hash) = FractalInstance(zone_hash) { image: "cassandra-v20150430-2145", machine_type: "n1-standard-1", tags+: ["fractal-db", "cassandra-server"], user:: cassandraUser, userPass:: credentials.cassandraUserPass, rootPass:: credentials.cassandraRootPass, conf:: cassandraConf, }, google_compute_instance: { // The application server instances have database credentials and the address of the // tilegen loadbalancer. This is all stored in a conf.json, read by the python // code. ["appserv" + k]: FractalInstance(k) { name: "appserv" + k, image: "appserv-v20150430-2145", conf:: ApplicationConf { database_name: cassandraKeyspace, database_user: cassandraUser, database_pass: credentials.cassandraUserPass, }, tags+: ["fractal-appserv", "http-server"], startup_script+: [self.addFile(self.conf, "/var/www/conf.json")], } for k in [1, 2, 3] } + { // Bootstrapping the Cassandra database is a little subtle. We bring up 3 nodes // in parallel, one of which is special and creates the initial database. The other // two join it. Note the two different mixins used to control that behavior. db1: CassandraInstance(1) + cassandra.GcpStarterMixin { name: "db1", // Replication of the system_auth table (user credentials). authReplication:: cassandraReplication, // The CQL code is used to bootstrap the database. initCql:: cassandraInitCql, }, // TopUpMixin creates an empty Cassandra node which can join an existing cluster. db2: CassandraInstance(2) + cassandra.GcpTopUpMixin { name: "db2", }, db3: CassandraInstance(3) + cassandra.GcpTopUpMixin { name: "db3", }, // To increase the size of the cluster, these can be used. Bring them up one by one // verifying the state of the database each time by logging onto an existing db node // and running "nodetool status fractal". To reduce the size of the cluster, use // nodetool -h $HOST decommission and then remove the node from this configuration. // If a node is removed without decommissioning, fix the cluster with nodetool // removenode . It is possible to completely recycle the nodes (including the // first node) without data loss as long as nodetool is used judiciously. /* db4: CassandraInstance(4) + cassandra.GcpTopUpMixin { name: "db4", }, */ } + { // The tile generation instances are similar to the application server ones, but do // not require database credentials so these are omitted for security. ["tilegen" + k]: FractalInstance(k) { name: "tilegen" + k, image: "tilegen-v20150430-2145", tags+: ["fractal-tilegen", "http-server"], startup_script+: [self.addFile(ApplicationConf, "/var/www/conf.json")], } for k in [1, 2, 3, 4] }, google_dns_managed_zone: { "fractaldemo-com": { name: "fractaldemo-com", dns_name: credentials.dnsPrefix + "fractaldemo.com.", description: "Fractal Demo DNS Zone", }, }, local instances = self.google_compute_instance, local addresses = self.google_compute_address, local DnsRecord = { managed_zone: "${google_dns_managed_zone.fractaldemo-com.name}", type: "A", ttl: 300, }, google_dns_record_set: { [name]: DnsRecord { name: name + ".${google_dns_managed_zone.fractaldemo-com.dns_name}", rrdatas: ["${google_compute_address." + name + ".address}"], } for name in std.objectFields(addresses) } + { [name]: DnsRecord { name: name + ".${google_dns_managed_zone.fractaldemo-com.dns_name}", rrdatas: ["${google_compute_instance." + name + ".network_interface.0.access_config.0.nat_ip}"], } for name in std.objectFields(instances) } + { www: { managed_zone: "${google_dns_managed_zone.fractaldemo-com.name}", name: "www.${google_dns_managed_zone.fractaldemo-com.dns_name}", type: "CNAME", ttl: 300, rrdatas: ["appserv.${google_dns_managed_zone.fractaldemo-com.dns_name}"], }, zone: { managed_zone: "${google_dns_managed_zone.fractaldemo-com.name}", name: "${google_dns_managed_zone.fractaldemo-com.dns_name}", type: "A", ttl: 300, rrdatas: ["${google_compute_address.appserv.address}"], }, }, }, output: { frontend: { value: "${google_compute_address.appserv.address}" }, }, }, // terraform.tf } ================================================ FILE: case_studies/fractal/tilegen/mandelbrot.cpp ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include #include #include #include #include #include int main(int argc, const char **argv) { if (argc != 8) { std::cerr <<"Usage: " << std::endl; std::cerr <<"PNG file is written to stdout." << std::endl; exit(EXIT_FAILURE); } unsigned long width = strtoul(argv[1], NULL, 10); unsigned long height = strtoul(argv[2], NULL, 10); unsigned long iterations = strtoul(argv[3], NULL, 10); double left = strtod(argv[4], NULL); double bottom = strtod(argv[5], NULL); double right = strtod(argv[6], NULL); double top = strtod(argv[7], NULL); struct Exception { const std::string &msg; Exception(const std::string &msg) : msg(msg) { } }; int code = EXIT_SUCCESS; png_structp png_write_struct = NULL; volatile png_infop info_ptr = NULL; try { png_write_struct = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_write_struct == NULL) throw Exception("Could not allocate libpng write_struct."); info_ptr = png_create_info_struct(png_write_struct); if (info_ptr == NULL) throw Exception("Could not allocate libpng info_struct."); if (setjmp(png_jmpbuf(png_write_struct))) throw Exception("Exception from libpng"); png_init_io(png_write_struct, stdout); png_set_IHDR(png_write_struct, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_write_struct, info_ptr); std::vector row(3 * width); for (unsigned long y=0 ; y 4) { // escaped // smooth with fractional escape velocity float escape_vel = i + 1 - ::log2f(::logf(::sqrtf(len2))); float tonemapped = escape_vel / (20 + escape_vel); row[3*x + 0] = png_byte(powf(tonemapped, 10) * 255); row[3*x + 1] = png_byte(powf(tonemapped, 2.5) * 255); row[3*x + 2] = png_byte(tonemapped * 255); /* row[3*x + 0] = png_byte(escape_vel); row[3*x + 1] = png_byte(escape_vel); row[3*x + 2] = png_byte(escape_vel); */ break; } const double o_x = c_x; c_x = c_x*c_x - c_y*c_y + mb_x; c_y = 2 * o_x * c_y + mb_y; } } png_write_row(png_write_struct, &row[0]); } png_write_end(png_write_struct, NULL); } catch (const Exception &e) { std::cerr << e.msg << std::endl; code = EXIT_FAILURE; } if (png_write_struct != NULL) png_destroy_write_struct(&png_write_struct, (png_infopp)NULL); if (info_ptr != NULL) png_free_data(png_write_struct, info_ptr, PNG_FREE_ALL, -1); return code; } ================================================ FILE: case_studies/fractal/tilegen/mandelbrot_service.py ================================================ #!/usr/bin/python # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Serve fractal images over HTTP by calling out to mandelbrot. """ import json import math import os import subprocess import sys import time import flask app = flask.Flask(__name__) CONF = None for i in xrange(1, 60): try: with open('conf.json') as conf_file: CONF = json.load(conf_file) except IOError: time.sleep(1) if CONF == None: sys.stderr.write('ERROR: Could not open conf.json.') sys.exit(1) @app.route("/") def handle_fractal(): """Get fractal coordinates from query string, call mandelbrot to generate image. Returns: The image, wrapped in an HTML response. """ level = int(flask.request.args.get("l", "0")) x = float(int(flask.request.args.get("x", "0"))) y = float(int(flask.request.args.get("y", "0"))) if level < 0: level = 0 grid_size = math.pow(2, level) x0 = "%.30g" % ((x - 0) / grid_size) y0 = "%.30g" % ((y - 0) / grid_size) x1 = "%.30g" % ((x + 1) / grid_size) y1 = "%.30g" % ((y + 1) / grid_size) print "Tile: %s %s %s %s" % (x0, y0, x1, y1) width = str(CONF['width']) height = str(CONF['height']) iters = str(CONF['iters']) cmd = ['./mandelbrot', width, height, iters, x0, y0, x1, y1] image_data = subprocess.check_output(cmd) response = flask.make_response(image_data) response.headers["Content-Type"] = "image/png" response.headers["cache-control"] = "public, max-age=600" return response @app.route("/thumb") def handle_thumb(): """Get fractal coordinates from query string, call mandelbrot to generate image. Returns: The image, wrapped in an HTML response. """ level = int(flask.request.args.get("l", "0")) x = float(flask.request.args.get("x", "0")) y = float(flask.request.args.get("y", "0")) if level < 0: level = 0 grid_size = math.pow(2, -level) x0 = "%.30g" % (x - 1.5*grid_size) y0 = "%.30g" % (y - 1.5*grid_size) x1 = "%.30g" % (x + 1.5*grid_size) y1 = "%.30g" % (y + 1.5*grid_size) print "Thumbnail: %s %s %s %s" % (x0, y0, x1, y1) width = str(CONF['thumb_width']) height = str(CONF['thumb_height']) iters = str(CONF['iters']) cmd = ['./mandelbrot', width, height, iters, x0, y0, x1, y1] image_data = subprocess.check_output(cmd) response = flask.make_response(image_data) response.headers["Content-Type"] = "image/png" response.headers["cache-control"] = "public, max-age=600" return response if __name__ == "__main__": app.run(host="0.0.0.0", port=8000, debug=True) ================================================ FILE: case_studies/kubernetes/README.md ================================================ # Kubernetes Example This Kubernetes example is based on https://github.com/vasanthbala/hackathon/tree/master/kbp/redis Generate the yaml (actually json) files for Kubernetes: ```sh jsonnet -m ./ example.jsonnet ``` Check they are the same as the original handwritten files: ```sh python test_same.py ``` Clean up ```sh rm -v *.out *.new.yaml ``` ================================================ FILE: case_studies/kubernetes/bigquery-controller.old.yaml ================================================ apiVersion: v1 kind: ReplicationController metadata: name: bigquery-controller labels: name: bigquery-controller spec: replicas: 2 template: metadata: labels: name: bigquery-controller spec: containers: - name: bigquery image: gcr.io/cooltool-1009/pipeline_image:latest env: - name: PROCESSINGSCRIPT value: redis-to-bigquery - name: REDISLIST value: twitter-stream # Change this to your project ID. - name: PROJECT_ID value: cooltool-1009 # Change the following two settings to your dataset and table. - name: BQ_DATASET value: rtda - name: BQ_TABLE value: tweets ================================================ FILE: case_studies/kubernetes/example.jsonnet ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. local Kube = import "kube.libsonnet"; { # The port that this service should serve on. redis_port:: 6379, # Change this to your project ID. project_id:: "cooltool-1009", # Change the following two settings to your BigQuery dataset and table. bq_dataset:: "rtda", bq_table:: "tweets", # Change the following four settings to your twitter credentials # information. twitter_consumer_key:: "xxxx", twitter_consumer_secret:: "xxxx", twitter_access_token:: "xxxx", twitter_access_token_sec:: "xxxx", "twitter-stream.new.yaml": Kube.v1.ReplicationController("twitter-stream") { spec: { replicas: 1, template: { metadata: { labels: { name: "twitter-stream", }, }, spec: { containers: [ { name: "twitter-to-redis", image: "gcr.io/%s/pipeline_image:latest" % $.project_id, env: Kube.pair_list({ PROCESSINGSCRIPT: "twitter-to-redis", REDISLIST: "twitter-stream", CONSUMERKEY: $.twitter_consumer_key, CONSUMERSECRET: $.twitter_consumer_secret, ACCESSTOKEN: $.twitter_access_token, ACCESSTOKENSEC: $.twitter_access_token_sec, TWSTREAMMODE: "sample", }), }, ], }, }, }, }, "redis-master-service.new.yaml": Kube.v1.Service("redis-master") { metadata+: { name: "redismaster", # Likely a typo in the original. }, spec: { ports: [ { port: $.redis_port, # You don't need to specify the targetPort if it is the same as the port, # though here we include it anyway, to show the syntax. targetPort: $.redis_port, }, ], selector: { name: "redis-master", }, }, }, "redis-master.new.yaml": Kube.v1.ReplicationController("redis-master") { spec: { replicas: 1, template: { metadata: { labels: { name: "redis-master", }, }, spec: { containers: [ { name: "master", image: "redis", ports: [ { containerPort: $.redis_port, }, ], }, { name: "collectd", image: "gcr.io/%s/collectd-redis:latest" % $.project_id, ports: [], }, ], }, }, }, }, "bigquery-controller.new.yaml": Kube.v1.ReplicationController("bigquery-controller") { spec: { replicas: 2, template: { metadata: { labels: { name: "bigquery-controller", }, }, spec: { containers: [ { name: "bigquery", image: "gcr.io/%s/pipeline_image:latest" % $.project_id, env: Kube.pair_list({ PROCESSINGSCRIPT: "redis-to-bigquery", REDISLIST: "twitter-stream", PROJECT_ID: $.project_id, # Change the following two settings to your dataset and table. BQ_DATASET: $.bq_dataset, BQ_TABLE: $.bq_table, }), }, ], }, }, }, }, } ================================================ FILE: case_studies/kubernetes/kube.libsonnet ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. /** A collection of templates and utility functions to make it easier to configure Kubernetes * workloads. */ { v1:: { local ApiVersion = { apiVersion: "v1" }, local Metadata(name) = { metadata: { name: name, labels: { name: name, }, }, }, ReplicationController(name): ApiVersion + Metadata(name) { kind: "ReplicationController", }, Service(name): ApiVersion + Metadata(name) { kind: "Service", }, }, pair_list_ex(tab, kfield, vfield):: [{ [kfield]: k, [vfield]: tab[k] } for k in std.objectFields(tab)], pair_list(tab):: self.pair_list_ex(tab, "name", "value"), } ================================================ FILE: case_studies/kubernetes/redis-master-service.old.yaml ================================================ apiVersion: v1 kind: Service metadata: name: redismaster labels: name: redis-master spec: ports: # The port that this service should serve on. # You don't need to specify the targetPort if it is the same as the port, # though here we include it anyway, to show the syntax. - port: 6379 targetPort: 6379 selector: name: redis-master ================================================ FILE: case_studies/kubernetes/redis-master.old.yaml ================================================ apiVersion: v1 kind: ReplicationController metadata: name: redis-master labels: name: redis-master spec: replicas: 1 template: metadata: labels: name: redis-master spec: containers: - name: master image: redis ports: - containerPort: 6379 - name: collectd image: gcr.io/cooltool-1009/collectd-redis:latest ports: [] ================================================ FILE: case_studies/kubernetes/test_same.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import yaml import json import sys import os os.chdir(os.path.dirname(os.path.abspath(__file__))) os.system("jsonnet -m ./ example.jsonnet") files = [ 'bigquery-controller', 'redis-master', 'redis-master-service', 'twitter-stream', ] def jsonstr(v): return json.dumps(v, sort_keys=True, indent=4, separators=(',', ': ')) def canonicalize(doc): """De-duplicate environment vars and sort them alphabetically.""" spec = doc.get('spec') if not spec: return doc template = spec.get('template') if not template: return doc spec2 = template.get('spec') if not spec2: return doc containers = spec2.get('containers') if not containers: return doc for container in containers: env = container.get('env') if not env: continue tab = { } for pair in env: tab[pair['name']] = pair['value'] new_env = [] for key in sorted(tab): new_env.append({'name': key, 'value': tab[key]}) container['env'] = new_env return doc for filename in files: with open(filename + '.old.yaml', 'r') as f: yaml_doc = canonicalize(yaml.load(f, Loader=yaml.SafeLoader)) with open(filename + '.new.yaml', 'r') as f: jsonnet_doc = yaml.load(f, Loader=yaml.SafeLoader) if jsonstr(yaml_doc) == jsonstr(jsonnet_doc): print('Identical: %s' % filename) else: print('Not identical, run: diff %s.old.yaml.out %s.new.yaml.out' % (filename, filename)) with open(filename + '.old.yaml.out', 'w') as f: f.write(jsonstr(yaml_doc)) with open(filename + '.new.yaml.out', 'w') as f: f.write(jsonstr(jsonnet_doc)) os.remove(filename + '.new.yaml') ================================================ FILE: case_studies/kubernetes/twitter-stream.old.yaml ================================================ apiVersion: v1 kind: ReplicationController metadata: name: twitter-stream labels: name: twitter-stream spec: replicas: 1 template: metadata: labels: name: twitter-stream spec: containers: - name: twitter-to-redis image: gcr.io/cooltool-1009/pipeline_image:latest env: - name: PROCESSINGSCRIPT value: twitter-to-redis - name: REDISLIST value: twitter-stream # Change the following four settings to your twitter credentials # information. - name: CONSUMERKEY value: xxxx - name: CONSUMERSECRET value: xxxx - name: ACCESSTOKEN value: xxxx - name: ACCESSTOKENSEC value: xxxx - name: TWSTREAMMODE value: sample ================================================ FILE: case_studies/micro_fractal/.gitignore ================================================ dev_service_account_key.json fractal_dev.jsonnet ================================================ FILE: case_studies/micro_fractal/appserv/main.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Frontend for Fractal demo. Application server for a 3 tier web app. Submits fractal image generation requests. Manages metadata database. Renders pages with Jinja2. """ import datetime import json import os import socket import sys import time import uuid import flask import httplib2 import jinja2 from cassandra.auth import PlainTextAuthProvider from cassandra.cluster import Cluster from cassandra.cluster import NoHostAvailable CONF = None for i in xrange(1, 60): try: with open('conf.json') as conf_file: CONF = json.load(conf_file) except IOError: time.sleep(1) if CONF == None: sys.stderr.write('ERROR: Could not open conf.json.') sys.exit(1) class DbError(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg session = None app = flask.Flask(__name__) app.config['PROPAGATE_EXCEPTIONS'] = True JINJA_ENVIRONMENT = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), extensions=['jinja2.ext.autoescape'], autoescape=True) def render_error(code, msg): return flask.make_response(my_render_template('error.html', code=code, msg=msg), code) def db_execute(s, t=()): global session if session == None: try: ap = PlainTextAuthProvider(username=CONF['database_user'], password=CONF['database_pass']) cluster = Cluster(CONF['db_endpoints'], auth_provider=ap) session = cluster.connect() session.set_keyspace(CONF['database_name']) return session.execute(s, t) except NoHostAvailable: session = None raise DbError("Unable to contact database backend.") else: try: return session.execute(s, t) except NoHostAvailable: session = None return db_execute(s, t) def my_render_template(filename, **context): context.update(CONF) return flask.render_template(filename, **context) def db_clear(): db_execute('TRUNCATE discoveries;') def db_partition(dt): #return "%04d-%02d-%02d" % (dt.year, dt.month, dt.day) return "FIXED" def db_remember(text, x, y, level): u = uuid.uuid1() dt = datetime.datetime.fromtimestamp((u.time - 0x01b21dd213814000L)*100/1e9) db_execute("INSERT INTO discoveries (Date, TimeId, X, Y, L, Text)" + " VALUES (%s, %s, %s, %s, %s, %s)", (db_partition(dt), u, x, y, level, text)) def db_list(): latest = [] # Keep executing calls until we find 10 or we reach 2013 day_counter = datetime.datetime.now() last_partition = None while len(latest) < 10 and day_counter >= datetime.datetime(2013, 1, 1): curr_partition = db_partition(day_counter) if last_partition != curr_partition: last_partition = curr_partition q = "SELECT * FROM discoveries WHERE Date = '%s' ORDER BY TimeId DESC limit 10;" rows = db_execute(q % curr_partition) for row in rows: u = row[1] dt = datetime.datetime.fromtimestamp((u.time - 0x01b21dd213814000L)*100/1e9) latest += [ {'text': row[3], 'x': row[4], 'y': row[5], 'l': row[2], 'ts': dt.isoformat()} ] day_counter -= datetime.timedelta(1) return latest @app.route("/") def default_handler(): return my_render_template('page.html') @app.route("/clear_db") def cleardb_handler(): try: db_clear() return 'Done' except DbError as dbe: return render_error(500, "Internal error: " + dbe.msg) @app.route("/remember", methods=['POST']) def remember_handler(): try: j = flask.request.json x = j['x'] y = j['y'] level = j['l'] text = j['text'][:50] if x < -2: x = -2 if x > 2: x = 2 if y < -2: y = -2 if y > 2: y = 2 if level < 0: level = 0 if level > 50: level = 50 db_remember(text, x, y, level) return 'Done' except DbError as dbe: return render_error(500, "Internal error: " + dbe.msg) @app.route("/remembered_list") def remembered_list_handler(): try: latest = db_list() return flask.jsonify(latest=latest) except DbError as dbe: return render_error(500, "Internal error: " + dbe.msg) @app.errorhandler(404) def error_404(error): return render_error(404, 'Nothing at this URL.') @app.errorhandler(500) def error_500(error): return render_error(500, 'Internal error') if __name__ == "__main__": app.run(host="0.0.0.0", debug=True) ================================================ FILE: case_studies/micro_fractal/appserv/static/style.css ================================================ html { height: 100%; } body { font-family: sans-serif; height: 100%; margin: 0; padding: 0; background-color: #112; color: #ccc; font-family: Arial, Helvetica, sans-serif; } a { color: #fff; text-decoration: none; } a:hover { text-decoration: underline; } a:visited { text-decoration: none; } div#left { float: left; width: 350px; height: 100%; margin: 0; padding: 0; overflow: hidden; } p#banner { padding: 24px 30px 0px 30px; font-size: 36pt; font-weight: bold; margin: 0; text-align: center; } p#instructions { margin: 0; padding: 12px 16px 0 16px; text-align: justify; } div#discoveries { height: 100%; margin: 0; padding: 16px; } div#discoveries h1 { margin: 12px 0 0 0; font-size: 1.5em; } ul#remembered_list { list-style-type:none; padding: 0; margin: 0; } a.remembered { text-decoration: none; } li.remembered { overflow: hidden; margin: 4px; } img.remembered { float: left; border-width: 1px; border-style: solid; border-color: #444; margin-right: 16px; } p.remembered { margin: 0; padding: 8px; font-size: 11pt; word-wrap:break-word; width: 210px; float: left; } div.remembered_date { position: relative; } span.remembered_date { float: right; color: #444; font-size: 10pt; position: absolute; right: 0; top: 44px; } div#remember_plus { margin: 8px 4px; overflow: hidden; } div#remember_plus_text { float: left; border-style: solid; border-width: 1px; border-color: #444; margin-right: 16px; } p#remember_plus { margin: 8px; font-size: 32pt; text-align: center; } textarea#remember_plus_input { padding: 8px; font-size: 11pt; resize: none; background-color: inherit; color: #fff; border: none; font-family: inherit; width: 210px; } div#right { background-color: #002; height: 100%; max-height: 100%; margin: 0; padding: 0; overflow: hidden; } div#fimg_inside { padding: 0; margin: 0; overflow: hidden; position: relative; } img.fimg { float: left; padding: 0; margin: 0; /* outline: 1px solid #f00; outline-offset: -1px; */ } div#err_box { padding: 16px; } ================================================ FILE: case_studies/micro_fractal/appserv/templates/base.html ================================================ Mandelbrot Viewer

{% block instructions %} {% endblock %}

{% block discoveries %} {% endblock %}
================================================ FILE: case_studies/micro_fractal/appserv/templates/error.html ================================================ {% extends "base.html" %} {% block page %}

ERROR: {{ code }}

{{ msg }}

{% endblock %} ================================================ FILE: case_studies/micro_fractal/appserv/templates/page.html ================================================ {% extends 'base.html' %} {% block instructions %} The Mandelbrot set is a beautiful fractal popularized by Benoit B. Mandelbrot. Explore it by clicking to zoom in, and shift+clicking to zoom out. Take a look at other peoples' discoveries below, or add your own! {% endblock %} {% block discoveries %}

Recent Discoveries

{% endblock %} {% block page %}
{% endblock %} ================================================ FILE: case_studies/micro_fractal/db/db_export.sh ================================================ #!/usr/bin/env bash if [ $# != 1 ] ; then echo "Usage: $0 " exit 1 fi DB_HOSTNAME=$1 CASSANDRA_USER="$(jsonnet -e '(import "main.jsonnet").fractal.cassandraUser')" CASSANDRA_PASS="$(jsonnet -e '(import "main.jsonnet").fractal.cassandraUserPass')" ssh -oStrictHostKeyChecking=no -- ${DB_HOSTNAME} sudo cqlsh '$HOSTNAME' -u "$CASSANDRA_USER" -p "$CASSANDRA_PASS" <<< 'COPY fractal.discoveries TO STDOUT;' | sed '$ d' | sed '$ d' ================================================ FILE: case_studies/micro_fractal/db/db_import.sh ================================================ #!/usr/bin/env bash if [ $# != 1 ] ; then echo "Usage: $0 " exit 1 fi DB_HOSTNAME=$1 CASSANDRA_USER="$(jsonnet -e '(import "main.jsonnet").fractal.cassandraUser')" CASSANDRA_PASS="$(jsonnet -e '(import "main.jsonnet").fractal.cassandraUserPass')" (echo 'COPY fractal.discoveries FROM STDIN;' cat echo '\.') | \ ssh -oStrictHostKeyChecking=no -- ${DB_HOSTNAME} sudo cqlsh '$HOSTNAME' -u "$CASSANDRA_USER" -p "$CASSANDRA_PASS" ================================================ FILE: case_studies/micro_fractal/db/nodetool.sh ================================================ #!/usr/bin/env bash if [ $# == 0 ] ; then echo "Usage: $0 ..." exit 1 fi DB_HOSTNAME=$1 shift ssh -oStrictHostKeyChecking=no -- ${DB_HOSTNAME} nodetool "$@" ================================================ FILE: case_studies/micro_fractal/fractal.jsonnet ================================================ local cmd = import 'mmlib/v0.1.2/cmd/cmd.libsonnet'; local cassandra = import 'mmlib/v0.1.2/db/cassandra.libsonnet'; local service_google = import 'mmlib/v0.1.2/service/google.libsonnet'; local web_solutions = import 'mmlib/v0.1.2/web/solutions.libsonnet'; local web = import 'mmlib/v0.1.2/web/web.libsonnet'; function(parent, name) service_google.Service(parent, name) { local app = self, cassandraUser:: 'fractal', dnsSuffix:: error 'Must override dnsSuffix', cassandraUserPass:: error 'Must override cassandraUserPass', cassandraRootPass:: error 'Must override cassandraRootPass', cassandraKeyspace:: 'fractal', cassandraNodes:: [self.prefixName(n) for n in ['db-n1', 'db-n2', 'db-n3', 'db-n4', 'db-n5']], // Configuration shared by the application server and tile generation nodes. ApplicationConf:: { width: 256, height: 256, thumb_width: 64, thumb_height: 64, }, DebugPackagesMixin:: { StandardRootImage+: { local network_debug = ['traceroute', 'lsof', 'iptraf', 'tcpdump', 'host', 'dnsutils'], aptPackages+: ['vim', 'git', 'psmisc', 'screen', 'strace'] + network_debug, }, }, zone: service_google.DnsZone(app, 'zone') { dnsName: app.dnsSuffix, }, www: service_google.DnsRecordWww(app, 'www') { zone: app.zone, target: app.appserv.fullName, }, network: service_google.Network(app, 'network'), appserv: service_google.Cluster3(app, 'appserv') + web.HttpService3 + web_solutions.DebianFlaskHttpService { local service = self, dnsZone: app.zone, networkName: app.network.nameRef, httpsPort: 443, Instance+: app.DebugPackagesMixin { machine_type: 'n1-standard-1', StandardRootImage+: { pipPackages+: ['httplib2', 'cassandra-driver', 'blist'], }, local version = self, conf:: app.ApplicationConf { database: app.cassandraKeyspace, db_endpoints: app.cassandraNodes, database_name: app.cassandraKeyspace, database_user: app.cassandraUser, database_pass: app.cassandraUserPass, tilegen_endpoint: app.tilegen.endpoint, }, module: 'main', // Entrypoint in the Python code. uwsgiConf+: { lazy: 'true' }, // cassandra-driver does not survive fork() // Copy website content and code. httpContentCmds+: [ "echo '%s tilegen' >> /etc/hosts" % app.tilegen.addressRef, cmd.CopyFile { from: std.resolvePath(std.thisFile, 'appserv/*'), to: '/var/www' }, cmd.LiteralFile { content: std.toString(version.conf), to: '/var/www/conf.json' }, ], }, }, tilegen: service_google.Cluster3(app, 'tilegen') + web.HttpService3 + web_solutions.DebianFlaskHttpService { local service = self, dnsZone: app.zone, endpoint:: self.httpsEndpoint, networkName: app.network.nameRef, httpsPort: 443, Instance+: app.DebugPackagesMixin { machine_type: 'n1-standard-1', StandardRootImage+: { aptPackages+: ['g++', 'libpng-dev'], }, local version = self, srcDir:: 'tilegen3', conf:: app.ApplicationConf { iters: 200, }, module: 'mandelbrot_service', httpContentCmds+: [ cmd.CopyFile { from: std.resolvePath(std.thisFile, 'tilegen/*'), to: '/var/www' }, 'g++ -Wall -Wextra -ansi -pedantic -O3 -ffast-math -g /var/www/mandelbrot.cpp -lpng -o /var/www/mandelbrot', cmd.LiteralFile { content: std.toString(version.conf), to: '/var/www/conf.json' }, ], }, }, db: cassandra.GcpDebianCassandra(app, 'db') { local db = self, dnsZone: app.zone, networkName: app.network.nameRef, rootPassword: app.cassandraRootPass, cassandraConf+: { rpc_address:: null, // Unset by making it hidden (::). listen_address:: null, // Unset by making it hidden (::). authenticator: 'PasswordAuthenticator', seed_provider: [ { class_name: 'org.apache.cassandra.locator.SimpleSeedProvider', parameters: [{ seeds: std.join(', ', app.cassandraNodes) }], }, ], }, Instance+: app.DebugPackagesMixin { machine_type: 'n1-standard-4', }, // Put some initial data in a fresh database. StarterNode+: { local cql_insert(uuid, x, y, l, n) = 'INSERT INTO discoveries (Date, TimeId, X, Y, L, Text) ' + ("VALUES ('FIXED', %s, %s, %s, %s, '%s');" % [uuid, x, y, l, n]), initCql: [ "CREATE USER %s WITH PASSWORD '%s';" % [app.cassandraUser, app.cassandraUserPass], 'CREATE KEYSPACE %s WITH REPLICATION = %s;' % [app.cassandraKeyspace, self.initReplication], 'USE %s;' % app.cassandraKeyspace, 'CREATE TABLE discoveries(' + 'Date TEXT, TimeId TIMEUUID, Text TEXT, X FLOAT, Y FLOAT, L INT, ' + 'PRIMARY KEY(Date, TimeId));', cql_insert('18063880-5a4d-11e4-ada4-247703d0f194', '0', '0', '0', 'Zoomed Out'), cql_insert('66b6d100-5a53-11e4-aa05-247703d0f194', '-1.21142578125', '0.3212890625', '4', 'Lightning'), cql_insert('77ffdd80-5a53-11e4-8ccf-247703d0f194', '-1.7568359375', '-0.0009765625', '5', 'Self-similarity'), cql_insert('7fbf8200-5a53-11e4-804a-247703d0f194', '0.342529296875', '0.419189453125', '5', 'Windmills'), cql_insert('9ae7bd00-5a66-11e4-9c66-247703d0f194', '-1.48309979046093', '0.00310595797955671', '39', 'Star'), cql_insert('75fe4480-5a7c-11e4-a747-247703d0f194', '-0.244976043701172', '0.716987609863281', '10', 'Baroque'), cql_insert('abf70380-5b24-11e4-8a46-247703d0f194', '-1.74749755859375', '0.009002685546875', '9', 'Hairy windmills'), ], }, nodes: { n1: db.StarterNode { initReplicationFactor: 1, }, }, }, prober: service_google.SingleInstance(app, 'prober') { zone: 'us-central1-b', dnsZone: app.zone, networkName: app.network.nameRef, perServiceFirewalls:: false, Instance+: app.DebugPackagesMixin { local version = self, script:: ||| from selenium import webdriver import sched import time # Option 1 - with ChromeOptions chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') s = sched.scheduler(time.time, time.sleep) def do_probe(sc): driver = webdriver.Chrome( chrome_options=chrome_options, service_args=['--verbose', '--log-path=/var/log/chromedriver.log']) driver.get('%(endpoint)s') time.sleep(5) print(driver.title) print(len(driver.find_element_by_id('remembered_list').find_elements_by_tag_name('img'))) s.enter(60, 1, do_probe, (sc,)) s.enter(0, 1, do_probe, (s,)) s.run() ||| % { endpoint: app.appserv.httpsEndpoint }, StandardRootImage+: { aptPackages+: ['chromium', 'unzip'], pipPackages+: ['selenium'], cmds+: [ 'wget https://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip', 'unzip chromedriver_linux64.zip', 'mv chromedriver /usr/local/bin', ], }, cmds+: [ cmd.EnsureDir { dir: '/var/prober', owner: 'root' }, cmd.LiteralFile { content: std.toString(version.script), to: '/var/prober/script.py' }, ], bootCmds+: [ 'python /var/prober/script.py', ], }, }, } ================================================ FILE: case_studies/micro_fractal/fractal_dev.jsonnet.TEMPLATE ================================================ local service_google = import "mmlib/v0.1.2/service/google.libsonnet"; local fractal = import "fractal.jsonnet"; { environments: { default: service_google.Credentials { project: "verbing-noun-123", // CHANGE ME! serviceAccount: import "dev_service_account_key.json", // PROVIDE THIS FILE! sshUser: "someuser", // CHANGE ME! } }, fractal: fractal { cassandraUserPass: "xxxxxxxx", // CHANGE ME! cassandraRootPass: "XXXXXXXX", // CHANGE ME! dnsSuffix: "dev.yourdomain.com.", // CHANGE ME! // By default only 1 node is deployed of each subservice. // However you can configure this. appserv+: { zones: ["XXX", "YYY", "ZZZ"], // CHANGE ME! }, tilegen+: { zones: ["XXX", "YYY", "ZZZ"], // CHANGE ME! }, db+: { nodes+: { n1+: { zone: "XXX", // CHANGE ME! }, }, }, }, } ================================================ FILE: case_studies/micro_fractal/tilegen/mandelbrot.cpp ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include #include #include #include #include #include int main(int argc, const char **argv) { if (argc != 8) { std::cerr <<"Usage: " << std::endl; std::cerr <<"PNG file is written to stdout." << std::endl; exit(EXIT_FAILURE); } unsigned long width = strtoul(argv[1], NULL, 10); unsigned long height = strtoul(argv[2], NULL, 10); unsigned long iterations = strtoul(argv[3], NULL, 10); double left = strtod(argv[4], NULL); double bottom = strtod(argv[5], NULL); double right = strtod(argv[6], NULL); double top = strtod(argv[7], NULL); struct Exception { const std::string &msg; Exception(const std::string &msg) : msg(msg) { } }; int code = EXIT_SUCCESS; png_structp png_write_struct = NULL; volatile png_infop info_ptr = NULL; try { png_write_struct = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_write_struct == NULL) throw Exception("Could not allocate libpng write_struct."); info_ptr = png_create_info_struct(png_write_struct); if (info_ptr == NULL) throw Exception("Could not allocate libpng info_struct."); if (setjmp(png_jmpbuf(png_write_struct))) throw Exception("Exception from libpng"); png_init_io(png_write_struct, stdout); png_set_IHDR(png_write_struct, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_write_struct, info_ptr); std::vector row(3 * width); for (unsigned long y=0 ; y 4) { // escaped // smooth with fractional escape velocity float escape_vel = i + 1 - ::log2f(::logf(::sqrtf(len2))); float tonemapped = escape_vel / (20 + escape_vel); row[3*x + 0] = png_byte(powf(tonemapped, 10) * 255); row[3*x + 1] = png_byte(powf(tonemapped, 2.5) * 255); row[3*x + 2] = png_byte(tonemapped * 255); /* row[3*x + 0] = png_byte(escape_vel); row[3*x + 1] = png_byte(escape_vel); row[3*x + 2] = png_byte(escape_vel); */ break; } const double o_x = c_x; c_x = c_x*c_x - c_y*c_y + mb_x; c_y = 2 * o_x * c_y + mb_y; } } png_write_row(png_write_struct, &row[0]); } png_write_end(png_write_struct, NULL); } catch (const Exception &e) { std::cerr << e.msg << std::endl; code = EXIT_FAILURE; } if (png_write_struct != NULL) png_destroy_write_struct(&png_write_struct, (png_infopp)NULL); if (info_ptr != NULL) png_free_data(png_write_struct, info_ptr, PNG_FREE_ALL, -1); return code; } ================================================ FILE: case_studies/micro_fractal/tilegen/mandelbrot_service.py ================================================ #!/usr/bin/python # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Serve fractal images over HTTP by calling out to mandelbrot. """ import hashlib import json import math import os import string import subprocess import time import flask app = flask.Flask(__name__) if __name__ == "__main__": CONF = { 'width': 128, 'height': 128, 'iters': 100, } else: CONF = None for i in xrange(1, 60): print 'Attempting to read conf.json: Attempt %d' % i try: with open('conf.json') as conf_file: CONF = json.load(conf_file) print 'Success!' break except IOError: time.sleep(1) if CONF == None: sys.stderr.write('ERROR: Could not open conf.json.') sys.exit(1) with open ('mandelbrot.cpp', "r") as stream: content = stream.read() ETAG = hashlib.sha1(content).hexdigest() def check_etag(): request_etag = flask.request.headers.get('If-None-Match', None) print 'Request ETag: %s' % request_etag return ETAG == request_etag @app.route("/") def handle_fractal(): """Get fractal coordinates from query string, call mandelbrot to generate image. Returns: The image, wrapped in an HTML response. """ if check_etag(): return flask.make_response(), 304 level = int(flask.request.args.get("l", "0")) x = float(int(flask.request.args.get("x", "0"))) y = float(int(flask.request.args.get("y", "0"))) if level < 0: level = 0 grid_size = math.pow(2, level) x0 = "%.30g" % ((x - 0) / grid_size) y0 = "%.30g" % ((y - 0) / grid_size) x1 = "%.30g" % ((x + 1) / grid_size) y1 = "%.30g" % ((y + 1) / grid_size) print "Tile: %s %s %s %s" % (x0, y0, x1, y1) width = str(CONF['width']) height = str(CONF['height']) iters = str(CONF['iters']) cmd = ['./mandelbrot', width, height, iters, x0, y0, x1, y1] image_data = subprocess.check_output(cmd) response = flask.make_response(image_data) response.headers["Content-Type"] = "image/png" response.headers["cache-control"] = "public, max-age=600" response.headers["ETag"] = ETAG return response @app.route("/thumb") def handle_thumb(): """Get fractal coordinates from query string, call mandelbrot to generate image. Returns: The image, wrapped in an HTML response. """ if check_etag(): return flask.make_response(), 304 level = int(flask.request.args.get("l", "0")) x = float(flask.request.args.get("x", "0")) y = float(flask.request.args.get("y", "0")) if level < 0: level = 0 grid_size = math.pow(2, -level) x0 = "%.30g" % (x - 1.5*grid_size) y0 = "%.30g" % (y - 1.5*grid_size) x1 = "%.30g" % (x + 1.5*grid_size) y1 = "%.30g" % (y + 1.5*grid_size) print "Thumbnail: %s %s %s %s" % (x0, y0, x1, y1) width = str(CONF['thumb_width']) height = str(CONF['thumb_height']) iters = str(CONF['iters']) cmd = ['./mandelbrot', width, height, iters, x0, y0, x1, y1] image_data = subprocess.check_output(cmd) response = flask.make_response(image_data) response.headers["Content-Type"] = "image/png" response.headers["cache-control"] = "public, max-age=600" response.headers["ETag"] = ETAG return response if __name__ == "__main__": app.run(host="0.0.0.0", port=8000, debug=False) ================================================ FILE: case_studies/micromanage/.gitignore ================================================ tests/testenv.libsonnet examples/test_google.jsonnet examples/test_amazon.jsonnet ================================================ FILE: case_studies/micromanage/build_artefact.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class BuildArtefact(object): def name(self): raise NotImplementedError("%s has no override" % self.__class__.__name__) def needsBuild(self): raise NotImplementedError("%s has no override" % self.__class__.__name__) def getOutputFiles(self, dirpath): raise NotImplementedError("%s has no override" % self.__class__.__name__) def outputFiles(self, dirpath): raise NotImplementedError("%s has no override" % self.__class__.__name__) def doBuild(self, dirpath): raise NotImplementedError("%s has no override" % self.__class__.__name__) def wait(self): raise NotImplementedError("%s has no override" % self.__class__.__name__) def postBuild(self): raise NotImplementedError("%s has no override" % self.__class__.__name__) ================================================ FILE: case_studies/micromanage/cmds.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import glob import os # E.g. replace Simon's cat with 'Simon'\''s cat'. def escape(s): return "'%s'" % s.replace("'", "'\"'\"'") def file_glob(given_glob, to, prefix): dirs = [] files = [] lp = len(prefix) for f in glob.glob(given_glob): if os.path.isdir(f): more_files = file_glob('%s/*' % f, to, prefix) files += more_files else: files.append((f, to + f[lp:])) return files def compile_command_to_bash(cmd): if isinstance(cmd, basestring): if len(cmd) > 1 and cmd[0] == '#': comment = cmd[1:].strip() return ['echo %s' % escape(comment)] return [cmd] elif cmd['kind'] == 'LiteralFile': return [ 'echo -n %s > %s' % (escape(cmd['content']), escape(cmd['to'])), 'chmod -v %s %s' % (cmd['filePermissions'], escape(cmd['to'])), 'chown -v %s.%s %s' % (cmd['owner'], cmd['group'], escape(cmd['to'])), ] elif cmd['kind'] == 'CopyFile': files = file_glob(cmd['from'], cmd['to'], os.path.dirname(cmd['from'])) dirs = set([os.path.dirname(f[1]) for f in files]) - {cmd['to']} lines = [] for d in sorted(dirs): lines += [ 'mkdir -v -p %s' % escape(d), 'chmod -v %s %s' % (cmd['dirPermissions'], escape(d)), 'chown -v %s.%s %s' % (cmd['owner'], cmd['group'], escape(d)), ] for f in sorted(files): with open (f[0], "r") as stream: content = stream.read() lines += [ 'echo -n %s > %s' % (escape(content), escape(f[1])), 'chmod -v %s %s' % (cmd['filePermissions'], escape(f[1])), 'chown -v %s.%s %s' % (cmd['owner'], cmd['group'], escape(f[1])), ] return lines elif cmd['kind'] == 'EnsureDir': return [ 'mkdir -v -p %s' % escape(cmd['dir']), 'chmod -v %s %s' % (cmd['dirPermissions'], escape(cmd['dir'])), 'chown -v %s.%s %s' % (cmd['owner'], cmd['group'], escape(cmd['dir'])), ] else: raise RuntimeError('Did not recognize image command kind: ' + cmd['kind']) ================================================ FILE: case_studies/micromanage/examples/hello_world_amazon.jsonnet ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. local service_amazon = import "mmlib/v0.1.2/service/amazon.libsonnet"; local service_google = import "mmlib/v0.1.2/service/google.libsonnet"; local web = import "mmlib/v0.1.2/web/web.libsonnet"; local web_solutions = import "mmlib/v0.1.2/web/solutions.libsonnet"; local debian_amis = import "mmlib/v0.1.2/amis/debian.libsonnet"; local cmd = import "mmlib/v0.1.2/cmd/cmd.libsonnet"; { environments: { default: { kind: "Amazon", accessKey: "XXXXXXXX", // Change this. secretKey: "xxxxxxxx", // Change this. region: "us-west-1", }, }, mynetwork: service_amazon.Network { subnets: { "us-west-1c": "10.0.0.0/24", }, }, // Simple case -- one machine serving this Python script. /* helloworld: service_amazon.SingleInstance(null, 'helloworld') + web.HttpSingleInstance + web_solutions.DebianFlaskHttpService { zone: "us-west-1c", keyName: "kp", uwsgiModuleContent: ||| import flask import socket app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'Hello from %s!' % socket.gethostname() |||, networkName: "mynetwork", }, */ // For production -- allows canarying changes, also use a dns zone helloworld2: service_amazon.Cluster3(null, 'helloworld2') + web.HttpService3 + web_solutions.DebianFlaskHttpService { local service = self, httpPort: 8080, zones: ["us-west-1c", "us-west-1b"], versions: { v1: service.Instance { uwsgiModuleContent: ||| import flask import socket app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'Hello from %s!' % socket.gethostname() |||, }, v2: service.Instance { uwsgiModuleContent: ||| import flask import socket app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'Greetings from %s!' % socket.gethostname() |||, }, }, deployment: { v1: { deployed: [1, 2, 3], attached: [1, 2, 3], }, v2: { deployed: [1], attached: [1], }, }, // dnsZone: $.dns, // dnsZoneName: "dns", }, /* dns: service_amazon.DnsZone(null, 'dns') { local service = self, dnsName: "hw.example.com.", }, // If you own a domain, enable this and the zone service below, then create an NS record to // the allocated nameserver. www: service_google.DnsRecordWww(null, 'dns') { zone: $.dns, zoneName: "dns", target: "helloworld2", }, */ } ================================================ FILE: case_studies/micromanage/examples/hello_world_google.jsonnet ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. local service_amazon = import "mmlib/v0.1.2/service/amazon.libsonnet"; local service_google = import "mmlib/v0.1.2/service/google.libsonnet"; local web = import "mmlib/v0.1.2/web/web.libsonnet"; local web_solutions = import "mmlib/v0.1.2/web/solutions.libsonnet"; { environments: { default: { kind: "Google", project: "readable-name-123", // Change this. region: "us-central1", // Maybe change this. // Download this file from the developers console. serviceAccount: import "service_account.json", sshUser: "yourusername", // Change this. }, }, // The following examples support HTTPs, thus require DNS names and // SSL certificates. Removing the httpsPort will disable that. // Simple case -- one machine serving this Python script. helloworld: service_google.SingleInstance(null, 'helloworld') + web.HttpSingleInstance + web_solutions.DebianFlaskHttpService { httpsPort: 443, sslCertificate: importstr 'cert.pem', // You will need to provision this. sslCertificateKey: importstr 'key.pem', // You will need to provision this. zone: "us-central1-f", uwsgiModuleContent: ||| import flask import socket app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'Hello from %s!' % socket.gethostname() |||, dnsZone: $.dns, }, // For production -- allows canarying changes, also use a dns zone helloworld2: service_google.Cluster3(null, 'helloworld2') + web.HttpService3 + web_solutions.DebianFlaskHttpService { local service = self, httpPort: null, httpsPort: 443, sslCertificate: importstr 'cert.pem', // You will need to provision this. sslCertificateKey: importstr 'key.pem', // You will need to provision this. zones: ["us-central1-b", "us-central1-c", "us-central1-f"], versions: { v1: service.Instance { uwsgiModuleContent: ||| import flask import socket app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'Hello from %s!' % socket.gethostname() |||, }, v2: service.Instance { uwsgiModuleContent: ||| import flask import socket app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'Greetings from %s!' % socket.gethostname() |||, }, }, deployment: { v1: { deployed: [1, 2, 3], attached: [1, 2, 3], }, v2: { deployed: [1], attached: [1], }, }, dnsZone: $.dns, }, dns: service_google.DnsZone(null, 'dns') { local service = self, dnsName: "hw.example.com.", }, // If you own a domain, enable this and the zone service below, then create an NS record to // the allocated nameserver. www: service_google.DnsRecordWww(null, 'www') { zone: $.dns, target: "helloworld2", }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/amis/debian.libsonnet ================================================ // Connect to machines using the username "admin" and -i yourkey.pem // Source: https://wiki.debian.org/Cloud/AmazonEC2Image/Wheezy // HVM, EBS { wheezy: { amd64: { '20150128': { 'ap-northeast-1': 'ami-b25d44b3', 'ap-southeast-1': 'ami-aeb49ffc', 'ap-southeast-2': 'ami-6b770351', 'eu-central-1': 'ami-98043785', 'eu-west-1': 'ami-61e56916', 'sa-east-1': 'ami-3d8b3720', 'us-east-1': 'ami-e0efab88', 'us-west-1': 'ami-b4869ff1', 'us-west-2': 'ami-431a4273', 'us-gov-west-1': 'ami-d13455f2', 'cn-north-1': 'ami-48029071', }, }, }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/amis/ubuntu.libsonnet ================================================ // Connect to machines using the username "ubuntu" and -i yourkey.pem // Source: https://cloud-images.ubuntu.com/locator/ec2/ // There is a URL to get a JSON dump but I've lost it. local regions(amis) = std.set([a[0] for a in amis]); local region_amis(amis, v) = [a for a in amis if a[0] == v]; local oses(amis) = std.set([a[1] for a in amis]); local os_amis(amis, v) = [a for a in amis if a[1] == v]; local archs(amis) = std.set([a[3] for a in amis]); local arch_amis(amis, v) = [a for a in amis if a[3] == v]; local dates(amis) = std.set([a[5] for a in amis]); local date_amis(amis, v) = [a for a in amis if a[5] == v]; local strip_from_url(url) = local suffix = std.split(url, '>')[1]; std.substr(suffix, 0, std.length(suffix) - 3); local all_amis = (import 'ubuntu_raw.json').aaData; { [os]: local this_os_amis = os_amis(all_amis, os); { [arch]: local this_arch_amis = arch_amis(this_os_amis, arch); { [date]: local this_date_amis = date_amis(this_arch_amis, date); { [r]: strip_from_url(region_amis(this_date_amis, r)[0][6]) for r in regions(this_date_amis) } for date in dates(this_arch_amis) } for arch in archs(this_os_amis) } for os in oses(all_amis) } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/amis/ubuntu_raw.json ================================================ { "aaData": [ ["ap-northeast-1","karmic","9.10 EOL","amd64","instance-store","20100826","ami-2a0fa42b","aki-260fa427"], ["ap-northeast-1","karmic","9.10 EOL","i386","instance-store","20100826","ami-240fa425","aki-0a0fa40b"], ["ap-southeast-1","karmic","9.10 EOL","amd64","instance-store","20100826","ami-90344ac2","aki-ec344abe"], ["ap-southeast-1","karmic","9.10 EOL","i386","instance-store","20100826","ami-e6344ab4","aki-e2344ab0"], ["eu-west-1","karmic","9.10 EOL","amd64","instance-store","20100826","ami-28b9935c","aki-20b99354"], ["eu-west-1","karmic","9.10 EOL","i386","instance-store","20100826","ami-24b99350","aki-34b99340"], ["us-east-1","karmic","9.10 EOL","amd64","instance-store","20100826","ami-6832d801","aki-1c3dd775"], ["us-east-1","karmic","9.10 EOL","i386","instance-store","20100826","ami-563dd73f","aki-c43cd6ad"], ["us-west-1","karmic","9.10 EOL","amd64","instance-store","20100826","ami-0af0a14f","aki-68f0a12d"], ["us-west-1","karmic","9.10 EOL","i386","instance-store","20100826","ami-6ef0a12b","aki-5af0a11f"], ["ap-northeast-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-48c27448","hvm"], ["ap-northeast-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-4ac2744a","hvm"], ["ap-northeast-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-50c27450","hvm"], ["ap-northeast-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-a4c472a4","hvm"], ["ap-northeast-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-3ec2743e","aki-176bf516"], ["ap-northeast-1","utopic","14.10 EOL","i386","ebs","20150723","ami-3cc2743c","aki-136bf512"], ["ap-northeast-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-42c27442","aki-176bf516"], ["ap-northeast-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-40c27440","aki-136bf512"], ["ap-northeast-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-46c27446","aki-176bf516"], ["ap-northeast-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-44c27444","aki-136bf512"], ["ap-northeast-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-ccda6ccc","aki-176bf516"], ["ap-northeast-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-18d16718","aki-136bf512"], ["ap-southeast-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-86e3e1d4","hvm"], ["ap-southeast-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-84e3e1d6","hvm"], ["ap-southeast-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-8ae3e1d8","hvm"], ["ap-southeast-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-e8e3e1ba","hvm"], ["ap-southeast-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-94e3e1c6","aki-503e7402"], ["ap-southeast-1","utopic","14.10 EOL","i386","ebs","20150723","ami-96e3e1c4","aki-ae3973fc"], ["ap-southeast-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-9ce3e1ce","aki-503e7402"], ["ap-southeast-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-98e3e1ca","aki-ae3973fc"], ["ap-southeast-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-80e3e1d2","aki-503e7402"], ["ap-southeast-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-82e3e1d0","aki-ae3973fc"], ["ap-southeast-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-f2dcdea0","aki-503e7402"], ["ap-southeast-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-f6dedca4","aki-ae3973fc"], ["ap-southeast-2","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-21eea81b","hvm"], ["ap-southeast-2","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-27eea81d","hvm"], ["ap-southeast-2","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-25eea81f","hvm"], ["ap-southeast-2","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-05e1a73f","hvm"], ["ap-southeast-2","utopic","14.10 EOL","amd64","ebs","20150723","ami-2beea811","aki-c362fff9"], ["ap-southeast-2","utopic","14.10 EOL","i386","ebs","20150723","ami-37eea80d","aki-cd62fff7"], ["ap-southeast-2","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-2feea815","aki-c362fff9"], ["ap-southeast-2","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-29eea813","aki-cd62fff7"], ["ap-southeast-2","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-23eea819","aki-c362fff9"], ["ap-southeast-2","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-2deea817","aki-cd62fff7"], ["ap-southeast-2","utopic","14.10 EOL","amd64","instance-store","20150723","ami-a7e3a59d","aki-c362fff9"], ["ap-southeast-2","utopic","14.10 EOL","i386","instance-store","20150723","ami-91e5a3ab","aki-cd62fff7"], ["eu-central-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-88333695","hvm"], ["eu-central-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-8a333697","hvm"], ["eu-central-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-84333699","hvm"], ["eu-central-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-2c303531","hvm"], ["eu-central-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-9633368b","aki-184c7a05"], ["eu-central-1","utopic","14.10 EOL","i386","ebs","20150723","ami-94333689","aki-3e4c7a23"], ["eu-central-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-9233368f","aki-184c7a05"], ["eu-central-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-9033368d","aki-3e4c7a23"], ["eu-central-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-8e333693","aki-184c7a05"], ["eu-central-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-8c333691","aki-3e4c7a23"], ["eu-central-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-f43633e9","aki-184c7a05"], ["eu-central-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-b63732ab","aki-3e4c7a23"], ["eu-west-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-c8a5eebf","hvm"], ["eu-west-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-b6a5eec1","hvm"], ["eu-west-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-b4a5eec3","hvm"], ["eu-west-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-b0ade6c7","hvm"], ["eu-west-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-daa5eead","aki-52a34525"], ["eu-west-1","utopic","14.10 EOL","i386","ebs","20150723","ami-dea5eea9","aki-68a3451f"], ["eu-west-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-c2a5eeb5","aki-52a34525"], ["eu-west-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-c6a5eeb1","aki-68a3451f"], ["eu-west-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-caa5eebd","aki-52a34525"], ["eu-west-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-cca5eebb","aki-68a3451f"], ["eu-west-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-56abe021","aki-52a34525"], ["eu-west-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-c091dab7","aki-68a3451f"], ["sa-east-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-1319960e","hvm"], ["sa-east-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-0d199610","hvm"], ["sa-east-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-0f199612","hvm"], ["sa-east-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-7d1a9560","hvm"], ["sa-east-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-19199604","aki-5553f448"], ["sa-east-1","utopic","14.10 EOL","i386","ebs","20150723","ami-1f199602","aki-5b53f446"], ["sa-east-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-15199608","aki-5553f448"], ["sa-east-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-1b199606","aki-5b53f446"], ["sa-east-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-1119960c","aki-5553f448"], ["sa-east-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-1719960a","aki-5b53f446"], ["sa-east-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-4b1b9456","aki-5553f448"], ["sa-east-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-cb1d92d6","aki-5b53f446"], ["us-east-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-d96cb0b2","hvm"], ["us-east-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-df6cb0b4","hvm"], ["us-east-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-d36cb0b8","hvm"], ["us-east-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-b77fa3dc","hvm"], ["us-east-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-cf6cb0a4","aki-919dcaf8"], ["us-east-1","utopic","14.10 EOL","i386","ebs","20150723","ami-f16cb09a","aki-8f9dcae6"], ["us-east-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-c36cb0a8","aki-919dcaf8"], ["us-east-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-cd6cb0a6","aki-8f9dcae6"], ["us-east-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-c76cb0ac","aki-919dcaf8"], ["us-east-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-c16cb0aa","aki-8f9dcae6"], ["us-east-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-514b973a","aki-919dcaf8"], ["us-east-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-5525f93e","aki-8f9dcae6"], ["us-west-1","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-6988752d","hvm"], ["us-west-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-6b88752f","hvm"], ["us-west-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-77887533","hvm"], ["us-west-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-d78e7393","hvm"], ["us-west-1","utopic","14.10 EOL","amd64","ebs","20150723","ami-65887521","aki-880531cd"], ["us-west-1","utopic","14.10 EOL","i386","ebs","20150723","ami-5b88751f","aki-8e0531cb"], ["us-west-1","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-63887527","aki-880531cd"], ["us-west-1","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-67887523","aki-8e0531cb"], ["us-west-1","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-6f88752b","aki-880531cd"], ["us-west-1","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-6d887529","aki-8e0531cb"], ["us-west-1","utopic","14.10 EOL","amd64","instance-store","20150723","ami-458c7101","aki-880531cd"], ["us-west-1","utopic","14.10 EOL","i386","instance-store","20150723","ami-95916cd1","aki-8e0531cb"], ["us-west-2","utopic","14.10 EOL","amd64","hvm:ebs","20150723","ami-d9353ae9","hvm"], ["us-west-2","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150723","ami-db353aeb","hvm"], ["us-west-2","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150723","ami-dd353aed","hvm"], ["us-west-2","utopic","14.10 EOL","amd64","hvm:instance-store","20150723","ami-ad3f339d","hvm"], ["us-west-2","utopic","14.10 EOL","amd64","ebs","20150723","ami-3f353a0f","aki-fc8f11cc"], ["us-west-2","utopic","14.10 EOL","i386","ebs","20150723","ami-dd3935ed","aki-f08f11c0"], ["us-west-2","utopic","14.10 EOL","amd64","ebs-io1","20150723","ami-ed353add","aki-fc8f11cc"], ["us-west-2","utopic","14.10 EOL","i386","ebs-io1","20150723","ami-eb353adb","aki-f08f11c0"], ["us-west-2","utopic","14.10 EOL","amd64","ebs-ssd","20150723","ami-d7353ae7","aki-fc8f11cc"], ["us-west-2","utopic","14.10 EOL","i386","ebs-ssd","20150723","ami-ef353adf","aki-f08f11c0"], ["us-west-2","utopic","14.10 EOL","amd64","instance-store","20150723","ami-85222eb5","aki-fc8f11cc"], ["us-west-2","utopic","14.10 EOL","i386","instance-store","20150723","ami-9b2824ab","aki-f08f11c0"], ["ap-northeast-1","oneiric","11.10 EOL","amd64","ebs","20130509","ami-29129d28","aki-44992845"], ["ap-northeast-1","oneiric","11.10 EOL","i386","ebs","20130509","ami-0d129d0c","aki-42992843"], ["ap-northeast-1","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-d11699d0","aki-44992845"], ["ap-northeast-1","oneiric","11.10 EOL","i386","instance-store","20130509","ami-8d17988c","aki-42992843"], ["ap-southeast-1","oneiric","11.10 EOL","amd64","ebs","20130509","ami-22e3ac70","aki-fe1354ac"], ["ap-southeast-1","oneiric","11.10 EOL","i386","ebs","20130509","ami-3ee3ac6c","aki-f81354aa"], ["ap-southeast-1","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-78e3ac2a","aki-fe1354ac"], ["ap-southeast-1","oneiric","11.10 EOL","i386","instance-store","20130509","ami-eee4abbc","aki-f81354aa"], ["ap-southeast-2","oneiric","11.10 EOL","amd64","ebs","20130509","ami-c71d8dfd","aki-31990e0b"], ["ap-southeast-2","oneiric","11.10 EOL","i386","ebs","20130509","ami-c31d8df9","aki-33990e09"], ["ap-southeast-2","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-ed1d8dd7","aki-31990e0b"], ["ap-southeast-2","oneiric","11.10 EOL","i386","instance-store","20130509","ami-a71d8d9d","aki-33990e09"], ["eu-west-1","oneiric","11.10 EOL","amd64","hvm:ebs","20130509","ami-f3bea887","hvm"], ["eu-west-1","oneiric","11.10 EOL","amd64","ebs","20130509","ami-09bea87d","aki-71665e05"], ["eu-west-1","oneiric","11.10 EOL","i386","ebs","20130509","ami-21bea855","aki-75665e01"], ["eu-west-1","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-adb0a6d9","aki-71665e05"], ["eu-west-1","oneiric","11.10 EOL","i386","instance-store","20130509","ami-e3b3a597","aki-75665e01"], ["sa-east-1","oneiric","11.10 EOL","amd64","ebs","20130509","ami-20419b3d","aki-c48f51d9"], ["sa-east-1","oneiric","11.10 EOL","i386","ebs","20130509","ami-2e419b33","aki-ca8f51d7"], ["sa-east-1","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-10419b0d","aki-c48f51d9"], ["sa-east-1","oneiric","11.10 EOL","i386","instance-store","20130509","ami-c0409add","aki-ca8f51d7"], ["us-east-1","oneiric","11.10 EOL","amd64","hvm:ebs","20130509","ami-59204e30","hvm"], ["us-east-1","oneiric","11.10 EOL","amd64","ebs","20130509","ami-77204e1e","aki-88aa75e1"], ["us-east-1","oneiric","11.10 EOL","i386","ebs","20130509","ami-8f234de6","aki-b6aa75df"], ["us-east-1","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-09244a60","aki-88aa75e1"], ["us-east-1","oneiric","11.10 EOL","i386","instance-store","20130509","ami-d12947b8","aki-b6aa75df"], ["us-west-1","oneiric","11.10 EOL","amd64","ebs","20130509","ami-83d9f6c6","aki-f77e26b2"], ["us-west-1","oneiric","11.10 EOL","i386","ebs","20130509","ami-f9d9f6bc","aki-f57e26b0"], ["us-west-1","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-15d9f650","aki-f77e26b2"], ["us-west-1","oneiric","11.10 EOL","i386","instance-store","20130509","ami-9fdaf5da","aki-f57e26b0"], ["us-west-2","oneiric","11.10 EOL","amd64","hvm:ebs","20130509","ami-ab6afc9b","hvm"], ["us-west-2","oneiric","11.10 EOL","amd64","ebs","20130509","ami-bb6afc8b","aki-fc37bacc"], ["us-west-2","oneiric","11.10 EOL","i386","ebs","20130509","ami-b16afc81","aki-fa37baca"], ["us-west-2","oneiric","11.10 EOL","amd64","instance-store","20130509","ami-396afc09","aki-fc37bacc"], ["us-west-2","oneiric","11.10 EOL","i386","instance-store","20130509","ami-b16dfb81","aki-fa37baca"], ["ap-northeast-1","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-070d7406","hvm"], ["ap-northeast-1","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-a90b72a8","hvm"], ["ap-northeast-1","quantal","12.10 EOL","amd64","ebs","20140409","ami-050d7404","aki-176bf516"], ["ap-northeast-1","quantal","12.10 EOL","i386","ebs","20140409","ami-030d7402","aki-136bf512"], ["ap-northeast-1","quantal","12.10 EOL","amd64","instance-store","20140409","ami-2f09702e","aki-176bf516"], ["ap-northeast-1","quantal","12.10 EOL","i386","instance-store","20140409","ami-53146d52","aki-136bf512"], ["ap-southeast-1","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-4c06551e","hvm"], ["ap-southeast-1","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-4a065518","hvm"], ["ap-southeast-1","quantal","12.10 EOL","amd64","ebs","20140409","ami-4e06551c","aki-503e7402"], ["ap-southeast-1","quantal","12.10 EOL","i386","ebs","20140409","ami-4806551a","aki-ae3973fc"], ["ap-southeast-1","quantal","12.10 EOL","amd64","instance-store","20140409","ami-56005304","aki-503e7402"], ["ap-southeast-1","quantal","12.10 EOL","i386","instance-store","20140409","ami-da015288","aki-ae3973fc"], ["ap-southeast-2","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-7b52ca41","hvm"], ["ap-southeast-2","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-3152ca0b","hvm"], ["ap-southeast-2","quantal","12.10 EOL","amd64","ebs","20140409","ami-0752ca3d","aki-c362fff9"], ["ap-southeast-2","quantal","12.10 EOL","i386","ebs","20140409","ami-0152ca3b","aki-cd62fff7"], ["ap-southeast-2","quantal","12.10 EOL","amd64","instance-store","20140409","ami-4951c973","aki-c362fff9"], ["ap-southeast-2","quantal","12.10 EOL","i386","instance-store","20140409","ami-d950c8e3","aki-cd62fff7"], ["eu-west-1","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-09c8337e","hvm"], ["eu-west-1","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-e7d42f90","hvm"], ["eu-west-1","quantal","12.10 EOL","amd64","ebs","20140409","ami-01c83376","aki-52a34525"], ["eu-west-1","quantal","12.10 EOL","i386","ebs","20140409","ami-05c83372","aki-68a3451f"], ["eu-west-1","quantal","12.10 EOL","amd64","instance-store","20140409","ami-ddd02baa","aki-52a34525"], ["eu-west-1","quantal","12.10 EOL","i386","instance-store","20140409","ami-dfd229a8","aki-68a3451f"], ["sa-east-1","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-f954f6e4","hvm"], ["sa-east-1","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-8754f69a","hvm"], ["sa-east-1","quantal","12.10 EOL","amd64","ebs","20140409","ami-ff54f6e2","aki-5553f448"], ["sa-east-1","quantal","12.10 EOL","i386","ebs","20140409","ami-fd54f6e0","aki-5b53f446"], ["sa-east-1","quantal","12.10 EOL","amd64","instance-store","20140409","ami-2d54f630","aki-5553f448"], ["sa-east-1","quantal","12.10 EOL","i386","instance-store","20140409","ami-e757f5fa","aki-5b53f446"], ["us-east-1","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-79071b10","hvm"], ["us-east-1","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-ab0e12c2","hvm"], ["us-east-1","quantal","12.10 EOL","amd64","ebs","20140409","ami-63071b0a","aki-919dcaf8"], ["us-east-1","quantal","12.10 EOL","i386","ebs","20140409","ami-6f071b06","aki-8f9dcae6"], ["us-east-1","quantal","12.10 EOL","amd64","instance-store","20140409","ami-c5100cac","aki-919dcaf8"], ["us-east-1","quantal","12.10 EOL","i386","instance-store","20140409","ami-d51a06bc","aki-8f9dcae6"], ["us-west-1","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-32c9f077","hvm"], ["us-west-1","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-7cd6ef39","hvm"], ["us-west-1","quantal","12.10 EOL","amd64","ebs","20140409","ami-28c9f06d","aki-880531cd"], ["us-west-1","quantal","12.10 EOL","i386","ebs","20140409","ami-2ec9f06b","aki-8e0531cb"], ["us-west-1","quantal","12.10 EOL","amd64","instance-store","20140409","ami-0ad7ee4f","aki-880531cd"], ["us-west-1","quantal","12.10 EOL","i386","instance-store","20140409","ami-9ad5ecdf","aki-8e0531cb"], ["us-west-2","quantal","12.10 EOL","amd64","hvm:ebs","20140409","ami-d4c5aee4","hvm"], ["us-west-2","quantal","12.10 EOL","amd64","hvm:instance-store","20140409","ami-00c7ac30","hvm"], ["us-west-2","quantal","12.10 EOL","amd64","ebs","20140409","ami-d0c5aee0","aki-fc8f11cc"], ["us-west-2","quantal","12.10 EOL","i386","ebs","20140409","ami-ecc5aedc","aki-f08f11c0"], ["us-west-2","quantal","12.10 EOL","amd64","instance-store","20140409","ami-18c1aa28","aki-fc8f11cc"], ["us-west-2","quantal","12.10 EOL","i386","instance-store","20140409","ami-82cca7b2","aki-f08f11c0"], ["ap-northeast-1","hardy","8.04 LTS","amd64","instance-store","20121003","ami-d669d6d7","aki-d069d6d1"], ["ap-northeast-1","hardy","8.04 LTS","i386","instance-store","20121003","ami-ca69d6cb","aki-c469d6c5"], ["ap-southeast-1","hardy","8.04 LTS","amd64","instance-store","20121003","ami-f4a8e8a6","aki-f2a8e8a0"], ["ap-southeast-1","hardy","8.04 LTS","i386","instance-store","20121003","ami-cca8e89e","aki-c0a8e892"], ["eu-west-1","hardy","8.04 LTS","amd64","instance-store","20121003","ami-897575fd","aki-877575f3"], ["eu-west-1","hardy","8.04 LTS","i386","instance-store","20121003","ami-917575e5","aki-a97575dd"], ["sa-east-1","hardy","8.04 LTS","amd64","instance-store","20121003","ami-7008d16d","aki-7e08d163"], ["sa-east-1","hardy","8.04 LTS","i386","instance-store","20121003","ami-4208d15f","aki-4a08d157"], ["us-east-1","hardy","8.04 LTS","amd64","instance-store","20121003","ami-3c229e55","aki-5e229e37"], ["us-east-1","hardy","8.04 LTS","i386","instance-store","20121003","ami-6c229e05","aki-9c219df5"], ["us-west-1","hardy","8.04 LTS","amd64","instance-store","20121003","ami-3ff8df7a","aki-37f8df72"], ["us-west-1","hardy","8.04 LTS","i386","instance-store","20121003","ami-2ff8df6a","aki-25f8df60"], ["us-west-2","hardy","8.04 LTS","amd64","instance-store","20121003","ami-dc31bfec","aki-d631bfe6"], ["us-west-2","hardy","8.04 LTS","i386","instance-store","20121003","ami-ea31bfda","aki-e231bfd2"], ["ap-northeast-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-08ca0808","aki-176bf516"], ["ap-northeast-1","lucid","10.04 LTS","i386","ebs","20150427","ami-06ca0806","aki-136bf512"], ["ap-northeast-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-0eca080e","aki-176bf516"], ["ap-northeast-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-0cca080c","aki-136bf512"], ["ap-northeast-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-16ca0816","aki-176bf516"], ["ap-northeast-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-12ca0812","aki-136bf512"], ["ap-northeast-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-b6c507b6","aki-176bf516"], ["ap-northeast-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-b4c103b4","aki-136bf512"], ["ap-southeast-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-6ab08c38","aki-503e7402"], ["ap-southeast-1","lucid","10.04 LTS","i386","ebs","20150427","ami-60b08c32","aki-ae3973fc"], ["ap-southeast-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-6cb08c3e","aki-503e7402"], ["ap-southeast-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-6eb08c3c","aki-ae3973fc"], ["ap-southeast-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-10b08c42","aki-503e7402"], ["ap-southeast-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-12b08c40","aki-ae3973fc"], ["ap-southeast-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-5cb08c0e","aki-503e7402"], ["ap-southeast-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-beb589ec","aki-ae3973fc"], ["ap-southeast-2","lucid","10.04 LTS","amd64","ebs","20150427","ami-a7a6da9d","aki-c362fff9"], ["ap-southeast-2","lucid","10.04 LTS","i386","ebs","20150427","ami-a1a6da9b","aki-cd62fff7"], ["ap-southeast-2","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-9ba6daa1","aki-c362fff9"], ["ap-southeast-2","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-a5a6da9f","aki-cd62fff7"], ["ap-southeast-2","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-9fa6daa5","aki-c362fff9"], ["ap-southeast-2","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-99a6daa3","aki-cd62fff7"], ["ap-southeast-2","lucid","10.04 LTS","amd64","instance-store","20150427","ami-a9a6da93","aki-c362fff9"], ["ap-southeast-2","lucid","10.04 LTS","i386","instance-store","20150427","ami-d3a5d9e9","aki-cd62fff7"], ["eu-central-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-cc2817d1","aki-184c7a05"], ["eu-central-1","lucid","10.04 LTS","i386","ebs","20150427","ami-d22817cf","aki-3e4c7a23"], ["eu-central-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-ca2817d7","aki-184c7a05"], ["eu-central-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-c82817d5","aki-3e4c7a23"], ["eu-central-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-c62817db","aki-184c7a05"], ["eu-central-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-c42817d9","aki-3e4c7a23"], ["eu-central-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-2c281731","aki-184c7a05"], ["eu-central-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-78291665","aki-3e4c7a23"], ["eu-west-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-0bfb947c","aki-52a34525"], ["eu-west-1","lucid","10.04 LTS","i386","ebs","20150427","ami-0dfb947a","aki-68a3451f"], ["eu-west-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-f7fb9480","aki-52a34525"], ["eu-west-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-09fb947e","aki-68a3451f"], ["eu-west-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-f3fb9484","aki-52a34525"], ["eu-west-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-f5fb9482","aki-68a3451f"], ["eu-west-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-ede08f9a","aki-52a34525"], ["eu-west-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-f9e38c8e","aki-68a3451f"], ["sa-east-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-096bef14","aki-5553f448"], ["sa-east-1","lucid","10.04 LTS","i386","ebs","20150427","ami-0f6bef12","aki-5b53f446"], ["sa-east-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-056bef18","aki-5553f448"], ["sa-east-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-0b6bef16","aki-5b53f446"], ["sa-east-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-016bef1c","aki-5553f448"], ["sa-east-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-076bef1a","aki-5b53f446"], ["sa-east-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-d76aeeca","aki-5553f448"], ["sa-east-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-056aee18","aki-5b53f446"], ["us-east-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-1e6f6176","aki-919dcaf8"], ["us-east-1","lucid","10.04 LTS","i386","ebs","20150427","ami-1c6f6174","aki-8f9dcae6"], ["us-east-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-126f617a","aki-919dcaf8"], ["us-east-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-106f6178","aki-8f9dcae6"], ["us-east-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-e86f6180","aki-919dcaf8"], ["us-east-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-166f617e","aki-8f9dcae6"], ["us-east-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-32505e5a","aki-919dcaf8"], ["us-east-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-5c565834","aki-8f9dcae6"], ["us-west-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-250fe361","aki-880531cd"], ["us-west-1","lucid","10.04 LTS","i386","ebs","20150427","ami-1b0fe35f","aki-8e0531cb"], ["us-west-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-210fe365","aki-880531cd"], ["us-west-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-270fe363","aki-8e0531cb"], ["us-west-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-290fe36d","aki-880531cd"], ["us-west-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-2f0fe36b","aki-8e0531cb"], ["us-west-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-310ee275","aki-880531cd"], ["us-west-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-ef0ce0ab","aki-8e0531cb"], ["us-west-2","lucid","10.04 LTS","amd64","ebs","20150427","ami-1b2a1c2b","aki-fc8f11cc"], ["us-west-2","lucid","10.04 LTS","i386","ebs","20150427","ami-192a1c29","aki-f08f11c0"], ["us-west-2","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-012a1c31","aki-fc8f11cc"], ["us-west-2","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-1f2a1c2f","aki-f08f11c0"], ["us-west-2","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-052a1c35","aki-fc8f11cc"], ["us-west-2","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-032a1c33","aki-f08f11c0"], ["us-west-2","lucid","10.04 LTS","amd64","instance-store","20150427","ami-3f2d1b0f","aki-fc8f11cc"], ["us-west-2","lucid","10.04 LTS","i386","instance-store","20150427","ami-2b2e181b","aki-f08f11c0"], ["ap-northeast-1","maverick","10.10 EOL","amd64","ebs","20120410","ami-741dac75","aki-d409a2d5"], ["ap-northeast-1","maverick","10.10 EOL","i386","ebs","20120410","ami-721dac73","aki-d209a2d3"], ["ap-northeast-1","maverick","10.10 EOL","amd64","instance-store","20120410","ami-701dac71","aki-d409a2d5"], ["ap-northeast-1","maverick","10.10 EOL","i386","instance-store","20120410","ami-641dac65","aki-d209a2d3"], ["ap-southeast-1","maverick","10.10 EOL","amd64","ebs","20120410","ami-0e8acd5c","aki-11d5aa43"], ["ap-southeast-1","maverick","10.10 EOL","i386","ebs","20120410","ami-0a8acd58","aki-13d5aa41"], ["ap-southeast-1","maverick","10.10 EOL","amd64","instance-store","20120410","ami-1a8acd48","aki-11d5aa43"], ["ap-southeast-1","maverick","10.10 EOL","i386","instance-store","20120410","ami-608acd32","aki-13d5aa41"], ["eu-west-1","maverick","10.10 EOL","amd64","ebs","20120410","ami-c57942b1","aki-4feec43b"], ["eu-west-1","maverick","10.10 EOL","i386","ebs","20120410","ami-db7942af","aki-4deec439"], ["eu-west-1","maverick","10.10 EOL","amd64","instance-store","20120410","ami-df7942ab","aki-4feec43b"], ["eu-west-1","maverick","10.10 EOL","i386","instance-store","20120410","ami-d57942a1","aki-4deec439"], ["sa-east-1","maverick","10.10 EOL","amd64","ebs","20120410","ami-10a9770d","aki-d63ce3cb"], ["sa-east-1","maverick","10.10 EOL","i386","ebs","20120410","ami-16a9770b","aki-863ce39b"], ["sa-east-1","maverick","10.10 EOL","amd64","instance-store","20120410","ami-1aa97707","aki-d63ce3cb"], ["sa-east-1","maverick","10.10 EOL","i386","instance-store","20120410","ami-1ea97703","aki-863ce39b"], ["us-east-1","maverick","10.10 EOL","amd64","ebs","20120410","ami-d78f57be","aki-427d952b"], ["us-east-1","maverick","10.10 EOL","i386","ebs","20120410","ami-d38f57ba","aki-407d9529"], ["us-east-1","maverick","10.10 EOL","amd64","instance-store","20120410","ami-098f5760","aki-427d952b"], ["us-east-1","maverick","10.10 EOL","i386","instance-store","20120410","ami-7b8f5712","aki-407d9529"], ["us-west-1","maverick","10.10 EOL","amd64","ebs","20120410","ami-3b154e7e","aki-9ba0f1de"], ["us-west-1","maverick","10.10 EOL","i386","ebs","20120410","ami-39154e7c","aki-99a0f1dc"], ["us-west-1","maverick","10.10 EOL","amd64","instance-store","20120410","ami-2b154e6e","aki-9ba0f1de"], ["us-west-1","maverick","10.10 EOL","i386","instance-store","20120410","ami-1d154e58","aki-99a0f1dc"], ["us-west-2","maverick","10.10 EOL","amd64","ebs","20120410","ami-64fd7154","aki-ace26f9c"], ["us-west-2","maverick","10.10 EOL","i386","ebs","20120410","ami-62fd7152","aki-dce26fec"], ["us-west-2","maverick","10.10 EOL","amd64","instance-store","20120410","ami-7efd714e","aki-ace26f9c"], ["us-west-2","maverick","10.10 EOL","i386","instance-store","20120410","ami-76fd7146","aki-dce26fec"], ["ap-northeast-1","natty","11.04 EOL","amd64","ebs","20121028","ami-087bc509","aki-d409a2d5"], ["ap-northeast-1","natty","11.04 EOL","i386","ebs","20121028","ami-067bc507","aki-d209a2d3"], ["ap-northeast-1","natty","11.04 EOL","amd64","instance-store","20121028","ami-e67ac4e7","aki-d409a2d5"], ["ap-northeast-1","natty","11.04 EOL","i386","instance-store","20121028","ami-8c7ac48d","aki-d209a2d3"], ["ap-southeast-1","natty","11.04 EOL","amd64","ebs","20121028","ami-240e4d76","aki-11d5aa43"], ["ap-southeast-1","natty","11.04 EOL","i386","ebs","20121028","ami-260e4d74","aki-13d5aa41"], ["ap-southeast-1","natty","11.04 EOL","amd64","instance-store","20121028","ami-220e4d70","aki-11d5aa43"], ["ap-southeast-1","natty","11.04 EOL","i386","instance-store","20121028","ami-180e4d4a","aki-13d5aa41"], ["eu-west-1","natty","11.04 EOL","amd64","hvm:ebs","20121028","ami-7b14170f","hvm"], ["eu-west-1","natty","11.04 EOL","amd64","ebs","20121028","ami-7914170d","aki-4feec43b"], ["eu-west-1","natty","11.04 EOL","i386","ebs","20121028","ami-7f14170b","aki-4deec439"], ["eu-west-1","natty","11.04 EOL","amd64","instance-store","20121028","ami-d71516a3","aki-4feec43b"], ["eu-west-1","natty","11.04 EOL","i386","instance-store","20121028","ami-23151657","aki-4deec439"], ["sa-east-1","natty","11.04 EOL","amd64","ebs","20121028","ami-d0548dcd","aki-d63ce3cb"], ["sa-east-1","natty","11.04 EOL","i386","ebs","20121028","ami-d6548dcb","aki-863ce39b"], ["sa-east-1","natty","11.04 EOL","amd64","instance-store","20121028","ami-a2548dbf","aki-d63ce3cb"], ["sa-east-1","natty","11.04 EOL","i386","instance-store","20121028","ami-b6548dab","aki-863ce39b"], ["us-east-1","natty","11.04 EOL","amd64","hvm:ebs","20121028","ami-beb10ad7","hvm"], ["us-east-1","natty","11.04 EOL","amd64","ebs","20121028","ami-bab10ad3","aki-427d952b"], ["us-east-1","natty","11.04 EOL","i386","ebs","20121028","ami-a6b10acf","aki-407d9529"], ["us-east-1","natty","11.04 EOL","amd64","instance-store","20121028","ami-1cb30875","aki-427d952b"], ["us-east-1","natty","11.04 EOL","i386","instance-store","20121028","ami-96b803ff","aki-407d9529"], ["us-west-1","natty","11.04 EOL","amd64","ebs","20121028","ami-132c0a56","aki-9ba0f1de"], ["us-west-1","natty","11.04 EOL","i386","ebs","20121028","ami-112c0a54","aki-99a0f1dc"], ["us-west-1","natty","11.04 EOL","amd64","instance-store","20121028","ami-712c0a34","aki-9ba0f1de"], ["us-west-1","natty","11.04 EOL","i386","instance-store","20121028","ami-b12f09f4","aki-99a0f1dc"], ["us-west-2","natty","11.04 EOL","amd64","hvm:ebs","20121028","ami-70b23b40","hvm"], ["us-west-2","natty","11.04 EOL","amd64","ebs","20121028","ami-0eb23b3e","aki-ace26f9c"], ["us-west-2","natty","11.04 EOL","i386","ebs","20121028","ami-0ab23b3a","aki-dce26fec"], ["us-west-2","natty","11.04 EOL","amd64","instance-store","20121028","ami-3ab23b0a","aki-ace26f9c"], ["us-west-2","natty","11.04 EOL","i386","instance-store","20121028","ami-a2b33a92","aki-dce26fec"], ["ap-northeast-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-a8f8d7c6","hvm"], ["ap-northeast-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-4afdd224","hvm"], ["ap-northeast-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-3df8d753","hvm"], ["ap-northeast-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-bef9d6d0","hvm"], ["ap-northeast-1","precise","12.04 LTS","amd64","ebs","20151130","ami-b8f9d6d6","aki-176bf516"], ["ap-northeast-1","precise","12.04 LTS","i386","ebs","20151130","ami-48fdd226","aki-136bf512"], ["ap-northeast-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-defad5b0","aki-176bf516"], ["ap-northeast-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-59fcd337","aki-136bf512"], ["ap-northeast-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-e0f6d98e","aki-176bf516"], ["ap-northeast-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-0cfbd462","aki-136bf512"], ["ap-northeast-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-0bfcd365","aki-176bf516"], ["ap-northeast-1","precise","12.04 LTS","i386","instance-store","20151130","ami-67ffd009","aki-136bf512"], ["ap-southeast-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-34cb0b57","hvm"], ["ap-southeast-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-87ca0ae4","hvm"], ["ap-southeast-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-70cc0c13","hvm"], ["ap-southeast-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-72cc0c11","hvm"], ["ap-southeast-1","precise","12.04 LTS","amd64","ebs","20151130","ami-48cc0c2b","aki-503e7402"], ["ap-southeast-1","precise","12.04 LTS","i386","ebs","20151130","ami-f8ca0a9b","aki-ae3973fc"], ["ap-southeast-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-e2c90981","aki-503e7402"], ["ap-southeast-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-73cc0c10","aki-ae3973fc"], ["ap-southeast-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-86ca0ae5","aki-503e7402"], ["ap-southeast-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-f9ca0a9a","aki-ae3973fc"], ["ap-southeast-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-06ca0a65","aki-503e7402"], ["ap-southeast-1","precise","12.04 LTS","i386","instance-store","20151130","ami-9fc707fc","aki-ae3973fc"], ["ap-southeast-2","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-bb5a02d8","hvm"], ["ap-southeast-2","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-1858007b","hvm"], ["ap-southeast-2","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-aa5800c9","hvm"], ["ap-southeast-2","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-7f58001c","hvm"], ["ap-southeast-2","precise","12.04 LTS","amd64","ebs","20151130","ami-d65a02b5","aki-c362fff9"], ["ap-southeast-2","precise","12.04 LTS","i386","ebs","20151130","ami-b35800d0","aki-cd62fff7"], ["ap-southeast-2","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-bd5a02de","aki-c362fff9"], ["ap-southeast-2","precise","12.04 LTS","i386","ebs-io1","20151130","ami-1a580079","aki-cd62fff7"], ["ap-southeast-2","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-b6277fd5","aki-c362fff9"], ["ap-southeast-2","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-fb590198","aki-cd62fff7"], ["ap-southeast-2","precise","12.04 LTS","amd64","instance-store","20151130","ami-27257d44","aki-c362fff9"], ["ap-southeast-2","precise","12.04 LTS","i386","instance-store","20151130","ami-63257d00","aki-cd62fff7"], ["eu-central-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-132b367f","hvm"], ["eu-central-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-862835ea","hvm"], ["eu-central-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-872835eb","hvm"], ["eu-central-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-6b2e3307","hvm"], ["eu-central-1","precise","12.04 LTS","amd64","ebs","20151130","ami-c22934ae","aki-184c7a05"], ["eu-central-1","precise","12.04 LTS","i386","ebs","20151130","ami-6629340a","aki-3e4c7a23"], ["eu-central-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-5b283537","aki-184c7a05"], ["eu-central-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-112b367d","aki-3e4c7a23"], ["eu-central-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-2029344c","aki-184c7a05"], ["eu-central-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-122b367e","aki-3e4c7a23"], ["eu-central-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-172e337b","aki-184c7a05"], ["eu-central-1","precise","12.04 LTS","i386","instance-store","20151130","ami-8c2934e0","aki-3e4c7a23"], ["eu-west-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-8315b2f0","hvm"], ["eu-west-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-0212b571","hvm"], ["eu-west-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-c509aeb6","hvm"], ["eu-west-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-5217b021","hvm"], ["eu-west-1","precise","12.04 LTS","amd64","ebs","20151130","ami-8115b2f2","aki-52a34525"], ["eu-west-1","precise","12.04 LTS","i386","ebs","20151130","ami-3910b74a","aki-68a3451f"], ["eu-west-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-3b10b748","aki-52a34525"], ["eu-west-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-6e14b31d","aki-68a3451f"], ["eu-west-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-8015b2f3","aki-52a34525"], ["eu-west-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-bd13b4ce","aki-68a3451f"], ["eu-west-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-c50bacb6","aki-52a34525"], ["eu-west-1","precise","12.04 LTS","i386","instance-store","20151130","ami-840cabf7","aki-68a3451f"], ["sa-east-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-2300854f","hvm"], ["sa-east-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-b70386db","hvm"], ["sa-east-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-e9078285","hvm"], ["sa-east-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-ec078280","hvm"], ["sa-east-1","precise","12.04 LTS","amd64","ebs","20151130","ami-5e018432","aki-5553f448"], ["sa-east-1","precise","12.04 LTS","i386","ebs","20151130","ami-59028735","aki-5b53f446"], ["sa-east-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-e706838b","aki-5553f448"], ["sa-east-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-7e038612","aki-5b53f446"], ["sa-east-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-c10184ad","aki-5553f448"], ["sa-east-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-060d886a","aki-5b53f446"], ["sa-east-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-e5058089","aki-5553f448"], ["sa-east-1","precise","12.04 LTS","i386","instance-store","20151130","ami-4300852f","aki-5b53f446"], ["us-east-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-1aa1e370","hvm"], ["us-east-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-54a0e23e","hvm"], ["us-east-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-309ddf5a","hvm"], ["us-east-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-3a90d250","hvm"], ["us-east-1","precise","12.04 LTS","amd64","ebs","20151130","ami-81a0e2eb","aki-919dcaf8"], ["us-east-1","precise","12.04 LTS","i386","ebs","20151130","ami-14a3e17e","aki-8f9dcae6"], ["us-east-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-5aa0e230","aki-919dcaf8"], ["us-east-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-f3a6e499","aki-8f9dcae6"], ["us-east-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-3ca3e156","aki-919dcaf8"], ["us-east-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-18a1e372","aki-8f9dcae6"], ["us-east-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-9c8fcdf6","aki-919dcaf8"], ["us-east-1","precise","12.04 LTS","i386","instance-store","20151130","ami-f482c09e","aki-8f9dcae6"], ["us-west-1","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-2bdbb24b","hvm"], ["us-west-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-2ddab34d","hvm"], ["us-west-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-caddb4aa","hvm"], ["us-west-1","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-87dab3e7","hvm"], ["us-west-1","precise","12.04 LTS","amd64","ebs","20151130","ami-f0dab390","aki-880531cd"], ["us-west-1","precise","12.04 LTS","i386","ebs","20151130","ami-1ad8b17a","aki-8e0531cb"], ["us-west-1","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-f1dab391","aki-880531cd"], ["us-west-1","precise","12.04 LTS","i386","ebs-io1","20151130","ami-c9ddb4a9","aki-8e0531cb"], ["us-west-1","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-2cdab34c","aki-880531cd"], ["us-west-1","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-84d8b1e4","aki-8e0531cb"], ["us-west-1","precise","12.04 LTS","amd64","instance-store","20151130","ami-65d9b005","aki-880531cd"], ["us-west-1","precise","12.04 LTS","i386","instance-store","20151130","ami-4ac7ae2a","aki-8e0531cb"], ["us-west-2","precise","12.04 LTS","amd64","hvm:ebs","20151130","ami-f7243696","hvm"], ["us-west-2","precise","12.04 LTS","amd64","hvm:ebs-io1","20151130","ami-5a21333b","hvm"], ["us-west-2","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151130","ami-9b2436fa","hvm"], ["us-west-2","precise","12.04 LTS","amd64","hvm:instance-store","20151130","ami-77223016","hvm"], ["us-west-2","precise","12.04 LTS","amd64","ebs","20151130","ami-9c2436fd","aki-fc8f11cc"], ["us-west-2","precise","12.04 LTS","i386","ebs","20151130","ami-c72133a6","aki-f08f11c0"], ["us-west-2","precise","12.04 LTS","amd64","ebs-io1","20151130","ami-9a2436fb","aki-fc8f11cc"], ["us-west-2","precise","12.04 LTS","i386","ebs-io1","20151130","ami-9d2436fc","aki-f08f11c0"], ["us-west-2","precise","12.04 LTS","amd64","ebs-ssd","20151130","ami-d02634b1","aki-fc8f11cc"], ["us-west-2","precise","12.04 LTS","i386","ebs-ssd","20151130","ami-962537f7","aki-f08f11c0"], ["us-west-2","precise","12.04 LTS","amd64","instance-store","20151130","ami-c72032a6","aki-fc8f11cc"], ["us-west-2","precise","12.04 LTS","i386","instance-store","20151130","ami-b9382ad8","aki-f08f11c0"], ["ap-northeast-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-05f2dd6b","hvm"], ["ap-northeast-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-48cfe026","hvm"], ["ap-northeast-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-88cde2e6","hvm"], ["ap-northeast-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-dcf3dcb2","hvm"], ["ap-northeast-1","vivid","15.04L","amd64","ebs","20151201","ami-02cde26c","aki-176bf516"], ["ap-northeast-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-8fcde2e1","aki-176bf516"], ["ap-northeast-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-8ccee1e2","aki-176bf516"], ["ap-northeast-1","vivid","15.04L","amd64","instance-store","20151201","ami-baf3dcd4","aki-176bf516"], ["ap-southeast-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-6dd0100e","hvm"], ["ap-southeast-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-99cc0cfa","hvm"], ["ap-southeast-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-a6ce0ec5","hvm"], ["ap-southeast-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-f9ce0e9a","hvm"], ["ap-southeast-1","vivid","15.04L","amd64","ebs","20151201","ami-6cd0100f","aki-503e7402"], ["ap-southeast-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-80cf0fe3","aki-503e7402"], ["ap-southeast-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-05d11166","aki-503e7402"], ["ap-southeast-1","vivid","15.04L","amd64","instance-store","20151201","ami-b9cc0cda","aki-503e7402"], ["ap-southeast-2","vivid","15.04L","amd64","hvm:ebs","20151201","ami-525f0731","hvm"], ["ap-southeast-2","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-a75d05c4","hvm"], ["ap-southeast-2","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-a45d05c7","hvm"], ["ap-southeast-2","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-245c0447","hvm"], ["ap-southeast-2","vivid","15.04L","amd64","ebs","20151201","ami-555f0736","aki-c362fff9"], ["ap-southeast-2","vivid","15.04L","amd64","ebs-io1","20151201","ami-a65d05c5","aki-c362fff9"], ["ap-southeast-2","vivid","15.04L","amd64","ebs-ssd","20151201","ami-815e06e2","aki-c362fff9"], ["ap-southeast-2","vivid","15.04L","amd64","instance-store","20151201","ami-f95d059a","aki-c362fff9"], ["eu-central-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-16273a7a","hvm"], ["eu-central-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-9d2439f1","hvm"], ["eu-central-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-432b362f","hvm"], ["eu-central-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-792a3715","hvm"], ["eu-central-1","vivid","15.04L","amd64","ebs","20151201","ami-802538ec","aki-184c7a05"], ["eu-central-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-15273a79","aki-184c7a05"], ["eu-central-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-a82538c4","aki-184c7a05"], ["eu-central-1","vivid","15.04L","amd64","instance-store","20151201","ami-a72b36cb","aki-184c7a05"], ["eu-west-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-8618bff5","hvm"], ["eu-west-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-7be24408","hvm"], ["eu-west-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-b0e543c3","hvm"], ["eu-west-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-f81abd8b","hvm"], ["eu-west-1","vivid","15.04L","amd64","ebs","20151201","ami-25e44256","aki-52a34525"], ["eu-west-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-4118bf32","aki-52a34525"], ["eu-west-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-76e54305","aki-52a34525"], ["eu-west-1","vivid","15.04L","amd64","instance-store","20151201","ami-391cbb4a","aki-52a34525"], ["sa-east-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-ff018493","hvm"], ["sa-east-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-5f0c8933","hvm"], ["sa-east-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-580c8934","hvm"], ["sa-east-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-0b0f8a67","hvm"], ["sa-east-1","vivid","15.04L","amd64","ebs","20151201","ami-af0085c3","aki-5553f448"], ["sa-east-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-fe018492","aki-5553f448"], ["sa-east-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-c00085ac","aki-5553f448"], ["sa-east-1","vivid","15.04L","amd64","instance-store","20151201","ami-840184e8","aki-5553f448"], ["us-east-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-32b5f758","hvm"], ["us-east-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-f2bbf998","hvm"], ["us-east-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-e6b8fa8c","hvm"], ["us-east-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-79b4f613","hvm"], ["us-east-1","vivid","15.04L","amd64","ebs","20151201","ami-b0b5f7da","aki-919dcaf8"], ["us-east-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-b1baf8db","aki-919dcaf8"], ["us-east-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-33b5f759","aki-919dcaf8"], ["us-east-1","vivid","15.04L","amd64","instance-store","20151201","ami-a0aceeca","aki-919dcaf8"], ["us-west-1","vivid","15.04L","amd64","hvm:ebs","20151201","ami-89deb7e9","hvm"], ["us-west-1","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-70dcb510","hvm"], ["us-west-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-42deb722","hvm"], ["us-west-1","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-44deb724","hvm"], ["us-west-1","vivid","15.04L","amd64","ebs","20151201","ami-e3d1b883","aki-880531cd"], ["us-west-1","vivid","15.04L","amd64","ebs-io1","20151201","ami-02dcb562","aki-880531cd"], ["us-west-1","vivid","15.04L","amd64","ebs-ssd","20151201","ami-c3deb7a3","aki-880531cd"], ["us-west-1","vivid","15.04L","amd64","instance-store","20151201","ami-f6dfb696","aki-880531cd"], ["us-west-2","vivid","15.04L","amd64","hvm:ebs","20151201","ami-d82d3fb9","hvm"], ["us-west-2","vivid","15.04L","amd64","hvm:ebs-io1","20151201","ami-fdd3c19c","hvm"], ["us-west-2","vivid","15.04L","amd64","hvm:ebs-ssd","20151201","ami-84d3c1e5","hvm"], ["us-west-2","vivid","15.04L","amd64","hvm:instance-store","20151201","ami-d5d2c0b4","hvm"], ["us-west-2","vivid","15.04L","amd64","ebs","20151201","ami-9ed0c2ff","aki-fc8f11cc"], ["us-west-2","vivid","15.04L","amd64","ebs-io1","20151201","ami-0fd0c26e","aki-fc8f11cc"], ["us-west-2","vivid","15.04L","amd64","ebs-ssd","20151201","ami-412c3e20","aki-fc8f11cc"], ["us-west-2","vivid","15.04L","amd64","instance-store","20151201","ami-e02e3c81","aki-fc8f11cc"], ["ap-northeast-1","wily","15.10","amd64","hvm:ebs","20151203","ami-87f7d9e9","hvm"], ["ap-northeast-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-6ef8d600","hvm"], ["ap-northeast-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-53f9d73d","hvm"], ["ap-northeast-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-caf6d8a4","hvm"], ["ap-northeast-1","wily","15.10","amd64","ebs","20151203","ami-78f9d716","aki-176bf516"], ["ap-northeast-1","wily","15.10","amd64","ebs-io1","20151203","ami-6ff7d901","aki-176bf516"], ["ap-northeast-1","wily","15.10","amd64","ebs-ssd","20151203","ami-f2f9d79c","aki-176bf516"], ["ap-northeast-1","wily","15.10","amd64","instance-store","20151203","ami-60f8d60e","aki-176bf516"], ["ap-southeast-1","wily","15.10","amd64","hvm:ebs","20151203","ami-781ada1b","hvm"], ["ap-southeast-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-d11adab2","hvm"], ["ap-southeast-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-791ada1a","hvm"], ["ap-southeast-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-d01adab3","hvm"], ["ap-southeast-1","wily","15.10","amd64","ebs","20151203","ami-691cdc0a","aki-503e7402"], ["ap-southeast-1","wily","15.10","amd64","ebs-io1","20151203","ami-7b1ada18","aki-503e7402"], ["ap-southeast-1","wily","15.10","amd64","ebs-ssd","20151203","ami-ce1bdbad","aki-503e7402"], ["ap-southeast-1","wily","15.10","amd64","instance-store","20151203","ami-2f18d84c","aki-503e7402"], ["ap-southeast-2","wily","15.10","amd64","hvm:ebs","20151203","ami-49673f2a","hvm"], ["ap-southeast-2","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-30643c53","hvm"], ["ap-southeast-2","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-87673fe4","hvm"], ["ap-southeast-2","wily","15.10","amd64","hvm:instance-store","20151203","ami-89673fea","hvm"], ["ap-southeast-2","wily","15.10","amd64","ebs","20151203","ami-199bc37a","aki-c362fff9"], ["ap-southeast-2","wily","15.10","amd64","ebs-io1","20151203","ami-0198c062","aki-c362fff9"], ["ap-southeast-2","wily","15.10","amd64","ebs-ssd","20151203","ami-86673fe5","aki-c362fff9"], ["ap-southeast-2","wily","15.10","amd64","instance-store","20151203","ami-3a663e59","aki-c362fff9"], ["eu-central-1","wily","15.10","amd64","hvm:ebs","20151203","ami-2f978a43","hvm"], ["eu-central-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-84978ae8","hvm"], ["eu-central-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-7d948911","hvm"], ["eu-central-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-6195880d","hvm"], ["eu-central-1","wily","15.10","amd64","ebs","20151203","ami-b4968bd8","aki-184c7a05"], ["eu-central-1","wily","15.10","amd64","ebs-io1","20151203","ami-7c948910","aki-184c7a05"], ["eu-central-1","wily","15.10","amd64","ebs-ssd","20151203","ami-e8968b84","aki-184c7a05"], ["eu-central-1","wily","15.10","amd64","instance-store","20151203","ami-ba9a87d6","aki-184c7a05"], ["eu-west-1","wily","15.10","amd64","hvm:ebs","20151203","ami-4934923a","hvm"], ["eu-west-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-c73791b4","hvm"], ["eu-west-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-fe35938d","hvm"], ["eu-west-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-ba3492c9","hvm"], ["eu-west-1","wily","15.10","amd64","ebs","20151203","ami-54309627","aki-52a34525"], ["eu-west-1","wily","15.10","amd64","ebs-io1","20151203","ami-823395f1","aki-52a34525"], ["eu-west-1","wily","15.10","amd64","ebs-ssd","20151203","ami-9a3294e9","aki-52a34525"], ["eu-west-1","wily","15.10","amd64","instance-store","20151203","ami-57228424","aki-52a34525"], ["sa-east-1","wily","15.10","amd64","hvm:ebs","20151203","ami-33d85c5f","hvm"], ["sa-east-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-ddd95db1","hvm"], ["sa-east-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-abd357c7","hvm"], ["sa-east-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-60dd590c","hvm"], ["sa-east-1","wily","15.10","amd64","ebs","20151203","ami-26d85c4a","aki-5553f448"], ["sa-east-1","wily","15.10","amd64","ebs-io1","20151203","ami-b0df5bdc","aki-5553f448"], ["sa-east-1","wily","15.10","amd64","ebs-ssd","20151203","ami-28df5b44","aki-5553f448"], ["sa-east-1","wily","15.10","amd64","instance-store","20151203","ami-4bd05427","aki-5553f448"], ["us-east-1","wily","15.10","amd64","hvm:ebs","20151203","ami-a75e12cd","hvm"], ["us-east-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-cc5e12a6","hvm"], ["us-east-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-305d115a","hvm"], ["us-east-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-304d015a","hvm"], ["us-east-1","wily","15.10","amd64","ebs","20151203","ami-135c1079","aki-919dcaf8"], ["us-east-1","wily","15.10","amd64","ebs-io1","20151203","ami-9b5f13f1","aki-919dcaf8"], ["us-east-1","wily","15.10","amd64","ebs-ssd","20151203","ami-9a5f13f0","aki-919dcaf8"], ["us-east-1","wily","15.10","amd64","instance-store","20151203","ami-42377b28","aki-919dcaf8"], ["us-west-1","wily","15.10","amd64","hvm:ebs","20151203","ami-586f0738","hvm"], ["us-west-1","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-756e0615","hvm"], ["us-west-1","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-b26e06d2","hvm"], ["us-west-1","wily","15.10","amd64","hvm:instance-store","20151203","ami-076d0567","hvm"], ["us-west-1","wily","15.10","amd64","ebs","20151203","ami-ab640ccb","aki-880531cd"], ["us-west-1","wily","15.10","amd64","ebs-io1","20151203","ami-596f0739","aki-880531cd"], ["us-west-1","wily","15.10","amd64","ebs-ssd","20151203","ami-b36e06d3","aki-880531cd"], ["us-west-1","wily","15.10","amd64","instance-store","20151203","ami-d96d05b9","aki-880531cd"], ["us-west-2","wily","15.10","amd64","hvm:ebs","20151203","ami-14415c75","hvm"], ["us-west-2","wily","15.10","amd64","hvm:ebs-io1","20151203","ami-8a465beb","hvm"], ["us-west-2","wily","15.10","amd64","hvm:ebs-ssd","20151203","ami-31475a50","hvm"], ["us-west-2","wily","15.10","amd64","hvm:instance-store","20151203","ami-4c4a572d","hvm"], ["us-west-2","wily","15.10","amd64","ebs","20151203","ami-a9435ec8","aki-fc8f11cc"], ["us-west-2","wily","15.10","amd64","ebs-io1","20151203","ami-a9415cc8","aki-fc8f11cc"], ["us-west-2","wily","15.10","amd64","ebs-ssd","20151203","ami-8f475aee","aki-fc8f11cc"], ["us-west-2","wily","15.10","amd64","instance-store","20151203","ami-804855e1","aki-fc8f11cc"], ["ap-northeast-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-31e2b430","hvm"], ["ap-northeast-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-33e2b432","hvm"], ["ap-northeast-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-35e2b434","hvm"], ["ap-northeast-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-9de4b29c","hvm"], ["ap-northeast-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-1fe2b41e","aki-176bf516"], ["ap-northeast-1","saucy","13.10 EOL","i386","ebs","20140709","ami-1de2b41c","aki-136bf512"], ["ap-northeast-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-29e2b428","aki-176bf516"], ["ap-northeast-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-23e2b422","aki-136bf512"], ["ap-northeast-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-2fe2b42e","aki-176bf516"], ["ap-northeast-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-2de2b42c","aki-136bf512"], ["ap-northeast-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-45f1a744","aki-176bf516"], ["ap-northeast-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-a9cb9da8","aki-136bf512"], ["ap-southeast-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-e4e4bab6","hvm"], ["ap-southeast-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-eae4bab8","hvm"], ["ap-southeast-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-90e4bac2","hvm"], ["ap-southeast-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-bee7b9ec","hvm"], ["ap-southeast-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-f8e4baaa","aki-503e7402"], ["ap-southeast-1","saucy","13.10 EOL","i386","ebs","20140709","ami-f4e4baa6","aki-ae3973fc"], ["ap-southeast-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-e2e4bab0","aki-503e7402"], ["ap-southeast-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-fce4baae","aki-ae3973fc"], ["ap-southeast-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-e6e4bab4","aki-503e7402"], ["ap-southeast-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-e0e4bab2","aki-ae3973fc"], ["ap-southeast-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-02e6b850","aki-503e7402"], ["ap-southeast-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-b0e8b6e2","aki-ae3973fc"], ["ap-southeast-2","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-a143249b","hvm"], ["ap-southeast-2","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-a743249d","hvm"], ["ap-southeast-2","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-a543249f","hvm"], ["ap-southeast-2","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-bb432481","hvm"], ["ap-southeast-2","saucy","13.10 EOL","amd64","ebs","20140709","ami-b543248f","aki-c362fff9"], ["ap-southeast-2","saucy","13.10 EOL","i386","ebs","20140709","ami-b743248d","aki-cd62fff7"], ["ap-southeast-2","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-a9432493","aki-c362fff9"], ["ap-southeast-2","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-ab432491","aki-cd62fff7"], ["ap-southeast-2","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-ad432497","aki-c362fff9"], ["ap-southeast-2","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-af432495","aki-cd62fff7"], ["ap-southeast-2","saucy","13.10 EOL","amd64","instance-store","20140709","ami-e54423df","aki-c362fff9"], ["ap-southeast-2","saucy","13.10 EOL","i386","instance-store","20140709","ami-ff4522c5","aki-cd62fff7"], ["eu-west-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-3910c44e","hvm"], ["eu-west-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-2710c450","hvm"], ["eu-west-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-2110c456","hvm"], ["eu-west-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-1316c264","hvm"], ["eu-west-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-3310c444","aki-52a34525"], ["eu-west-1","saucy","13.10 EOL","i386","ebs","20140709","ami-3510c442","aki-68a3451f"], ["eu-west-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-3f10c448","aki-52a34525"], ["eu-west-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-3110c446","aki-68a3451f"], ["eu-west-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-3b10c44c","aki-52a34525"], ["eu-west-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-3d10c44a","aki-68a3451f"], ["eu-west-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-39eb3f4e","aki-52a34525"], ["eu-west-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-f9e93d8e","aki-68a3451f"], ["sa-east-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-316dc32c","hvm"], ["sa-east-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-336dc32e","hvm"], ["sa-east-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-2d6dc330","hvm"], ["sa-east-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-a16cc2bc","hvm"], ["sa-east-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-036dc31e","aki-5553f448"], ["sa-east-1","saucy","13.10 EOL","i386","ebs","20140709","ami-016dc31c","aki-5b53f446"], ["sa-east-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-396dc324","aki-5553f448"], ["sa-east-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-3f6dc322","aki-5b53f446"], ["sa-east-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-376dc32a","aki-5553f448"], ["sa-east-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-356dc328","aki-5b53f446"], ["sa-east-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-076cc21a","aki-5553f448"], ["sa-east-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-716fc16c","aki-5b53f446"], ["us-east-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-5661a13e","hvm"], ["us-east-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-3c61a154","hvm"], ["us-east-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-3661a15e","hvm"], ["us-east-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-145b9b7c","hvm"], ["us-east-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-7461a11c","aki-919dcaf8"], ["us-east-1","saucy","13.10 EOL","i386","ebs","20140709","ami-7261a11a","aki-8f9dcae6"], ["us-east-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-4061a128","aki-919dcaf8"], ["us-east-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-4861a120","aki-8f9dcae6"], ["us-east-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-5a61a132","aki-919dcaf8"], ["us-east-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-5861a130","aki-8f9dcae6"], ["us-east-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-a65393ce","aki-919dcaf8"], ["us-east-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-d24c8cba","aki-8f9dcae6"], ["us-west-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-812d2dc4","hvm"], ["us-west-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-832d2dc6","hvm"], ["us-west-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-8d2d2dc8","hvm"], ["us-west-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-c32c2c86","hvm"], ["us-west-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-fd2d2db8","aki-880531cd"], ["us-west-1","saucy","13.10 EOL","i386","ebs","20140709","ami-f12d2db4","aki-8e0531cb"], ["us-west-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-f92d2dbc","aki-880531cd"], ["us-west-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-ff2d2dba","aki-8e0531cb"], ["us-west-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-872d2dc2","aki-880531cd"], ["us-west-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-fb2d2dbe","aki-8e0531cb"], ["us-west-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-bb2a2afe","aki-880531cd"], ["us-west-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-1b29295e","aki-8e0531cb"], ["us-west-2","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-8b88f6bb","hvm"], ["us-west-2","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-8d88f6bd","hvm"], ["us-west-2","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-8f88f6bf","hvm"], ["us-west-2","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-138bf523","hvm"], ["us-west-2","saucy","13.10 EOL","amd64","ebs","20140709","ami-9f88f6af","aki-fc8f11cc"], ["us-west-2","saucy","13.10 EOL","i386","ebs","20140709","ami-9988f6a9","aki-f08f11c0"], ["us-west-2","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-8388f6b3","aki-fc8f11cc"], ["us-west-2","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-8188f6b1","aki-f08f11c0"], ["us-west-2","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-8988f6b9","aki-fc8f11cc"], ["us-west-2","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-8588f6b5","aki-f08f11c0"], ["us-west-2","saucy","13.10 EOL","amd64","instance-store","20140709","ami-c18ff1f1","aki-fc8f11cc"], ["us-west-2","saucy","13.10 EOL","i386","instance-store","20140709","ami-9f91efaf","aki-f08f11c0"], ["ap-northeast-1","raring","13.04 EOL","amd64","ebs","20140111","ami-bfc0afbe","aki-44992845"], ["ap-northeast-1","raring","13.04 EOL","i386","ebs","20140111","ami-bdc0afbc","aki-42992843"], ["ap-northeast-1","raring","13.04 EOL","amd64","instance-store","20140111","ami-31c1ae30","aki-44992845"], ["ap-northeast-1","raring","13.04 EOL","i386","instance-store","20140111","ami-37c2ad36","aki-42992843"], ["ap-southeast-1","raring","13.04 EOL","amd64","ebs","20140111","ami-92aef9c0","aki-fe1354ac"], ["ap-southeast-1","raring","13.04 EOL","i386","ebs","20140111","ami-ecaef9be","aki-f81354aa"], ["ap-southeast-1","raring","13.04 EOL","amd64","instance-store","20140111","ami-c6aef994","aki-fe1354ac"], ["ap-southeast-1","raring","13.04 EOL","i386","instance-store","20140111","ami-0caef95e","aki-f81354aa"], ["ap-southeast-2","raring","13.04 EOL","amd64","ebs","20140111","ami-57f16f6d","aki-31990e0b"], ["ap-southeast-2","raring","13.04 EOL","i386","ebs","20140111","ami-51f16f6b","aki-33990e09"], ["ap-southeast-2","raring","13.04 EOL","amd64","instance-store","20140111","ami-53f16f69","aki-31990e0b"], ["ap-southeast-2","raring","13.04 EOL","i386","instance-store","20140111","ami-adf06e97","aki-33990e09"], ["eu-west-1","raring","13.04 EOL","amd64","hvm:ebs","20140111","ami-dca653ab","hvm"], ["eu-west-1","raring","13.04 EOL","amd64","ebs","20140111","ami-dea653a9","aki-71665e05"], ["eu-west-1","raring","13.04 EOL","i386","ebs","20140111","ami-d2a653a5","aki-75665e01"], ["eu-west-1","raring","13.04 EOL","amd64","instance-store","20140111","ami-1aa7526d","aki-71665e05"], ["eu-west-1","raring","13.04 EOL","i386","instance-store","20140111","ami-b2a451c5","aki-75665e01"], ["sa-east-1","raring","13.04 EOL","amd64","ebs","20140111","ami-4da10150","aki-c48f51d9"], ["sa-east-1","raring","13.04 EOL","i386","ebs","20140111","ami-53a1014e","aki-ca8f51d7"], ["sa-east-1","raring","13.04 EOL","amd64","instance-store","20140111","ami-29a10134","aki-c48f51d9"], ["sa-east-1","raring","13.04 EOL","i386","instance-store","20140111","ami-0fa10112","aki-ca8f51d7"], ["us-east-1","raring","13.04 EOL","amd64","hvm:ebs","20140111","ami-971524fe","hvm"], ["us-east-1","raring","13.04 EOL","amd64","ebs","20140111","ami-951524fc","aki-88aa75e1"], ["us-east-1","raring","13.04 EOL","i386","ebs","20140111","ami-931524fa","aki-b6aa75df"], ["us-east-1","raring","13.04 EOL","amd64","instance-store","20140111","ami-1d132274","aki-88aa75e1"], ["us-east-1","raring","13.04 EOL","i386","instance-store","20140111","ami-3d112054","aki-b6aa75df"], ["us-west-1","raring","13.04 EOL","amd64","ebs","20140111","ami-b0784af5","aki-f77e26b2"], ["us-west-1","raring","13.04 EOL","i386","ebs","20140111","ami-b6784af3","aki-f57e26b0"], ["us-west-1","raring","13.04 EOL","amd64","instance-store","20140111","ami-e4784aa1","aki-f77e26b2"], ["us-west-1","raring","13.04 EOL","i386","instance-store","20140111","ami-4c784a09","aki-f57e26b0"], ["us-west-2","raring","13.04 EOL","amd64","hvm:ebs","20140111","ami-38d6b008","hvm"], ["us-west-2","raring","13.04 EOL","amd64","ebs","20140111","ami-36d6b006","aki-fc37bacc"], ["us-west-2","raring","13.04 EOL","i386","ebs","20140111","ami-34d6b004","aki-fa37baca"], ["us-west-2","raring","13.04 EOL","amd64","instance-store","20140111","ami-7ac9af4a","aki-fc37bacc"], ["us-west-2","raring","13.04 EOL","i386","instance-store","20140111","ami-40c8ae70","aki-fa37baca"], ["ap-northeast-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-37092a59","hvm"], ["ap-northeast-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-08092a66","hvm"], ["ap-northeast-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-eb0a2985","hvm"], ["ap-northeast-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-37042759","hvm"], ["ap-northeast-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-69052607","aki-176bf516"], ["ap-northeast-1","trusty","14.04 LTS","i386","ebs","20151117","ami-68052606","aki-136bf512"], ["ap-northeast-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-6a052604","aki-176bf516"], ["ap-northeast-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-08042766","aki-136bf512"], ["ap-northeast-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-ae0a29c0","aki-176bf516"], ["ap-northeast-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-46042728","aki-136bf512"], ["ap-northeast-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-960526f8","aki-176bf516"], ["ap-northeast-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-4102212f","aki-136bf512"], ["ap-southeast-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-788e4f1b","hvm"], ["ap-southeast-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-1e8c4d7d","hvm"], ["ap-southeast-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-f78c4d94","hvm"], ["ap-southeast-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-798e4f1a","hvm"], ["ap-southeast-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-e88c4d8b","aki-503e7402"], ["ap-southeast-1","trusty","14.04 LTS","i386","ebs","20151117","ami-778f4e14","aki-ae3973fc"], ["ap-southeast-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-b48d4cd7","aki-503e7402"], ["ap-southeast-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-4b8e4f28","aki-ae3973fc"], ["ap-southeast-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-3a8f4e59","aki-503e7402"], ["ap-southeast-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-4e91502d","aki-ae3973fc"], ["ap-southeast-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-488f4e2b","aki-503e7402"], ["ap-southeast-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-7a8c4d19","aki-ae3973fc"], ["ap-southeast-2","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-b95d04da","hvm"], ["ap-southeast-2","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-ba5f06d9","hvm"], ["ap-southeast-2","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-a25a03c1","hvm"], ["ap-southeast-2","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-025c0561","hvm"], ["ap-southeast-2","trusty","14.04 LTS","amd64","ebs","20151117","ami-f05e0793","aki-c362fff9"], ["ap-southeast-2","trusty","14.04 LTS","i386","ebs","20151117","ami-015c0562","aki-cd62fff7"], ["ap-southeast-2","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-bb5f06d8","aki-c362fff9"], ["ap-southeast-2","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-945c05f7","aki-cd62fff7"], ["ap-southeast-2","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-425c0521","aki-c362fff9"], ["ap-southeast-2","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-ba5d04d9","aki-cd62fff7"], ["ap-southeast-2","trusty","14.04 LTS","amd64","instance-store","20151117","ami-4e5f062d","aki-c362fff9"], ["ap-southeast-2","trusty","14.04 LTS","i386","instance-store","20151117","ami-995d04fa","aki-cd62fff7"], ["eu-central-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-ba2230d6","hvm"], ["eu-central-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-e5223089","hvm"], ["eu-central-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-02392b6e","hvm"], ["eu-central-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-433c2e2f","hvm"], ["eu-central-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-bb2230d7","aki-184c7a05"], ["eu-central-1","trusty","14.04 LTS","i386","ebs","20151117","ami-842230e8","aki-3e4c7a23"], ["eu-central-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-643c2e08","aki-184c7a05"], ["eu-central-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-a53c2ec9","aki-3e4c7a23"], ["eu-central-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-a43c2ec8","aki-184c7a05"], ["eu-central-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-03392b6f","aki-3e4c7a23"], ["eu-central-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-613e2c0d","aki-184c7a05"], ["eu-central-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-4a3c2e26","aki-3e4c7a23"], ["eu-west-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-8d16ccfe","hvm"], ["eu-west-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-be10cacd","hvm"], ["eu-west-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-6514ce16","hvm"], ["eu-west-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-a113c9d2","hvm"], ["eu-west-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-9513c9e6","aki-52a34525"], ["eu-west-1","trusty","14.04 LTS","i386","ebs","20151117","ami-0014ce73","aki-68a3451f"], ["eu-west-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-d714cea4","aki-52a34525"], ["eu-west-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-5415cf27","aki-68a3451f"], ["eu-west-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-9613c9e5","aki-52a34525"], ["eu-west-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-f311cb80","aki-68a3451f"], ["eu-west-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-5910ca2a","aki-52a34525"], ["eu-west-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-601cc613","aki-68a3451f"], ["sa-east-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-6645ff0a","hvm"], ["sa-east-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-7a47fd16","hvm"], ["sa-east-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-9f4cf6f3","hvm"], ["sa-east-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-5f4df733","hvm"], ["sa-east-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-9d43f9f1","aki-5553f448"], ["sa-east-1","trusty","14.04 LTS","i386","ebs","20151117","ami-de4cf6b2","aki-5b53f446"], ["sa-east-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-894df7e5","aki-5553f448"], ["sa-east-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-1c45ff70","aki-5b53f446"], ["sa-east-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-f840fa94","aki-5553f448"], ["sa-east-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-6745ff0b","aki-5b53f446"], ["sa-east-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-444cf628","aki-5553f448"], ["sa-east-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-cc4bf1a0","aki-5b53f446"], ["us-east-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-0f8bce65","hvm"], ["us-east-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-b988cdd3","hvm"], ["us-east-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-7b89cc11","hvm"], ["us-east-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-9883c6f2","hvm"], ["us-east-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-7388cd19","aki-919dcaf8"], ["us-east-1","trusty","14.04 LTS","i386","ebs","20151117","ami-988bcef2","aki-8f9dcae6"], ["us-east-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-548acf3e","aki-919dcaf8"], ["us-east-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-578acf3d","aki-8f9dcae6"], ["us-east-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-0a8acf60","aki-919dcaf8"], ["us-east-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-9295d0f8","aki-8f9dcae6"], ["us-east-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-0a7a3f60","aki-919dcaf8"], ["us-east-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-2a6a2f40","aki-8f9dcae6"], ["us-west-1","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-f898f698","hvm"], ["us-west-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-c59bf5a5","hvm"], ["us-west-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-809df3e0","hvm"], ["us-west-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-769af416","hvm"], ["us-west-1","trusty","14.04 LTS","amd64","ebs","20151117","ami-5f9af43f","aki-880531cd"], ["us-west-1","trusty","14.04 LTS","i386","ebs","20151117","ami-839df3e3","aki-8e0531cb"], ["us-west-1","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-829df3e2","aki-880531cd"], ["us-west-1","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-289bf548","aki-8e0531cb"], ["us-west-1","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-4d99f72d","aki-880531cd"], ["us-west-1","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-059af465","aki-8e0531cb"], ["us-west-1","trusty","14.04 LTS","amd64","instance-store","20151117","ami-139ef073","aki-880531cd"], ["us-west-1","trusty","14.04 LTS","i386","instance-store","20151117","ami-cf9cf2af","aki-8e0531cb"], ["us-west-2","trusty","14.04 LTS","amd64","hvm:ebs","20151117","ami-534d5d32","hvm"], ["us-west-2","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151117","ami-9d4f5ffc","hvm"], ["us-west-2","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-d24c5cb3","hvm"], ["us-west-2","trusty","14.04 LTS","amd64","hvm:instance-store","20151117","ami-874a5ae6","hvm"], ["us-west-2","trusty","14.04 LTS","amd64","ebs","20151117","ami-e54f5f84","aki-fc8f11cc"], ["us-west-2","trusty","14.04 LTS","i386","ebs","20151117","ami-9c4f5ffd","aki-f08f11c0"], ["us-west-2","trusty","14.04 LTS","amd64","ebs-io1","20151117","ami-2b4e5e4a","aki-fc8f11cc"], ["us-west-2","trusty","14.04 LTS","i386","ebs-io1","20151117","ami-a74b5bc6","aki-f08f11c0"], ["us-west-2","trusty","14.04 LTS","amd64","ebs-ssd","20151117","ami-d04c5cb1","aki-fc8f11cc"], ["us-west-2","trusty","14.04 LTS","i386","ebs-ssd","20151117","ami-06706067","aki-f08f11c0"], ["us-west-2","trusty","14.04 LTS","amd64","instance-store","20151117","ami-b04959d1","aki-fc8f11cc"], ["us-west-2","trusty","14.04 LTS","i386","instance-store","20151117","ami-ce4454af","aki-f08f11c0"], ["cn-north-1","utopic","14.10 EOL","amd64","hvm:ebs","20150708","ami-9871eca1","hvm"], ["cn-north-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150708","ami-9071eca9","hvm"], ["cn-north-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150708","ami-9671ecaf","hvm"], ["cn-north-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150708","ami-3671ec0f","hvm"], ["cn-north-1","utopic","14.10 EOL","amd64","ebs","20150708","ami-7a71ec43","aki-9e8f1da7"], ["cn-north-1","utopic","14.10 EOL","i386","ebs","20150708","ami-1a71ec23","aki-908f1da9"], ["cn-north-1","utopic","14.10 EOL","amd64","ebs-io1","20150708","ami-4c71ec75","aki-9e8f1da7"], ["cn-north-1","utopic","14.10 EOL","i386","ebs-io1","20150708","ami-5871ec61","aki-908f1da9"], ["cn-north-1","utopic","14.10 EOL","amd64","ebs-ssd","20150708","ami-b671ec8f","aki-9e8f1da7"], ["cn-north-1","utopic","14.10 EOL","i386","ebs-ssd","20150708","ami-bc71ec85","aki-908f1da9"], ["cn-north-1","utopic","14.10 EOL","amd64","instance-store","20150708","ami-8c76ebb5","aki-9e8f1da7"], ["cn-north-1","utopic","14.10 EOL","i386","instance-store","20150708","ami-6876eb51","aki-908f1da9"], ["cn-north-1","lucid","10.04 LTS","amd64","ebs","20150427","ami-8ccd50b5","aki-9e8f1da7"], ["cn-north-1","lucid","10.04 LTS","i386","ebs","20150427","ami-8acd50b3","aki-908f1da9"], ["cn-north-1","lucid","10.04 LTS","amd64","ebs-io1","20150427","ami-80cd50b9","aki-9e8f1da7"], ["cn-north-1","lucid","10.04 LTS","i386","ebs-io1","20150427","ami-8ecd50b7","aki-908f1da9"], ["cn-north-1","lucid","10.04 LTS","amd64","ebs-ssd","20150427","ami-86cd50bf","aki-9e8f1da7"], ["cn-north-1","lucid","10.04 LTS","i386","ebs-ssd","20150427","ami-84cd50bd","aki-908f1da9"], ["cn-north-1","lucid","10.04 LTS","amd64","instance-store","20150427","ami-88cd50b1","aki-9e8f1da7"], ["cn-north-1","lucid","10.04 LTS","i386","instance-store","20150427","ami-96cd50af","aki-908f1da9"], ["cn-north-1","precise","12.04 LTS","amd64","hvm:ebs","20151117","ami-a3ed24ce","hvm"], ["cn-north-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20151117","ami-a2ed24cf","hvm"], ["cn-north-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20151117","ami-cceb22a1","hvm"], ["cn-north-1","precise","12.04 LTS","amd64","hvm:instance-store","20151117","ami-17ef267a","hvm"], ["cn-north-1","precise","12.04 LTS","amd64","ebs","20151117","ami-5be12836","aki-9e8f1da7"], ["cn-north-1","precise","12.04 LTS","i386","ebs","20151117","ami-73ee271e","aki-908f1da9"], ["cn-north-1","precise","12.04 LTS","amd64","ebs-io1","20151117","ami-01ee276c","aki-9e8f1da7"], ["cn-north-1","precise","12.04 LTS","i386","ebs-io1","20151117","ami-9ded24f0","aki-908f1da9"], ["cn-north-1","precise","12.04 LTS","amd64","ebs-ssd","20151117","ami-67e32a0a","aki-9e8f1da7"], ["cn-north-1","precise","12.04 LTS","i386","ebs-ssd","20151117","ami-19ef2674","aki-908f1da9"], ["cn-north-1","precise","12.04 LTS","amd64","instance-store","20151117","ami-5eee2733","aki-9e8f1da7"], ["cn-north-1","precise","12.04 LTS","i386","instance-store","20151117","ami-89e128e4","aki-908f1da9"], ["cn-north-1","vivid","15.04L","amd64","hvm:ebs","20151117","ami-4ce12821","hvm"], ["cn-north-1","vivid","15.04L","amd64","hvm:ebs-io1","20151117","ami-0fea2362","hvm"], ["cn-north-1","vivid","15.04L","amd64","hvm:ebs-ssd","20151117","ami-95ed24f8","hvm"], ["cn-north-1","vivid","15.04L","amd64","hvm:instance-store","20151117","ami-14eb2279","hvm"], ["cn-north-1","vivid","15.04L","amd64","ebs","20151117","ami-47ee272a","aki-9e8f1da7"], ["cn-north-1","vivid","15.04L","amd64","ebs-io1","20151117","ami-4aed2427","aki-9e8f1da7"], ["cn-north-1","vivid","15.04L","amd64","ebs-ssd","20151117","ami-0aef2667","aki-9e8f1da7"], ["cn-north-1","vivid","15.04L","amd64","instance-store","20151117","ami-48ed2425","aki-9e8f1da7"], ["cn-north-1","wily","15.10","amd64","hvm:ebs","20151116.1","ami-cfd41da2","hvm"], ["cn-north-1","wily","15.10","amd64","hvm:ebs-io1","20151116.1","ami-74eb2219","hvm"], ["cn-north-1","wily","15.10","amd64","hvm:ebs-ssd","20151116.1","ami-79eb2214","hvm"], ["cn-north-1","wily","15.10","amd64","hvm:instance-store","20151116.1","ami-87d51cea","hvm"], ["cn-north-1","wily","15.10","amd64","ebs","20151116.1","ami-8bd019e6","aki-9e8f1da7"], ["cn-north-1","wily","15.10","amd64","ebs-io1","20151116.1","ami-91de17fc","aki-9e8f1da7"], ["cn-north-1","wily","15.10","amd64","ebs-ssd","20151116.1","ami-a1d21bcc","aki-9e8f1da7"], ["cn-north-1","wily","15.10","amd64","instance-store","20151116.1","ami-e1d0198c","aki-9e8f1da7"], ["cn-north-1","saucy","13.10 EOL","amd64","hvm:ebs","20140709","ami-86a331bf","hvm"], ["cn-north-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140709","ami-f8a331c1","hvm"], ["cn-north-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140709","ami-faa331c3","hvm"], ["cn-north-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140709","ami-88a331b1","hvm"], ["cn-north-1","saucy","13.10 EOL","amd64","ebs","20140709","ami-8ca331b5","aki-9e8f1da7"], ["cn-north-1","saucy","13.10 EOL","i386","ebs","20140709","ami-8aa331b3","aki-908f1da9"], ["cn-north-1","saucy","13.10 EOL","amd64","ebs-io1","20140709","ami-80a331b9","aki-9e8f1da7"], ["cn-north-1","saucy","13.10 EOL","i386","ebs-io1","20140709","ami-8ea331b7","aki-908f1da9"], ["cn-north-1","saucy","13.10 EOL","amd64","ebs-ssd","20140709","ami-84a331bd","aki-9e8f1da7"], ["cn-north-1","saucy","13.10 EOL","i386","ebs-ssd","20140709","ami-82a331bb","aki-908f1da9"], ["cn-north-1","saucy","13.10 EOL","amd64","instance-store","20140709","ami-96a331af","aki-9e8f1da7"], ["cn-north-1","saucy","13.10 EOL","i386","instance-store","20140709","ami-94a331ad","aki-908f1da9"], ["cn-north-1","trusty","14.04 LTS","amd64","hvm:ebs","20151015","ami-4264f87b","hvm"], ["cn-north-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151015","ami-aa64f893","hvm"], ["cn-north-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151015","ami-a664f89f","hvm"], ["cn-north-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151015","ami-e66bf7df","hvm"], ["cn-north-1","trusty","14.04 LTS","amd64","ebs","20151015","ami-d66bf7ef","aki-9e8f1da7"], ["cn-north-1","trusty","14.04 LTS","i386","ebs","20151015","ami-da6bf7e3","aki-908f1da9"], ["cn-north-1","trusty","14.04 LTS","amd64","ebs-io1","20151015","ami-0664f83f","aki-9e8f1da7"], ["cn-north-1","trusty","14.04 LTS","i386","ebs-io1","20151015","ami-2264f81b","aki-908f1da9"], ["cn-north-1","trusty","14.04 LTS","amd64","ebs-ssd","20151015","ami-5864f861","aki-9e8f1da7"], ["cn-north-1","trusty","14.04 LTS","i386","ebs-ssd","20151015","ami-7264f84b","aki-908f1da9"], ["cn-north-1","trusty","14.04 LTS","amd64","instance-store","20151015","ami-f46bf7cd","aki-9e8f1da7"], ["cn-north-1","trusty","14.04 LTS","i386","instance-store","20151015","ami-906bf7a9","aki-908f1da9"], ["us-gov-west-1","utopic","14.10 EOL","amd64","hvm:ebs","20150620","ami-25fc9c06","hvm"], ["us-gov-west-1","utopic","14.10 EOL","amd64","hvm:ebs-io1","20150620","ami-2bfc9c08","hvm"], ["us-gov-west-1","utopic","14.10 EOL","amd64","hvm:ebs-ssd","20150620","ami-33fc9c10","hvm"], ["us-gov-west-1","utopic","14.10 EOL","amd64","hvm:instance-store","20150620","ami-cdfb9bee","hvm"], ["us-gov-west-1","utopic","14.10 EOL","amd64","ebs","20150620","ami-dbfb9bf8","aki-1de98d3e"], ["us-gov-west-1","utopic","14.10 EOL","i386","ebs","20150620","ami-d7fb9bf4","aki-1fe98d3c"], ["us-gov-west-1","utopic","14.10 EOL","amd64","ebs-io1","20150620","ami-dffb9bfc","aki-1de98d3e"], ["us-gov-west-1","utopic","14.10 EOL","i386","ebs-io1","20150620","ami-d9fb9bfa","aki-1fe98d3c"], ["us-gov-west-1","utopic","14.10 EOL","amd64","ebs-ssd","20150620","ami-27fc9c04","aki-1de98d3e"], ["us-gov-west-1","utopic","14.10 EOL","i386","ebs-ssd","20150620","ami-21fc9c02","aki-1fe98d3c"], ["us-gov-west-1","utopic","14.10 EOL","amd64","instance-store","20150620","ami-cbfb9be8","aki-1de98d3e"], ["us-gov-west-1","utopic","14.10 EOL","i386","instance-store","20150620","ami-fdfb9bde","aki-1fe98d3c"], ["us-gov-west-1","lucid","10.04 LTS","amd64","ebs","20150127","ami-61375642","aki-1de98d3e"], ["us-gov-west-1","lucid","10.04 LTS","i386","ebs","20150127","ami-63375640","aki-1fe98d3c"], ["us-gov-west-1","lucid","10.04 LTS","amd64","ebs-io1","20150127","ami-65375646","aki-1de98d3e"], ["us-gov-west-1","lucid","10.04 LTS","i386","ebs-io1","20150127","ami-67375644","aki-1fe98d3c"], ["us-gov-west-1","lucid","10.04 LTS","amd64","ebs-ssd","20150127","ami-6937564a","aki-1de98d3e"], ["us-gov-west-1","lucid","10.04 LTS","i386","ebs-ssd","20150127","ami-6b375648","aki-1fe98d3c"], ["us-gov-west-1","lucid","10.04 LTS","amd64","instance-store","20150127","ami-973253b4","aki-1de98d3e"], ["us-gov-west-1","lucid","10.04 LTS","i386","instance-store","20150127","ami-3f32531c","aki-1fe98d3c"], ["us-gov-west-1","precise","12.04 LTS","amd64","hvm:ebs","20150930","ami-6f70124c","hvm"], ["us-gov-west-1","precise","12.04 LTS","amd64","hvm:ebs-io1","20150930","ami-6d70124e","hvm"], ["us-gov-west-1","precise","12.04 LTS","amd64","hvm:ebs-ssd","20150930","ami-71701252","hvm"], ["us-gov-west-1","precise","12.04 LTS","amd64","hvm:instance-store","20150930","ami-17701234","hvm"], ["us-gov-west-1","precise","12.04 LTS","amd64","ebs","20150930","ami-1f70123c","aki-1de98d3e"], ["us-gov-west-1","precise","12.04 LTS","i386","ebs","20150930","ami-1b701238","aki-1fe98d3c"], ["us-gov-west-1","precise","12.04 LTS","amd64","ebs-io1","20150930","ami-63701240","aki-1de98d3e"], ["us-gov-west-1","precise","12.04 LTS","i386","ebs-io1","20150930","ami-1d70123e","aki-1fe98d3c"], ["us-gov-west-1","precise","12.04 LTS","amd64","ebs-ssd","20150930","ami-67701244","aki-1de98d3e"], ["us-gov-west-1","precise","12.04 LTS","i386","ebs-ssd","20150930","ami-61701242","aki-1fe98d3c"], ["us-gov-west-1","precise","12.04 LTS","amd64","instance-store","20150930","ami-11701232","aki-1de98d3e"], ["us-gov-west-1","precise","12.04 LTS","i386","instance-store","20150930","ami-13701230","aki-1fe98d3c"], ["us-gov-west-1","wily","15.10","amd64","hvm:ebs","20151029","ami-aebad88d","hvm"], ["us-gov-west-1","wily","15.10","amd64","hvm:ebs-io1","20151029","ami-acbad88f","hvm"], ["us-gov-west-1","wily","15.10","amd64","hvm:ebs-ssd","20151029","ami-b0bad893","hvm"], ["us-gov-west-1","wily","15.10","amd64","hvm:instance-store","20151029","ami-1cbad83f","hvm"], ["us-gov-west-1","wily","15.10","amd64","ebs","20151029","ami-60bad843","aki-1de98d3e"], ["us-gov-west-1","wily","15.10","amd64","ebs-io1","20151029","ami-66bad845","aki-1de98d3e"], ["us-gov-west-1","wily","15.10","amd64","ebs-ssd","20151029","ami-a8bad88b","aki-1de98d3e"], ["us-gov-west-1","wily","15.10","amd64","instance-store","20151029","ami-0cbad82f","aki-1de98d3e"], ["us-gov-west-1","saucy","13.10 EOL","amd64","hvm:ebs","20140608","ami-034c2b20","hvm"], ["us-gov-west-1","saucy","13.10 EOL","amd64","hvm:ebs-io1","20140608","ami-6f5a3d4c","hvm"], ["us-gov-west-1","saucy","13.10 EOL","amd64","hvm:ebs-ssd","20140608","ami-73563150","hvm"], ["us-gov-west-1","saucy","13.10 EOL","amd64","hvm:instance-store","20140608","ami-374c2b14","hvm"], ["us-gov-west-1","saucy","13.10 EOL","amd64","ebs","20140608","ami-3d4c2b1e","aki-1de98d3e"], ["us-gov-west-1","saucy","13.10 EOL","i386","ebs","20140608","ami-3f4c2b1c","aki-1fe98d3c"], ["us-gov-west-1","saucy","13.10 EOL","amd64","ebs-io1","20140608","ami-655a3d46","aki-1de98d3e"], ["us-gov-west-1","saucy","13.10 EOL","i386","ebs-io1","20140608","ami-635a3d40","aki-1fe98d3c"], ["us-gov-west-1","saucy","13.10 EOL","amd64","ebs-ssd","20140608","ami-6b563148","aki-1de98d3e"], ["us-gov-west-1","saucy","13.10 EOL","i386","ebs-ssd","20140608","ami-61563142","aki-1fe98d3c"], ["us-gov-west-1","saucy","13.10 EOL","amd64","instance-store","20140608","ami-314c2b12","aki-1de98d3e"], ["us-gov-west-1","saucy","13.10 EOL","i386","instance-store","20140608","ami-334c2b10","aki-1fe98d3c"], ["us-gov-west-1","trusty","14.04 LTS","amd64","hvm:ebs","20151019","ami-d6bbd9f5","hvm"], ["us-gov-west-1","trusty","14.04 LTS","amd64","hvm:ebs-io1","20151019","ami-22b8da01","hvm"], ["us-gov-west-1","trusty","14.04 LTS","amd64","hvm:ebs-ssd","20151019","ami-30b8da13","hvm"], ["us-gov-west-1","trusty","14.04 LTS","amd64","hvm:instance-store","20151019","ami-e2bbd9c1","hvm"], ["us-gov-west-1","trusty","14.04 LTS","amd64","ebs","20151019","ami-ecbbd9cf","aki-1de98d3e"], ["us-gov-west-1","trusty","14.04 LTS","i386","ebs","20151019","ami-e6bbd9c5","aki-1fe98d3c"], ["us-gov-west-1","trusty","14.04 LTS","amd64","ebs-io1","20151019","ami-f6bbd9d5","aki-1de98d3e"], ["us-gov-west-1","trusty","14.04 LTS","i386","ebs-io1","20151019","ami-f0bbd9d3","aki-1fe98d3c"], ["us-gov-west-1","trusty","14.04 LTS","amd64","ebs-ssd","20151019","ami-f8bbd9db","aki-1de98d3e"], ["us-gov-west-1","trusty","14.04 LTS","i386","ebs-ssd","20151019","ami-fabbd9d9","aki-1fe98d3c"], ["us-gov-west-1","trusty","14.04 LTS","amd64","instance-store","20151019","ami-5cbbd97f","aki-1de98d3e"], ["us-gov-west-1","trusty","14.04 LTS","i386","instance-store","20151019","ami-58bbd97b","aki-1fe98d3c"], ] } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/cmd/apt.libsonnet ================================================ local pip = import 'pip.libsonnet'; { local libos = self, // A mixin on top of an image or instance that allows high level installation of apt packages. Mixin:: { aptSourcesListDir:: '/etc/apt/sources.list.d', aptKeyUrls:: [], // { foo: "..." } will add a foo.list containing the given content. aptRepoLines:: {}, // { foo: "..." } will add a foo.list fetched from the given URL. aptRepoUrls:: {}, // Add the packages you want to install here. aptPackages:: [], local repoLineCommands = [ 'echo "%s" > %s/%s.list' % [self.aptRepoLines[k], self.aptSourcesListDir, k] for k in std.objectFields(self.aptRepoLines) ], local repoUrlCommands = [ 'curl -o %s/%s.list %s' % [self.aptSourcesListDir, k, self.aptRepoUrls[k]] for k in std.objectFields(self.aptRepoUrls) ], local keyCommands = ['curl --silent %s | apt-key add -' % [url] for url in self.aptKeyUrls], local env = 'DEBIAN_FRONTEND=noninteractive', local opts = '-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold', local updateCommands = ['apt-get -qq -y update'], local installCommands = [ '%s apt-get -qq -y %s install %s' % [env, opts, std.join(' ', self.aptPackages)], ], cmds+: repoLineCommands + repoUrlCommands + keyCommands + updateCommands + installCommands, }, // TODO: This would work better as an AptPipGlue mixin instead of extending Mixin. // Similar to `Mixin` above but also installs pip if you have any pip packages. PipMixin:: pip.Mixin { aptPackages+: if std.length(self.pipPackages) == 0 then [] else ['python-pip'], }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/cmd/cmd.libsonnet ================================================ { local HasPermissions = { owner: 'root', group: self.owner, }, local FilePermissions = { filePermissions: '0664', }, local DirPermissions = { dirPermissions: '0775', }, local AbstractFile = HasPermissions + FilePermissions { to: error self.kind + " must have 'to'", }, CopyFile:: AbstractFile + DirPermissions { kind: 'CopyFile', from: error self.kind + " must have 'from'", }, LiteralFile:: AbstractFile { kind: 'LiteralFile', content: error self.kind + " must have 'content'", }, EnsureDir:: HasPermissions + DirPermissions { kind: 'EnsureDir', dir: error self.kind + " must have 'dir'", }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/cmd/pip.libsonnet ================================================ { // A mixin on top of an image or instance that allows high level installation of pip packages. Mixin:: { // Add pip packages here. pipPackages:: [], cmds+: if std.length(self.pipPackages) == 0 then [] else ['pip install ' + std.join(' ', self.pipPackages)], }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/db/cassandra.libsonnet ================================================ local service_google = import '../service/google.libsonnet'; // TODO(dcunnin): Separate into 5 mixins: // 1) mixins that sets up cassandra via 1) grabbing JAR from cassandra.org, 2) using Debian // 2) mixins that sets up infrastructure on 1) GCP 2) AWS // 3) mixin that does the common stuff (different types of nodes, cassandra bootstrap) { // Unchanged from Cassandra distribution local default_conf = { authenticator: 'AllowAllAuthenticator', authorizer: 'AllowAllAuthorizer', auto_snapshot: true, batch_size_warn_threshold_in_kb: 5, batchlog_replay_throttle_in_kb: 1024, cas_contention_timeout_in_ms: 1000, client_encryption_options: { enabled: false, keystore: 'conf/.keystore', keystore_password: 'cassandra', }, cluster_name: 'Unnamed Cluster', column_index_size_in_kb: 64, commit_failure_policy: 'stop', commitlog_directory: '/var/lib/cassandra/commitlog', commitlog_segment_size_in_mb: 32, commitlog_sync: 'periodic', commitlog_sync_period_in_ms: 10000, compaction_throughput_mb_per_sec: 16, concurrent_counter_writes: 32, concurrent_reads: 32, concurrent_writes: 32, counter_cache_save_period: 7200, counter_cache_size_in_mb: null, counter_write_request_timeout_in_ms: 5000, cross_node_timeout: false, data_file_directories: ['/var/lib/cassandra/data'], disk_failure_policy: 'stop', dynamic_snitch_badness_threshold: 0.1, dynamic_snitch_reset_interval_in_ms: 600000, dynamic_snitch_update_interval_in_ms: 100, endpoint_snitch: 'SimpleSnitch', hinted_handoff_enabled: true, hinted_handoff_throttle_in_kb: 1024, incremental_backups: false, index_summary_capacity_in_mb: null, index_summary_resize_interval_in_minutes: 60, inter_dc_tcp_nodelay: false, internode_compression: 'all', key_cache_save_period: 14400, key_cache_size_in_mb: null, listen_address: 'localhost', max_hint_window_in_ms: 10800000, max_hints_delivery_threads: 2, memtable_allocation_type: 'heap_buffers', native_transport_port: 9042, num_tokens: 256, partitioner: 'org.apache.cassandra.dht.Murmur3Partitioner', permissions_validity_in_ms: 2000, range_request_timeout_in_ms: 10000, read_request_timeout_in_ms: 5000, request_scheduler: 'org.apache.cassandra.scheduler.NoScheduler', request_timeout_in_ms: 10000, row_cache_save_period: 0, row_cache_size_in_mb: 0, rpc_address: 'localhost', rpc_keepalive: true, rpc_port: 9160, rpc_server_type: 'sync', saved_caches_directory: '/var/lib/cassandra/saved_caches', seed_provider: [ { class_name: 'org.apache.cassandra.locator.SimpleSeedProvider', parameters: [{ seeds: '127.0.0.1' }], }, ], server_encryption_options: { internode_encryption: 'none', keystore: 'conf/.keystore', keystore_password: 'cassandra', truststore: 'conf/.truststore', truststore_password: 'cassandra', }, snapshot_before_compaction: false, ssl_storage_port: 7001, sstable_preemptive_open_interval_in_mb: 50, start_native_transport: true, start_rpc: true, storage_port: 7000, thrift_framed_transport_size_in_mb: 15, tombstone_failure_threshold: 100000, tombstone_warn_threshold: 1000, trickle_fsync: false, trickle_fsync_interval_in_kb: 10240, truncate_request_timeout_in_ms: 60000, write_request_timeout_in_ms: 2000, }, // A line of bash that will wait for a Cassandra service to be "up". local wait_for_cqlsh(user, pass, host) = 'while ! echo show version | cqlsh -u %s -p %s %s ; do sleep 1; done' % [user, pass, host], // A Cassandra database service. This supports: // - Installation of Cassandra // - Bootstrapping of the database in a secure manner // - Configuring a replicated database // - Opening all ports through the firewall // - Jmx monitoring for Cassandra // - Independent management of the boot disks, to allow recreation of the instances without // losing the disks. // TODO: changing the base image still replaces the disk, losing the data. Using a non-OS // disk for the database would fix this. // // How to use: // Create a service with a single StarterNode with initReplicationFactor set. The rest are // TopUpNode. Don't use the instance in the Instance field, although you can extend it in order to // add more base config that should be applied to both StarterNode and TopUpNode. Then, when it's // up and running, you can replace the StarterNode with a TopUpNode. // // cassandra.GcpDebianCassandra(..., ...) { // ... // nodes: { // n1: StarterNode { // initReplicationFactor: 3, // zone: "us-central1-b", // }, // n2: TopUpNode { // zone: "us-central1-c", // }, // // n3: TopUpNode { // zone: "us-central1-f", // }, // }, // } GcpDebianCassandra(outer, name): service_google.InstanceBasedService(outer, name) { local service = self, // Set the root password here. rootPassword:: error 'Cassandra Service must have field: rootPassword', // This is the name Cassandra uses to identify peers that form quorum on the same database. // It can be overridden but this default seems reasonable. clusterName:: self.fullName, cassandraConf:: default_conf { authenticator: 'PasswordAuthenticator', cluster_name: service.clusterName, }, nodes:: error 'Configure nodes for the cassandra database.', gossipPorts:: ['7000', '7001', '7199'], // Only between this pool. fwTcpPorts+: ['9042', '9160'], Instance+: { local node = self, // Beefier machine type, since this is Java. machine_type: 'n1-standard-1', rootPassword:: service.rootPassword, conf:: service.cassandraConf, // Aside from installing Cassandra, we initialise the database using a bootstrap configuration // and some simple SQL to set the root password. Further configuration is done not in Packer // but as the Instances boot for the first time. StandardRootImage+: { aptKeyUrls+: ['https://www.apache.org/dist/cassandra/KEYS'], aptRepoLines+: { cassandra: 'deb https://www.apache.org/dist/cassandra/debian 311x main', }, aptPackages+: ['cassandra'], local bootstrap_conf = default_conf { authenticator: 'PasswordAuthenticator', }, cmds+: [ '# Shut it down', '/etc/init.d/cassandra stop', '# Remove junk from unconfigured startup', 'rm -rfv /var/lib/cassandra/*', '# Enable authentication', local dest = '/etc/cassandra/cassandra.yaml'; 'echo %s > %s' % [std.escapeStringBash('' + bootstrap_conf), dest], '# Start it up again (for some reason "start" does not do anything...)', '/etc/init.d/cassandra restart', '# Wait for it to be ready', wait_for_cqlsh('cassandra', 'cassandra', 'localhost'), '# Set root password.', local cql = "ALTER USER cassandra WITH PASSWORD '%s';" % node.rootPassword; 'echo %s | cqlsh -u cassandra -p cassandra' % std.escapeStringBash(cql), ], }, enableMonitoring: self.supportsMonitoring, enableJmxMonitoring: self.supportsJmxMonitoring, jmxHost: 'localhost', jmxPort: 7199, jmxLocalhostConfig+: { SdCassQuery:: self.SdQuery { attr: [ 'ActiveCount', 'CompletedTasks', 'CurrentlyBlockedTasks', 'PendingTasks', ], }, queries+: [ self.SdQuery { resultAlias: 'cassandra.storageservice', obj: 'org.apache.cassandra.db:type=StorageService', attr: ['Load', 'ExceptionCount'], }, self.SdQuery { resultAlias: 'cassandra.commitlog', obj: 'org.apache.cassandra.db:type=Commitlog', attr: ['CompletedTasks', 'PendingTasks', 'TotalCommitlogSize'], }, self.SdQuery { resultAlias: 'cassandra.compactionmanager', obj: 'org.apache.cassandra.db:type=CompactionManager', attr: ['PendingTasks', 'CompletedTasks'], }, self.SdQuery { resultAlias: 'cassandra.stage.MutationStage', obj: 'org.apache.cassandra.request:type=MutationStage', }, self.SdCassQuery { resultAlias: 'cassandra.stage.ReadRepairStage', obj: 'org.apache.cassandra.request:type=ReadRepairStage', }, self.SdCassQuery { resultAlias: 'cassandra.stage.ReadStage', obj: 'org.apache.cassandra.request:type=ReadStage', }, self.SdCassQuery { resultAlias: 'cassandra.stage.ReplicateOnWriteStage', obj: 'org.apache.cassandra.request:type=ReplicateOnWriteStage', }, self.SdCassQuery { resultAlias: 'cassandra.stage.RequestResponseStage', obj: 'org.apache.cassandra.request:type=RequestResponseStage', }, self.SdCassQuery { resultAlias: 'cassandra.internal.AntiEntropySessions', obj: 'org.apache.cassandra.internal:type=AntiEntropySessions', }, self.SdCassQuery { resultAlias: 'cassandra.internal.AntiEntropyStage', obj: 'org.apache.cassandra.internal:type=AntiEntropyStage', }, self.SdCassQuery { resultAlias: 'cassandra.internal.FlushWriter', obj: 'org.apache.cassandra.internal:type=FlushWriter', }, self.SdCassQuery { resultAlias: 'cassandra.internal.GossipStage', obj: 'org.apache.cassandra.internal:type=GossipStage', }, self.SdCassQuery { resultAlias: 'cassandra.internal.HintedHandoff', obj: 'org.apache.cassandra.internal:type=HintedHandoff', }, self.SdCassQuery { resultAlias: 'cassandra.internal.InternalResponseStage', obj: 'org.apache.cassandra.internal:type=InternalResponseStage', }, self.SdCassQuery { resultAlias: 'cassandra.internal.MemtablePostFlusher', obj: 'org.apache.cassandra.internal:type=MemtablePostFlusher', }, self.SdCassQuery { resultAlias: 'cassandra.internal.MigrationStage', obj: 'org.apache.cassandra.internal:type=MigrationStage', }, self.SdCassQuery { resultAlias: 'cassandra.internal.MiscStage', obj: 'org.apache.cassandra.internal:type=MiscStage', }, self.SdCassQuery { resultAlias: 'cassandra.internal.StreamStage', obj: 'org.apache.cassandra.internal:type=StreamStage', }, self.SdQuery { resultAlias: 'cassandra.internal.StorageProxy', obj: 'org.apache.cassandra.db:type=StorageProxy', attr: [ 'RecentReadLatencyMicros', 'RecentWriteLatencyMicros', 'RecentRangeLatencyMicros', 'HintsInProgress', ], }, ], }, enableLogging: self.supportsLogging, }, // Bootstraps the database. // When the database initially boots, it is password protected with rootPassword, but is not // configured otherwise. We wait for it to start, do some basic configuration and then // restart it with version.conf installed. StarterNode:: self.Instance { initReplicationFactor:: error "Needs 'initReplicationFactor'", initCql:: [], initReplication:: "{ 'class' : 'SimpleStrategy', 'replication_factor' : %d }" % self.initReplicationFactor, initAuthReplication:: self.initReplication, cmds+: [ '# Wait for the misconfigured cassandra to start up.', wait_for_cqlsh('cassandra', self.rootPassword, 'localhost'), '# Change the cluster name.', 'echo %s | cqlsh -u cassandra -p %s localhost' % [ std.escapeStringBash( "UPDATE system.local SET cluster_name = '%s' where key='local';" % self.conf.cluster_name ), self.rootPassword, ], '# Set up system_auth replication level.', 'echo %s | cqlsh -u cassandra -p %s localhost' % [ std.escapeStringBash('ALTER KEYSPACE system_auth WITH REPLICATION = %s;' % self.initAuthReplication), self.rootPassword, ], '# Drop in the correct configuration.', 'echo %s > %s' % [std.escapeStringBash('' + self.conf), '/etc/cassandra/cassandra.yaml'], '# Restart with new configuration.', '/etc/init.d/cassandra restart', '# Wait for the properly configured cassandra to start up and reach quorum.', wait_for_cqlsh('cassandra', self.rootPassword, '$HOSTNAME'), // See https://issues.apache.org/jira/browse/CASSANDRA-11942 // This was added because otherwise self.initCql cannot set up users. 'sleep 10', '# Set up users, empty tables, etc.', 'echo %s | cqlsh -u cassandra -p %s $HOSTNAME' % [std.escapeStringBash(std.lines(self.initCql)), self.rootPassword], ], }, // Simply restarts with the given config. The assumption is that when it starts it will join // with seeds and initialize itself, including receiving any replicated copies of the data it // needs. TopUpNode:: self.Instance { cmds+: [ // Wait for the misconfigured cassandra to start up. wait_for_cqlsh('cassandra', self.rootPassword, 'localhost'), // Kill it. '/etc/init.d/cassandra stop', // Clean up the mess it caused due to being misconfigured. 'rm -rf /var/lib/cassandra/*', // Drop in the correct configuration. 'echo %s > %s' % [std.escapeStringBash('' + self.conf), '/etc/cassandra/cassandra.yaml'], // Start it up again. '/etc/init.d/cassandra restart', ], }, infrastructure+: { google_compute_disk: { [service.prefixName(n)]: { name: service.prefixName(n), image: service.nodes[n].StandardRootImage, zone: service.nodes[n].zone, } for n in std.objectFields(service.nodes) }, google_compute_instance: { [service.prefixName(n)]: service.nodes[n] { local instance = self, name: service.prefixName(n), networkName: service.networkName, tags+: [service.clusterName], boot_disk: { source: '${google_compute_disk.%s.name}' % instance.name, auto_delete: false, }, } for n in std.objectFields(service.nodes) }, }, }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/service/amazon.libsonnet ================================================ local amis_debian = import '../amis/debian.libsonnet'; local apt = import '../cmd/apt.libsonnet'; local cmd = import '../cmd/cmd.libsonnet'; local base = import 'base.libsonnet'; { Credentials:: { kind: 'Amazon', accessKey: error "Amazon credentials must have 'accessKey'", secretKey: error "Amazon credentials must have 'secretKey'", region: 'us-east-1', }, Image:: { sourceAmi: error "Amazon AMI must have 'sourceAmi'", instanceType: 't2.small', sshUser: error "Amazon AMI must have 'sshUser'", cmds: [], }, DebianImage:: $.Image + apt.Mixin + apt.PipMixin { sourceAmi: amis_debian.wheezy.amd64['20150128']['us-west-1'], sshUser: 'admin', }, StandardInstance:: { local instance = self, instance_type: 't2.small', ami: instance.StandardRootImage, associate_public_ip_address: true, cmds: [], bootCmds: [], tags: {}, // TODO(dcunnin): Figure out an equivalent here. supportsLogging:: false, supportsMonitoring:: false, supportsJmxMonitoring:: false, enableLogging:: false, enableMonitoring:: false, enableJmxMonitoring:: false, MonitoringLoggingImageMixin:: { }, StandardRootImage:: $.DebianImage + instance.MonitoringLoggingImageMixin, }, Service(parent, name):: base.Service(parent, name) { // TODO: Automatic registration of domain names for instances and addresses. }, Network(parent, name):: $.Service(parent, name) { local service = self, idRef:: '${aws_vpc.%s.id}' % service.fullName, nameRef:: '${aws_vpc.%s.name}' % service.fullName, subnetIdRef(zone):: '${aws_subnet.%s-%s.id}' % [service.fullName, zone], ipv4Range:: '10.0.0.0/16', infrastructure: { aws_vpc: { [service.fullName]: { cidr_block: service.ipv4Range, }, }, aws_internet_gateway: { [service.fullName]: { vpc_id: '${aws_vpc.%s.id}' % service.fullName, }, }, aws_route_table: { [service.fullName]: { vpc_id: '${aws_vpc.%s.id}' % service.fullName, route: { cidr_block: '0.0.0.0/0', gateway_id: '${aws_internet_gateway.%s.id}' % service.fullName, }, }, }, aws_subnet: { [service.prefixName(zone)]: { vpc_id: '${aws_vpc.%s.id}' % service.fullName, cidr_block: service.subnets[zone], availability_zone: zone, } for zone in std.objectFields(service.subnets) }, aws_route_table_association: { [service.prefixName(zone)]: { subnet_id: '${aws_subnet.%s.id}' % service.prefixName(zone), route_table_id: '${aws_route_table.%s.id}' % service.fullName, } for zone in std.objectFields(service.subnets) }, }, // Maps zone to CIDR. subnets:: {}, }, InstanceBasedService(parent, name): $.Service(parent, name) { local service = self, fwTcpPorts:: [22], fwUdpPorts:: [], networkName:: null, keyName:: error 'InstanceBasedService needs keyName', Mixin:: ( if service.networkName != null then { vpc_security_group_ids: ['${aws_security_group.%s.id}' % service.fullName], subnet_id: $.Network.subnetIdRef(self.availability_zone), } else { security_groups: ['${aws_security_group.%s.name}' % service.fullName], } ) + { [if service.keyName != null then 'key_name']: service.keyName, }, Instance:: $.StandardInstance + service.Mixin, infrastructure+: { aws_security_group: { [service.fullName]: { name: service.fullName, local IngressRule(p, protocol) = { from_port: p, to_port: p, protocol: protocol, cidr_blocks: ['0.0.0.0/0'], }, ingress: [IngressRule(p, 'tcp') for p in service.fwTcpPorts] + [IngressRule(p, 'udp') for p in service.fwUdpPorts], [if service.networkName != null then 'vpc_id']: $.Network.idRef, }, }, aws_instance: error 'InstanceBasedService should define some instances.', }, }, Cluster3(parent, name): $.InstanceBasedService(parent, name) { local service = self, lbTcpPorts:: [], lbUdpPorts:: [], fwTcpPorts:: [22], fwUdpPorts:: [], httpHealthCheckPort:: 80, zones:: error 'Cluster3 version (or service) needs an array of zones.', // In this service, the 'instance' is really an instance template used to create // a pool of instances. Mixin:: { zones:: service.zones, }, versions:: {}, deployment:: {}, local merge(objs) = std.foldl(function(a, b) a + b, objs, {}), local instances = merge([ { [service.prefixName('%s-%d' % [vname, i])]: if std.objectHas(service.versions, vname) then service.versions[vname] { availability_zone: self.zones[i % std.length(self.zones)], tags+: { version: vname, index: i, }, } else error 'Undefined version: %s' % vname for i in std.set(service.deployment[vname].deployed) } for vname in std.objectFields(service.deployment) ]), local attached_instances = std.join([], [ local attached = std.set(service.deployment[vname].attached); local deployed = std.set(service.deployment[vname].deployed); [service.prefixName('%s-%d' % [vname, i]) for i in std.setInter(attached, deployed)] for vname in std.objectFields(service.deployment) ]), addressRef:: '${aws_elb.%s.dns_name}' % service.fullName, infrastructure+: { aws_instance: instances, aws_elb: { [service.fullName]: { name: service.fullName, availability_zones: service.zones, listener: [{ instance_port: p, instance_protocol: 'tcp', lb_port: p, lb_protocol: 'tcp', } for p in service.lbTcpPorts], health_check: { healthy_threshold: 2, unhealthy_threshold: 2, timeout: 3, target: 'HTTP:%d/' % service.httpHealthCheckPort, interval: 5, }, instances: ['${aws_instance.%s.id}' % iname for iname in attached_instances], cross_zone_load_balancing: true, idle_timeout: 60, connection_draining: true, connection_draining_timeout: 60, }, }, aws_security_group: { [service.prefixName('elb')]: { name: service.prefixName('elb'), local IngressRule(p, protocol) = { from_port: p, to_port: p, protocol: protocol, cidr_blocks: ['0.0.0.0/0'], }, ingress: [IngressRule(p, 'tcp') for p in service.lbTcpPorts] + [IngressRule(p, 'udp') for p in service.lbUdpPorts], //[if service.networkName != null then "vpc_id"]: $.Network.idRef, }, }, }, }, SingleInstance(parent, name): $.InstanceBasedService(parent, name) { local service = self, zone:: error 'SingleInstance needs a zone.', addressRef:: '${aws_instance.%s.public_ip}' % service.fullName, infrastructure+: { aws_instance: { [service.fullName]: service.Instance { availability_zone: service.zone, }, }, }, }, DnsZone(parent, name):: self.Service(parent, name) { local service = self, dnsName:: error 'DnsZone must have dnsName, e.g. example.com', nameRef:: '${aws_route53_zone.%s.zone_id}' % service.fullName, description:: 'Zone for ' + self.dnsName, infrastructure+: { aws_route53_zone: { [service.fullName]: { dns_name: service.dnsName, comment: service.description, }, }, aws_dns_record: { }, }, outputs+: { [service.prefixName('name_servers')]: '${google_dns_managed_zone.%s.name_servers.0}' % service.fullName, }, }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/service/base.libsonnet ================================================ { // The base class of all Micromanage services. While it is not necessary to use this, it does // provide the following useful features: // - Utilities for generating unique names and easy referencing via those names. // - Defaults for required fields. // The function takes the outer service (or null) and a name that must be unique amongst its // siblings. Service(outer, name): { // The final component of the full name. name:: std.toString(name), // The sequence of names leading from the outermost service to here. nameArray:: (if outer == null then [] else outer.nameArray) + [self.name], // The globally unique name of the service. fullName:: std.join('-', self.nameArray), // Generate a new name prefixed by the service's name. prefixName(name):: std.join('-', self.nameArray + [std.toString(name)]), // The environment in which this service will be deployed. environment: 'default', // The Terraform resources that will be deployed. infrastructure: {}, // Any Terraform outputs that cannot be known until after deployment, such as generated // addresses. outputs: {}, } + { // Prevent overriding the fundamental identity characteristics. assert self.name == super.name, assert self.nameArray == super.nameArray, assert self.fullName == super.fullName, assert self.prefixName('foo') == super.prefixName('foo'), }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/service/google.libsonnet ================================================ local apt = import '../cmd/apt.libsonnet'; local cmd = import '../cmd/cmd.libsonnet'; local base = import 'base.libsonnet'; { // A helper for defining Micromange credentials for a Google environment. Credentials:: { kind: 'Google', project: error "Google credentials must have 'project'", // A path to a file on disk where the service account can be found. serviceAccount: error "Google credentials must have 'serviceAccount'", // Packer uses ssh to run commands on the instance during an image build. sshUser: error "Google credentials must have 'sshUser'", // Unused. region: 'us-central1', }, // A helper for building a Google image with Packer. Image:: { // The image from which we derive a new image. source: error "Google Image must have 'source'", // The kind of instance used to build the new image. machineType: 'n1-standard-1', // The zone in which to deploy the instance that builds the image. zone: 'us-central1-f', // The commands that are executed to build the new image. cmds: [], }, // A higher level image config that lets you add Apt and Pip packages. DebianImage:: $.Image + apt.Mixin + apt.PipMixin { source: 'debian-9-stretch-v20191121', }, // A base instance config for a GCE instance resource in Terraform. It sets up: // - Default service account scopes. // - Default values for the network and machine type. // - A default image // - Basic system-level monitoring / logging // Instances are intended to be created in services, so the service must be provided as an // argument here. StandardInstance(service):: { local instance = self, machine_type: 'f1-micro', // Either the string 'default' or create a hard dependency on a custom network, use // somenetwork.nameRef, where somenetwork evaluates to a service derived from $.Network. networkName:: 'default', // Commands that are run when the instance is booted for the first time. // This is useful for deploying configuration files and other artefacts. cmds: [], // Commands that are run every boot. This is useful for running processes. bootCmds: [], // The default network interface has a generated external ip. network_interface: { network: instance.networkName, access_config: { }, }, // The capabiltiies of the service account whose credentials are available within the instance. scopes:: ['cloud-platform'], // Use the 'scopes' field as an easier way to override the default service account scopes. service_account: [ { scopes: ['https://www.googleapis.com/auth/' + s for s in instance.scopes], }, ], // By default, the root disk is built at VM creation time from the StandardRootImage // which can be overridden at the top level to e.g. add commands or packages. boot_disk: { initialize_params: { image: instance.StandardRootImage, }, }, // By default the OS is debian and has support for monitoring and logging agents. StandardRootImage:: $.DebianImage + instance.MonitoringLoggingImageMixin, supportsLogging:: true, supportsMonitoring:: true, supportsJmxMonitoring:: true, enableLogging:: false, enableMonitoring:: false, enableJmxMonitoring:: false, jmxHost:: 'localhost', jmxPort:: 9012, jmxHotspotConfig:: { host: instance.jmxHost, port: instance.jmxPort, numQueryThreads: 2, SdQuery:: { outputWriters: [ { '@class': 'com.googlecode.jmxtrans.model.output.StackdriverWriter', settings: { token: 'STACKDRIVER_API_KEY', detectInstance: 'GCE', url: 'https://jmx-gateway.google.stackdriver.com/v1/custom', }, }, ], }, queries: [ self.SdQuery { resultAlias: 'jvm.localhost.Threading', obj: 'java.lang:type=Threading', attr: ['DaemonThreadCount', 'ThreadCount', 'PeakThreadCount'], }, self.SdQuery { resultAlias: 'jvm.localhost.Memory', obj: 'java.lang:type=Memory', attr: ['HeapMemoryUsage', 'NonHeapMemoryUsage'], }, self.SdQuery { resultAlias: 'jvm.localhost.Runtime', obj: 'java.lang:type=Runtime', attr: ['Uptime'], }, self.SdQuery { resultAlias: 'jvm.localhost.os', obj: 'java.lang:type=OperatingSystem', attr: [ 'CommittedVirtualMemorySize', 'FreePhysicalMemorySize', 'FreeSwapSpaceSize', 'OpenFileDescriptorCount', 'ProcessCpuTime', 'SystemLoadAverage', ], }, self.SdQuery { resultAlias: 'jvm.localhost.gc', obj: 'java.lang:type=GarbageCollector,name=*', attr: ['CollectionCount', 'CollectionTime'], }, ], }, jmxLocalhostConfig:: instance.jmxHotspotConfig, jmxConfig:: { servers: [ instance.jmxLocalhostConfig, ], }, MonitoringLoggingImageMixin:: { local monitoring = if instance.enableMonitoring then [ 'curl -s https://dl.google.com/cloudagents/install-monitoring-agent.sh | bash', ] else [], local jmx = if instance.enableJmxMonitoring then [ 'mkdir -p /opt/jmxtrans/{conf,log}', 'curl https://repo.stackdriver.com/jmxtrans/jmxtrans-all.jar -o /opt/jmxtrans/jmxtrans-all.jar', cmd.LiteralFile { to: '/etc/cron.d/jmxtrans', content: '@reboot root /usr/bin/java -Djmxtrans.log.dir=/opt/jmxtrans/log -jar /opt/jmxtrans/jmxtrans-all.jar -j /opt/jmxtrans/conf/ &\n', filePermissions: '700', }, cmd.LiteralFile { to: '/opt/jmxtrans/conf/jmx.json', content: std.toString(instance.jmxConfig), }, ] else [], local logging = if instance.enableLogging then [ 'curl -s https://dl.google.com/cloudagents/install-logging-agent.sh | bash', ] else [], cmds+: monitoring + jmx + logging, }, // This Terraform policy allows changing some properties by stopping the instance. allow_stopping_for_update: true, // By default, the only tag is the name of the service owning the instance. tags: [service.fullName], // By default there is no metadata (startup scripts are managed by micromanage so do not appear // here. metadata: {}, }, // The base class of all Google services. This provides support for automatic but optional // creation of DNS names for any instances or address resources with external IPs. Service(outer, name): base.Service(outer, name) { local service = self, infrastructure+: if self.dnsZone == null then { // Don't do anything if we have no DNS zone. } else { local infra = self, local DnsRecord = { managed_zone: service.dnsZone.nameRef, type: 'A', ttl: 300, }, // Ensure the fields exist, since we access them below. google_compute_instance+: { }, google_compute_address+: { }, // Add a record for every address and instance google_dns_record_set+: { [name]: DnsRecord { name: '${google_compute_instance.' + name + '.name}.' + service.dnsZone.dnsName, rrdatas: [ '${google_compute_instance.' + name + '.network_interface.0.access_config.0.nat_ip}', ], // Use a short TTL for instance addresses, because instances churn a fair amount and the // majority of traffic does not go to specific instances but load balancer frontends. ttl: 5, } for name in std.objectFields(infra.google_compute_instance) } + { // Note that if an address and an instance has the same name, the domain will go to the // address's IP, not the instance's IP. [name]: DnsRecord { name: '${google_compute_address.' + name + '.name}.' + service.dnsZone.dnsName, rrdatas: ['${google_compute_address.' + name + '.address}'], } for name in std.objectFields(infra.google_compute_address) }, }, // A reference to a service which is derived from $.DnsZone, or null. dnsZone:: null, }, // A custom network into which other services can be deployed. To put a resource in this network // use the .nameRef field, which tells Terraform to create the network first. Network(outer, name):: $.Service(outer, name) { local service = self, nameRef:: '${google_compute_network.%s.name}' % service.fullName, infrastructure: { google_compute_network: { [service.fullName]: { name: service.fullName, }, }, }, }, // A base class for a service whose compute facility is provided by instances. This supports: // - Optional automatic creation of firewall rules that assume the instances in this service // have the service's full name as a tag (by default, this is the case). // - Creation of a reserved address for the front end of the service, which might be a load // balancer or a VM. InstanceBasedService(outer, name): $.Service(outer, name) { local service = self, // The firewall rule generation can be turned off here. This is useful if your firewalls // are centrally managed using tags. perServiceFirewalls:: true, // Ports to allow through the firewall. fwTcpPorts:: [22], fwUdpPorts:: [], // Either the string 'default' or create a hard dependency on a custom network, use // somenetwork.nameRef, where somenetwork evaluates to a service derived from $.Network. networkName:: 'default', // A base class for the instances that will be used in this service. This can be extended // to customise the instances. The networkName is conveniently populated since it must // be the same for all instances in the service. // // Note that InstanceBasedService does not actually create any instances, it's up to sub-classes // to do that. But they can use (and extend) this onfiguration when they do so. Instance:: $.StandardInstance(service) { networkName: service.networkName, }, // Use this when you depend on this service to access the generated IP address. This creates // an ordering dependency in Terraform because the IP address is not known until deployment // of this service has begun. addressRef:: '${google_compute_address.%s.address}' % service.fullName, infrastructure+: { google_compute_address: { // The single frontend IP for this service. [service.fullName]: { name: service.fullName }, }, google_compute_firewall: if !service.perServiceFirewalls then {} else { [service.fullName]: { name: service.fullName, source_ranges: ['0.0.0.0/0'], network: service.networkName, allow: [{ protocol: 'tcp', ports: [std.toString(p) for p in service.fwTcpPorts] }], target_tags: [service.fullName], }, }, google_compute_instance: error 'InstanceBasedService should define some instances.', }, }, // A service that is provided by many instances behind a network load balancer. This supports: // - Automatic creation of the loadbalancer resources on whatever ports you need and bound to the // reserved IP address from $.InstanceBasedService (referencable with .addressRef). // - Health checking for the network load balancer. // - Management of a set of VMs across a range of zones (round robin). // - The facility to "Drain" VMs on demand by removing them from the load balancer. // - Canarying new instance configurations or blue / green deployment strategies. Cluster3(outer, name): self.InstanceBasedService(outer, name) { local service = self, lbTcpPorts:: [], lbUdpPorts:: [], httpHealthCheckPort:: 80, zones:: error 'Cluster3 version (or service) needs an array of zones.', Instance+: { // Creating a layer of indirection through the instance zones:: service.zones, }, // This is a map from version code to instance configuration. These are the versions that may // be deployed. versions:: {}, // Which versions are deployed. This is a map from version code to an object with fields // 'deployed' and 'attached'. Those must hold arrays of integers, indicating which VM indexes // within that version are deployed or attached. The instance names are generated as so: // ${service_name}-${version_name}-${index} deployment:: {}, local instances = std.foldl(function(a, b) a + b, [ { [service.prefixName('%s-%d' % [vname, i])]: if std.objectHas(service.versions, vname) then service.versions[vname] { name: service.prefixName('%s-%d' % [vname, i]), zone: self.zones[i % std.length(self.zones)], tags+: [vname, 'index-%d' % i], } else error 'Undefined version: %s' % vname for i in std.set(service.deployment[vname].deployed) } for vname in std.objectFields(service.deployment) ], {}), local attached_instances = std.join( [], [ local attached = std.set(service.deployment[vname].attached); local deployed = std.set(service.deployment[vname].deployed); [service.prefixName('%s-%d' % [vname, i]) for i in std.setInter(attached, deployed)] for vname in std.objectFields(service.deployment) ] ), infrastructure+: { google_compute_http_health_check: { [service.fullName]: { name: service.fullName, port: service.httpHealthCheckPort, }, }, google_compute_target_pool: { [service.fullName]: { name: service.fullName, depends_on: ['google_compute_http_health_check.' + service.fullName], health_checks: [service.fullName], instances: [ '%s/%s' % [instances[iname].zone, iname] for iname in attached_instances ], }, }, google_compute_forwarding_rule: { [service.prefixName(port)]: { name: service.prefixName(port), ip_address: '${google_compute_address.%s.address}' % service.fullName, target: '${google_compute_target_pool.%s.self_link}' % service.fullName, port_range: port, } for port in [std.toString(p) for p in service.lbTcpPorts] }, google_compute_instance: instances, }, outputs+: { [service.prefixName('addr')]: service.addressRef, }, }, // A service that is provided by a single instance. This is less reliable than $.Cluster3 but is // useful for testing or other non-highly available services. It supports: // - Management of the single instance on the frontend address created by $.InstanceBasedService. // This IP does not change when the instance is rebuilt. SingleInstance(outer, name): self.InstanceBasedService(outer, name) { local service = self, // Control the zone at the top-level. zone:: error 'SingleInstance needs a zone.', Instance+: { zone: service.zone, network_interface+: { access_config: { // Use the service's predefined IP address. nat_ip: '${google_compute_address.%s.address}' % service.fullName, }, }, }, infrastructure+: { google_compute_instance: { [service.fullName]: service.Instance { name: service.fullName, }, }, }, outputs+: { [service.prefixName('addr')]: service.addressRef, }, }, // A service that manages a DNS zone, which can then be used by other services to expose their // addresses via DNS. The DNS zone should be recreated rarely, because its domain name servers // will change and then upstream NS record will need to be updated. However, the records within // the zone can change at any time as the set of mapped IP addresses changes. DnsZone(outer, name):: self.Service(outer, name) { local service = self, // The suffix that some authority will delegate to the generated nameserves via a NS record. // All domain names managed by this zone will have the same suffix. dnsName:: error 'DnsZone must have dnsName, e.g. example.com', // Use this reference to ensure Terraform deploys the DNS zone before services that need to // register domain names in it. nameRef:: '${google_dns_managed_zone.%s.name}' % self.fullName, // A convenient field to override the description in the underlying resource. description:: 'Zone for ' + self.dnsName, infrastructure+: { google_dns_managed_zone: { [service.fullName]: { name: service.fullName, dns_name: service.dnsName, description: service.description, }, }, }, outputs+: { // The first of the set of nameservers, which can be used to configure the upstream authority // to delegate to the new DNS zone via an NS record. [service.prefixName('name_servers')]: '${google_dns_managed_zone.%s.name_servers.0}' % service.fullName, }, }, // A simple service that creates a domain name alias from www.foo to target.foo, where target is // the front end IP of some other service. This allows designating the front end of your // application, and allowing it to be accessed via www.foo instead of servicename.foo. DnsRecordWww(outer, name):: self.Service(outer, name) { local service = self, // A service derived from $.DnsZone. zone:: error 'DnsRecordWww requires zone.', // The name of the service within the zone, e.g. 'servicename' without the '.foo' suffix. target:: error 'DnsRecordWww requires target.', infrastructure+: { google_dns_record_set: { [service.fullName]: { managed_zone: service.zone.nameRef, name: 'www.' + service.zone.dnsName, type: 'CNAME', ttl: 300, rrdatas: [service.target + '.' + service.zone.dnsName], }, }, }, }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/web/nginx.libsonnet ================================================ local cmd = import '../cmd/cmd.libsonnet'; { // A mixin on top of a Debian instance that manages an Nginx installation. This supports: // - Monitoring is enabled by default. // - /var/www is initialized correctly // - Two hooks are provided for running handler daemons. DebianNginxMixin:: { local version = self, enableMonitoring: version.supportsMonitoring, enableLogging: version.supportsLogging, StandardRootImage+: { local image = self, aptPackages+: ['nginx'], // Configure nginx to expose stub_status to the local agent. nginxMonitoringConf:: ||| server { listen 80; server_name local-stackdriver-agent.stackdriver.com; location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } location / { root /dev/null; } } |||, // Configure Collectd to find the stub_status at the above URL. nginxCollectdConf:: ||| LoadPlugin "nginx" URL "http://local-stackdriver-agent.stackdriver.com/nginx_status" |||, cmds+: (if version.enableMonitoring then [ cmd.LiteralFile { to: '/etc/nginx/conf.d/monitoring.conf', content: image.nginxMonitoringConf, }, cmd.LiteralFile { to: '/opt/stackdriver/collectd/etc/collectd.d/nginx.conf', content: image.nginxCollectdConf, }, ] else []) + [ // Disable the default site. 'rm /etc/nginx/sites-enabled/default', ], }, // Files that must exist on top before any handling daemons are started. // Extend this to deploy more content for the web server. httpContentCmds:: [ cmd.EnsureDir { dir: '/var/www', owner: 'www-data' }, ], // Running handling daemons. httpHandlerCmds:: [ ], // Additional Nginx config commands. nginxAdditionalCmds:: [ ], cmds+: self.httpContentCmds + self.httpHandlerCmds + self.nginxAdditionalCmds + [ 'nginx -s reload', ], }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/web/solutions.libsonnet ================================================ local nginx = import 'nginx.libsonnet'; local uwsgi_flask = import 'uwsgi_flask.libsonnet'; { /** A mixin on top of an HttpService with Debian instances that adds nginx, uwsgi, and flask. */ DebianFlaskHttpService: { local service = self, uwsgiModuleContent:: ||| import flask app = flask.Flask(__name__) @app.route('/') def hello_world(): return 'No content is configured for this web service.' |||, Instance+: nginx.DebianNginxMixin + uwsgi_flask.DebianUwsgiFlask + uwsgi_flask.NginxUwsgiGlue { httpPort:: service.httpPort, httpsPort:: service.httpsPort, sslCertificate:: service.sslCertificate, sslCertificateKey:: service.sslCertificateKey, uwsgiModuleContent: service.uwsgiModuleContent, }, }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/web/uwsgi_flask.libsonnet ================================================ local cmd = import '../cmd/cmd.libsonnet'; { // A Debian instance mixin that manages Flash and Uwsgi for the purposes of handling HTTP // requests. This supports: // - Installation of the correct packages // - Configuration of uwsgi // - Management of a python file that receives the request from uwsgi. // - Running the uwsgi daemon. DebianUwsgiFlask:: { local version = self, StandardRootImage+: { aptPackages+: ['python-dev'], pipPackages+: ['flask', 'uwsgi'], /* cmds+: [ cmd.LiteralFile { to: '/etc/cron.d/emperor', content: '@reboot root /usr/local/bin/uwsgi --master --emperor /etc/uwsgi/vassals ' + '--daemonize /var/log/uwsgi/emperor.log --pidfile /var/run/uwsgi.pid ' + '--die-on-term --uid www-data --gid www-data\n', filePermissions: '700', }, ], */ }, // Name of the Python module (written in Flask) that will be called. module:: 'uwsgi_module', // Name of the application in Flask. application:: 'app', // The frontend interface to uwsgi, necessary to configure web servers to find it. uwsgiSocket:: '/var/www/uwsgi.sock', uwsgiConf:: { chdir: '/var/www', base: '/var/www', module: version.module, pythonpath: '/var/www', socket: version.uwsgiSocket, 'chmod-socket': '644', callable: version.application, logto: '/var/log/uwsgi/uwsgi.log', }, // Python code to handle requests goes here, as a string. uwsgiModuleContent:: null, httpContentCmds+: if version.uwsgiModuleContent == null then [] else [ cmd.LiteralFile { content: version.uwsgiModuleContent, to: '/var/www/%s.py' % version.module, }, ], httpHandlerCmds+: [ cmd.EnsureDir { dir: '/etc/uwsgi/vassals' }, cmd.LiteralFile { to: '/etc/uwsgi/vassals/uwsgi.ini', content: std.manifestIni({ sections: { uwsgi: version.uwsgiConf, }, }), }, cmd.EnsureDir { dir: '/var/log/uwsgi', owner: 'www-data' }, ], bootCmds+: [ '/usr/local/bin/uwsgi --master --emperor /etc/uwsgi/vassals ' + '--daemonize /var/log/uwsgi/emperor.log --pidfile /var/run/uwsgi.pid ' + '--die-on-term --uid www-data --gid www-data\n', ], }, // A mixin on top of an instance that configures Nginx to use Uwsgi. It is expected that both // are already installed and configured. // TODO: There's a lot of general nginx stuff here that could be factored out, e.g. the keys. NginxUwsgiGlue: { local version = self, local base_conf = [ 'server_name ~^.*$;', 'charset utf-8;', 'client_max_body_size 75M;', 'location / { try_files $uri @yourapplication; }', 'location @yourapplication {', ' include uwsgi_params;', ' uwsgi_pass unix:%s;' % version.uwsgiSocket, '}', ], local http_part = [ 'listen %d;' % version.httpPort, ], local https_part = [ 'listen %d ssl;' % version.httpsPort, 'ssl_certificate /etc/ssl/cert.pem;', 'ssl_certificate_key /etc/ssl/key.pem;', ], local all_parts = base_conf + (if version.httpPort != null then http_part else []) + (if version.httpsPort != null then https_part else []), local whole_conf = std.lines(['server {'] + [' ' + line for line in all_parts] + ['}']), nginxAdditionalCmds+: [ cmd.LiteralFile { to: '/etc/nginx/conf.d/frontend_nginx.conf', content: whole_conf, }, ] + (if version.httpsPort != null then [ cmd.LiteralFile { to: '/etc/ssl/cert.pem', content: version.sslCertificate, }, cmd.LiteralFile { to: '/etc/ssl/key.pem', content: version.sslCertificateKey, }, ] else []), }, } ================================================ FILE: case_studies/micromanage/lib/mmlib/v0.1.2/web/web.libsonnet ================================================ { // A mixin on top of an InstanceBasedService that turns it into an HTTP and/or HTTPs service. // This is only really suitable for SingleInstance services because it does not configure load // balancing. For that, use HttpService3. HttpSingleInstance: { local service = self, // You must have either an httpPort or an httpsPort or both. assert service.httpPort != null || service.httpsPort != null, // HTTPs requires SSL certificates which need DNS. assert service.httpsPort == null || service.dnsZone != null : 'HTTPs requires DNS.', // Set this to null for an https only service. However that is not compatible with // health checking so may not be a good idea. httpPort:: 80, // Enable HTTPs here. Use importstr if your certificate and keys are files on disk. httpsPort:: null, sslCertificate:: error 'sslCertificate field was required but not present.', sslCertificateKey:: error 'sslCertificateKey field was required but not present.', // Ensure the firewall permits traffic to the ports. fwTcpPorts+: (if self.httpPort != null then [self.httpPort] else []) + (if self.httpsPort != null then [self.httpsPort] else []), // A useful way to determine the URL where this service can be accessed. httpEndpoint:: if service.httpPort == 80 then 'http://%s' % [service.addressRef] else 'http://%s:%d' % [service.addressRef, service.httpPort], // A useful way to determine the URL where this service can be accessed. httpsEndpoint:: if service.httpsPort == 443 then 'https://%s.%s' % [service.fullName, service.dnsZone.dnsName] else 'https://%s.%s:%d' % [service.fullName, service.dnsZone.dnsName, service.httpsPort], outputs: { [if service.httpPort != null then service.prefixName('http-endpoint')]: service.httpEndpoint, [if service.httpsPort != null then service.prefixName('https-endpoint')]: service.httpsEndpoint, }, // Adding the http-server and https-server tags makes the instances compatible with GCE's // default firewall configuration, if that is being used. Instance+: { tags+: (if service.httpPort != null then ['http-server'] else []) + (if service.httpsPort != null then ['https-server'] else []), }, }, // A mixin on top of a cluster3 service that turns it into an HTTP service. HttpService3: self.HttpSingleInstance { // Ensure the correct ports are forwarded in. lbTcpPorts+: (if self.httpPort != null then [self.httpPort] else []) + (if self.httpsPort != null then [self.httpsPort] else []), // Prefer http port for health checking unless only https is available. httpHealthCheckPort: if self.httpPort != null then self.httpPort else self.httpsPort, }, } ================================================ FILE: case_studies/micromanage/micromanage ================================================ #!/usr/bin/env bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. BASE_DIR="$(dirname $0)" exec python "$BASE_DIR/micromanage.py" "$@" ================================================ FILE: case_studies/micromanage/micromanage.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import collections import json import os import shutil import subprocess import sys import tempfile import traceback import _jsonnet import service_google import service_amazon import util import validate # TODO: Simple constraint language for expressing constraints on the generated # infrastructure of production services that if broken during an apply, will # cause the apply to be rejected. Examples: # - resource x must exist (e.g. data integrity) # - at least x things of this type must exist service_compilers = { 'Google': service_google.GoogleService(), 'Amazon': service_amazon.AmazonService(), } def get_compiler(config, service): environments = config['environments'] environment = environments[service.get('environment', 'default')] return service_compilers[environment['kind']], environment def config_check_service(environments, root, path): try: service = validate.path_val(root, path, 'object') env_name = validate.path_val(root, path + ['environment'], 'string', 'default') default = env_name == 'default' env = environments.get(env_name) if env is None: if default: raise validate.ConfigError(('The service at %s has no environment field, and no ' + 'environment called "default" exists.') % validate.render_path(path)) else: raise validate.ConfigError('In %s, unrecognized environment %s' % (validate.render_path(path), env_name)) compiler = service_compilers.get(env['kind']) compiler.validateService(root, path) for child_name, child in compiler.children(service): config_check_service(environments, root, path + [child_name]) except validate.ConfigError as e: if e.note is None: e.note = ('Did you mean for %s to be a visible field (: instead of ::)?' % validate.render_path(path)) raise def services(config): for service_name, service in config.iteritems(): if service_name == 'environments': continue yield service_name, service def config_check(config): validate.path_val(config, [], 'object') environments = validate.path_val(config, ['environments'], 'object', {}) # Check environments environments = config['environments'] for env_name, env in environments.iteritems(): kind_name = validate.path_val(config, ['environments', env_name, 'kind'], 'string') compiler = service_compilers.get(kind_name) if not compiler: raise validate.ConfigError( 'Unrecognized kind "%s" in environment %s' % (kind_name, env_name)) compiler.validateEnvironment(config, ['environments', env_name]) # Check services for service_name, service in services(config): config_check_service(environments, config, [service_name]) ext_vars = {} # For Jsonnet evaluation search_paths = [ # Where we look for imported jsonnet files os.path.dirname(os.path.realpath(__file__)) + '/lib/' ] debug = False # Returns content if worked, None if file not found, or throws an exception def jsonnet_try_path(dir, rel): if not rel: raise RuntimeError('Got invalid filename (empty string).') if rel[0] == '/': full_path = rel else: full_path = dir + rel if full_path[-1] == '/': raise RuntimeError('Attempted to import a directory') if not os.path.isfile(full_path): return full_path, None with open(full_path) as f: return full_path, f.read() def jsonnet_import_callback(dir, rel): full_path, content = jsonnet_try_path(dir, rel) if content: return full_path, content for path in search_paths: full_path, content = jsonnet_try_path(path, rel) if content: return full_path, content raise RuntimeError('File not found') def config_load(filename, ext_vars): try: text = _jsonnet.evaluate_file( filename, max_trace=100, ext_vars=ext_vars, import_callback=jsonnet_import_callback) except RuntimeError as e: # Error from Jsonnet sys.stderr.write(e.message) sys.stderr.write('\n') sys.exit(1) config = json.loads(text) try: config_check(config) except validate.ConfigError as e: if debug: traceback.print_exc() else: sys.stderr.write('Config error: %s\n' % e.message) if e.note: sys.stderr.write('%s\n' % e.note) sys.exit(1) return config def preprocess(config): """Return a copy of the config with ${-} handled.""" def aux(service): compiler, _ = get_compiler(config, service) r2 = compiler.preprocess(service) for child_name, child in compiler.children(service): r2[child_name] = aux(child) return r2 r = { 'environments': config['environments'], } for service_name, service in services(config): r[service_name] = aux(service) return r def get_build_artefacts(config): """Create all required build artefacts, modify config to refer to them.""" def aux(ctx, service_name, service): compiler, environment = get_compiler(config, service) new_service, service_barts = compiler.getBuildArtefacts(environment, ctx, service) ctx = ctx + [service_name] for child_name, child in compiler.children(service): new_child, child_barts = aux(ctx, child_name, child) util.merge_into(service_barts, child_barts) new_service[child_name] = new_child return new_service, service_barts barts = {} new_config = { 'environments': config['environments'], } for service_name, service in services(config): new_service, service_barts = aux([], service_name, service) new_config[service_name] = new_service util.merge_into(barts, service_barts) return new_config, barts def compile(config, barts): def aux(ctx, service_name, service): compiler, _ = get_compiler(config, service) service_tfs = compiler.compile(ctx, service_name, service, barts) ctx = ctx + [service_name] for child_name, child in compiler.children(service): util.merge_into(service_tfs, aux(ctx, child_name, child)) return service_tfs tfs = {} for service_name, service in services(config): util.merge_into(tfs, aux([], service_name, service)) for environment_name, environment in config['environments'].iteritems(): compiler = service_compilers[environment['kind']] tf_dict = compiler.compileProvider(environment_name, environment) util.merge_into(tfs, tf_dict) # Avoid a warning from Terraform that there are no tf files if not len(tfs): tfs['empty.tf.json'] = {} # Clean up the output JSON files because Terraform is quite picky. for tfname, tf in tfs.iteritems(): if 'resource' in tf: new_resources = {} for rtype_name, rtype_dict in list(tf['resource'].iteritems()): for r_name, r_dict in rtype_dict.iteritems(): # Ensure depends_on is always a list. # TODO: Doesn't seem too hard to make sure all configs just provide it as a # list. if 'depends_on' in r_dict and isinstance(r_dict['depends_on'], basestring): r_dict['depends_on'] = [r_dict['depends_on']] # Remove types that have no resources. if not rtype_dict: del tf['resource'][rtype_name] # Remove empty dicts at the top level. if 'resource' in tf and not tf['resource']: del tf['resource'] if 'output' in tf and not tf['output']: del tf['output'] return tfs def confirmation_dialog(msg): sys.stdout.write('%s [y/N]: ' % msg) while True: choice = raw_input().lower() if choice == '': choice = 'n' if choice in ['y', 'n']: break sys.stdout.write('Please press either y or n, then hit enter: ') return choice def output_delete(dirpath): shutil.rmtree(dirpath) def action_blueprint(config_file, config, args): if args: sys.stderr.write('Action "blueprint" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) print(util.jsonstr(config)) def generate(dirpath, config, do_build): files = [] config = preprocess(config) config, barts = get_build_artefacts(config) for bart_name, bart in barts.iteritems(): bart.outputFiles(dirpath) files += bart.getOutputFiles(dirpath) if not do_build: barts = {} barts_to_build = {} # Output build artefact files for bart_name, bart in barts.iteritems(): if bart.needsBuild(): barts_to_build[bart_name] = bart for bart_name, bart in barts_to_build.iteritems(): bart.doBuild(dirpath) for bart_name, bart in barts_to_build.iteritems(): bart.wait(dirpath) for bart_name, bart in barts.iteritems(): bart.postBuild() tfs = compile(config, barts) # Output Terraform configs for filename, tf in tfs.iteritems(): dirfilename = '%s/%s' % (dirpath, filename) files += [dirfilename] with open(dirfilename, 'w') as f: f.write(util.jsonstr(tf)) # Copy terraform plugins into the output dir. config_dir = '%s/.terraform' % os.getcwd() if os.path.exists(config_dir): shutil.copytree(config_dir, dirpath + '/.terraform') return files class SubprocessException(Exception): pass def sync_popen(dirpath, command, successful_exit_codes=(0,), stdout=None): try: process = subprocess.Popen(command, cwd=dirpath, stdout=stdout) exit_code = process.wait() if successful_exit_codes is not None and exit_code not in successful_exit_codes: raise SubprocessException('Error from subprocess, ran in %s: %s' % (dirpath, command)) return exit_code except OSError as e: raise SubprocessException('Error starting subprocess, ran in %s: %s: (%s)' % (dirpath, command, e)) def action_generate_to_editor(config_file, config, args): if args: sys.stderr.write('Action "generate-to-editor" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) dirpath = tempfile.mkdtemp() files = generate(dirpath, config, False) sync_popen(os.getcwd(), [os.getenv('EDITOR')] + files, None) output_delete(dirpath) def action_diff(config_file, config, args): if args: sys.stderr.write('Action "diff" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) dirpath = tempfile.mkdtemp() generate(dirpath, config, True) command = ['terraform', 'plan', '-state', '%s/%s' % (os.getcwd(), config_file + '.tfstate')] sync_popen(dirpath, command, (0, 2)) output_delete(dirpath) def action_apply(config_file, config, args): if args: sys.stderr.write('Action "apply" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) dirpath = tempfile.mkdtemp() generate(dirpath, config, True) state_file = config_file + '.tfstate' command = ['terraform', 'plan', '-state', '%s/%s' % (os.getcwd(), state_file), '-detailed-exitcode', '-out', 'tf.plan'] plan_exitcode = sync_popen(dirpath, command, (0, 2)) if plan_exitcode == 0: pass # Empty plan, nothing to do elif plan_exitcode == 2: choice = confirmation_dialog('Apply these changes?') if choice == 'y': command = ['terraform', 'apply', '-state', '%s/%s' % (os.getcwd(), state_file), 'tf.plan'] sync_popen(dirpath, command) else: print 'Not applying the changes.' output_delete(dirpath) def action_destroy(config_file, config, args): if args: sys.stderr.write('Action "destroy" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) choice = confirmation_dialog('Destroy all infrastructure?') if choice != 'y': print 'Phew, that was a close one.' return dirpath = tempfile.mkdtemp() generate(dirpath, config, False) command = ['terraform', 'destroy', '-force', '-state', '%s/%s.tfstate' % (os.getcwd(), config_file)] exitcode = sync_popen(dirpath, command) output_delete(dirpath) def action_graph(config_file, config, args): if args: sys.stderr.write('Action "graph" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) dirpath = tempfile.mkdtemp() generate(dirpath, config, False) exitcode = sync_popen(dirpath, ['terraform', 'init']) output_delete(dirpath) def action_init(config_file, config, args): if args: sys.stderr.write('Action "destroy" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) dirpath = tempfile.mkdtemp() generate(dirpath, config, False) sync_popen(dirpath, ['terraform', 'init']) # Copy terraform plugins into the output dir. shutil.copytree('%s/.terraform' % dirpath, os.getcwd() + '/.terraform') output_delete(dirpath) def action_list_services(config_file, config, args): if args: sys.stderr.write('Action "list-services" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) def services_in_subtree(ctx, service_name, service): compiler, environment = get_compiler(config, service) services = ['-'.join(ctx + [service_name])] for child_name, child in compiler.children(service): services += services_in_subtree(ctx + [service_name], child_name, child) return services services = [] for key in sorted(config.keys()): if key == 'environments': continue services += services_in_subtree([], key, config[key]) for service in services: print(service) def action_show(config_file, config, args): if args: sys.stderr.write('Action "show" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) # terraform show assumes that the current directory contains a filename called terraform.tfstate # so create such a directory to make it happy. dirpath = tempfile.mkdtemp() generate(dirpath, config, False) shutil.copyfile('%s/%s.tfstate' % (os.getcwd(), config_file), dirpath + '/terraform.tfstate') sync_popen(dirpath, ['terraform', 'show']) output_delete(dirpath) def action_output(config_file, config, args): if args: sys.stderr.write('Action "output" accepts no arguments, but got: %s\n' % ' '.join(args)) sys.exit(1) dirpath = tempfile.mkdtemp() generate(dirpath, config, False) tfstate = '%s/%s.tfstate' % (os.getcwd(), config_file) exitcode = sync_popen(dirpath, ['terraform', 'output', '-state=' + tfstate]) output_delete(dirpath) def action_image_gc(config_file, config, args): raise RuntimeError('Sorry, this code is currently in a state of ill-repair.') config = preprocess(config) config, barts = get_build_artefacts(config) used_images = collections.defaultdict(lambda: []) google_images = {} amazon_amis = {} for bart_name, bart in barts.iteritems(): environment = bart.environment if environment['kind'] == 'Google': project = environment['project'] used_images[project].append(bart.name()) if project not in google_images: image_tuples = google_get_images_json_key(project, environment['serviceAccount']) google_images[project] = image_tuples elif environment['kind'] == 'Amazon': access_key = environment['accessKey'] used_images[access_key].append(bart.name()) if access_key not in amazon_amis: image_tuples = amazon_get_images(environment['region'], access_key, environment['secretKey']) amazon_amis[access_key] = [image_tuple + (environment,) for image_tuple in image_tuples] else: raise RuntimeError('Got invalid environment kind: "%s"' % iamge_type) # Delete all images older than X which are not currently in a version / module got_any_google = False for project, imgs in google_images.iteritems(): mmimgs = [img for img in imgs if img[0].startswith('micromanage-') and not img[0] in used_images[project]] for img in mmimgs: if (now - img[1]).days > 7: if not got_any_google: got_any_google = True print 'Execute the following commands to clean up Google images:' print 'gcloud --project=%s compute images delete -q %s # %s days old' % (project, img[0], (now - img[1]).days) if not got_any_google: print 'There were no Google images to clean up.' got_any_amazon = False for access_key, imgs in google_images.iteritems(): mmimgs = [img for img in imgs if img[0].startswith('micromanage-') and not img[0] in used_images[access_key]] for img in mmimgs: img_name, img_creation_timestamp, env = img days_old = (now - img_creation_timestamp).days if days_old > 7: if not got_any_amazon: got_any_amazon = True print 'Execute the following commands to clean up Amazon AMIs:' region = img[2]['region'] secret_key = img[2]['secretKey'] print 'ec2-deregister --region %s --aws-access-key %s --aws-secret-key %s %s # %s days old' % (region, access_key, secret_key, img_name, days_old) #TODO(dcunnin): need to delete by snapshot-id, not by ami id print 'ec2-delete-snapshot --region %s --aws-access-key %s --aws-secret-key %s %s # %s days old' % (region, access_key, secret_key, img_name, days_old) if not got_any_amazon: print 'There were no Amazon AMIs to clean up.' actions = { 'blueprint': action_blueprint, 'generate-to-editor': action_generate_to_editor, 'apply': action_apply, 'destroy': action_destroy, 'diff': action_diff, 'graph': action_graph, 'init': action_init, 'list-services': action_list_services, 'show': action_show, 'output': action_output, 'image-gc': action_image_gc, } def print_usage(channel): channel.write("Usage: python micromanage.py \n") channel.write("Available actions: %s\n" % ', '.join(actions.keys())) remaining_args = [] i = 1 def next_arg(i): i += 1 if i >= len(sys.argv): sys.stderr.write('Expected another commandline argument.\n') sys.exit(1) return i, sys.argv[i] while i < len(sys.argv): arg = sys.argv[i] if arg == '-E' or arg == '--env': i, env_var = next_arg(i) env_val = os.environ.get(env_var) if not env_val: sys.stderr.write('-E referred to non-existent environment variable "%s".\n' % env_var) sys.exit(1) ext_vars[env_var] = env_val elif arg == '-V' or arg == '--var': i, data = next_arg(i) splits = string.split(data, '=', 1) if len(splits) < 2: sys.stderr.write('-V must be followed by key=val, got "%s".\n' % data) sys.exit(1) ext_vars[splits[0]] = splits[1] elif arg == '-J' or arg == '--jpath': i, val = next_arg(i) search_paths = [val] + search_paths elif arg == '-d' or arg == '--debug': debug = True elif arg == '-f' or arg == '--force': force = True else: remaining_args.append(arg) i += 1 if len(remaining_args) < 2: sys.stderr.write('Not enough commandline arguments.\n') print_usage(sys.stderr) sys.exit(1) config_file = remaining_args[0] action = remaining_args[1] args = remaining_args[2:] if action not in actions: sys.stderr.write('Invalid action: "%s"' % action) print_usage(sys.stderr) sys.exit(1) config = config_load(config_file, ext_vars) try: actions[action](config_file, config, args) except SubprocessException as e: sys.stderr.write('%s\n' % e) sys.exit(1) ================================================ FILE: case_studies/micromanage/packer.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import hashlib import subprocess import build_artefact import cmds import util _NAME_CHARS = ([chr(i) for i in range(ord('0'), ord('9'))] + [chr(i) for i in range(ord('a'), ord('z'))]) def hash_code_as_domain_name(n): n = n % (2**63) if n < 0: n += 2**63 radix = len(_NAME_CHARS) s = "" for i in range(12): d = n % radix s = _NAME_CHARS[d] + s n /= radix return s def hash_string(s): r = long(hashlib.sha1(s).hexdigest(), 16) if r < 0: r += 2**63 return r def hash_cmds(cmds): hash_code = 0l for cmd in cmds: if isinstance(cmd, basestring): hash_code ^= hash_string(cmd) elif cmd['kind'] == 'LiteralFile': hash_code ^= hash_string(cmd['content']) hash_code ^= hash_string(cmd['to']) hash_code ^= hash_string(cmd['filePermissions']) hash_code ^= hash_string(cmd['owner']) hash_code ^= hash_string(cmd['group']) elif cmd['kind'] == 'CopyFile': # TODO(dcunnin): Scan these files, compute hash hash_code ^= hash_string(cmd['from']) hash_code ^= hash_string(cmd['to']) hash_code ^= hash_string(cmd['dirPermissions']) hash_code ^= hash_string(cmd['filePermissions']) hash_code ^= hash_string(cmd['owner']) hash_code ^= hash_string(cmd['group']) raise RuntimeError('CopyFile not supported in image') elif cmd['kind'] == 'EnsureDir': hash_code ^= hash_string(cmd['dir']) hash_code ^= hash_string(cmd['dirPermissions']) hash_code ^= hash_string(cmd['owner']) hash_code ^= hash_string(cmd['group']) else: raise RuntimeError('Did not recognize image command kind: ' + cmd['kind']) return hash_code class PackerBuildArtefact(build_artefact.BuildArtefact): def __init__(self, cmds): self.cmds = cmds self.cachedHashCode = None self.packerProcess = None def builderHashCode(self): raise NotImplementedError("%s has no override" % self.__class__.__name__) def hashCode(self): if self.cachedHashCode is None: self.cachedHashCode = self.builderHashCode() ^ hash_cmds(self.cmds) return self.cachedHashCode def _configFileName(self): return self.name() + '.packer.json' def _config(self): provs = [ { 'type': 'shell', 'execute_command': "{{ .Vars }} sudo -HE /bin/bash '{{ .Path }}'", 'inline': cmds.compile_command_to_bash(cmd), } for cmd in self.cmds ] return util.jsonstr({'builders': [self.builder()], 'provisioners': provs}) def name(self): return 'micromanage-%s' % hash_code_as_domain_name(self.hashCode()) def getOutputFiles(self, dirpath): return ['%s/%s' % (dirpath, self._configFileName())] def outputFiles(self, dirpath): config = self._config() dirfilename = '%s/%s' % (dirpath, self._configFileName()) with open(dirfilename, 'w') as f: f.write(config) def doBuild(self, dirpath): filename = self._configFileName() if self.packerProcess is not None: raise RuntimeError('Packer build already in progress.') command = ['packer', 'build', filename] logfilename = '%s/%s.log' % (dirpath, filename) print '%s > %s' % (' '.join(command), logfilename) with open(logfilename, 'w') as logfile: self.packerProcess = subprocess.Popen(command, stdout=logfile, cwd=dirpath) def wait(self, dirpath): if self.packerProcess is None: raise RuntimeError('No Packer build in progress.') exitcode = self.packerProcess.wait() if exitcode != 0: print 'Packer error: Exit code was %d' % exitcode filename = self._configFileName() logfilename = '%s/%s.log' % (dirpath, filename) with open(logfilename, 'r') as logfile: print logfile.read() raise RuntimeError('Error from packer.') ================================================ FILE: case_studies/micromanage/service.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import cmds as cmds_lib import validate class Service(object): def validateCmds(self, root, path): cmds = validate.array(root, path, validate.is_any_type({'string', 'object'}), []) for i, cmd in enumerate(cmds): cmd_path = path + [i] if isinstance(cmd, basestring): # Any string will do for validation purposes. pass elif isinstance(cmd, dict): kinds = {'CopyFile', 'LiteralFile', 'EnsureDir'} kind = validate.path_val(root, cmd_path + ['kind'], validate.is_any_value(kinds)) if kind == 'CopyFile': fields = {'owner', 'group', 'dirPermissions', 'filePermissions', 'from', 'to'} for f in fields: validate.path_val(root, cmd_path + [f], 'string') validate.obj_only(root, cmd_path, fields | {'kind'}) elif kind == 'LiteralFile': fields = {'owner', 'group', 'filePermissions', 'content', 'to'} for f in fields: validate.path_val(root, cmd_path + [f], 'string') validate.obj_only(root, cmd_path, fields | {'kind'}) elif cmd['kind'] == 'EnsureDir': fields = {'owner', 'group', 'dirPermissions', 'dir'} for f in fields: validate.path_val(root, cmd_path + [f], 'string') validate.obj_only(root, cmd_path, fields | {'kind'}) else: raise RuntimeError('Internal error: %s' % kind) else: raise RuntimeError('Internal error: %s' % type(cmd)) def validateImage(self, root, path): # Superclasses override this method and validate specific image attributes. # Byt here we can do the cmds. self.validateCmds(root, path + ['cmds']) def children(self, service): for child_name in sorted(service.keys()): if child_name in {'environment', 'infrastructure', 'outputs'}: continue yield child_name, service[child_name] def validateInfrastructure(self, root, service_name, path): infrastructure = validate.path_val(root, path, 'object', {}) for rtype in sorted(infrastructure.keys()): resources = validate.path_val(root, path + [rtype], 'object', {}) for resource_name in sorted(resources.keys()): if not resource_name.startswith(service_name): validate.err(path, 'Expected "%s" to be prefixed by "%s"' % (resource_name, service_name), 'Resource names must be prefixed by the name of the service defining them') def validateService(self, root, path): validate.path_val(root, path + ['outputs'], validate.is_string_map, {}) self.validateInfrastructure(root, self.fullName(path), path + ['infrastructure']) def fullName(self, path): return '-'.join(path) def preprocess(self, service): return { 'environment': service.get('environment', 'default'), 'infrastructure': service.get('infrastructure',{}), 'outputs': service.get('outputs', {}), } def compileStartupScript(self, cmds, bootCmds): lines = [] lines.append('#!/usr/bin/env bash') lines.append('if [ ! -r /micromanage_instance_initialized ] ; then') for cmd in cmds: lines += cmds_lib.compile_command_to_bash(cmd) lines.append('touch /micromanage_instance_initialized') lines.append('fi') for cmd in bootCmds: lines += cmds_lib.compile_command_to_bash(cmd) return '\n'.join(lines) ================================================ FILE: case_studies/micromanage/service_amazon.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import dateutil.parser import json import re import boto3 import packer import service import util import validate AMI_CACHE = {} def amazon_get_amis(region_name, aws_access_key_id, aws_secret_access_key): ec2 = boto3.client('ec2', region_name=region_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key) result = ec2.describe_images(Owners=['self']) images = result['Images'] return [(i['Name'], dateutil.parser.parse(i['CreationDate'])) for i in images] def amazon_get_ami_by_name(region_name, aws_access_key_id, aws_secret_access_key, name): ec2 = boto3.client('ec2', region_name=region_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key) result = ec2.describe_images(Owners=['self'], Filters=[{'Name': 'name', 'Values': [name]}]) images = result['Images'] assert len(images) == 1, util.jsonstr(images) image = images[0] return image['ImageId'] class AmazonPackerBuildArtefact(packer.PackerBuildArtefact): def __init__(self, image, environment): super(AmazonPackerBuildArtefact, self).__init__(image['cmds']) self.instanceType = image['instanceType'] self.sourceAmi = image['sourceAmi'] self.accessKey = environment['accessKey'] self.secretKey = environment['secretKey'] self.sshUser = image['sshUser'] self.region = environment['region'] def builderHashCode(self): builder_hash = 0; builder_hash ^= packer.hash_string(self.sourceAmi) builder_hash ^= packer.hash_string(self.instanceType) return builder_hash def builder(self): return { 'ami_name': self.name(), 'type': 'amazon-ebs', 'access_key': self.accessKey, 'secret_key': self.secretKey, 'region': self.region, 'source_ami': self.sourceAmi, 'instance_type': self.instanceType, 'ssh_username': self.sshUser, } def needsBuild(self): print 'Checking if AMI exists: %s/%s' % (self.region, self.name()) if (self.region, self.accessKey) in AMI_CACHE: existing_image_names = AMI_CACHE[self.region, self.accessKey] else: existing_image_names = [ img[0] for img in amazon_get_amis(self.region, self.accessKey, self.secretKey)] AMI_CACHE[self.region, self.accessKey] = existing_image_names return self.name() not in existing_image_names def doBuild(self, dirpath): super(AmazonPackerBuildArtefact, self).doBuild(dirpath) if self.accessKey not in AMI_CACHE: AMI_CACHE[self.accessKey] = [] AMI_CACHE[self.accessKey] += [self.name()] def postBuild(self): self.id = amazon_get_ami_by_name(self.region, self.accessKey, self.secretKey, self.name()) class AmazonService(service.Service): def validateEnvironment(self, root, path): fields = {'kind', 'accessKey', 'secretKey', 'region'} validate.obj_only(root, path, fields) validate.path_val(root, path + ['region'], 'string') validate.path_val(root, path + ['accessKey'], 'string') validate.path_val(root, path + ['secretKey'], 'string') def validateService(self, root, path): super(AmazonService, self).validateService(root, path) infra_path = path + ['infrastructure'] validate.path_val(root, infra_path, 'object', {}) inst_path = infra_path + ['aws_instance'] instances = validate.path_val(root, inst_path, 'object', {}) # Validate image configs for inst_name, inst in instances.iteritems(): self.validateCmds(root, inst_path + [inst_name, 'cmds']) self.validateCmds(root, inst_path + [inst_name, 'bootCmds']) image = inst.get('ami') if isinstance(image, dict): self.validateImage(root, inst_path + [inst_name, 'ami']) def validateImage(self, root, path): super(AmazonService, self).validateImage(root, path) validate.path_val(root, path + ['instanceType'], 'string', 'n1-standard-1') validate.path_val(root, path + ['sourceAmi'], 'string') validate.path_val(root, path + ['sshUser'], 'string') validate.obj_only(root, path, {'cmds', 'instanceType', 'sourceAmi', 'sshUser'}) def compileProvider(self, environment_name, environment): return { 'environment.%s.tf.json' % environment_name: { 'provider': { 'aws': { 'alias': environment_name, 'access_key': environment['accessKey'], 'secret_key': environment['secretKey'], 'region' : environment['region'], }, }, }, } def getBuildArtefacts(self, environment, ctx, service): service = copy.deepcopy(service) barts = {} # Build artefacts. infra = service['infrastructure'] instances = infra.get('aws_instance', {}) # Process AMI configs for inst_name, inst in instances.iteritems(): ami = inst['ami'] if isinstance(ami, dict): bart = AmazonPackerBuildArtefact(ami, environment) barts[bart.name()] = bart inst['ami'] = bart.name() return service, barts def compile(self, ctx, service_name, service, barts): infra = service['infrastructure'] # Add provider attributes for res_kind_name, res_kind_obj in infra.iteritems(): for res_name, res in res_kind_obj.iteritems(): res['provider'] = 'aws.%s' % service.get('environment', 'default') instances = infra.get('aws_instance', {}) # Convert AMI name to id for inst_name, inst in instances.iteritems(): ami = inst['ami'] if ami in barts: inst['ami'] = barts[ami].id # Process commands for inst_name, inst in instances.iteritems(): cmds = inst['cmds'] boot_cmds = inst['bootCmds'] if inst.get('user_data'): raise RuntimeError('Cannot use user_data, use cmds instead.') inst['user_data'] = self.compileStartupScript(cmds, boot_cmds) inst.pop('cmds', None) inst.pop('bootCmds', None) return { 'service.%s.tf.json' % self.fullName(ctx + [service_name]): { 'resource': infra, 'output': { k: { 'value': v } for k, v in service['outputs'].iteritems() } } } ================================================ FILE: case_studies/micromanage/service_google.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import json import dateutil.parser import re from oauth2client.client import flow_from_clientsecrets from oauth2client.client import GoogleCredentials from oauth2client.service_account import ServiceAccountCredentials from googleapiclient.discovery import build import packer import service import validate IMAGE_CACHE = {} def google_get_images_json_key(project, key_json): credentials = ServiceAccountCredentials.from_json_keyfile_dict( key_json, scopes=['https://www.googleapis.com/auth/compute']) compute = build('compute', 'v1', credentials=credentials) images = compute.images().list(project=project).execute() items = images.get('items', []) return [(i['name'], dateutil.parser.parse(i['creationTimestamp'])) for i in items] class GooglePackerBuildArtefact(packer.PackerBuildArtefact): def __init__(self, image, environment): super(GooglePackerBuildArtefact, self).__init__(image['cmds']) self.machineType = image['machineType'] self.source = image['source'] self.zone = image['zone'] self.project = environment['project'] self.sshUser = environment['sshUser'] self.serviceAccount = environment['serviceAccount'] def builderHashCode(self): builder_hash = 0; builder_hash ^= packer.hash_string(self.machineType) builder_hash ^= packer.hash_string(self.source) builder_hash ^= packer.hash_string(self.zone) return builder_hash def builder(self): return { 'name': self.name(), 'image_name': self.name(), 'instance_name': self.name(), 'type': 'googlecompute', 'image_description': 'Image built by micromanage', 'project_id': self.project, 'account_file': json.dumps(self.serviceAccount), 'machine_type': self.machineType, 'source_image': self.source, 'zone': self.zone, 'ssh_username': self.sshUser, } def needsBuild(self): print 'Checking if image exists: %s/%s' % (self.project, self.name()) if self.project in IMAGE_CACHE: existing_image_names = IMAGE_CACHE[self.project] else: existing_image_names = [img[0] for img in google_get_images_json_key(self.project, self.serviceAccount)] IMAGE_CACHE[self.project] = existing_image_names return self.name() not in existing_image_names def doBuild(self, dirpath): super(GooglePackerBuildArtefact, self).doBuild(dirpath) if self.project not in IMAGE_CACHE: IMAGE_CACHE[self.project] = [] IMAGE_CACHE[self.project] += [self.name()] def postBuild(self): pass class GoogleService(service.Service): def validateEnvironment(self, root, path): fields = {'kind', 'project', 'region', 'sshUser', 'serviceAccount'} validate.obj_only(root, path, fields) validate.path_val(root, path + ['project'], 'string') validate.path_val(root, path + ['region'], 'string') validate.path_val(root, path + ['sshUser'], 'string') acc = validate.path_val(root, path + ['serviceAccount'], 'object') validate.path_val(root, path + ['serviceAccount', 'client_email'], 'string') validate.path_val(root, path + ['serviceAccount', 'private_key'], 'string') validate.path_val(root, path + ['serviceAccount', 'type'], validate.is_value('service_account'), 'service_account') validate.path_val(root, path + ['serviceAccount', 'client_id'], 'string', '') validate.path_val(root, path + ['serviceAccount', 'private_key_id'], 'string', '') fields = {'client_email', 'private_key', 'type', 'client_id', 'private_key_id'} validate.obj_only(root, path + ['serviceAccount'], fields) def validateService(self, root, path): super(GoogleService, self).validateService(root, path) infra_path = path + ['infrastructure'] validate.path_val(root, infra_path, 'object', {}) inst_path = infra_path + ['google_compute_instance'] instances = validate.path_val(root, inst_path, 'object', {}) disk_path = infra_path + ['google_compute_disk'] disks = validate.path_val(root, disk_path, 'object', {}) # Validate image configs for inst_name, inst in instances.iteritems(): self.validateCmds(root, inst_path + [inst_name, 'cmds']) self.validateCmds(root, inst_path + [inst_name, 'bootCmds']) # Assume instances have a boot disk. validate.path_val(root, inst_path + [inst_name, 'boot_disk'], 'object') boot_disk_path = inst_path + [inst_name, 'boot_disk'] boot_disk = validate.path_val(root, boot_disk_path, 'object') image = boot_disk.get('initialize_params', {}).get('image') if isinstance(image, dict): self.validateImage(root, boot_disk_path + ['initialize_params', 'image']) for disk_name, disk in disks.iteritems(): image = disk.get('image') if isinstance(image, dict): self.validateImage(root, disk_path + [disk_name, 'image']) def validateImage(self, root, path): super(GoogleService, self).validateImage(root, path) validate.path_val(root, path + ['machineType'], 'string', 'n1-standard-1') validate.path_val(root, path + ['source'], 'string') validate.path_val(root, path + ['zone'], 'string') validate.obj_only(root, path, {'cmds', 'machineType', 'source', 'zone'}) def compileProvider(self, environment_name, environment): return { 'environment.%s.tf.json' % environment_name: { 'provider': { 'google': { # 'alias': environment_name, 'credentials': json.dumps(environment['serviceAccount']), 'project': environment['project'], 'region' : environment['region'], }, }, }, } def getBuildArtefacts(self, environment, ctx, service): service = copy.deepcopy(service) barts = {} # Build artefacts. instances = service['infrastructure']['google_compute_instance'] disks = service['infrastructure']['google_compute_disk'] # Process image configs for inst_name, inst in instances.iteritems(): image = inst['boot_disk'].get('initialize_params', {}).get('image') if isinstance(image, dict): bart = GooglePackerBuildArtefact(image, environment) barts[bart.name()] = bart inst['boot_disk']['initialize_params']['image'] = bart.name() for disk_name, disk in disks.iteritems(): image = disk.get('image') if isinstance(image, dict): bart = GooglePackerBuildArtefact(image, environment) barts[bart.name()] = bart disk['image'] = bart.name() return service, barts def compile(self, ctx, service_name, service, barts): infra = service['infrastructure'] # Add provider attributes for res_kind_name, res_kind_obj in infra.iteritems(): for res_name, res in res_kind_obj.iteritems(): # res['provider'] = 'google.%s' % service['environment'] pass # Process instance commands instances = infra.get('google_compute_instance', {}) for inst_name, inst in instances.iteritems(): cmds = inst['cmds'] boot_cmds = inst['bootCmds'] metadata = inst['metadata'] def curl_md(k): md_pref = 'http://169.254.169.254/computeMetadata/v1/instance/attributes' return 'curl -s -H Metadata-Flavor:Google %s/%s' % (md_pref, k) if 'startup-script' in metadata: # Move user startup script out of the way (but still run it at every boot). metadata['micromanage-user-startup-script'] = metadata['startup-script'] metadata.pop('startup-script', None) bootCmds += ['%s | bash' % curl_md('micromanage-user-startup-script')] inst['metadata'] = metadata inst['metadata_startup_script'] = self.compileStartupScript(cmds, boot_cmds) inst.pop('cmds', None) inst.pop('bootCmds', None) return { 'service.%s.tf.json' % self.fullName(ctx + [service_name]): { 'resource': infra, 'output': { k: { 'value': v } for k, v in service['outputs'].iteritems() } } } ================================================ FILE: case_studies/micromanage/tests/amazon/test_single_instance.jsonnet ================================================ local cmd = import "mmlib/v0.1.2/cmd/cmd.libsonnet"; local amis_ubuntu = import "mmlib/v0.1.2/amis/ubuntu.libsonnet"; local amis_debian = import "mmlib/v0.1.2/amis/debian.libsonnet"; { environments: import "../testenv.libsonnet", local SingleAmazonInstance(name) = { local service = self, // Cut off the last letter local region_from_zone(z) = std.substr(z, 0, std.length(z) - 1), environment: "amazon", zone:: error "Must override zone.", amiMap:: error "Must override amiMap or ami.", ami:: null, machineType:: "t2.small", keyName:: null, cmds:: [], externalIp:: false, infrastructure: { aws_instance: { name: { [if service.keyName != null then "key_name"]: service.keyName, instance_type: service.machineType, availability_zone: service.zone, ami: if service.ami != null then service.ami else service.amiMap[region_from_zone(service.zone)], associate_public_ip_address: service.externalIp, cmds: service.cmds, }, }, }, outputs: { name + "-address": "${aws_instance.%s.public_ip}" % name, name + "-id": "${aws_instance.%s.id}" % name, }, }, ubuntu_aws: SingleAmazonInstance('ubuntu_aws') { externalIp: true, keyName: "kp", amiMap: amis_ubuntu.trusty.amd64["20151117"], zone: "us-west-1b", cmds: [ "echo hi > /hi.txt", cmd.LiteralFile { to: "/var/log/bye.txt", content: ||| bye |||, filePermissions: "700", }, ], }, ubuntu_aws_ami: SingleAmazonInstance('ubuntu_aws_ami') { externalIp: true, keyName: "kp", ami: { sourceAmi: amis_ubuntu.trusty.amd64["20151117"]["us-west-1"], instanceType: "t2.small", sshUser: "ubuntu", cmds: [ "echo hi > /hi.txt", cmd.LiteralFile { to: "/var/log/bye.txt", content: ||| bye |||, filePermissions: "700", }, ], }, zone: "us-west-1b", }, debian_aws: SingleAmazonInstance('debian_aws') { externalIp: true, keyName: "kp", amiMap: amis_debian.wheezy.amd64["20150128"], zone: "us-west-1c", cmds: [ "echo hi > /hi.txt", cmd.LiteralFile { to: "/var/log/bye.txt", content: ||| bye |||, filePermissions: "700", }, ], }, debian_aws_ami: SingleAmazonInstance('debian_aws_ami') { externalIp: true, keyName: "kp", ami: { sourceAmi: amis_debian.wheezy.amd64["20150128"]["us-west-1"], instanceType: "t2.small", sshUser: "admin", cmds: [ "echo hi > /hi.txt", cmd.LiteralFile { to: "/var/log/bye.txt", content: ||| bye |||, filePermissions: "700", }, ], }, zone: "us-west-1b", }, } ================================================ FILE: case_studies/micromanage/tests/google/test_empty.jsonnet ================================================ { environments: import "../testenv.libsonnet", empty_service: { environment: "google", }, } ================================================ FILE: case_studies/micromanage/tests/google/test_nested.jsonnet ================================================ { environments: import "../testenv.libsonnet", local SingleInstance(name) = { local service = self, environment: "google", zone:: error "Must override zone.", image:: error "Must override zone.", machineType:: "g1-small", externalIp:: false, infrastructure: { google_compute_instance: { name: { name: name, machine_type: service.machineType, zone: service.zone, boot_disk: { initialize_params: { image: service.image, }, }, network_interface: { network: "default", access_config: if service.externalIp then [{}] else [], }, metadata: { }, cmds: [], bootCmds: [], }, }, }, outputs: { [if service.externalIp then "address"]: "${google_compute_instance.%s.network_interface[0].access_config.0.nat_ip}" % name, }, }, instances: { environment: "google", infrastructure: {}, wheezy: SingleInstance('wheezy') { image: "debian-9-stretch-v20191121", zone: "us-central1-b", }, ubuntu: SingleInstance('ubuntu') { image: "ubuntu-1804-bionic-v20191113", zone: "us-central1-c", }, "core-os": SingleInstance('core-os') { image: "coreos-stable-2303-3-0-v20191203", zone: "us-central1-f", externalIp: true, }, }, } ================================================ FILE: case_studies/micromanage/tests/google/test_single_instance.jsonnet ================================================ local cmd = import "mmlib/v0.1.2/cmd/cmd.libsonnet"; { environments: import "../testenv.libsonnet", local SingleGoogleInstance(name) = { local service = self, environment: "google", zone:: error "Must override zone.", image:: error "Must override zone.", machineType:: "g1-small", cmds:: [], externalIp:: false, infrastructure: { google_compute_instance: { name: { name: name, machine_type: service.machineType, zone: service.zone, boot_disk: { initialize_params: { image: service.image, }, }, network_interface: { network: "default", access_config: if service.externalIp then [{}] else [], }, metadata: { }, cmds: service.cmds, bootCmds: [], }, }, }, outputs: { [if service.externalIp then name + "-address"]: "${google_compute_instance.%s.network_interface[0].access_config.0.nat_ip}" % name, }, }, wheezy: SingleGoogleInstance('wheezy') { image: "debian-9-stretch-v20191121", zone: "us-central1-b", }, ubuntu: SingleGoogleInstance('ubuntu') { image: "ubuntu-1804-bionic-v20191113", zone: "us-central1-c", }, "core-os": SingleGoogleInstance('core-os') { image: "coreos-stable-2303-3-0-v20191203", zone: "us-central1-f", externalIp: true, cmds: [ "echo hi > /hi.txt", cmd.LiteralFile { to: "/var/log/bye.txt", content: ||| bye |||, filePermissions: "700", }, ], }, custom: SingleGoogleInstance('custom') { image: { source: "ubuntu-1804-bionic-v20191113", zone: "us-central1-f", cmds: [ "echo hi > /hi.txt", cmd.LiteralFile { to: "/var/log/bye.txt", content: ||| bye |||, filePermissions: "700", }, ], }, zone: "us-central1-f", externalIp: true, }, } ================================================ FILE: case_studies/micromanage/tests/test_really_empty.jsonnet ================================================ { environments: { }, } ================================================ FILE: case_studies/micromanage/tests/testenv.libsonnet.TEMPLATE ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. { google: { kind: "Google", project: "readable-name-123", // Change this. region: "us-central1", // Maybe change this. // Download this file from the developers console. serviceAccount: import "service_account.json", sshUser: "yourusername" // Change this. }, amazon: { kind: "Amazon", region: "us-west-1", // Maybe change this. sshUser: "yourusername" // Change this. }, } ================================================ FILE: case_studies/micromanage/util.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import json def jsonstr(v): return json.dumps(v, sort_keys=True, indent=4, separators=(',', ': ')) def utc_now(): class UTC(datetime.tzinfo): def utcoffset(self, dt): return datetime.timedelta(0) def tzname(self, dt): return "UTC" def dst(self, dt): return datetime.timedelta(0) now = datetime.datetime.now(UTC()) # Merge b into a, preferring b on conflicts def merge_into(a, b): for k, v in b.iteritems(): a[k] = v ================================================ FILE: case_studies/micromanage/validate.py ================================================ # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import numbers import re class ConfigError (Exception): def __init__(self, msg, note): super(ConfigError, self).__init__(msg) self.note = note def render_path(path): if isinstance(path, basestring): return path def aux(p): if not isinstance(p, basestring): return '[%d]' % p return ('.%s' if _isidentifier(p) else '["%s"]') % p return '$' + ''.join([aux(p) for p in path]) # Utilities def err(path, msg, note=None): raise ConfigError('%s: %s' % (render_path(path), msg), note) _KEYWORDS = { 'import', 'importstr', 'importbin', 'function', 'self', 'super', 'assert', 'if', 'then', 'else', 'for', 'in', 'local', 'tailstrict', 'true', 'false', 'null', 'error', } def _isidentifier(name): return name not in _KEYWORDS and re.match("[_A-Za-z_][_a-zA-Z0-9]*$", name) _TYPE_FROM_STR = { 'null': type(None), 'bool': bool, 'number': numbers.Number, 'object': dict, 'array': list, 'string': basestring, } def _type_err(v): if isinstance(v, basestring): return '"%s"' % v if isinstance(v, numbers.Number): return '%s' % v if isinstance(v, bool): return '%s' % v return _type_str(v) def _type_str(v): if v is None: return 'null' if isinstance(v, basestring): return 'string' if isinstance(v, dict): return 'object' if isinstance(v, list): return 'array' if isinstance(v, numbers.Number): return 'number' if isinstance(v, bool): return 'bool' class _Empty: pass _NO_DEFAULT = _Empty() # Return the value obtained by resolving the path from root. If the value does not exist in the # last object, create it using the default and return that instead. def _resolve_path(root, path, default=_NO_DEFAULT): for i, v in enumerate(path): if isinstance(root, dict) and i == len(path) - 1: if v not in root and default != _NO_DEFAULT: root[v] = default root = root[v] return root # Validators def is_string_map(v): msg = is_type('object')(v) if msg: return msg for k, v2 in v.iteritems(): if not isinstance(v2, basestring): return 'Expected field %s type to be string but got %s' % (k, _type_err(v2)) # Validator generating functions. # Pretty-print a small set of strings. def _set_str(s): return '{%s}' % ', '.join(sorted(s)) def is_any_type(types): def check(v): if _type_str(v) not in types: return 'Expected type to be one of %s but found %s' % (_set_str(types), _type_err(v)) return check def is_type(t): def check(v): if _type_str(v) != t: return 'Expected type %s but found %s' % (t, _type_err(v)) return check def is_value(expected): def check(v): if v != expected: return 'Expected value %s, got %s' % (expected, v) return check def is_any_value(expected): def check(v): if v not in expected: return 'Expected value to be one of %s, got %s' % (_set_str(expected), v) return check # User-friendly validation routines: # You can just give a type name in place of a function. def _sanitize_func(func): if isinstance(func, basestring): return is_type(func) return func # Ensure path validates by func. If not proesent, inserts default. def path_val(root, path, func, default=None): func = _sanitize_func(func) v = _resolve_path(root, path, default) msg = func(v) if msg is not None: err(path, msg) return v # Ensure path is an array and all elements validate by element_func. def array(root, path, element_func, default): v = path_val(root, path, 'array', default) element_func = _sanitize_func(element_func) for i, el in enumerate(v): path_val(root, path + [i], element_func) return v # Ensure path is an object and only has the given fields. If not present, inserts # default. def obj_only(root, path, fields, default=None): v = path_val(root, path, 'object', default) for field in v: if field not in fields: err(path, 'Unexpected field: %s' % field) return v ================================================ FILE: cmd/BUILD ================================================ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") package(default_visibility = ["//visibility:private"]) cc_library( name = "utils", srcs = ["utils.cpp"], hdrs = ["utils.h"], includes = ["."], ) cc_binary( name = "jsonnet", srcs = ["jsonnet.cpp"], visibility = ["//visibility:public"], deps = [ ":utils", "//core:libjsonnet", ], ) cc_binary( name = "jsonnetfmt", srcs = ["jsonnetfmt.cpp"], visibility = ["//visibility:public"], deps = [ ":utils", "//core:libjsonnet", ], ) ================================================ FILE: cmd/jsonnet.cpp ================================================ /* Copyright 2015 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" extern "C" { #include } #ifdef _WIN32 const char PATH_SEP = ';'; #else const char PATH_SEP = ':'; #endif void version(std::ostream &o) { o << "Jsonnet commandline interpreter " << jsonnet_version() << std::endl; } void usage(std::ostream &o) { version(o); o << "\n"; o << "jsonnet {