1
1
mirror of https://github.com/mcuadros/ascode synced 2024-11-22 08:52:00 +01:00

cmd: version command

This commit is contained in:
Máximo Cuadros 2020-03-27 18:33:10 +01:00
parent e627a3db53
commit b5d48116cc
No known key found for this signature in database
GPG Key ID: 17A5DFEDC735AE4B
10 changed files with 183 additions and 445 deletions

@ -1,7 +1,11 @@
FROM golang:1.14.0-alpine AS builder
RUN apk --no-cache add make
WORKDIR $GOPATH/src/github.com/mcuadros/ascode
COPY . .
RUN GO111MODULE=on CGO_ENABLED=0 GOPROXY="https://proxy.golang.org" go build -o /bin/ascode .
RUN GO111MODULE=on CGO_ENABLED=0 GOPROXY="https://proxy.golang.org" \
go build \
-ldflags "$(make goldflags)" \
-o /bin/ascode .
FROM alpine:latest
RUN apk --no-cache add ca-certificates

@ -1,12 +1,7 @@
# Package configuration
PROJECT = ascode
COMMANDS = .
DEPENDENCIES =
# Documentation
OUTLINE_CMD ?= outline
DOCUMENTATION_PATH ?= _documentation
DOCUMENTATION_RUNTIME_PATH ?= $(DOCUMENTATION_PATH)/runtime
DOCUMENTATION_RUNTIME_PATH ?= $(DOCUMENTATION_PATH)/reference
EXAMPLES_PATH ?= starlark/types/testdata/examples
RUNTIME_MODULES = \
@ -20,32 +15,13 @@ RUNTIME_MODULES = \
github.com/qri-io/starlib/re \
github.com/qri-io/starlib/http
# Build information
BUILD ?= $(shell date +"%m-%d-%Y_%H_%M_%S")
COMMIT ?= $(shell git rev-parse --short HEAD)
GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "-dirty" || true)
DEV_PREFIX := dev
VERSION ?= $(DEV_PREFIX)-$(COMMIT)$(GIT_DIRTY)
# Build Info
GO_LDFLAGS_CMD = go run _scripts/goldflags.go
GO_LDFLAGS_PACKAGE = cmd
GO_LDFLAGS_PACKAGES = \
starlarkVersion=go.starlark.net \
terraformVersion=github.com/hashicorp/terraform
# Travis CI
ifneq ($(TRAVIS_TAG), )
VERSION := $(TRAVIS_TAG)
endif
# Packages content
PKG_OS ?= darwin linux
PKG_ARCH = amd64
# Golang config
LD_FLAGS ?= -X main.version=$(VERSION) -X main.build=$(BUILD) -X main.commit=$(COMMIT)
GO_CMD = go
GO_GET = $(GO_CMD) get -v -t
GO_BUILD = $(GO_CMD) build -ldflags "$(LD_FLAGS)"
GO_TEST = $(GO_CMD) test -v
# Environment
BUILD_PATH := build
BIN_PATH := $(BUILD_PATH)/bin
# Rules
.PHONY: $(RUNTIME_MODULES) $(COMMANDS) documentation
@ -58,30 +34,5 @@ $(RUNTIME_MODULES): $(DOCUMENTATION_RUNTIME_PATH)
$(DOCUMENTATION_RUNTIME_PATH):
mkdir -p $@
build: $(COMMANDS)
$(COMMANDS):
@if [ "$@" == "." ]; then \
BIN=`basename $(CURDIR)` ; \
else \
BIN=`basename $@` ; \
fi && \
for os in $(PKG_OS); do \
NBIN="$${BIN}" ; \
if [ "$${os}" == windows ]; then \
NBIN="$${NBIN}.exe"; \
fi && \
for arch in $(PKG_ARCH); do \
mkdir -p $(BUILD_PATH)/$(PROJECT)_$${os}_$${arch} && \
$(GO_BUILD_ENV) GOOS=$${os} GOARCH=$${arch} \
$(GO_BUILD) -o "$(BUILD_PATH)/$(PROJECT)_$${os}_$${arch}/$${NBIN}" ./$@; \
done; \
done
packages: build
@cd $(BUILD_PATH); \
for os in $(PKG_OS); do \
for arch in $(PKG_ARCH); do \
TAR_VERSION=`echo $(VERSION) | tr "/" "-"`; \
tar -cvzf $(PROJECT)_$${TAR_VERSION}_$${os}_$${arch}.tar.gz $(PROJECT)_$${os}_$${arch}/; \
done; \
done
goldflags:
@$(GO_LDFLAGS_CMD) $(GO_LDFLAGS_PACKAGE) . $(GO_LDFLAGS_PACKAGES)

