From 8075dc12e050432f9abed9b95cb7cd6a5458fabf Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 13 Dec 2016 10:11:39 -0800 Subject: [PATCH] Start from a copy of manfest-yaml editor --- vscode-extensions/pom.xml | 1 + vscode-extensions/vscode-concourse/.gitignore | 11 + .../vscode-concourse/.vscode/launch.json | 28 + .../vscode-concourse/.vscode/settings.json | 13 + .../vscode-concourse/.vscode/tasks.json | 30 ++ .../vscode-concourse/.vscodeignore | 30 ++ vscode-extensions/vscode-concourse/README.md | 62 +++ vscode-extensions/vscode-concourse/icon.png | Bin 0 -> 37540 bytes .../vscode-concourse/lib/.gitignore | 1 + .../vscode-concourse/lib/Main.ts | 58 +++ .../vscode-concourse/package.json | 49 ++ vscode-extensions/vscode-concourse/pom.xml | 71 +++ .../vscode-concourse/scripts/preinstall.sh | 5 + .../ide/vscode/manifest/yaml/Main.java | 14 + .../yaml/ManifestYamlLanguageServer.java | 90 ++++ .../manifest/yaml/ManifestYmlSchema.java | 129 +++++ .../yaml/ManifestYmlValueParsers.java | 90 ++++ .../description-by-prop-name/buildpack.html | 11 + .../description-by-prop-name/buildpack.md | 11 + .../description-by-prop-name/command.html | 34 ++ .../description-by-prop-name/command.md | 30 ++ .../description-by-prop-name/disk_quota.html | 9 + .../description-by-prop-name/disk_quota.md | 9 + .../description-by-prop-name/domain.html | 27 + .../description-by-prop-name/domain.md | 25 + .../description-by-prop-name/domains.html | 10 + .../description-by-prop-name/domains.md | 11 + .../description-by-prop-name/env.html | 28 + .../resources/description-by-prop-name/env.md | 24 + .../health-check-type.html | 9 + .../health-check-type.md | 9 + .../description-by-prop-name/host.html | 9 + .../description-by-prop-name/host.md | 9 + .../description-by-prop-name/hosts.html | 11 + .../description-by-prop-name/hosts.md | 11 + .../description-by-prop-name/inherit.html | 62 +++ .../description-by-prop-name/inherit.md | 63 +++ .../description-by-prop-name/instances.html | 11 + .../description-by-prop-name/instances.md | 11 + .../description-by-prop-name/memory.html | 11 + .../description-by-prop-name/memory.md | 11 + .../description-by-prop-name/name.html | 10 + .../description-by-prop-name/name.md | 9 + .../description-by-prop-name/no-hostname.html | 9 + .../description-by-prop-name/no-hostname.md | 9 + .../description-by-prop-name/no-route.html | 18 + .../description-by-prop-name/no-route.md | 16 + .../description-by-prop-name/path.html | 9 + .../description-by-prop-name/path.md | 9 + .../random-route.html | 11 + .../description-by-prop-name/random-route.md | 9 + .../description-by-prop-name/services.html | 18 + .../description-by-prop-name/services.md | 17 + .../description-by-prop-name/stack.html | 11 + .../description-by-prop-name/stack.md | 11 + .../description-by-prop-name/timeout.html | 14 + .../description-by-prop-name/timeout.md | 15 + .../manifest/yaml/ManifestYamlEditorTest.java | 492 ++++++++++++++++++ .../yaml/ManifestYamlLanguageServerTest.java | 67 +++ .../manifest/yaml/ManifestYmlSchemaTest.java | 155 ++++++ .../src/test/resources/workspace/manifest.yml | 5 + .../test/examples/destroy-pipeline.sh | 4 + .../test/examples/docker/Dockerfile | 17 + .../test/examples/docker/npmrc | 1 + .../test/examples/pipeline.yml | 116 +++++ .../test/examples/set-pipeline.sh | 3 + .../test/examples/tasks/build-stuff.yml | 0 .../examples/tasks/build-vscode-extensions.sh | 10 + .../tasks/build-vscode-extensions.yml | 11 + .../test/examples/tasks/build-website.sh | 43 ++ .../test/examples/tasks/build-website.yml | 13 + .../test/examples/tasks/test-stuff.yml | 0 .../vscode-concourse/tsconfig.json | 21 + vscode-extensions/vscode-concourse/tsd.json | 12 + 74 files changed, 2303 insertions(+) create mode 100644 vscode-extensions/vscode-concourse/.gitignore create mode 100644 vscode-extensions/vscode-concourse/.vscode/launch.json create mode 100644 vscode-extensions/vscode-concourse/.vscode/settings.json create mode 100644 vscode-extensions/vscode-concourse/.vscode/tasks.json create mode 100644 vscode-extensions/vscode-concourse/.vscodeignore create mode 100644 vscode-extensions/vscode-concourse/README.md create mode 100644 vscode-extensions/vscode-concourse/icon.png create mode 100644 vscode-extensions/vscode-concourse/lib/.gitignore create mode 100644 vscode-extensions/vscode-concourse/lib/Main.ts create mode 100644 vscode-extensions/vscode-concourse/package.json create mode 100644 vscode-extensions/vscode-concourse/pom.xml create mode 100755 vscode-extensions/vscode-concourse/scripts/preinstall.sh create mode 100644 vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java create mode 100644 vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java create mode 100644 vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java create mode 100644 vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.md create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.html create mode 100644 vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.md create mode 100644 vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java create mode 100644 vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java create mode 100644 vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java create mode 100644 vscode-extensions/vscode-concourse/src/test/resources/workspace/manifest.yml create mode 100755 vscode-extensions/vscode-concourse/test/examples/destroy-pipeline.sh create mode 100644 vscode-extensions/vscode-concourse/test/examples/docker/Dockerfile create mode 100644 vscode-extensions/vscode-concourse/test/examples/docker/npmrc create mode 100644 vscode-extensions/vscode-concourse/test/examples/pipeline.yml create mode 100755 vscode-extensions/vscode-concourse/test/examples/set-pipeline.sh create mode 100644 vscode-extensions/vscode-concourse/test/examples/tasks/build-stuff.yml create mode 100755 vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.sh create mode 100644 vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.yml create mode 100755 vscode-extensions/vscode-concourse/test/examples/tasks/build-website.sh create mode 100644 vscode-extensions/vscode-concourse/test/examples/tasks/build-website.yml create mode 100644 vscode-extensions/vscode-concourse/test/examples/tasks/test-stuff.yml create mode 100644 vscode-extensions/vscode-concourse/tsconfig.json create mode 100644 vscode-extensions/vscode-concourse/tsd.json diff --git a/vscode-extensions/pom.xml b/vscode-extensions/pom.xml index 3e3414c8f..42d31f2e7 100644 --- a/vscode-extensions/pom.xml +++ b/vscode-extensions/pom.xml @@ -13,5 +13,6 @@ commons vscode-boot-properties vscode-manifest-yaml + vscode-concourse diff --git a/vscode-extensions/vscode-concourse/.gitignore b/vscode-extensions/vscode-concourse/.gitignore new file mode 100644 index 000000000..2932a75be --- /dev/null +++ b/vscode-extensions/vscode-concourse/.gitignore @@ -0,0 +1,11 @@ +out +node_modules +target +*.log +*.log.* +.idea +*.iml +dependency-reduced-pom.xml +classpath.txt +*.vsix +repo \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/.vscode/launch.json b/vscode-extensions/vscode-concourse/.vscode/launch.json new file mode 100644 index 000000000..52ebf9f9e --- /dev/null +++ b/vscode-extensions/vscode-concourse/.vscode/launch.json @@ -0,0 +1,28 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], + "stopOnEntry": false, + "sourceMaps": true, + "outDir": "${workspaceRoot}/out/lib", + "preLaunchTask": "npm" + }, + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], + "stopOnEntry": false, + "sourceMaps": true, + "outDir": "${workspaceRoot}/out/test", + "preLaunchTask": "npm" + } + ] +} diff --git a/vscode-extensions/vscode-concourse/.vscode/settings.json b/vscode-extensions/vscode-concourse/.vscode/settings.json new file mode 100644 index 000000000..7b54d1cef --- /dev/null +++ b/vscode-extensions/vscode-concourse/.vscode/settings.json @@ -0,0 +1,13 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.wordWrap": false, + "files.exclude": { + "out": false, // set this to true to hide the "out" folder with the compiled JS files + "node_modules": false, + "target": true + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version +} \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/.vscode/tasks.json b/vscode-extensions/vscode-concourse/.vscode/tasks.json new file mode 100644 index 000000000..108f5e94d --- /dev/null +++ b/vscode-extensions/vscode-concourse/.vscode/tasks.json @@ -0,0 +1,30 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process + +// A task runner that calls a custom npm script that compiles the extension. +{ + "version": "0.1.0", + + // we want to run npm + "command": "npm", + + // the command is a shell script + "isShellCommand": true, + + // show the output window only if unrecognized errors occur. + "showOutput": "always", + + // we run the custom script "compile" as defined in package.json + "args": ["run", "compile"], + + // The tsc compiler is started in watching mode + "isWatching": true, + + // use the standard tsc in watch mode problem matcher to find compile problems in the output. + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/.vscodeignore b/vscode-extensions/vscode-concourse/.vscodeignore new file mode 100644 index 000000000..3902f54bb --- /dev/null +++ b/vscode-extensions/vscode-concourse/.vscodeignore @@ -0,0 +1,30 @@ +# IDE configs +.vscode/** +.idea/** +*.iml +javaconfig.json +classpath.txt +tsconfig.json +tsd.json +*.xml + +# Logs +*.log* + +# Sources +typings/** +src/** +test/** +lib/** +!lib/javaconfig.schema.json +repo/** +scripts/** + +# Compiler output +out/test/** +target/** +!target/vscode-concourse-*.jar + +# Extensions +.gitignore +**/*.map diff --git a/vscode-extensions/vscode-concourse/README.md b/vscode-extensions/vscode-concourse/README.md new file mode 100644 index 000000000..60d78f5c9 --- /dev/null +++ b/vscode-extensions/vscode-concourse/README.md @@ -0,0 +1,62 @@ +# VS Code Language Server for Concourse Pipeline and Task Configuration Files + +A VSCode extension and Language Server providing support for +editing Concourse CI configuration files. Supports editing both +pipeline definition files and task definition files. + +The editor provides content assist and validation as you type. +These feature are activated for `.yml` files that follow certain +naming conventions: + + - `**/*pipeline*.yml` : activates support for editing pipelines + - `**/tasks/*.yml` : activates support for editing tasks. + +# Developer notes + +## Bulding and Running + +This project consists of three pieces: + + - a vscode-extension which is a language-server client implemented in TypeScript. + - commons-vscode: a local npm module with some utilities implemented in TypeScript. + - a language server implemented in Java. + +To build all these pieces you normally only need to run: + + npm install + +**However, the first time you build** it might fail trying to +find the `commons-vscode` module on npm central. Once we publish a stable +version of that module on npm central that will no longer be a problem. +Until that time, you can work around this by doing a one time manual +run of the `preinstall` script prior to running `npm install`: + + ./scripts/preinstall.sh + npm install + +Now you can open the client-app in vscode. From the root of this project. + + code . + +To launch the language server in a vscode runtime, press F5. + +## Debugging + +To debug the language server, open `lib/Main.ts` and edit to set the +`DEBUG` option to `true`. When you launch the app next by pressing +`F5` it will launch with debug options being passed to the JVM. + +You can then connect a 'Remote Java' Eclipse debugger on port 8000. + +## Packaging as a vscode extension + +First make sure the stuff is all built locally: + + ./scripts/preinstall.sh # only needed if this is the first build. + npm install + +Then package it: + + npm run vsce-package + +This produces a `.vsix` file which you can install directly into vscode. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/icon.png b/vscode-extensions/vscode-concourse/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc246bfeb3cb9ca3e1e5cee37b9899d746fd76b GIT binary patch literal 37540 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 Rn5jQF;+AGM2(qbH>_5%7c`yI~ literal 0 HcmV?d00001 diff --git a/vscode-extensions/vscode-concourse/lib/.gitignore b/vscode-extensions/vscode-concourse/lib/.gitignore new file mode 100644 index 000000000..a6c7c2852 --- /dev/null +++ b/vscode-extensions/vscode-concourse/lib/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/vscode-extensions/vscode-concourse/lib/Main.ts b/vscode-extensions/vscode-concourse/lib/Main.ts new file mode 100644 index 000000000..add2093d0 --- /dev/null +++ b/vscode-extensions/vscode-concourse/lib/Main.ts @@ -0,0 +1,58 @@ +'use strict'; +// The module 'vscode' contains the VS Code extensibility API +// Import the module and reference it with the alias vscode in your code below + +import * as VSCode from 'vscode'; +import * as commons from 'commons-vscode'; +import * as Path from 'path'; +import * as FS from 'fs'; +import * as Net from 'net'; +import * as ChildProcess from 'child_process'; +import {LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo} from 'vscode-languageclient'; +import {TextDocument, OutputChannel} from 'vscode'; + +var DEBUG = false; +const DEBUG_ARG = '-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y'; + +var log_output : OutputChannel = null; + +function log(msg : string) { + if (log_output) { + log_output.append(msg +"\n"); + } +} + +function error(msg : string) { + if (log_output) { + log_output.append("ERR: "+msg+"\n"); + } +} + +/** Called when extension is activated */ +export function activate(context: VSCode.ExtensionContext) { + let options : commons.ActivatorOptions = { + DEBUG : false, + extensionId: 'vscode-concourse', + fatJarFile: 'target/vscode-concourse-0.0.1-SNAPSHOT.jar', + clientOptions: { + // HACK!!! documentSelector only takes string|string[] where string is language id, but DocumentFilter object is passed instead + // Reasons: + // 1. documentSelector is just passed over to functions like #registerHoverProvider(documentSelector, ...) that take documentSelector + // parameter in string | DocumentFilter | string[] | DocumentFilter[] format + // 2. Combination of non string|string[] documentSelector parameter and synchronize.textDocumentFilter function makes doc synchronization + // events pass on to Language Server only for documents for which function passed via textDocumentFilter property return true + + // TODO: Remove cast ones https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved + documentSelector: [ {language: 'yaml', pattern: '**/*pipeline*.yml'}], + synchronize: { + // TODO: Remove textDocumentFilter property once https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved + textDocumentFilter: function(textDocument : TextDocument) : boolean { + let result : boolean = /^(.*\/)?pipeline[^\s\\/]*.yml$/i.test(textDocument.fileName); + return result; + } + } + } + }; + commons.activate(options, context); +} + diff --git a/vscode-extensions/vscode-concourse/package.json b/vscode-extensions/vscode-concourse/package.json new file mode 100644 index 000000000..5a223147f --- /dev/null +++ b/vscode-extensions/vscode-concourse/package.json @@ -0,0 +1,49 @@ +{ + "name": "vscode-concourse", + "displayName": "Concourse CI Pipeline Editor", + "description": "Provides validation and content assist for Concourse CI pipeline and task configuration yml files", + "icon": "icon.png", + "version": "0.0.1", + "publisher": "Pivotal", + "repository": { + "type": "git", + "url": "https://github.com/spring-projects/sts4.git" + }, + "license": "EPL-1.0", + "engines": { + "npm": "^3.0.0", + "vscode": "^1.5.0" + }, + "categories": [ + "Languages", + "Linters" + ], + "keywords": [ + "yaml", + "concourse", + "pipeline.yml" + ], + "activationEvents": [ + "onLanguage:yaml" + ], + "main": "./out/lib/Main", + "preview": true, + "scripts": { + "prepublish": "tsc -p .", + "clean": "rm -fr node_modules out *.vsix", + "compile": "tsc -watch -p ./", + "preinstall": "./scripts/preinstall.sh", + "postinstall": "node ./node_modules/vscode/bin/install", + "vsce-package": "vsce package" + }, + "dependencies": { + "vscode-languageclient": "2.5.x", + "commons-vscode": "^0.0.1" + }, + "devDependencies": { + "vsce": "^1.17.0", + "typescript": "^2.0.x", + "@types/node": "^6.0.40", + "vscode": "^1.0.0" + } +} diff --git a/vscode-extensions/vscode-concourse/pom.xml b/vscode-extensions/vscode-concourse/pom.xml new file mode 100644 index 000000000..f902a4fb9 --- /dev/null +++ b/vscode-extensions/vscode-concourse/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + vscode-concourse + jar + + + org.springframework.ide.vscode + commons-parent + 0.0.1-SNAPSHOT + ../commons/pom.xml + + + + + distribution-repository + Temporary Staging Repository + file://${basedir}/dist + + + + + + + org.springframework.ide.vscode + commons-language-server + ${project.version} + + + + org.springframework.ide.vscode + commons-yaml + ${project.version} + + + + org.springframework.ide.vscode + language-server-test-harness + ${project.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + org.springframework.boot + spring-boot-maven-plugin + 1.4.1.RELEASE + + + + repackage + + + + + + + diff --git a/vscode-extensions/vscode-concourse/scripts/preinstall.sh b/vscode-extensions/vscode-concourse/scripts/preinstall.sh new file mode 100755 index 000000000..ae0cb376f --- /dev/null +++ b/vscode-extensions/vscode-concourse/scripts/preinstall.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e +(cd ../commons-vscode ; npm install) +npm install ../commons-vscode +../mvnw -U -f ../pom.xml -pl vscode-concourse -am clean install diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java new file mode 100644 index 000000000..7876bd711 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java @@ -0,0 +1,14 @@ +package org.springframework.ide.vscode.manifest.yaml; + +import java.io.IOException; + +import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; + +public class Main { + SimpleLanguageServer server = new ManifestYamlLanguageServer(); + + public static void main(String[] args) throws IOException { + LaunguageServerApp.start(ManifestYamlLanguageServer::new); + } +} diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java new file mode 100644 index 000000000..6e2ac5259 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java @@ -0,0 +1,90 @@ +package org.springframework.ide.vscode.manifest.yaml; + +import java.util.Collection; + +import javax.inject.Provider; + +import org.eclipse.lsp4j.CompletionOptions; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngine; +import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; +import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; +import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngine; +import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter; +import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; +import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; +import org.springframework.ide.vscode.commons.yaml.completion.SchemaBasedYamlAssistContextProvider; +import org.springframework.ide.vscode.commons.yaml.completion.YamlAssistContextProvider; +import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngine; +import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider; +import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; +import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider; +import org.yaml.snakeyaml.Yaml; + +import com.google.common.collect.ImmutableList; + +public class ManifestYamlLanguageServer extends SimpleLanguageServer { + + private static final Provider> NO_BUILDPACKS = () -> ImmutableList.of(); + + private Yaml yaml = new Yaml(); + private YamlSchema schema = new ManifestYmlSchema(NO_BUILDPACKS); + + + public ManifestYamlLanguageServer() { + SimpleTextDocumentService documents = getTextDocumentService(); + + YamlASTProvider parser = new YamlParser(yaml); + + YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT; + YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); + YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider); + VscodeCompletionEngine completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine); + HoverInfoProvider infoProvider = new YamlHoverInfoProvider(parser, structureProvider, contextProvider); + VscodeHoverEngine hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider); + IReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema); + +// SimpleWorkspaceService workspace = getWorkspaceService(); + documents.onDidChangeContent(params -> { + TextDocument doc = params.getDocument(); + validateWith(doc, engine); + }); + +// workspace.onDidChangeConfiguraton(settings -> { +// System.out.println("Config changed: "+params); +// Integer val = settings.getInt("languageServerExample", "maxNumberOfProblems"); +// if (val!=null) { +// maxProblems = ((Number) val).intValue(); +// for (TextDocument doc : documents.getAll()) { +// validateDocument(documents, doc); +// } +// } +// }); + + documents.onCompletion(completionEngine::getCompletions); + documents.onCompletionResolve(completionEngine::resolveCompletion); + documents.onHover(hoverEngine ::getHover); + } + + + @Override + protected ServerCapabilities getServerCapabilities() { + ServerCapabilities c = new ServerCapabilities(); + + c.setTextDocumentSync(TextDocumentSyncKind.Incremental); + c.setHoverProvider(true); + + CompletionOptions completionProvider = new CompletionOptions(); + completionProvider.setResolveProvider(false); + c.setCompletionProvider(completionProvider); + + return c; + } +} diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java new file mode 100644 index 000000000..8840d1873 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import java.util.Collection; +import java.util.Set; + +import javax.inject.Provider; + +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.yaml.schema.YType; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; + +import com.google.common.collect.ImmutableSet; + +/** + * @author Kris De Volder + */ +public class ManifestYmlSchema implements YamlSchema { + + private final YBeanType TOPLEVEL_TYPE; + private final YTypeUtil TYPE_UTIL; + private final Provider> buildpackProvider; + + private static final Set TOPLEVEL_EXCLUDED = ImmutableSet.of( + "name", "host", "hosts" + ); + + public ManifestYmlSchema(Provider> buildpackProvider) { + this.buildpackProvider = buildpackProvider; + YTypeFactory f = new YTypeFactory(); + TYPE_UTIL = f.TYPE_UTIL; + + // define schema types + TOPLEVEL_TYPE = f.ybean("manifest.yml schema"); + + YBeanType application = f.ybean("Application"); + YAtomicType t_path = f.yatomic("Path"); + + YAtomicType t_buildpack = f.yatomic("Buildpack"); + + t_buildpack.addHintProvider(this.buildpackProvider); + + YAtomicType t_boolean = f.yenum("boolean", "true", "false"); + YType t_string = f.yatomic("String"); + YType t_strings = f.yseq(t_string); + + YAtomicType t_memory = f.yatomic("Memory"); + t_memory.addHints("256M", "512M", "1024M"); + t_memory.parseWith(ManifestYmlValueParsers.MEMORY); + + YAtomicType t_health_check_type = f.yenum("Health Check Type", "none", "port"); + + YAtomicType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer"); + t_strictly_pos_integer.parseWith(ManifestYmlValueParsers.integerAtLeast(1)); + + YAtomicType t_pos_integer = f.yatomic("Positive Integer"); + t_pos_integer.parseWith(ManifestYmlValueParsers.POS_INTEGER); + + YType t_env = f.ymap(t_string, t_string); + + // define schema structure... + TOPLEVEL_TYPE.addProperty("applications", f.yseq(application)); + TOPLEVEL_TYPE.addProperty("inherit", t_string, descriptionFor("inherit")); + + YTypedPropertyImpl[] props = { + f.yprop("buildpack", t_buildpack), + f.yprop("command", t_string), + f.yprop("disk_quota", t_memory), + f.yprop("domain", t_string), + f.yprop("domains", t_strings), + f.yprop("env", t_env), + f.yprop("host", t_string), + f.yprop("hosts", t_strings), + f.yprop("instances", t_strictly_pos_integer), + f.yprop("memory", t_memory), + f.yprop("name", t_string), + f.yprop("no-hostname", t_boolean), + f.yprop("no-route", t_boolean), + f.yprop("path", t_path), + f.yprop("random-route", t_boolean), + f.yprop("services", t_strings), + f.yprop("stack", t_string), + f.yprop("timeout", t_pos_integer), + f.yprop("health-check-type", t_health_check_type) + }; + + for (YTypedPropertyImpl prop : props) { + prop.setDescriptionProvider(descriptionFor(prop)); + if (!TOPLEVEL_EXCLUDED.contains(prop.getName())) { + TOPLEVEL_TYPE.addProperty(prop); + } + application.addProperty(prop); + } + } + + private Renderable descriptionFor(String propName) { + return Renderables.fromClasspath(this.getClass(), "/description-by-prop-name/"+propName); + } + + private Renderable descriptionFor(YTypedPropertyImpl prop) { + return descriptionFor(prop.getName()); + } + + @Override + public YBeanType getTopLevelType() { + return TOPLEVEL_TYPE; + } + + @Override + public YTypeUtil getTypeUtil() { + return TYPE_UTIL; + } +} diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java new file mode 100644 index 000000000..a1dae8b28 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import java.util.Set; + +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.ValueParser; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +/** + * Methods and constants to create/get parsers for some atomic types + * used in manifest yml schema. + * + * @author Kris De Volder + */ +public class ManifestYmlValueParsers { + + public static final ValueParser POS_INTEGER = integerRange(0, null); + + public static final ValueParser MEMORY = new ValueParser() { + + private final ImmutableSet GIGABYTE = ImmutableSet.of("G", "GB"); + private final ImmutableSet MEGABYTE = ImmutableSet.of("M", "MB"); + private final Set UNITS = Sets.union(GIGABYTE, MEGABYTE); + + @Override + public Object parse(String str) { + str = str.trim(); + String unit = getUnit(str.toUpperCase()); + if (unit==null) { + throw new NumberFormatException( + "'"+str+"' doesn't end with a valid unit of memory ('M', 'MB', 'G' or 'GB')" + ); + } + str = str.substring(0, str.length()-unit.length()); + int unitSize = GIGABYTE.contains(unit)?1024:1; + int value = Integer.parseInt(str); + if (value<0) { + throw new NumberFormatException("Negative value is not allowed"); + } + return value * unitSize; + } + + private String getUnit(String str) { + for (String u : UNITS) { + if (str.endsWith(u)) { + return u; + } + } + return null; + } + }; + + public static ValueParser integerAtLeast(final Integer lowerBound) { + return integerRange(lowerBound, null); + } + + public static ValueParser integerRange(final Integer lowerBound, final Integer upperBound) { + Assert.isLegal(lowerBound==null || upperBound==null || lowerBound <= upperBound); + return new ValueParser() { + @Override + public Object parse(String str) { + int value = Integer.parseInt(str); + if (lowerBound!=null && valueupperBound) { + throw new NumberFormatException("Value must be at most "+upperBound); + } + return value; + } + }; + } + +} diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.html new file mode 100644 index 000000000..2f3fb4cb2 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.html @@ -0,0 +1,11 @@ +

