Skip to content

Commit

Permalink
Add support for Draft.
Browse files Browse the repository at this point in the history
  • Loading branch information
technosophos committed May 30, 2017
1 parent 9aff1f1 commit 0f64173
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 28 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ This Visual Studio Code extension provides [Kubernetes Helm](http://helm.sh) cha
- Code lenses for:
- requirements.yaml (Add and update dependencies)

### Experimental Draft Support

Draft is a tool for rapidly creating cloud native applications. Behind the scenes, it use Helm to manage its resources. This extension provides Draft support:

- Commands for...
- **Draft: Create**: Ceate a new project with Draft
- **Draft: Up**: Deploy your current application straight out of VS Code

## Requirements

You must have [Helm](http://helm.sh) installed and configured. From there, you should install `helm-template`:
Expand Down
57 changes: 41 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-helm",
"displayName": "vscode-helm",
"description": "Chart development tools for Kubernetes Helm",
"version": "0.1.0",
"version": "0.1.1",
"publisher": "technosophos",
"icon": "images/helm_icon.png",
"engines": {
Expand Down Expand Up @@ -65,6 +65,21 @@
"command": "extension.helmInspectValues",
"title": "Inspect Chart",
"description": "Inspect a Helm Chart"
},
{
"command": "extension.draftVersion",
"title": "Draft: Version",
"description": "Get the version of the local Draft client."
},
{
"command": "extension.draftUp",
"title": "Draft: Up",
"description": "Build, Deploy, and Run the app in-cluster."
},
{
"command": "extension.draftCreate",
"title": "Draft: Create",
"description": "Create a new Draft project"
}
],
"menus": {
Expand All @@ -82,17 +97,27 @@
}
]
},
"languages": [{
"id": "helm",
"aliases": ["helm-template", "helm"],
"extensions": [".yaml",".tpl"],
"configuration": "./language-configuration.json"
}],
"grammars": [{
"language": "helm",
"scopeName": "source.helm",
"path": "./syntaxes/helm.tmLanguage.json"
}],
"languages": [
{
"id": "helm",
"aliases": [
"helm-template",
"helm"
],
"extensions": [
".yaml",
".tpl"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "helm",
"scopeName": "source.helm",
"path": "./syntaxes/helm.tmLanguage.json"
}
],
"snippets": [
{
"language": "helm",
Expand All @@ -114,8 +139,8 @@
"@types/mocha": "^2.2.32"
},
"dependencies": {
"shelljs":"0.7.7",
"yamljs":"0.2.10",
"lodash":">3"
"shelljs": "0.7.7",
"yamljs": "0.2.10",
"lodash": ">3"
}
}
}
12 changes: 3 additions & 9 deletions rawsnippets/pod.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-{{.Values.Name}}"
name: "${1}"
labels:
# The "heritage" label is used to track which tool deployed a given chart.
# It is useful for admins who want to see what releases a particular tool
# is responsible for.
heritage: {{.Release.Service | quote }}
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{.Release.Name | quote }}
# This makes it easy to audit chart usage.
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
spec:
# This shows how to use a simple value. This will look for a passed-in value
# called restartPolicy. If it is not found, it will use the default value.
# {{default "Never" .restartPolicy}} is a slightly optimized version of the
# more conventional syntax: {{.restartPolicy | default "Never"}}
restartPolicy: {{default "Never" .Values.restartPolicy}}
restartPolicy: "Never"
containers:
- name: main
image: "{{ default "alpine:3.3" .Values.image }}"
image: "${2:debian-slim}:${3:latest}"
165 changes: 164 additions & 1 deletion src/completionProvider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,172 @@
import * as vscode from 'vscode';
import { FuncMap } from './funcmap';
import {logger} from './logger';
import * as YAML from 'yamljs';
import * as exec from './exec';
import * as path from 'path';
import * as _ from 'lodash';
import {existsSync} from 'fs';