134
_scripts/goldflags.go Normal file

@ -0,0 +1,134 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/rogpeppe/go-internal/modfile"
)
func main() {
pkg := os.Args[1]
path := os.Args[2]
f, err := readGoMod(path)
if err != nil {
log.Fatal(err)
}
flags, err := getFlags(f, path, os.Args[3:])
if err != nil {
log.Fatal(err)
}
if pkg != "main" {
pkg = filepath.Join(f.Module.Mod.Path, pkg)
}
fmt.Printf(renderLDFLAGS(pkg, flags))
}
func getFlags(f *modfile.File, path string, pkgs []string) (map[string]string, error) {
var err error
flags := make(map[string]string, 0)
flags["build"] = time.Now().Format(time.RFC3339)
flags["version"], flags["commit"], err = readVersion(path)
if err != nil {
return nil, err
}
for _, v := range pkgs {
parts := strings.SplitN(v, "=", 2)
key := parts[0]
pkg := parts[1]
flags[key] = getPackageVersion(f, pkg)
}
return flags, nil
}
func readVersion(path string) (string, string, error) {
r, err := git.PlainOpen(path)
if err != nil {
return "", "", err
}
ref, err := r.Head()
if err != nil {
return "", "", err
}
if !ref.Name().IsBranch() {
ref, err = findTag(r, ref.Hash())
if err != nil {
return "", "", err
}
}
return ref.Name().Short(), ref.Hash().String()[:7], nil
}
func findTag(r *git.Repository, h plumbing.Hash) (*plumbing.Reference, error) {
tagrefs, err := r.Tags()
if err != nil {
return nil, err
}
var match *plumbing.Reference
err = tagrefs.ForEach(func(t *plumbing.Reference) error {
if t.Hash() == h {
match = t
}
return nil
})
return match, err
}
func getVersionFromBranch(ref *plumbing.Reference) string {
name := ref.Name().Short()
pattern := "dev-%s"
if name != "master" {
pattern = fmt.Sprintf("dev-%s-%%s", name)
}
hash := ref.Hash().String()[:7]
return fmt.Sprintf(pattern, hash)
}
func readGoMod(path string) (*modfile.File, error) {
content, err := ioutil.ReadFile(filepath.Join(path, "go.mod"))
if err != nil {
return nil, err
}
return modfile.ParseLax("", content, nil)
}
func getPackageVersion(f *modfile.File, pkg string) string {
for _, r := range f.Require {
if r.Mod.Path == pkg {
return r.Mod.Version
}
}
return ""
}
func renderLDFLAGS(pkg string, flags map[string]string) string {
output := make([]string, 0)
for k, v := range flags {
output = append(output, fmt.Sprintf("-X %s.%s=%s", pkg, k, v))
}
return strings.Join(output, " ")
}

@ -1,81 +0,0 @@
---
layout: "ignition"
page_title: "Ignition: ignition_file"
sidebar_current: "docs-ignition-datasource-file"
description: |-
Describes a file to be written in a particular filesystem.
---
# ignition\_file
Describes a file to be written in a particular filesystem.
## Example Usage
File with inline content:
```hcl
data "ignition_file" "hello" {
filesystem = "foo"
path = "/hello.txt"
content {
content = "Hello World!"
}
}
```
File with remote content:
```hcl
data "ignition_file" "hello" {
filesystem = "qux"
path = "/hello.txt"
source {
source = "http://example.com/hello.txt.gz"
compression = "gzip"
verification = "sha512-0123456789abcdef0123456789...456789abcdef"
}
}
```
## Argument Reference
The following arguments are supported:
* `filesystem` - (Required) The internal identifier of the filesystem. This matches the last filesystem with the given identifier. This should be a valid name from a _ignition\_filesystem_ resource.
* `path` - (Required) The absolute path to the file.
* `content` - (Optional) Block to provide the file content inline.
* `source` - (Optional) Block to retrieve the file content from a remote location.
__Note__: `content` and `source` are mutually exclusive.
* `mode` - (Optional) The file's permission mode. The mode must be properly specified as a decimal value (i.e. 0644 -> 420).
* `uid` - (Optional) The user ID of the owner.
* `gid` - (Optional) The group ID of the owner.
The `content` block supports:
* `mime` - (Required) MIME format of the content (default _text/plain_).
* `content` - (Required) Content of the file.
The `source` block supports:
* `source` - (Required) The URL of the file contents. Supported schemes are http, https, tftp, s3, and [data][rfc2397]. When using http, it is advisable to use the verification option to ensure the contents haven't been modified.
* `compression` - (Optional) The type of compression used on the contents (null or gzip). Compression cannot be used with S3.
* `verification` - (Optional) The hash of the config, in the form _\<type\>-\<value\>_ where type is sha512.
## Attributes Reference
The following attributes are exported:
* `id` - ID used to reference this resource in _ignition_config_.
[rfc2397]: https://tools.ietf.org/html/rfc2397