If your application requires a custom buildpack, you can use the buildpack attribute to specify its URL or name:

+ +
+---
+  ...
+  buildpack: buildpack_URL
+
+ +

Note: The cf buildpacks command lists the buildpacks that you can refer to by name in a manifest or a command line option.

+ +

The command line option that overrides this attribute is -b.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.md new file mode 100644 index 000000000..afc713276 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/buildpack.md @@ -0,0 +1,11 @@ +If your application requires a custom buildpack, you can use the `buildpack` attribute to specify its URL or name: + +``` +--- + ... + buildpack: buildpack_URL +``` + +**Note**: The `cf buildpacks` command lists the buildpacks that you can refer to by name in a manifest or a command line option. + +The command line option that overrides this attribute is `-b`. diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.html new file mode 100644 index 000000000..aab1e4651 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.html @@ -0,0 +1,34 @@ +

Some languages and frameworks require that you provide a custom command to start an application. Refer to the buildpack documentation to determine if you need to provide a custom start command.

+ +

You can provide the custom start command in your application manifest or on the command line.

+ +

To specify the custom start command in your application manifest, add it in the command: START-COMMAND format as the following example shows:

+ +
+---
+  ...
+  command: bundle exec rake VERBOSE=true
+
+ +

On the command line, use the -c option to specify the custom start command as the following example shows:

