Skip to content
This repository has been archived by the owner on Oct 16, 2023. It is now read-only.

Commit

Permalink
Initial public release
Browse files Browse the repository at this point in the history
  • Loading branch information
sampointer committed Nov 6, 2019
1 parent 10a4c05 commit 23bb169
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 1 deletion.
19 changes: 19 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# .circleci/config.yml
version: 2.1
workflows:
main:
jobs:
- release:
# Only run this job on git tag pushes
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
jobs:
release:
docker:
- image: circleci/golang:1.13
steps:
- checkout
- run: curl -sL https://git.io/goreleaser | bash
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Binary when built locally
dy
44 changes: 44 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Main build and github release
before:
hooks:
# you may remove this if you don't use vgo
# - go mod tidy
# you may remove this if you don't need go generate
# - go generate ./...
builds:
- env:
- CGO_ENABLED=0
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

# Homebrew
brews:
-
github:
owner: sampointer
name: homebrew-dy

commit_author:
name: goreleaserbot
email: [email protected]

folder: Formula
homepage: "https://github.com/sampointer/dy"
description: "Construct YAML from a directory tree"
install : |
bin.install "dy"
75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,75 @@
# dy
# dy [![Go Report Card](https://goreportcard.com/badge/github.com/sampointer/dy)](https://goreportcard.com/report/github.com/sampointer/dy) [![CircleCI](https://circleci.com/gh/sampointer/dy.svg?style=shield)](https://circleci.com/gh/sampointer/dy)
Construct YAML from a directory tree

## Description
The entire world seems to think declarative configuration is best represented as YAML. This is especially prevailant in the land of Kubernetes and related tools. Terrible ideas have a tendency to accumulate leading to [awful solutions](https://twitter.com/sam_pointer/status/1182321989895311362) to the wrong problems.

Whilst this tool doesn't pretend to move the mountain it does try to nudge it back in the right direction.

Put simply, `dy` allows one to build a YAML document from a directory tree containing snippets of YAML. The aim is to make the document easier to reason about and maintain.

## Introducing Divvy Yaml
> **divvy** */ˈdɪvi/* - To share out. *Informal, British* - A foolish or stupid person
`dy` parses a directory tree according to the following rules:

* A directory is a text key
* A file name has contents that are rendered under a key named after the file prefix
* A file name that begins with an underscore is rendered without a key at the current indentation level

Consider the following [example](https://github.com/sampointer/dy/tree/master/examples/k8s_deployment):

```
$ tree k8s_deployment/
k8s_deployment/
├── _header.yaml
├── metadata.yaml
└── spec
├── _replicas.yaml
├── selector.yaml
└── template
├── metadata
│   └── labels.yaml
└── spec
└── containers.yaml
4 directories, 6 files
```

```
$ dy k8s_deployment/
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
```

```
$ dy k8s_deployment/ | kubectl apply --validate=true --dry-run=true -f -
deployment.apps/nginx-deployment created (dry run)
```

## Installing
### Homebrew
1. `brew tap sampointer/dy`
1. `brew install dy`

### Manually
Download the latest [release](https://github.com/sampointer/dy/releases) and unpack it into an appropriate place in your `${PATH}`.
113 changes: 113 additions & 0 deletions divvyyaml/divvyyaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package divvyyaml

import (
"bufio"
"os"
"path/filepath"
"strings"
)

// Represents a DivvyYaml document and its methods
type DivvyYaml struct {
Doc string // The constructed YAML document
}

// Parse populates the structure from the given path and options
func (d *DivvyYaml) Parse(path string) error {

// Store the current working directory
cwd, err := os.Getwd()
defer os.Chdir(cwd)
if err != nil {
return err
}

// Change to the directory given
path = filepath.Clean(path)
err = os.Chdir(path)
if err != nil {
return err
}

// Since our cwd is where we need to be, walk the current directory
err = filepath.Walk(".", processWalk)
if err != nil {
return err
}

d.Doc = doc
return nil
}

var doc string // Constructed YAML document

func processWalk(path string, info os.FileInfo, err error) error {
// Pass all errors back up the call chain
if err != nil {
return err
}

// Skip the root directory
if path == "." {
return nil
}

if info.IsDir() {
// A directory is a key
key := filepath.Base(path)
depth := directoryElements(path)
doc += indentString(depth, key) + ":\n"
} else {
// A file has contents that exist under a key named after the file prefix
// unless the file name starts with an underscore, in which case no key
// is written
var key string
var depth int

base := filepath.Base(path)
if strings.HasPrefix(base, "_") {
key = ""
depth = directoryElements(path) - 1
} else {
key = strings.TrimSuffix(base, filepath.Ext(path)) + ":\n"
depth = directoryElements(path)
}

doc += indentString(depth, key)

file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
doc += indentString(depth+1, scanner.Text()) + "\n"
}

if err := scanner.Err(); err != nil {
return err
}

}

return err
}

// indentString returns the passed string prefixed with the specified number of double spaces
func indentString(indent int, s string) string {
var rs string

for i := 0; i < indent; i++ {
rs += " "
}

rs += s
return rs
}

// directoryElements returns a count of the number of directories in a path string
func directoryElements(path string) int {
return len(strings.Split(path, string(os.PathSeparator))) - 1
}
2 changes: 2 additions & 0 deletions examples/k8s_deployment/_header.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
apiVersion: apps/v1
kind: Deployment
3 changes: 3 additions & 0 deletions examples/k8s_deployment/metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: nginx-deployment
labels:
app: nginx
1 change: 1 addition & 0 deletions examples/k8s_deployment/spec/_replicas.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
replicas: 3
2 changes: 2 additions & 0 deletions examples/k8s_deployment/spec/selector.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
matchLabels:
app: nginx
1 change: 1 addition & 0 deletions examples/k8s_deployment/spec/template/metadata/labels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
app: nginx
4 changes: 4 additions & 0 deletions examples/k8s_deployment/spec/template/spec/containers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/sampointer/dy

go 1.13
24 changes: 24 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"fmt"
divvy "github.com/sampointer/dy/divvyyaml"
"os"
)

func main() {
var dy divvy.DivvyYaml

// Do the most basic argument parsing possible
if len(os.Args) < 2 {
os.Stderr.WriteString("you must pass a path as an argument\n")
os.Exit(1)
}

err := dy.Parse(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
} else {
fmt.Println(dy.Doc)
}
}

0 comments on commit 23bb169

Please sign in to comment.