@ -1,225 +0,0 @@
package doc
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strings"
"github.com/b5/outline/lib"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/providers"
"github.com/mcuadros/ascode/starlark/types"
)
type ResourceDocumentation struct {
Name string
Type string
Attribs map[string]string
Blocks map[string]map[string]string
}
func NewResourceDocumentation(typ, name string) *ResourceDocumentation {
return &ResourceDocumentation{
Name: name,
Type: typ,
Attribs: make(map[string]string, 0),
Blocks: make(map[string]map[string]string, 0),
}
}
var re = regexp.MustCompile(`\*[. ][\x60](.*)[\x60].*\) (.*)`)
// https://regex101.com/r/hINfBI/2
var blockRe = regexp.MustCompile(`^[^\*].*\x60(.*)\x60 block(?:s?)`)
func (r *ResourceDocumentation) Decode(doc io.Reader) error {
var block string
buf := bufio.NewReader(doc)
for {
line, err := buf.ReadString('\n')
if err == io.EOF {
return nil
}
if err != nil {
return err
}
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
parts := re.FindStringSubmatch(line)
if len(parts) == 3 {
if block == "" {
r.AddAttrib(parts[1], parts[2])
continue
}
r.AddBlockAttrib(block, parts[1], parts[2])
continue
}
parts = blockRe.FindStringSubmatch(line)
if len(parts) == 2 {
block = parts[1]
}
}
return nil
}
func (r *ResourceDocumentation) AddAttrib(name, desc string) {
r.Attribs[name] = desc
}
func (r *ResourceDocumentation) AddBlockAttrib(block, name, desc string) {
if _, ok := r.Blocks[block]; !ok {
r.Blocks[block] = make(map[string]string, 0)
}
r.Blocks[block][name] = desc
}
type Documentation struct {
name string
repository *git.Repository
head *object.Commit
resources map[string]map[string]string
}
func NewDocumentation(name string) (*Documentation, error) {
d := &Documentation{
name: name,
resources: make(map[string]map[string]string, 0),
}
return d, d.initRepository()
}
func (d *Documentation) initRepository() error {
storer := memory.NewStorage()
var err error
d.repository, err = git.Clone(storer, nil, &git.CloneOptions{
URL: fmt.Sprintf("https://github.com/terraform-providers/terraform-provider-%s.git", d.name),
Depth: 1,
// as git does, when you make a clone, pull or some other operations the
// server sends information via the sideband, this information can being
// collected provinding a io.Writer to the CloneOptions options
Progress: os.Stdout,
})
h, err := d.repository.Head()
if err != nil {
return err
}
d.head, err = d.repository.CommitObject(h.Hash())
return err
}
func (d *Documentation) Resource(typ, name string) (*ResourceDocumentation, error) {
parts := strings.SplitN(name, "_", 2)
name = parts[1]
filename := fmt.Sprintf("website/docs/%s/%s.html.md", typ, name)
file, err := d.head.File(filename)
if err != nil {
return nil, err
}
r, err := file.Reader()
if err != nil {
return nil, err
}
resource := NewResourceDocumentation(typ, name)
return resource, resource.Decode(r)
}
func (d *Documentation) Do(name string, schema providers.GetSchemaResponse) *lib.Doc {
doc := &lib.Doc{}
doc.Name = name
doc.Path = name
for name, schema := range schema.DataSources {
doc.Types = append(doc.Types, d.schemaToDoc(name, &schema)...)
}
return doc
}
func (d *Documentation) schemaToDoc(resource string, s *providers.Schema) []*lib.Type {
rd, err := d.Resource("d", resource)
if err != nil {
panic(err)
}
fmt.Println(resource, rd)
typ := &lib.Type{}
typ.Name = resource
for name, attr := range s.Block.Attributes {
typ.Fields = append(typ.Fields, d.attributeToField(rd.Attribs, name, attr))
}
types := []*lib.Type{typ}
for name, block := range s.Block.BlockTypes {
types = append(types, d.blockToType(rd, resource, name, block))
typ.Fields = append(typ.Fields, d.blockToField(rd, resource, name, block))
}
return types
}
func (d *Documentation) blockToType(rd *ResourceDocumentation, resource, name string, block *configschema.NestedBlock) *lib.Type {
typ := &lib.Type{}
typ.Name = fmt.Sprintf("%s.%s", resource, name)
for n, attr := range block.Attributes {
//fmt.Println(rd.Blocks[name])
typ.Fields = append(typ.Fields, d.attributeToField(rd.Blocks[name], n, attr))
}
return typ
}
func (d *Documentation) blockToField(rd *ResourceDocumentation, resource, name string, block *configschema.NestedBlock) *lib.Field {
field := &lib.Field{}
nested := fmt.Sprintf("%s.%s", resource, name)
field.Name = name
if block.MaxItems != 1 {
field.Type = fmt.Sprintf("collection<%s>", nested)
} else {
field.Type = nested
}
return field
}
func (d *Documentation) attributeToField(doc map[string]string, name string, attr *configschema.Attribute) *lib.Field {
field := &lib.Field{}
field.Name = name
field.Description, _ = doc[name]
if attr.Computed && !attr.Optional {
field.Type = "computed"
} else {
field.Type = types.MustTypeFromCty(attr.Type).Starlark()
}
return field
}