export class HelmTemplateCompletionProvider implements vscode.CompletionItemProvider {

private valuesMatcher = new RegExp('\\s+\\.Values\\.([a-zA-Z0-9\\._-]+)?$')
private funcmap = new FuncMap()

// TODO: On focus, rebuild the values.yaml cache
private valuesCache

public constructor(){
this.refreshValues()
}

public refreshValues() {
let ed = vscode.window.activeTextEditor
if (!ed) {
return
}

let self = this
exec.pickChartForFile(ed.document.fileName, f => {
let valsYaml = path.join(f, "values.yaml")
if (!existsSync(valsYaml)) {
return
}
try {
self.valuesCache = YAML.load(valsYaml)
} catch (err) {
logger.log(err.message)
return
}
})
}

public provideCompletionItems(doc: vscode.TextDocument, pos: vscode.Position) {
console.log("called helmTemplateCompletionProvider")

// If the preceding character is a '.', we kick it into dot resolution mode.
// Otherwise, we go with function completion.
let wordPos = doc.getWordRangeAtPosition(pos)
let word = doc.getText(wordPos)
let line = doc.lineAt(pos.line).text
let lineUntil = line.substr(0, wordPos.start.character)

logger.log(lineUntil)
if (lineUntil.endsWith(".")) {
logger.log("sending to dotCompletionItems ")
return this.dotCompletionItems(doc, pos, word, lineUntil)
}

return new vscode.CompletionList((new FuncMap).all());
}

dotCompletionItems(doc: vscode.TextDocument, pos: vscode.Position, word: string, lineUntil: string): vscode.CompletionItem[] {
if (lineUntil.endsWith(" .")) {
return this.funcmap.helmVals()
} else if (lineUntil.endsWith(".Release.")) {
return this.funcmap.releaseVals()
} else if (lineUntil.endsWith(".Chart.")) {
return this.funcmap.chartVals()
} else if (lineUntil.endsWith(".Files.")) {
return this.funcmap.filesVals()
} else if (lineUntil.endsWith(".Capabilities.")) {
return this.funcmap.capabilitiesVals()
} else if (lineUntil.endsWith(".Values.")) {
if (!_.isPlainObject(this.valuesCache)) {
return
}
let keys = _.keys(this.valuesCache)
let res = []
keys.forEach(key => {
res.push(this.funcmap.v(key, ".Values."+key, "" + this.valuesCache[key]))
});
return res

} else {

logger.log("in else block")
let res
try {
res = this.valuesMatcher.exec(lineUntil)
} catch (err) {
logger.log(err.message)
}

if (!res || res.length == 0) {
logger.log("no matches for line " + lineUntil)
return []
}
logger.log("Match: " + res[0] + " ("+res[1]+" matches)")
let parts = res[1].substr(1).split(".")
if (!res[1]) {
return []
}

let words = []
parts.forEach(part => {
// Get the map for that part
});
/*
let doc = vscode.window.activeTextEditor.document
exec.pickChartForFile(doc.fileName, f => {
let valsYaml = path.join(f, "values.yaml")
var vals
try {
vals = YAML.load(valsYaml)
} catch (err) {
logger.log(err.message)
return []
}
var target = vals
for (var i = 0; i < parts.length; i++) {
let key = parts[i]
if (target[key]) {
target = target[key]
} else if (key == "") {
continue
} else {
// Not found
return []
}
}
let res = []
let v = this.v
_.forEach(target, (key, val) => {
res.push(v(key, res[1], "values.yaml: " + val))
})
return res
})
*/
return []


}
//return []
}
v(name: string, use: string, doc: string): vscode.CompletionItem {
let i = new vscode.CompletionItem(name, vscode.CompletionItemKind.Constant)
i.detail = use
i.documentation = doc
return i
}
f(name: string, args: string, doc: string): vscode.CompletionItem {
let i = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function)
i.detail = args
i.documentation = doc
return i
}
withValues(fn) {
let doc = vscode.window.activeTextEditor.document
exec.pickChartForFile(doc.fileName, f => {
let valsYaml = path.join(f, "values.yaml")
var vals
try {
vals = YAML.load(valsYaml)
} catch (err) {
logger.log(err.message)
fn({})
}
fn(vals)
})
}
}
76 changes: 76 additions & 0 deletions src/draft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as vscode from 'vscode';
import * as shell from 'shelljs';
import * as path from 'path';
import {logger} from './logger';

/*
* This file contains experimental support for Draft.
*/

export function draftVersion() {
draftExec("version", function(code, out, err){
if (code != 0) {
vscode.window.showErrorMessage(err)
return
}
vscode.window.showInformationMessage(out)
})
}

export function draftExec(args: string, fn) {
if (!shell.which("draft")) {
vscode.window.showErrorMessage("Install Draft on your executable path")
return
}
let cmd = "draft " + args
shell.exec(cmd, fn)
}

export function draftCreate() {
// This is a lame hack because draft does not take a path argument.
//shell.cd(vscode.workspace.rootPath)

vscode.window.showInputBox({prompt: "Project Name", placeHolder: "helloWorld"}).then(name => {
selectPack(pack => {
let cmd = "create -p " + pack + " -a " + name + " -o " + vscode.workspace.rootPath
draftExec(cmd, (code, out, err) => {
if (code != 0) {
vscode.window.showErrorMessage(err)
return
}
vscode.window.showInformationMessage("Created " + name)
})
})
})
}

export function draftUp() {
logger.log("===== Starting new Draft build ======")
if (!shell.which("draft")) {
vscode.window.showErrorMessage("Install Draft on your executable path")
return
}
let cmd = "draft up -s " + vscode.workspace.rootPath
let proc = shell.exec(cmd, { async:true }, (code) => {
if (code != 0) {
logger.log("ERROR: draft up exited with code " + code)
}
logger.log("===== Draft build is complete =====")
})
proc.stdout.on('data', data => {logger.log(data)})
proc.stderr.on('data', data => {logger.log(data)})
}

function selectPack(fn) {
draftExec("home", (code, out, err) => {
if (code != 0 ) {
vscode.window.showErrorMessage(err)
return
}
let dir = path.join(out.slice(0, -1), "packs")
let dirs = shell.ls(dir)
vscode.window.showQuickPick(dirs).then(val => {
fn(val)
})
})
}
Loading

0 comments on commit 0f64173

Please sign in to comment.