+ +
+$ cf push my-app -c "bundle exec rake VERBOSE=true"
+
+ +

Note: The -c option with a value of ‘null’ forces cf push to use the buildpack start command. See About Starting Applications for more information.

+ +

If you override the start command for a Buildpack application, Linux uses +bash -c YOUR-COMMAND to invoke your application. +If you override the start command for a Docker application, Linux uses sh -c YOUR-COMMAND to invoke your application. +Because of this, if you override a start command, you should prefix exec to the final command in your custom composite start command.

+ +

exec causes the last command to become the root process of your application. The Cloud Foundry Updates and Your Application section of the Considerations for Designing and Running an Application in the Cloud topic explains why your application should handle a termination signal during Cloud Foundry updates. +Without an exec statement, the parent process remains as the implied bash process, and does not propagate signals to your application process.

+ +

For example, both of the following composite start commands run database migrations when the first instance of the app starts, then start the app to serve requests, but they behave differently on graceful shutdown.

+ +
    +
  • bin/rake cf:on_first_instance db:migrate && bin/rails server -p $PORT -e $RAILS_ENV: The process tree is bash -> ruby, so on graceful shutdown only the bash process receives the TERM signal, and not the ruby process.

  • +
  • bin/rake cf:on_first_instance db:migrate && exec bin/rails server -p $PORT -e $RAILS_ENV: Because of the exec prefix on the final command, the ruby process invoked by rails takes over the bash process managing the execution of the composite command. The process tree is only ruby, so the ruby web server receives the TERM signal can shutdown gracefully for 10 seconds.

  • +
\ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.md new file mode 100644 index 000000000..2d17d293a --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/command.md @@ -0,0 +1,30 @@ +Some languages and frameworks require that you provide a custom command to start an application. Refer to the [buildpack](/buildpacks/) documentation to determine if you need to provide a custom start command. + +You can provide the custom start command in your application manifest or on the command line. + +To specify the custom start command in your application manifest, add it in the `command: START-COMMAND` format as the following example shows: + + +``` +--- + ... + command: bundle exec rake VERBOSE=true +``` + +On the command line, use the `-c` option to specify the custom start command as the following example shows: + +``` +$ cf push my-app -c "bundle exec rake VERBOSE=true" +``` + +**Note**: The `-c` option with a value of ‘null’ forces `cf push` to use the buildpack start command. See [About Starting Applications](./app-startup.html) for more information. + +If you override the start command for a Buildpack application, Linux uses `bash -c YOUR-COMMAND` to invoke your application. If you override the start command for a Docker application, Linux uses `sh -c YOUR-COMMAND` to invoke your application. Because of this, if you override a start command, you should prefix `exec` to the final command in your custom composite start command. + +`exec` causes the last command to become the root process of your application. The [Cloud Foundry Updates and Your Application](./prepare-to-deploy.html#moving-apps) section of the _Considerations for Designing and Running an Application in the Cloud_ topic explains why your application should handle a `termination signal` during Cloud Foundry updates. Without an `exec` statement, the parent process remains as the implied bash process, and does not propagate signals to your application process. + +For example, both of the following composite start commands run database migrations when the first instance of the app starts, then start the app to serve requests, but they behave differently on graceful shutdown. + +* `bin/rake cf:on_first_instance db:migrate && bin/rails server -p $PORT -e $RAILS_ENV`: The process tree is `bash -> ruby`, so on graceful shutdown only the `bash` process receives the TERM signal, and not the `ruby` process. + +* `bin/rake cf:on_first_instance db:migrate && exec bin/rails server -p $PORT -e $RAILS_ENV`: Because of the `exec` prefix on the final command, the `ruby` process invoked by `rails` takes over the `bash` process managing the execution of the composite command. The process tree is only `ruby`, so the ruby web server receives the TERM signal can shutdown gracefully for 10 seconds. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.html new file mode 100644 index 000000000..f2463dbea --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.html @@ -0,0 +1,9 @@ +

Use the disk_quota attribute to allocate the disk space for your app instance. This attribute requires a unit of measurement: M, MB, G, or GB, in upper case or lower case.

+ +
+---
+  ...
+  disk_quota: 1024M
+
+ +

The command line option that overrides this attribute is -k.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.md new file mode 100644 index 000000000..dfd347ccd --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/disk_quota.md @@ -0,0 +1,9 @@ +Use the `disk_quota` attribute to allocate the disk space for your app instance. This attribute requires a unit of measurement: `M`, `MB`, `G`, or `GB`, in upper case or lower case. + +``` +--- + ... + disk_quota: 1024M +``` + +The command line option that overrides this attribute is `-k`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.html new file mode 100644 index 000000000..8f1af1c01 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.html @@ -0,0 +1,27 @@ +

Every cf push deploys applications to one particular Cloud Foundry instance. +Every Cloud Foundry instance may have a shared domain set by an admin. +Unless you specify a domain, Cloud Foundry incorporates that shared domain in the route to your application.

+ +

You can use the domain attribute when you want your application to be served from a domain other than the default shared domain.

+ +
+---
+  ...
+  domain: unique-example.com
+
+ +

The command line option that overrides this attribute is -d.

+ +

The domains attribute

+ +

Use the domains attribute to provide multiple domains. If you define both domain and domains attributes, Cloud Foundry creates routes for domains defined in both of these fields.

+ +
+---
+  ...
+  domains:
+  - domain-example1.com
+  - domain-example2.org
+
+ +

The command line option that overrides this attribute is -d.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.md new file mode 100644 index 000000000..e25a36620 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domain.md @@ -0,0 +1,25 @@ +Every `cf push` deploys applications to one particular Cloud Foundry instance. Every Cloud Foundry instance may have a shared domain set by an admin. Unless you specify a domain, Cloud Foundry incorporates that shared domain in the route to your application. + +You can use the `domain` attribute when you want your application to be served from a domain other than the default shared domain. + +``` +--- + ... + domain: unique-example.com +``` + +The command line option that overrides this attribute is `-d`. + +### The domains attribute + +Use the `domains` attribute to provide multiple domains. If you define both `domain` and `domains` attributes, Cloud Foundry creates routes for domains defined in both of these fields. + +``` +--- + ... + domains: + - domain-example1.com + - domain-example2.org +``` + +The command line option that overrides this attribute is `-d`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.html new file mode 100644 index 000000000..0df6ef33b --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.html @@ -0,0 +1,10 @@ +

Use the domains attribute to provide multiple domains. If you define both domain and domains attributes, Cloud Foundry creates routes for domains defined in both of these fields.

+ +
---
+  ...
+  domains:
+  - domain-example1.com
+  - domain-example2.org
+
+ +

The command line option that overrides this attribute is -d.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.md new file mode 100644 index 000000000..d593178e6 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/domains.md @@ -0,0 +1,11 @@ +Use the `domains` attribute to provide multiple domains. If you define both `domain` and `domains` attributes, Cloud Foundry creates routes for domains defined in both of these fields. + +``` +--- + ... + domains: + - domain-example1.com + - domain-example2.org +``` + +The command line option that overrides this attribute is `-d`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.html new file mode 100644 index 000000000..4c35fe3d7 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.html @@ -0,0 +1,28 @@ +

The env block consists of a heading, then one or more environment variable/value pairs.

+ +

For example:

+ +
+---
+  ...
+  env:
+    RAILS_ENV: production
+    RACK_ENV: production
+
+ +

cf push deploys the application to a container on the server. The variables belong to the container environment.

+ +

While the application is running, Cloud Foundry allows you to operate on environment variables.

+ +
    +
  • View all variables: cf env my-app
  • +
  • Set an individual variable: cf set-env my-app my-variable_name my-variable_value
  • +
  • Unset an individual variable: cf unset-env my-app my-variable_name my-variable_value
  • +
+ +

Environment variables interact with manifests in the following ways:

+ +
    +
  • When you deploy an application for the first time, Cloud Foundry reads the variables described in the environment block of the manifest, and adds them to the environment of the container where the application is deployed.

  • +
  • When you stop and then restart an application, its environment variables persist.

  • +
diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.md new file mode 100644 index 000000000..88bd86d01 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/env.md @@ -0,0 +1,24 @@ +The `env` block consists of a heading, then one or more environment variable/value pairs. + +For example: + +``` +--- + ... + env: + RAILS_ENV: production + RACK_ENV: production +``` + +`cf push` deploys the application to a container on the server. The variables belong to the container environment. + +While the application is running, Cloud Foundry allows you to operate on environment variables. + +* View all variables: `cf env my-app` +* Set an individual variable: `cf set-env my-app my-variable_name my-variable_value` +* Unset an individual variable: `cf unset-env my-app my-variable_name my-variable_value` + +Environment variables interact with manifests in the following ways: + +* When you deploy an application for the first time, Cloud Foundry reads the variables described in the environment block of the manifest, and adds them to the environment of the container where the application is deployed. +* When you stop and then restart an application, its environment variables persist. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.html new file mode 100644 index 000000000..30be9affb --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.html @@ -0,0 +1,9 @@ +

Use the health-check-type attribute to set the health_check_type +flag to either port or none. If you do not provide +a health-check-type attribute, it defaults to port.

+ +
+---
+  ...
+  health-check-type: none
+
\ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.md new file mode 100644 index 000000000..fac9be7d7 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/health-check-type.md @@ -0,0 +1,9 @@ +Use the `health-check-type` attribute to set the `health_check_type` +flag to either `port` or `none`. If you do not provide a `health-check-type` +attribute, it defaults to `port`. + +``` +--- + ... + health-check-type: none +``` diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.html new file mode 100644 index 000000000..04de4aa7b --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.html @@ -0,0 +1,9 @@ +

Use the host attribute to provide a hostname, or subdomain, in the form of a string. This segment of a route helps to ensure that the route is unique. If you do not provide a hostname, the URL for the app takes the form of APP-NAME.DOMAIN.

+ +
+---
+  ...
+  host: my-app
+
+ +

The command line option that overrides this attribute is -n.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.md new file mode 100644 index 000000000..4658d50bf --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/host.md @@ -0,0 +1,9 @@ +Use the `host` attribute to provide a hostname, or subdomain, in the form of a string. This segment of a route helps to ensure that the route is unique. If you do not provide a hostname, the URL for the app takes the form of `APP-NAME.DOMAIN`. + +``` +--- + ... + host: my-app +``` + +The command line option that overrides this attribute is `-n`. diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.html new file mode 100644 index 000000000..de25f32ef --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.html @@ -0,0 +1,11 @@ +

Use the hosts attribute to provide multiple hostnames, or subdomains. Each hostname generates a unique route for the app. hosts can be used in conjunction with host. If you define both attributes, Cloud Foundry creates routes for hostnames defined in both host and hosts.

+ +
+---
+  ...
+  hosts:
+  - app_host1
+  - app_host2
+
+ +

The command line option that overrides this attribute is -n.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.md new file mode 100644 index 000000000..9b69c9cf2 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/hosts.md @@ -0,0 +1,11 @@ +Use the `hosts` attribute to provide multiple hostnames, or subdomains. Each hostname generates a unique route for the app. `hosts` can be used in conjunction with `host`. If you define both attributes, Cloud Foundry creates routes for hostnames defined in both `host` and `hosts`. + +``` +--- + ... + hosts: + - app_host1 + - app_host2 +``` + +The command line option that overrides this attribute is `-n`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.html new file mode 100644 index 000000000..184350b67 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.html @@ -0,0 +1,62 @@ +

A single manifest can describe multiple applications. Another powerful technique is to create multiple manifests with inheritance. Here, manifests have parent-child relationships such that children inherit descriptions from a parent. Children can use inherited descriptions as-is, extend them, or override them.

+ +

Content in the child manifest overrides content in the parent manifest, if the two conflict.

+ +

This technique helps in these and other scenarios:

+ +
    +
  • An application has a set of different deployment modes, such as debug, local, and public. Each deployment mode is described in child manifests that extend the settings in a base parent manifest.

  • +
  • An application is packaged with a basic configuration described by a parent manifest. Users can extend the basic configuration by creating child manifests that add new properties or override those in the parent manifest.

  • +
+ +

The benefits of multiple manifests with inheritance are similar to those of minimizing duplicated content within single manifests. With inheritance, though, we “promote” content by placing it in the parent manifest.

+ +

Every child manifest must contain an “inherit” line that points to the parent manifest. Place the inherit line immediately after the three dashes at the top of the child manifest. For example, every child of a parent manifest called base-manifest.yml begins like this:

+ +
---
+  ...
+  inherit: base-manifest.yml
+
+ +

You do not need to add anything to the parent manifest.

+ +

In the simple example below, a parent manifest gives each application minimal resources, while a production child manifest scales them up.

+ +

simple-base-manifest.yml

+ +
---
+path: .
+domain: shared-domain.com
+memory: 256M
+instances: 1
+services:
+- singular-backend
+
+# app-specific configuration
+applications:
+ - name: springtock
+   host: 765shower
+   path: ./april/build/libs/april-weather.war
+ - name: wintertick
+   host: 321flurry
+   path: ./december/target/december-weather.war
+
+ +

simple-prod-manifest.yml

+ +
---
+inherit: simple-base-manifest.yml
+applications:
+ - name:springstorm
+   memory: 512M
+   instances: 1
+   host: 765deluge
+   path: ./april/build/libs/april-weather.war
+ - name: winterblast
+   memory: 1G
+   instances: 2
+   host: 321blizzard
+   path: ./december/target/december-weather.war
+
+ +

Note: Inheritance can add an additional level of complexity to manifest creation and maintenance. Comments that precisely explain how the child manifest extends or overrides the descriptions in the parent manifest can alleviate this complexity. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.md new file mode 100644 index 000000000..5270dee89 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/inherit.md @@ -0,0 +1,63 @@ +A single manifest can describe multiple applications. Another powerful technique is to create multiple manifests with inheritance. Here, manifests have parent-child relationships such that children inherit descriptions from a parent. Children can use inherited descriptions as-is, extend them, or override them. + +Content in the child manifest overrides content in the parent manifest, if the two conflict. + +This technique helps in these and other scenarios: + +* An application has a set of different deployment modes, such as debug, local, and public. Each deployment mode is described in child manifests that extend the settings in a base parent manifest. + +* An application is packaged with a basic configuration described by a parent manifest. Users can extend the basic configuration by creating child manifests that add new properties or override those in the parent manifest. + +The benefits of multiple manifests with inheritance are similar to those of minimizing duplicated content within single manifests. With inheritance, though, we “promote” content by placing it in the parent manifest. + +Every child manifest must contain an “inherit” line that points to the parent manifest. Place the inherit line immediately after the three dashes at the top of the child manifest. For example, every child of a parent manifest called `base-manifest.yml` begins like this: + +``` +--- + ... + inherit: base-manifest.yml +``` +You do not need to add anything to the parent manifest. + +In the simple example below, a parent manifest gives each application minimal resources, while a production child manifest scales them up. + +**simple-base-manifest.yml** + +``` +--- +path: . +domain: shared-domain.com +memory: 256M +instances: 1 +services: +- singular-backend + +# app-specific configuration +applications: + - name: springtock + host: 765shower + path: ./april/build/libs/april-weather.war + - name: wintertick + host: 321flurry + path: ./december/target/december-weather.war +``` + +**simple-prod-manifest.yml** + +``` +--- +inherit: simple-base-manifest.yml +applications: + - name:springstorm + memory: 512M + instances: 1 + host: 765deluge + path: ./april/build/libs/april-weather.war + - name: winterblast + memory: 1G + instances: 2 + host: 321blizzard + path: ./december/target/december-weather.war +``` + +**Note**: Inheritance can add an additional level of complexity to manifest creation and maintenance. Comments that precisely explain how the child manifest extends or overrides the descriptions in the parent manifest can alleviate this complexity. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.html new file mode 100644 index 000000000..021991fa5 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.html @@ -0,0 +1,11 @@ +

Use the instances attribute to specify the number of app instances that you want to start upon push:

+ +
+---
+  ...
+  instances: 2
+
+ +

We recommend that you run at least two instances of any apps for which fault tolerance matters.

+ +

The command line option that overrides this attribute is -i.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.md new file mode 100644 index 000000000..27bd30d5d --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/instances.md @@ -0,0 +1,11 @@ +Use the `instances` attribute to specify the number of app instances that you want to start upon push: + +``` +--- + ... + instances: 2 +``` + +We recommend that you run at least two instances of any apps for which fault tolerance matters. + +The command line option that overrides this attribute is `-i`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.html new file mode 100644 index 000000000..e25ee286e --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.html @@ -0,0 +1,11 @@ +

Use the memory attribute to specify the memory limit for all instances of an app. This attribute requires a unit of measurement: M, MB, G, or GB, in upper case or lower case. For example:

+ +
+---
+  ...
+  memory: 1024M
+
+ +

The default memory limit is 1G. You might want to specify a smaller limit to conserve quota space if you know that your app instances do not require 1G of memory.

+ +

The command line option that overrides this attribute is -m.

\ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.md new file mode 100644 index 000000000..3efb02e41 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/memory.md @@ -0,0 +1,11 @@ +Use the `memory` attribute to specify the memory limit for all instances of an app. This attribute requires a unit of measurement: `M`, `MB`, `G`, or `GB`, in upper case or lower case. For example: + +``` +--- + ... + memory: 1024M +``` + +The default memory limit is 1G. You might want to specify a smaller limit to conserve quota space if you know that your app instances do not require 1G of memory. + +The command line option that overrides this attribute is `-m`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.html new file mode 100644 index 000000000..39d0aac6a --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.html @@ -0,0 +1,10 @@ +

The name attribute is the only required attribute +for an application in a manifest file.

+ +

This is an example of a minimal manifest:

+ +
+---
+applications:
+- name: nifty-gui
+
\ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.md new file mode 100644 index 000000000..09419ceee --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/name.md @@ -0,0 +1,9 @@ +The `name` attribute is the only required attribute for an application in a manifest file. + +This is an example of a minimal manifest: + +``` +--- +applications: +- name: nifty-gui +``` \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.html new file mode 100644 index 000000000..b3a6047de --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.html @@ -0,0 +1,9 @@ +

By default, if you do not provide a hostname, the URL for the app takes the form of APP-NAME.DOMAIN. If you want to override this and map the root domain to this app then you can set no-hostname as true.

+ +
+---
+  ...
+  no-hostname: true
+
+ +

The command line option that corresponds to this attribute is --no-hostname.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.md new file mode 100644 index 000000000..19b73ed31 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-hostname.md @@ -0,0 +1,9 @@ +By default, if you do not provide a hostname, the URL for the app takes the form of `APP-NAME.DOMAIN`. If you want to override this and map the root domain to this app then you can set no-hostname as true. + +``` +--- + ... + no-hostname: true +``` + +The command line option that corresponds to this attribute is `--no-hostname`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.html new file mode 100644 index 000000000..d631024fd --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.html @@ -0,0 +1,18 @@ +

By default, cf push assigns a route to every application. But some applications process data while running in the background, and should not be assigned routes.

+ +

You can use the no-route attribute with a value of true to prevent a route from being created for your application.

+ +
+---
+  ...
+  no-route: true
+
+ +

The command line option that corresponds to this attribute is --no-route.

+ +

If you find that an application which should not have a route does have one:

+ +
    +
  1. Remove the route using the cf unmap-route command.
  2. +
  3. Push the app again with the no-route: true attribute in the manifest or the --no-route command line option.
  4. +
diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.md new file mode 100644 index 000000000..7cef4e28f --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/no-route.md @@ -0,0 +1,16 @@ +By default, `cf push` assigns a route to every application. But some applications process data while running in the background, and should not be assigned routes. + +You can use the `no-route` attribute with a value of `true` to prevent a route from being created for your application. + +``` +--- + ... + no-route: true +``` + +The command line option that corresponds to this attribute is `--no-route`. + +If you find that an application which should not have a route does have one: + +1. Remove the route using the `cf unmap-route` command. +2. Push the app again with the `no-route: true` attribute in the manifest or the `--no-route` command line option. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.html new file mode 100644 index 000000000..a1b1cfb23 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.html @@ -0,0 +1,9 @@ +

You can use the path attribute to tell Cloud Foundry where to find your application. This is generally not necessary when you run cf push from the directory where an application is located.

+ +
+---
+  ...
+  path: path_to_application_bits
+
+ +

The command line option that overrides this attribute is -p.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.md new file mode 100644 index 000000000..24d48cb6a --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/path.md @@ -0,0 +1,9 @@ +You can use the `path` attribute to tell Cloud Foundry where to find your application. This is generally not necessary when you run `cf push` from the directory where an application is located. + +``` +--- + ... + path: path_to_application_bits +``` + +The command line option that overrides this attribute is `-p`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.html new file mode 100644 index 000000000..ef432da78 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.html @@ -0,0 +1,11 @@ +

Use the random-route attribute to create a URL that includes the app name and +random words. +Use this attribute to avoid URL collision when pushing the same app to multiple spaces, or to avoid managing app URLs.

+ +

The command line option that corresponds to this attribute is --random-route.

+ +
+---
+  ...
+  random-route: true
+
diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.md new file mode 100644 index 000000000..9a7792b77 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/random-route.md @@ -0,0 +1,9 @@ +Use the `random-route` attribute to create a URL that includes the app name and random words. Use this attribute to avoid URL collision when pushing the same app to multiple spaces, or to avoid managing app URLs. + +The command line option that corresponds to this attribute is `--random-route`. + +``` +--- + ... + random-route: true +``` \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.html new file mode 100644 index 000000000..c10713143 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.html @@ -0,0 +1,18 @@ +

Applications can bind to services such as databases, messaging, and key-value stores.

+ +

Applications are deployed into App Spaces. An application can only bind to services instances that exist in the target App Space before the application is deployed.

+ +

The services block consists of a heading, then one or more service instance names.

+ +

Whoever creates the service chooses the service instance names. These names can convey logical information, as in backend_queue, describe the nature of the service, as in mysql_5.x, or do neither, as in the example below.

+ +
+---
+  ...
+  services:
+   - instance_ABC
+   - instance_XYZ
+
+ +

Binding to a service instance is a special case of setting an environment +variable, namely VCAP_SERVICES. diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.md new file mode 100644 index 000000000..5fb3e1cb9 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/services.md @@ -0,0 +1,17 @@ +Applications can bind to services such as databases, messaging, and key-value stores. + +Applications are deployed into App Spaces. An application can only bind to services instances that exist in the target App Space before the application is deployed. + +The `services` block consists of a heading, then one or more service instance names. + +Whoever creates the service chooses the service instance names. These names can convey logical information, as in `backend_queue`, describe the nature of the service, as in `mysql_5.x`, or do neither, as in the example below. + +``` +--- + ... + services: + - instance_ABC + - instance_XYZ +``` + +Binding to a service instance is a special case of setting an environment variable, namely `VCAP_SERVICES`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.html new file mode 100644 index 000000000..021e2788a --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.html @@ -0,0 +1,11 @@ +

Use the stack attribute to specify which stack to deploy your application to.

+ +

To see a list of available stacks, run cf stacks from the cf cli.

+ +
+---
+  ...
+  stack: cflinuxfs2
+
+ +

The command line option that overrides this attribute is -s.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.md new file mode 100644 index 000000000..c46611e1d --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/stack.md @@ -0,0 +1,11 @@ +Use the `stack` attribute to specify which stack to deploy your application to. + +To see a list of available stacks, run `cf stacks` from the cf cli. + +``` +--- + ... + stack: cflinuxfs2 +``` + +The command line option that overrides this attribute is `-s`. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.html b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.html new file mode 100644 index 000000000..063e26405 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.html @@ -0,0 +1,14 @@ +

The timeout attribute defines the number of seconds Cloud Foundry allocates for starting your application.

+ +

For example:

+ +
+---
+  ...
+  timeout: 80
+
+ +

You can increase the timeout length for very large apps that require more time to start. The default timeout is 60 seconds with an upper bound of 180 seconds.

+

Note: Administrators can set the upper bound of the maximum_health_check_timeout property to any value. Any changes to Cloud Controller properties in the deployment manifest require running bosh deploy.

+ +

The command line option that overrides the timeout attribute for the shell is -t. Manifest values still apply to applications pushed to the deployment.

diff --git a/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.md b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.md new file mode 100644 index 000000000..bc6f8fa29 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/resources/description-by-prop-name/timeout.md @@ -0,0 +1,15 @@ +The `timeout` attribute defines the number of seconds Cloud Foundry allocates for starting your application. + +For example: + +``` +--- + ... + timeout: 80 +``` + +You can increase the timeout length for very large apps that require more time to start. The default timeout is 60 seconds with an upper bound of 180 seconds. + +**Note**: Administrators can set the upper bound of the `maximum_health_check_timeout` property to any value. Any changes to Cloud Controller properties in the deployment manifest require running `bosh deploy`. + +The command line option that overrides the timeout attribute for the shell is `-t`. Manifest values still apply to applications pushed to the deployment. \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java new file mode 100644 index 000000000..9491a37c1 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -0,0 +1,492 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; + +public class ManifestYamlEditorTest { + + LanguageServerHarness harness; + + @Before public void setup() throws Exception { + harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + harness.intialize(null); + } + + @Test public void testReconcileCatchesParseError() throws Exception { + + Editor editor = harness.newEditor( + "somemap: val\n"+ + "- sequence" + ); + editor.assertProblems( + "-|expected " + ); + } + + @Test public void reconcileRunsOnDocumentOpenAndChange() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + harness.intialize(null); + + Editor editor = harness.newEditor( + "somemap: val\n"+ + "- sequence" + ); + + editor.assertProblems( + "-|expected " + ); + + editor.setText( + "- sequence\n" + + "zomemap: val" + ); + + editor.assertProblems( + "z|expected " + ); + } + + @Test + public void reconcileMisSpelledPropertyNames() throws Exception { + Editor editor; + + editor = harness.newEditor( + "memory: 1G\n" + + "aplications:\n" + + " - buildpack: zbuildpack\n" + + " domain: zdomain\n" + + " name: foo" + ); + editor.assertProblems("aplications|Unknown property"); + + //mispelled or not allowed at toplevel + editor = harness.newEditor( + "name: foo\n" + + "buildpeck: yahah\n" + + "memory: 1G\n" + + "memori: 1G\n" + ); + editor.assertProblems( + "name|Unknown property", + "buildpeck|Unknown property", + "memori|Unknown property" + ); + + //mispelled or not allowed as nested + editor = harness.newEditor( + "applications:\n" + + "- name: fine\n" + + " buildpeck: yahah\n" + + " memory: 1G\n" + + " memori: 1G\n" + + " applications: bad\n" + ); + editor.assertProblems( + "buildpeck|Unknown property", + "memori|Unknown property", + "applications|Unknown property" + ); + } + + @Test + public void reconcileStructuralProblems() throws Exception { + Editor editor; + + //forgot the 'applications:' heading + editor = harness.newEditor( + "- name: foo" + ); + editor.assertProblems( + "- name: foo|Expecting a 'Map' but found a 'Sequence'" + ); + + //forgot to make the '-' after applications + editor = harness.newEditor( + "applications:\n" + + " name: foo" + ); + editor.assertProblems( + "name: foo|Expecting a 'Sequence' but found a 'Map'" + ); + + //Using a 'composite' element where a scalar type is expected + editor = harness.newEditor( + "memory:\n"+ + "- bad sequence\n" + + "buildpack:\n" + + " bad: map\n" + ); + editor.assertProblems( + "- bad sequence|Expecting a 'Memory' but found a 'Sequence'", + "bad: map|Expecting a 'Buildpack' but found a 'Map'" + ); + } + + @Test + public void reconcileSimpleTypes() throws Exception { + Editor editor; + + //check for 'format' errors: + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: not a number\n" + + " no-route: notBool\n"+ + " memory: 1024\n" + + " disk_quota: 2048\n" + + " health-check-type: unhealthy" + ); + editor.assertProblems( + "not a number|Positive Integer", + "notBool|boolean", + "1024|Memory", + "2048|Memory", + "unhealthy|Health Check Type" + ); + + //check for 'range' errors: + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: -3\n" + + " memory: -1024M\n" + + " disk_quota: -2048M\n" + ); + editor.assertProblems( + "-3|Positive Integer", + "-1024M|Memory", + "-2048M|Memory" + ); + + //check that correct values are indeed accepted + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: 2\n" + + " no-route: true\n"+ + " memory: 1024M\n" + + " disk_quota: 2048MB\n" + ); + editor.assertProblems(/*none*/); + + //check that correct values are indeed accepted + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: 2\n" + + " no-route: false\n" + + " memory: 1024m\n" + + " disk_quota: 2048mb\n" + ); + editor.assertProblems(/*none*/); + + editor = harness.newEditor( + "applications:\n" + + "- name: foo\n" + + " instances: 2\n" + + " memory: 1G\n" + + " disk_quota: 2g\n" + ); + editor.assertProblems(/*none*/); + } + + @Test + public void noListIndent() throws Exception { + Editor editor; + editor = harness.newEditor("appl<*>"); + editor.assertCompletions( + "applications:\n"+ + "- <*>" + ); + } + + @Test + public void toplevelCompletions() throws Exception { + Editor editor; + editor = harness.newEditor("<*>"); + editor.assertCompletions( + "applications:\n"+ + "- <*>", + // --------------- + "buildpack: <*>", + // --------------- + "command: <*>", + // --------------- + "disk_quota: <*>", + // --------------- + "domain: <*>", + // --------------- + "domains:\n"+ + "- <*>", + // --------------- + "env:\n"+ + " <*>", + // --------------- + "health-check-type: <*>", + // --------------- +// "host: <*>", + // --------------- +// "hosts: \n"+ +// " - <*>", + // --------------- + "inherit: <*>", + // --------------- + "instances: <*>", + // --------------- + "memory: <*>", + // --------------- +// "name: <*>", + // --------------- + "no-hostname: <*>", + // --------------- + "no-route: <*>", + // --------------- + "path: <*>", + // --------------- + "random-route: <*>", + // --------------- + "services:\n"+ + "- <*>", + // --------------- + "stack: <*>", + // --------------- + "timeout: <*>" + ); + + editor = harness.newEditor("ranro<*>"); + editor.assertCompletions( + "random-route: <*>" + ); + } + + @Test + public void nestedCompletions() throws Exception { + Editor editor; + editor = harness.newEditor( + "applications:\n" + + "- <*>" + ); + editor.assertCompletions( + // --------------- + "applications:\n" + + "- buildpack: <*>", + // --------------- + "applications:\n" + + "- command: <*>", + // --------------- + "applications:\n" + + "- disk_quota: <*>", + // --------------- + "applications:\n" + + "- domain: <*>", + // --------------- + "applications:\n" + + "- domains:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- env:\n"+ + " <*>", + // --------------- + "applications:\n" + + "- health-check-type: <*>", + // --------------- + "applications:\n" + + "- host: <*>", + // --------------- + "applications:\n" + + "- hosts:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- instances: <*>", + // --------------- + "applications:\n" + + "- memory: <*>", + // --------------- + "applications:\n" + + "- name: <*>", + // --------------- + "applications:\n" + + "- no-hostname: <*>", + // --------------- + "applications:\n" + + "- no-route: <*>", + // --------------- + "applications:\n" + + "- path: <*>", + // --------------- + "applications:\n" + + "- random-route: <*>", + // --------------- + "applications:\n" + + "- services:\n"+ + " - <*>", + // --------------- + "applications:\n" + + "- stack: <*>", + // --------------- + "applications:\n" + + "- timeout: <*>" + ); + } + + @Test + public void completionDetailsAndDocs() throws Exception { + Editor editor = harness.newEditor( + "applications:\n" + + "- build<*>" + ); + editor.assertCompletionDetails("buildpack", "Buildpack", "If your application requires a custom buildpack"); + } + + @Test + public void valueCompletions() throws Exception { + assertCompletions("disk_quota: <*>", + "disk_quota: 1024M<*>", + "disk_quota: 256M<*>", + "disk_quota: 512M<*>" + ); + assertCompletions("memory: <*>", + "memory: 1024M<*>", + "memory: 256M<*>", + "memory: 512M<*>" + ); + assertCompletions("no-hostname: <*>", + "no-hostname: false<*>", + "no-hostname: true<*>" + ); + assertCompletions("no-route: <*>", + "no-route: false<*>", + "no-route: true<*>" + ); + assertCompletions("random-route: <*>", + "random-route: false<*>", + "random-route: true<*>" + ); + + assertCompletions("health-check-type: <*>", + "health-check-type: none<*>", + "health-check-type: port<*>" + ); + } + + @Test + public void hoverInfos() throws Exception { + Editor editor = harness.newEditor( + "memory: 1G\n" + + "#comment\n" + + "inherit: base-manifest.yml\n"+ + "applications:\n" + + "- buildpack: zbuildpack\n" + + " domain: zdomain\n" + + " name: foo\n" + + " command: java main.java\n" + + " disk_quota: 1024M\n" + + " domains:\n" + + " - pivotal.io\n" + + " - otherdomain.org\n" + + " env:\n" + + " RAILS_ENV: production\n" + + " RACK_ENV: production\n" + + " host: apppage\n" + + " hosts:\n" + + " - apppage2\n" + + " - appage3\n" + + " instances: 2\n" + + " no-hostname: true\n" + + " no-route: true\n" + + " path: somepath/app.jar\n" + + " random-route: true\n" + + " services:\n" + + " - instance_ABC\n" + + " - instance_XYZ\n" + + " stack: cflinuxfs2\n" + + " timeout: 80\n" + + " health-check-type: none\n" + ); + + editor.assertIsHoverRegion("memory"); + editor.assertIsHoverRegion("inherit"); + editor.assertIsHoverRegion("applications"); + editor.assertIsHoverRegion("buildpack"); + editor.assertIsHoverRegion("domain"); + editor.assertIsHoverRegion("name"); + editor.assertIsHoverRegion("command"); + editor.assertIsHoverRegion("disk_quota"); + editor.assertIsHoverRegion("domains"); + editor.assertIsHoverRegion("env"); + editor.assertIsHoverRegion("host"); + editor.assertIsHoverRegion("hosts"); + editor.assertIsHoverRegion("instances"); + editor.assertIsHoverRegion("no-hostname"); + editor.assertIsHoverRegion("no-route"); + editor.assertIsHoverRegion("path"); + editor.assertIsHoverRegion("random-route"); + editor.assertIsHoverRegion("services"); + editor.assertIsHoverRegion("stack"); + editor.assertIsHoverRegion("timeout"); + editor.assertIsHoverRegion("health-check-type"); + + editor.assertHoverContains("memory", "Use the `memory` attribute to specify the memory limit"); + editor.assertHoverContains("1G", "Use the `memory` attribute to specify the memory limit"); + editor.assertHoverContains("inherit", "For example, every child of a parent manifest called `base-manifest.yml` begins like this"); + editor.assertHoverContains("buildpack", "use the `buildpack` attribute to specify its URL or name"); + editor.assertHoverContains("name", "The `name` attribute is the only required attribute for an application in a manifest file"); + editor.assertHoverContains("command", "On the command line, use the `-c` option to specify the custom start command as the following example shows"); + editor.assertHoverContains("disk_quota", "Use the `disk_quota` attribute to allocate the disk space for your app instance"); + editor.assertHoverContains("domain", "You can use the `domain` attribute when you want your application to be served"); + editor.assertHoverContains("domains", "Use the `domains` attribute to provide multiple domains"); + editor.assertHoverContains("env", "The `env` block consists of a heading, then one or more environment variable/value pairs"); + editor.assertHoverContains("host", "Use the `host` attribute to provide a hostname, or subdomain, in the form of a string"); + editor.assertHoverContains("hosts", "Use the `hosts` attribute to provide multiple hostnames, or subdomains"); + editor.assertHoverContains("instances", "Use the `instances` attribute to specify the number of app instances that you want to start upon push"); + editor.assertHoverContains("no-hostname", "By default, if you do not provide a hostname, the URL for the app takes the form of `APP-NAME.DOMAIN`"); + editor.assertHoverContains("no-route", "You can use the `no-route` attribute with a value of `true` to prevent a route from being created for your application"); + editor.assertHoverContains("path", "You can use the `path` attribute to tell Cloud Foundry where to find your application"); + editor.assertHoverContains("random-route", "Use the `random-route` attribute to create a URL that includes the app name and random words"); + editor.assertHoverContains("services", "The `services` block consists of a heading, then one or more service instance names"); + editor.assertHoverContains("stack", "Use the `stack` attribute to specify which stack to deploy your application to."); + editor.assertHoverContains("timeout", "The `timeout` attribute defines the number of seconds Cloud Foundry allocates for starting your application"); + editor.assertHoverContains("health-check-type", "Use the `health-check-type` attribute to"); + } + + @Test + public void noHoverInfos() throws Exception { + Editor editor = harness.newEditor( + "#comment\n" + + "applications:\n" + + "- buildpack: zbuildpack\n" + + " name: foo\n" + + " domains:\n" + + " - pivotal.io\n" + + " - otherdomain.org\n" + + ); + editor.assertNoHover("comment"); + + // May fail in the future if hover support is added, but if hover support is added in the future, + // it is expected that these should start to fail, as right now they have no hover + editor.assertNoHover("pivotal.io"); + editor.assertNoHover("otherdomain.org"); + } + + ////////////////////////////////////////////////////////////////////////////// + + private void assertCompletions(String textBefore, String... textAfter) throws Exception { + Editor editor = harness.newEditor(textBefore); + editor.assertCompletions(textAfter); + } +} diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java new file mode 100644 index 000000000..7d9d3aeb7 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java @@ -0,0 +1,67 @@ +package org.springframework.ide.vscode.manifest.yaml; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.junit.Test; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; + +public class ManifestYamlLanguageServerTest { + + public static File getTestResource(String name) throws URISyntaxException { + return Paths.get(ManifestYamlLanguageServerTest.class.getResource(name).toURI()).toFile(); + } + + @Test + public void createAndInitializeServerWithWorkspace() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + File workspaceRoot = getTestResource("/workspace/"); + assertExpectedInitResult(harness.intialize(workspaceRoot)); + } + + @Test + public void createAndInitializeServerWithoutWorkspace() throws Exception { + File workspaceRoot = null; + LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + assertExpectedInitResult(harness.intialize(workspaceRoot)); + } + +// @Test public void completions() throws Exception { +// LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); +// +// File workspaceRoot = getTestResource("/workspace/"); +// assertExpectedInitResult(harness.intialize(workspaceRoot)); +// +// TextDocumentInfo doc = harness.openDocument(getTestResource("/workspace/testfile.yml")); +// +// CompletionList completions = harness.getCompletions(doc, doc.positionOf("foo")); +// assertThat(completions.isIncomplete()).isFalse(); +// assertThat(completions.getItems()) +// .extracting(CompletionItem::getLabel) +// .containsExactly("TypeScript", "JavaScript"); +// +// List resolved = harness.resolveCompletions(completions); +// assertThat(resolved) +// .extracting(CompletionItem::getLabel) +// .containsExactly("TypeScript", "JavaScript"); +// +// assertThat(resolved) +// .extracting(CompletionItem::getDetail) +// .containsExactly("TypeScript details", "JavaScript details"); +// +// assertThat(resolved) +// .extracting(CompletionItem::getDocumentation) +// .containsExactly("TypeScript docs", "JavaScript docs"); +// } + + private void assertExpectedInitResult(InitializeResult initResult) { + assertThat(initResult.getCapabilities().getCompletionProvider().getResolveProvider()).isFalse(); + assertThat(initResult.getCapabilities().getTextDocumentSync()).isEqualTo(TextDocumentSyncKind.Incremental); + } + +} diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java new file mode 100644 index 000000000..02a5f4727 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java @@ -0,0 +1,155 @@ +package org.springframework.ide.vscode.manifest.yaml; +/******************************************************************************* + * Copyright (c) 2015, 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YSeqType; +import org.springframework.ide.vscode.manifest.yaml.ManifestYmlSchema; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; + +/** + * @author Kris De Volder + */ +public class ManifestYmlSchemaTest { + + private static final String[] NESTED_PROP_NAMES = { +// "applications", + "buildpack", + "command", + "disk_quota", + "domain", + "domains", + "env", + "health-check-type", + "host", + "hosts", +// "inherit", + "instances", + "memory", + "name", + "no-hostname", + "no-route", + "path", + "random-route", + "services", + "stack", + "timeout" + }; + + private static final String[] TOPLEVEL_PROP_NAMES = { + "applications", + "buildpack", + "command", + "disk_quota", + "domain", + "domains", + "env", + "health-check-type", +// "host", +// "hosts", + "inherit", + "instances", + "memory", +// "name", + "no-hostname", + "no-route", + "path", + "random-route", + "services", + "stack", + "timeout" + }; + + ManifestYmlSchema schema = new ManifestYmlSchema(null); + + @Test + public void toplevelProperties() throws Exception { + assertPropNames(schema.getTopLevelType().getProperties(), TOPLEVEL_PROP_NAMES); + assertPropNames(schema.getTopLevelType().getPropertiesMap(), TOPLEVEL_PROP_NAMES); + } + + @Test + public void nestedProperties() throws Exception { + assertPropNames(getNestedProps(), NESTED_PROP_NAMES); + } + + @Test + public void toplevelPropertiesHaveDescriptions() { + for (YTypedProperty p : schema.getTopLevelType().getProperties()) { + if (!p.getName().equals("applications")) { + assertHasRealDescription(p); + } + } + } + + @Test + public void nestedPropertiesHaveDescriptions() { + for (YTypedProperty p : getNestedProps()) { + assertHasRealDescription(p); + } + } + + ////////////////////////////////////////////////////////////////////////////// + + private void assertHasRealDescription(YTypedProperty p) { + { + String noDescriptionText = Renderables.NO_DESCRIPTION.toHtml(); + String actual = p.getDescription().toHtml(); + String msg = "Description missing for '"+p.getName()+"'"; + assertTrue(msg, StringUtil.hasText(actual)); + assertFalse(msg, noDescriptionText.equals(actual)); + } + { + String noDescriptionText = Renderables.NO_DESCRIPTION.toMarkdown(); + String actual = p.getDescription().toMarkdown(); + String msg = "Description missing for '"+p.getName()+"'"; + assertTrue(msg, StringUtil.hasText(actual)); + assertFalse(msg, noDescriptionText.equals(actual)); + } + } + + private List getNestedProps() { + YSeqType applications = (YSeqType) schema.getTopLevelType().getPropertiesMap().get("applications").getType(); + YBeanType application = (YBeanType) applications.getDomainType(); + return application.getProperties(); + } + + private void assertPropNames(List properties, String... expectedNames) { + assertEquals(ImmutableSet.copyOf(expectedNames), getNames(properties)); + } + + private void assertPropNames(Map propertiesMap, String[] toplevelPropNames) { + assertEquals(ImmutableSet.copyOf(toplevelPropNames), ImmutableSet.copyOf(propertiesMap.keySet())); + } + + private ImmutableSet getNames(Iterable properties) { + Builder builder = ImmutableSet.builder(); + for (YTypedProperty p : properties) { + builder.add(p.getName()); + } + return builder.build(); + } + +} diff --git a/vscode-extensions/vscode-concourse/src/test/resources/workspace/manifest.yml b/vscode-extensions/vscode-concourse/src/test/resources/workspace/manifest.yml new file mode 100644 index 000000000..7160866ac --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/test/resources/workspace/manifest.yml @@ -0,0 +1,5 @@ +#Comment +applications: +- name: foo + buildpack: something + \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/test/examples/destroy-pipeline.sh b/vscode-extensions/vscode-concourse/test/examples/destroy-pipeline.sh new file mode 100755 index 000000000..09a9e09db --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/destroy-pipeline.sh @@ -0,0 +1,4 @@ +#!/bin/bash +branch=`git rev-parse --abbrev-ref HEAD` +fly -t tools destroy-pipeline -p sts4-${branch} + diff --git a/vscode-extensions/vscode-concourse/test/examples/docker/Dockerfile b/vscode-extensions/vscode-concourse/test/examples/docker/Dockerfile new file mode 100644 index 000000000..02f4a5a1f --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/docker/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:16.04 + +ADD npmrc /root/.npmrc + +RUN apt-get update && apt-get install -y \ + build-essential \ + gettext-base \ + git \ + jq \ + openjdk-8-jdk \ + maven \ + curl + +RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \ + && apt-get install -y nodejs + +CMD /bin/bash diff --git a/vscode-extensions/vscode-concourse/test/examples/docker/npmrc b/vscode-extensions/vscode-concourse/test/examples/docker/npmrc new file mode 100644 index 000000000..3364a1762 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/docker/npmrc @@ -0,0 +1 @@ +unsafe-perm=true \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/test/examples/pipeline.yml b/vscode-extensions/vscode-concourse/test/examples/pipeline.yml new file mode 100644 index 000000000..243fb8854 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/pipeline.yml @@ -0,0 +1,116 @@ +########################################################## +resource_types: +- name: s3-multi + type: docker-image + source: + repository: kdvolder/s3-resource-simple +- name: slack-notification + type: docker-image + source: + repository: cfcommunity/slack-notification-resource + tag: latest +######################################################### +resources: +- name: docker-git + type: git + source: + uri: git@github.com:spring-projects/sts4.git + branch: {{branch}} + username: kdvolder + private_key: {{rsa_id}} + paths: + - concourse/docker +- name: sts4 + type: git + source: + uri: git@github.com:spring-projects/sts4.git + branch: {{branch}} + private_key: {{rsa_id}} +- name: s3-boot-properties-vsix + type: s3 + source: + bucket: {{s3_bucket}} + access_key_id: {{s3_accesskey}} + secret_access_key: {{s3_secretkey}} + region_name: {{s3_region}} + regexp: sts4/vscode-extensions/vscode-boot-properties-(.*).vsix +- name: s3-manifest-yaml-vsix + type: s3 + source: + bucket: {{s3_bucket}} + access_key_id: {{s3_accesskey}} + secret_access_key: {{s3_secretkey}} + region_name: {{s3_region}} + regexp: sts4/vscode-extensions/vscode-manifest-yaml-(.*).vsix +- name: website + type: s3-multi + source: + bucket: {{s3_prod_bucket}} + access_key_id: {{s3_prod_accesskey}} + secret_access_key: {{s3_prod_secretkey}} + region_name: {{s3_region}} + path: snapshot/STS4/vscode-extensions + options: + - "--acl public-read" +- name: slack-notification + type: slack-notification + source: + url: https://hooks.slack.com/services/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 +- name: docker-image + type: docker-image + source: + username: {{docker_hub_username}} + email: {{docker_hub_email}} + password: {{docker_hub_password}} + repository: kdvolder/sts4-build-env +######################################################################################## +jobs: +- name: build-docker-image + serial: true + plan: + - get: docker-git + trigger: true + - put: docker-image + params: + build: docker-git/concourse/docker + get_params: + skip_download: true +- name: build-vsix + plan: + - get: sts4 + trigger: true + - task: build-vscode-extensions + file: sts4/concourse/tasks/build-vscode-extensions.yml + on_success: + aggregate: + - put: s3-manifest-yaml-vsix + params: + file: vsix-files/vscode-manifest-yaml-*.vsix + acl: public-read + - put: s3-boot-properties-vsix + params: + file: vsix-files/vscode-boot-properties-*.vsix + acl: public-read + on_failure: + put: slack-notification + params: + channel: "@kdvolder" + text: | + Concourse ${BUILD_PIPELINE_NAME}/${BUILD_JOB_NAME}/${BUILD_NAME} has failed! +- name: build-website + plan: + - aggregate: + - get: sts4 + - get: s3-manifest-yaml-vsix + passed: + - build-vsix + trigger: true + - get: s3-boot-properties-vsix + passed: + - build-vsix + trigger: true + - task: build-website + file: sts4/concourse/tasks/build-website.yml + - put: website + params: + path: website diff --git a/vscode-extensions/vscode-concourse/test/examples/set-pipeline.sh b/vscode-extensions/vscode-concourse/test/examples/set-pipeline.sh new file mode 100755 index 000000000..2fcdaabc5 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/set-pipeline.sh @@ -0,0 +1,3 @@ +#!/bin/bash +branch=`git rev-parse --abbrev-ref HEAD` +fly -t tools set-pipeline --var "branch=${branch}" --load-vars-from ${HOME}/.sts4-concourse-credentials.yml -p sts4-${branch} -c pipeline.yml diff --git a/vscode-extensions/vscode-concourse/test/examples/tasks/build-stuff.yml b/vscode-extensions/vscode-concourse/test/examples/tasks/build-stuff.yml new file mode 100644 index 000000000..e69de29bb diff --git a/vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.sh b/vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.sh new file mode 100755 index 000000000..c18fdfb05 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e +workdir=`pwd` + +cd sts4/vscode-extensions +./build-all.sh + +cd $workdir +cp `find sts4/vscode-extensions -name "*.vsix"` vsix-files + diff --git a/vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.yml b/vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.yml new file mode 100644 index 000000000..11a108490 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/tasks/build-vscode-extensions.yml @@ -0,0 +1,11 @@ +inputs: +- name: sts4 +outputs: +- name: vsix-files +platform: linux +image_resource: + type: docker-image + source: + repository: kdvolder/sts4-build-env +run: + path: "sts4/concourse/tasks/build-vscode-extensions.sh" diff --git a/vscode-extensions/vscode-concourse/test/examples/tasks/build-website.sh b/vscode-extensions/vscode-concourse/test/examples/tasks/build-website.sh new file mode 100755 index 000000000..50ec31994 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/tasks/build-website.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e +workdir=`pwd` +sources=$workdir/sts4/eclipse-distribution/common/html +target=$workdir/website + +#cp -r "${sources}/stylesheet.css" "$target" +#cp -r ${sources}/*.js "$target" +#cp s3-manifest-yaml-vsix/*.vsix "$target" +#cp s3-boot-properties-vsix/*.vsix "$target" + +export vscode_manifest_yaml=$(basename s3-manifest-yaml-vsix/*.vsix) +echo "vscode_manifest_yaml=$vscode_manifest_yaml" +export vscode_boot_properties=$(basename s3-boot-properties-vsix/*.vsix) +echo "vscode_boot_properties=$vscode_boot_properties" + +envsubst > "$target/vscode-extensions-snippet.html" << XXXXXX + +XXXXXX + +export vscode_snippet=`cat "$target/vscode-extensions-snippet.html"` + +envsubst > "$target/vscode-extensions.html" << XXXXXX + + + + +

STS4 Vscode Extensions

+ +$vscode_snippet + + + +XXXXXX + +cat $target/vscode-extensions.html diff --git a/vscode-extensions/vscode-concourse/test/examples/tasks/build-website.yml b/vscode-extensions/vscode-concourse/test/examples/tasks/build-website.yml new file mode 100644 index 000000000..c6325f3e7 --- /dev/null +++ b/vscode-extensions/vscode-concourse/test/examples/tasks/build-website.yml @@ -0,0 +1,13 @@ +inputs: +- name: sts4 +- name: s3-manifest-yaml-vsix +- name: s3-boot-properties-vsix +outputs: +- name: website +platform: linux +image_resource: + type: docker-image + source: + repository: kdvolder/sts4-build-env +run: + path: "sts4/concourse/tasks/build-website.sh" diff --git a/vscode-extensions/vscode-concourse/test/examples/tasks/test-stuff.yml b/vscode-extensions/vscode-concourse/test/examples/tasks/test-stuff.yml new file mode 100644 index 000000000..e69de29bb diff --git a/vscode-extensions/vscode-concourse/tsconfig.json b/vscode-extensions/vscode-concourse/tsconfig.json new file mode 100644 index 000000000..1905ffea7 --- /dev/null +++ b/vscode-extensions/vscode-concourse/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "moduleResolution": "node", + "target": "es6", + "lib": [ + "es6" + ], + "declaration": true, + "outDir": "out", + "sourceMap": true, + "rootDir": "." + }, + "include": [ + "typings/*.d.ts", + "lib/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/vscode-extensions/vscode-concourse/tsd.json b/vscode-extensions/vscode-concourse/tsd.json new file mode 100644 index 000000000..50cd71dcf --- /dev/null +++ b/vscode-extensions/vscode-concourse/tsd.json @@ -0,0 +1,12 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "typings", + "bundle": "typings/tsd.d.ts", + "installed": { + "node/node.d.ts": { + "commit": "d22516f9f089de107d7e7d5938566377370631f6" + } + } +}