@ -1,76 +0,0 @@
package doc
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"text/template"
"github.com/hashicorp/terraform/plugin"
"github.com/mcuadros/ascode/terraform"
"github.com/stretchr/testify/assert"
)
func TestDocumentation(t *testing.T) {
f, err := os.Open("fixtures/ignition_file.md")
assert.NoError(t, err)
res := NewResourceDocumentation("d", "ignition_file")
res.Decode(f)
assert.Len(t, res.Attribs, 7)
assert.Len(t, res.Blocks["content"], 2)
assert.Len(t, res.Blocks["source"], 3)
}
func TestDo(t *testing.T) {
t.Skip()
pm := &terraform.PluginManager{".providers"}
cli, meta, err := pm.Provider("ignition", "1.1.0", false)
if err != nil {
panic(err)
}
fmt.Println(meta)
rpc, err := cli.Client()
if err != nil {
panic(err)
}
raw, err := rpc.Dispense(plugin.ProviderPluginName)
if err != nil {
panic(err)
}
provider := raw.(*plugin.GRPCProvider)
response := provider.GetSchema()
rd, err := NewDocumentation("ignition")
assert.NoError(t, err)
doc := rd.Do(meta.Name, response)
str, err := ioutil.ReadFile("../../_scripts/template.md")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
tplFuncMap := make(template.FuncMap)
tplFuncMap["split"] = strings.Split
temp, err := template.New("foo").Funcs(tplFuncMap).Parse(string(str))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := temp.Execute(os.Stdout, []interface{}{doc}); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}

@ -2,7 +2,7 @@ package cmd
const (
REPLCmdShortDescription = "Run as interactive shell."
REPLCmdLongDescription = RunCmdShortDescription + "\n\n" +
REPLCmdLongDescription = REPLCmdShortDescription + "\n\n" +
"The REPL shell provides the same capabilities as the regular `run`\n" +
"command."
)

32
cmd/version.go Normal file

@ -0,0 +1,32 @@
package cmd
import (
"fmt"
"runtime"
)
const (
VersionCmdShortDescription = "Version prints information about this binary."
VersionCmdLongDescription = VersionCmdShortDescription + "\n\n" +
"Includes build information about AsCode like version and build\n" +
"date but also versions from the Go runtime and other dependencies."
)
var version string
var commit string
var build string
var terraformVersion string
var starlarkVersion string
type VersionCmd struct{}
func (c *VersionCmd) Execute(args []string) error {
fmt.Printf("Go Version: %s\n", runtime.Version())
fmt.Printf("AsCode Version: %s\n", version)
fmt.Printf("AsCode Commit: %s\n", commit)
fmt.Printf("AsCode Build Date: %s\n", build)
fmt.Printf("Terraform Version: %s\n", terraformVersion)
fmt.Printf("Starlark Version: %s\n", starlarkVersion)
return nil
}

@ -1,7 +1,6 @@
package main
import (
"fmt"
"os"
"github.com/jessevdk/go-flags"
@ -13,13 +12,13 @@ var build string
func main() {
parser := flags.NewNamedParser("ascode", flags.Default)
parser.LongDescription = "AsCode - The real infrastructure as code."
parser.LongDescription = "AsCode - Terraform Alternative Syntax."
parser.AddCommand("run", cmd.RunCmdShortDescription, cmd.RunCmdLongDescription, &cmd.RunCmd{})
parser.AddCommand("repl", cmd.REPLCmdShortDescription, cmd.REPLCmdLongDescription, &cmd.REPLCmd{})
parser.AddCommand("version", cmd.VersionCmdShortDescription, cmd.VersionCmdLongDescription, &cmd.VersionCmd{})
if _, err := parser.Parse(); err != nil {
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
fmt.Printf("Build information\n commit: %s\n date:%s\n", version, build)
os.Exit(0)
}