diff --git a/Makefile b/Makefile index 20370ed..c182e98 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ OUTLINE_CMD ?= outline DOCUMENTATION_PATH ?= $(BASE_PATH)/_documentation DOCUMENTATION_REFERENCE_PATH ?= $(DOCUMENTATION_PATH)/reference DOCUMENTATION_REFERENCE_TEMPLATE ?= $(DOCUMENTATION_REFERENCE_PATH)/reference.md.tmpl -EXAMPLES_PATH ?= starlark/types/testdata/examples +DOCUMENTATION_INLINE_EXAMPLES_PATH ?= starlark/types/testdata/examples RUNTIME_MODULES = \ github.com/mcuadros/ascode/starlark/module/os \ @@ -25,6 +25,12 @@ STARLIB_PKG ?= github.com/qri-io/starlib STARLIB_COMMIT ?= $(shell $(QUERY_GO_MOD_CMD) . $(STARLIB_PKG)) STARLIB_PKG_LOCATION = $(GOPATH)/src/$(STARLIB_PKG) +# Examples +EXAMPLE_TO_MD_CMD = go run _scripts/example-to-md.go +EXAMPLES = functions.star runtime.star +EXAMPLES_PATH = $(BASE_PATH)/_examples +DOCUMENTATION_EXAMPLES_PATH = $(DOCUMENTATION_PATH)/example + # Build Info GO_LDFLAGS_CMD = go run _scripts/goldflags.go GO_LDFLAGS_PACKAGE = cmd @@ -39,7 +45,8 @@ HUGO_SITE_TEMPLATE_PATH ?= $(HUGO_SITE_PATH)/themes/hugo-ascode-theme HUGO_THEME_URL ?= https://github.com/mcuadros/hugo-ascode-theme HUGO_PARAMS_VERSION ?= dev export HUGO_PARAMS_VERSION - + + # Rules .PHONY: documentation clean hugo-server @@ -47,7 +54,7 @@ documentation: $(RUNTIME_MODULES) $(RUNTIME_MODULES): $(DOCUMENTATION_RUNTIME_PATH) $(STARLIB_PKG_LOCATION) $(OUTLINE_CMD) package \ -t $(DOCUMENTATION_REFERENCE_TEMPLATE) \ - -d $(EXAMPLES_PATH) \ + -d $(DOCUMENTATION_INLINE_EXAMPLES_PATH) \ $@ \ > $(DOCUMENTATION_REFERENCE_PATH)/`basename $@`.md @@ -60,13 +67,20 @@ $(STARLIB_PKG_LOCATION): git checkout $(STARLIB_COMMIT); \ cd $(BASE_PATH); +examples: $(EXAMPLES) + +$(EXAMPLES): + $(EXAMPLE_TO_MD_CMD) \ + $(EXAMPLES_PATH)/$@ $(shell ls -1 $(DOCUMENTATION_EXAMPLES_PATH) | wc -l) \ + > $(DOCUMENTATION_EXAMPLES_PATH)/$@.md + goldflags: @$(GO_LDFLAGS_CMD) $(GO_LDFLAGS_PACKAGE) . $(GO_LDFLAGS_PACKAGES) -hugo-build: $(HUGO_SITE_PATH) documentation +hugo-build: $(HUGO_SITE_PATH) documentation examples hugo --minify --source $(HUGO_SITE_PATH) --config $(DOCUMENTATION_PATH)/config.toml -hugo-server: $(HUGO_SITE_PATH) documentation +hugo-server: $(HUGO_SITE_PATH) documentation examples hugo server --source $(HUGO_SITE_PATH) --config $(DOCUMENTATION_PATH)/config.toml $(HUGO_SITE_PATH): $(HUGO_SITE_TEMPLATE_PATH) diff --git a/_documentation/example/.gitignore b/_documentation/example/.gitignore new file mode 100644 index 0000000..e5e6f0a --- /dev/null +++ b/_documentation/example/.gitignore @@ -0,0 +1,5 @@ +# Ignore +*.md + +# Allow +!_index.md \ No newline at end of file diff --git a/_documentation/example/_index.md b/_documentation/example/_index.md new file mode 100644 index 0000000..5ab3a6d --- /dev/null +++ b/_documentation/example/_index.md @@ -0,0 +1,5 @@ +--- +title: 'Examples' +weight: 30 +summary: true +--- diff --git a/_examples/aws.star b/_examples/aws.star deleted file mode 100644 index 2348aad..0000000 --- a/_examples/aws.star +++ /dev/null @@ -1,37 +0,0 @@ -aws = tf.provider("aws") -aws.region = "us-west-2" - -# It creates a new instance for the given name, distro and type. -def new_instance(name, distro, type="t2.micro"): - instance = aws.resource.instance(name) - instance.instance_type = type - instance.ami = get_ami_id(distro) - - return instance - -amis = {} -ami_names_owners = { - "ubuntu": ["ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*", "099720109477"], - "ecs": ["*amazon-ecs-optimized", "591542846629"], -} - -# We create the AMI data-source for the given distro. -def get_ami_id(distro): - if distro in amis: - return amis[distro] - - data = ami_names_owners[distro] - - ami = aws.data.ami(distro) - ami.most_recent = True - ami.filter(name="name", values=[data[0]]) - ami.filter(name="virtualization-type", values=["hvm"]) - ami.owners = [data[1]] - - amis[distro] = ami.id - return ami.id - -# Creates 20 instances of each distro. -for i in range(20): - new_instance("ubuntu_%d" % i, "ubuntu") - new_instance("ecs_%d" % i, "ecs") \ No newline at end of file diff --git a/_examples/backend.star b/_examples/backend.star deleted file mode 100644 index 045acdf..0000000 --- a/_examples/backend.star +++ /dev/null @@ -1,3 +0,0 @@ -tf.backend = backend("gcs") -tf.backend.bucket = "tf-state-prod" -tf.backend.prefix = "terraform/state" \ No newline at end of file diff --git a/_examples/docker.star b/_examples/docker.star deleted file mode 100644 index bbf6653..0000000 --- a/_examples/docker.star +++ /dev/null @@ -1,12 +0,0 @@ -load("experimental/docker", "docker") - -p = tf.provider("docker", "2.7.0", "foo") - -# using docker.image semver can be used to choose the docker image, ` -golang = docker.image("golang", "1.13.x") - -foo = p.resource.container("foo") -foo.name = "foo" - -# version queries the docker repository and returns the correct tag. -foo.image = golang.version(full=True) \ No newline at end of file diff --git a/_examples/functions.star b/_examples/functions.star new file mode 100644 index 0000000..41c04c1 --- /dev/null +++ b/_examples/functions.star @@ -0,0 +1,126 @@ +# Using functions +# This example illustrates how with the usage through the usage of functions, +# we can simplify and improve the readability of our infrastructure declaration. +# + +# Instantiates a new AWS provider, `aws` will be available in the context of +# the functions. +aws = tf.provider("aws", region="us-west-2") + +# Every instance requires an `ami` data source; this data source contains a +# very specif configuration like the ID of the owner or a name pattern. So +# we define a dictionary with the different values we want to use. +ami_names_owners = { + "ubuntu": ["ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*", "099720109477"], + "ecs": ["*amazon-ecs-optimized", "591542846629"], +} + +# `get_ami` returns the ami for the given `distro`. It searches in the +# `ResouceCollection` of the `ami` data source, if finds the `ami` it simply +# returns it; if not creates a new one using the data from the `ami_names_owners` +# dictionary. +def get_ami(distro): + amis = aws.data.ami.search(distro) + if len(amis) != 0: + return amis[0] + + data = ami_names_owners[distro] + + ami = aws.data.ami(distro) + ami.most_recent = True + ami.filter(name="name", values=[data[0]]) + ami.filter(name="virtualization-type", values=["hvm"]) + ami.owners = [data[1]] + + return ami + +# `new_instance` instantiates a new `instance` for the given name, distro and +# type, the type has a default value `t2.micro`. The `distro` value is resolved +# to an `ami` resource using the previously defined function `get_ami`. +def new_instance(name, distro, type="t2.micro"): + instance = aws.resource.instance(name) + instance.instance_type = type + instance.ami = get_ami(distro).id + +# Now using a basic `for` loop we can instantiate 5 different web servers, +# where the even machines are using ubuntu and the odd ones ecs. +for i in range(5): + distro = "ubuntu" + if i % 2: + distro = "ecs" + + new_instance("web_%d" % i, distro) + +# ### Output +# If we execute this script with the flag `--print-hcl` the result shuld be +# something like this: + +"""hcl +provider "aws" { + alias = "id_01E4KEA5ZAA1PYERQ8KM5D04GC" + version = "2.13.0" + region = "us-west-2" +} + +data "aws_ami" "ubuntu" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + most_recent = true + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +data "aws_ami" "ecs" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + most_recent = true + owners = ["591542846629"] + + filter { + name = "name" + values = ["*amazon-ecs-optimized"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_instance" "web_0" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" +} + +resource "aws_instance" "web_1" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + ami = "${data.aws_ami.ecs.id}" + instance_type = "t2.micro" +} + +resource "aws_instance" "web_2" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" +} + +resource "aws_instance" "web_3" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + ami = "${data.aws_ami.ecs.id}" + instance_type = "t2.micro" +} + +resource "aws_instance" "web_4" { + provider = aws.id_01E4KEA5ZAA1PYERQ8KM5D04GC + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" +} +""" diff --git a/_examples/ignition.star b/_examples/ignition.star deleted file mode 100644 index dfac907..0000000 --- a/_examples/ignition.star +++ /dev/null @@ -1,22 +0,0 @@ -ignition = tf.provider("ignition", "1.1.0") - -user = ignition.data.user() -user.name = "foo" -user.uid = 42 -user.groups = ["foo", "bar"] -user.system = True - -disk = ignition.data.disk() -disk.device = "/dev/sda" - -root = disk.partition() -root.start = 2048 -root.size = 4 * 1024 * 1024 - -home = disk.partition() -home.start = root.size + root.start -home.size = 4 * 1024 * 1024 - -ignition.data.config(disks=[disk.id], users=[user.id]) - - diff --git a/_examples/runtime.star b/_examples/runtime.star index 5afc154..88bd941 100644 --- a/_examples/runtime.star +++ b/_examples/runtime.star @@ -1,7 +1,51 @@ +# Runtime Modules +# AsCode comes with a variety of modules available like `http`, `math`, +# `encoding/json`, etc. All this [modules](/docs/reference/) are available +# runtime through the [`load`](/docs/starlark/statements/#load-statements) +# statement. This example shows the usage of this modules and some others. +# + +# ## Basic Module +# The load statement expects at least two arguments; the first is the name of +# the module, and the same the symbol to extract to it. The runtime modules +# always define a symbol called equals to the last part of the module name. load("encoding/base64", "base64") load("http", "http") +# This modules are very usuful to do basic operations such as encoding of +# strings, like in this case to `base64` or to make HTTP requests. dec = base64.encode("ascode is amazing") msg = http.get("https://httpbin.org/base64/%s" % dec) -print(msg.body()) \ No newline at end of file +print(msg.body()) + +# ### Output +"""sh +ascode is amazing +""" + +# ## Advanced Modules +# Also, AsCode has some more specif modules, like the `docker` module. The +# docker modules allow you to manipulate docker image names. +load("experimental/docker", "docker") + +# A docker image tag can be defined using semver, instead of using the infamous +# 'latest' tag, or fixing a particular version. This allows us to be up-to-date +# without breaking our deployment. +golang = docker.image("golang", "1.13.x") + +# We can use this in the definition of resources, allowing use to upgrade +# the version of our containers in every `terraform apply` +p = tf.provider("docker", "2.7.0") +container = p.resource.container("golang", image=golang.version(full=True)) + +# version queries the docker repository and returns the correct tag. +print(hcl(container)) + +# ### Output +"""hcl +resource "docker_container" "foo" { + provider = docker.id_01E4KHW2RSW0FQM93KN5W70Y42 + image = "docker.io/library/golang:1.13.9" +} +""" \ No newline at end of file diff --git a/_scripts/example-to-md.go b/_scripts/example-to-md.go new file mode 100644 index 0000000..f5ccfda --- /dev/null +++ b/_scripts/example-to-md.go @@ -0,0 +1,102 @@ +package main + +import ( + "bufio" + "bytes" + "fmt" + "log" + "os" + "strings" +) + +func main() { + md, err := exampleToMD(os.Args[1], os.Args[2]) + if err != nil { + log.Fatal(err) + } + + fmt.Println(md) +} + +func exampleToMD(filename string, weight string) (string, error) { + b := bytes.NewBuffer(nil) + f, err := os.Open(filename) + if err != nil { + return "", err + } + + var isComment, isCodeBlock, isPrint bool + var preLine string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + + line := scanner.Text() + curIsComment := len(line) > 0 && line[0] == '#' + if isCodeBlock && len(preLine) == 0 && curIsComment { + isPrint = false + } + + if len(preLine) >= 3 && preLine[:3] == `"""` { + isPrint = false + } + + if isPrint { + + preLine = strings.Trim(preLine, "#") + fmt.Fprintln(b, preLine) + isPrint = false + } + + preLine = line + if curIsComment { + line = strings.TrimSpace(line[1:]) + + isComment = true + if isCodeBlock { + fmt.Fprintln(b, "```\n\n") + isCodeBlock = false + } + + if b.Len() == 0 { + fmt.Fprintf(b, + "---\ntitle: '%s'\nweight: %s\n---\n\n", + line, weight, + ) + continue + } + + isPrint = true + continue + } + + if len(line) == 0 && isComment { + isPrint = true + continue + } + + if isComment { + isComment = false + isCodeBlock = true + + if len(line) >= 3 && line[:3] == `"""` { + fmt.Fprintln(b, "```"+line[3:]) + continue + } + + fmt.Fprintln(b, "\n\n```python") + + } + + isPrint = true + } + + if isCodeBlock { + fmt.Fprintln(b, "```") + } + + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading standard input:", err) + } + + return b.String(), nil +}