Compare commits

..

2 Commits

Author SHA1 Message Date
Sérgio Ramos
90bd588ed6 compose demo 2017-05-11 16:31:14 +01:00
Sérgio Ramos
f1a83885f8 docker-compose-client initial implementation 2017-05-11 16:31:14 +01:00
2289 changed files with 967653 additions and 175354 deletions

View File

@ -1,9 +0,0 @@
artifacts
reports
.nyc_output
coverage
dist
styleguide
build
consoles/*/lib/app
node_modules

View File

@ -1,10 +0,0 @@
{
"extends": "joyent-portal",
"rules": {
"no-console": 1,
"new-cap": 0,
"jsx-a11y/href-no-hash": 0,
"no-negated-condition": 1,
"camelcase": 1
}
}

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sketch filter=lfs diff=lfs merge=lfs -text

View File

@ -1,25 +0,0 @@
# Commit Guidelines
```
<type><(scope)?>: <msg>
```
Where type is one of:
```
build
chore
ci
docs
feat
fix
perf
refactor
revert
style
test
```
And where scope is one of ui-toolkit, my-joy-beta, cloudapi-gql, boilerplate, and create-instance.
_The recommended method to commit should be by running npm run commit._

View File

@ -1,17 +0,0 @@
## I'm submitting a...
* [ ] bug report
* [ ] feature request
* [ ] design request
## What is the current behavior?
## If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem
## What is the expected behavior?
## What is the motivation / use case for changing the behavior?
## If the current behavior is a bug, please provide your browser
## Other information

View File

@ -1,14 +0,0 @@
**Please check if the PR fulfills these requirements**
* [ ] The commit message follows our [guidelines](https://github.com/yldio/joyent-portal/blob/master/.github/COMMIT_GUIDELINES.md)
* [ ] Tests for the changes have been added (for bug fixes / features)
**What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
**Does this PR close an issue?** (If not please create one)
**What is the new behavior (if this is a feature change)?**
**Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
**Other information**

19
.gitignore vendored
View File

@ -118,6 +118,9 @@ Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
### Windows ###
# Windows image file caches
@ -151,19 +154,3 @@ $RECYCLE.BIN/
tap-xunit
/ui/dist
_todo
packages/*/dist
prototypes/*/dist
packages/*/buid
prototypes/*/buid
packages/*/.next
prototypes/*/.next
packages/ui-toolkit/styleguide/
packages/ui-toolkit/.snapguidist/
packages/*/package-lock.json
prototypes/*/package-lock.json
_env*
keys*
/packages/*/public/index.html
/consoles/*/public/index.html

View File

@ -1,24 +0,0 @@
{
"ignoreDevDependencies": true,
"allowedPackages": [
{
"name": "colors",
"extraFieldsForDocumentation":
"Licence is MIT, but was not found by tool: https://github.com/Marak/colors.js/blob/v0.5.1/MIT-LICENSE.txt",
"date": "17 January 2017",
"reason": "MIT Licenced"
}
],
"allowedLicenses": [
"CC-BY-4.0",
"CC0-1.0",
"MIT",
"ISC",
"Apache",
"BSD",
"WTF",
"Public Domain",
"MPL",
"Unlicense"
]
}

View File

@ -1,29 +0,0 @@
.git/*
.DS_Store
license
yarn.lock
.travis.yml
.yarnclean
.eslintignore
.prettierignore
.npmignore
.gitignore
.dockerignore
dist
build
packages/*/lib/app
consoles/*/lib/app
*.ico
*.html
*.log
*.svg
*.map
*.png
*.snap
*.ttf
*.sh
*.txt

View File

@ -1,31 +0,0 @@
{
"bracketSpacing": true,
"jsxBracketSameLine": false,
"printWidth": 80,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false,
"overrides": [
{
"files": [
".prettierrc",
".eslintrc",
".babelrc",
".tern-project",
".stylelintrc",
".lighthouserc"
],
"options": {
"parser": "json"
}
},
{
"files": ["package.json"],
"options": {
"printWidth": 180
}
}
]
}

View File

@ -1,12 +0,0 @@
{
"libs": ["ecmascript", "browser"],
"plugins": {
"doc_comment": true,
"local-scope": true,
"jsx": true,
"node": true,
"webpack": {
"configPath": "./webpack/index.js"
}
}
}

View File

@ -1,5 +0,0 @@
language: node_js
node_js:
- '9'
script:
- yarn run test:ci

15
.vscode/launch.json vendored
View File

@ -1,15 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3069/",
"webRoot": "${workspaceRoot}"
}
]
}

View File

@ -1 +0,0 @@
{}

View File

@ -1,36 +0,0 @@
# test directories
__tests__
test
tests
powered-test
# asset directories
docs
doc
website
images
assets
# code coverage directories
coverage
.nyc_output
# build scripts
Makefile
Gulpfile.js
Gruntfile.js
# configs
.tern-project
.gitattributes
.editorconfig
.*ignore
.eslintrc
.jshintrc
.flowconfig
.documentup.json
.yarn-metadata.json
# misc
*.gz
*.md

33
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,33 @@
## Development Workflow
### Small Feature Development
Contributors who have write access to the repository will practise continuous
delivery (CD as known from now on in this document).
We will define CD in this document as a method of developing a feature per commit
with an encapsulating test that proves that the functionality is working, the
contributor will test their code locally and if all is passing will push to *master*.
For contributors that do not have write access, follow the same conventions but
open a Pull Request instead.
### Large changesets
When larger changes need to be made, or the work that is carried out spans multiple
components / services of the application at the same time a single commit will
not suffice.
In this scenario, the contributor should open a pull request instead.
## Commit messages
Follow [Git blessed](http://chris.beams.io/posts/git-commit/)
1. Separate subject from body with a blank line
2. Limit the subject line to 50 characters
3. Capitalize the subject line
4. Do not end the subject line with a period
5. Use the imperative mood in the subject line
6. Wrap the body at 72 characters
7. Use the body to explain what and why vs. how

57
Makefile Normal file
View File

@ -0,0 +1,57 @@
.PHONY: check
check:
@yarn install --prefer-offline
-@./bin/setup
.PHONE: licence
licence:
./node_modules/.bin/license-to-fail ./licence.js
make licence-check
SUBDIRS := $(dir $(wildcard */Makefile))
TARGETS := install clean test test-ci lint lint-ci licence-check# whatever else, but must not contain '/'
# foo/.all bar/.all foo/.clean bar/.clean
SUBDIRS_TARGETS := \
$(foreach t,$(TARGETS),$(addsuffix $t,$(SUBDIRS)))
.PHONY: $(TARGETS) $(SUBDIRS_TARGETS)
# static pattern rule, expands into:
# all clean: %: foo/.% bar/.%
$(TARGETS): %: $(addsuffix %,$(SUBDIRS))
@echo 'Done "$*" target'
# here, for foo/.all:
# $(@D) is foo
# $(@F) is .all, with leading period
# $(@F:.%=%) is just all
$(SUBDIRS_TARGETS):
$(MAKE) --no-print-directory -C $(@D) $(@F:.%=%)
DIFF := $(lastword $(subst /, ,${CIRCLE_COMPARE_URL}))
CHANGED_FILES := $(subst /, , $(dir $(shell git diff --name-only $(DIFF))))
CHANGES := $(patsubst %, %/, $(sort $(filter $(subst /, ,$(SUBDIRS)), $(CHANGED_FILES))))
.PHONY: diff
diff:
echo $(CHANGES)
BUILDS := build push
# foo/.all bar/.all foo/.clean bar/.clean
BUILDS_TARGETS := \
$(foreach t,$(BUILDS),$(addsuffix $t,$(CHANGES)))
.PHONY: $(BUILDS) $(BUILDS_TARGETS)
# static pattern rule, expands into:
# all clean: %: foo/.% bar/.%
$(BUILDS): %: $(addsuffix %,$(CHANGES))
@echo 'Done "$*" target'
# here, for foo/.all:
# $(@D) is foo
# $(@F) is .all, with leading period
# $(@F:.%=%) is just all
$(BUILDS_TARGETS):
$(MAKE) --no-print-directory -C $(@D) $(@F:.%=%)

120
README.md Normal file
View File

@ -0,0 +1,120 @@
[![CircleCI](https://circleci.com/gh/yldio/joyent-portal.svg?style=shield&circle-token=0bbeaaafc4868c707ca0ed0568f5193a04daddb4)](https://circleci.com/gh/yldio/joyent-portal)
[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0)
# Prototype Triton Portal
This is a prototype project intended to explore some ideas that might contribute to new capabilities and a new user experience for managing applications on [Joyent's Triton](https://www.joyent.com/triton).
**This is not intended for general use and is completely unsupported.**
## Our Principles
We have designed this product with these principles in mind (to be completed).
## Our Design Library
We use inVision craft for our design components, you can downoad this library [here](https://drive.google.com/open?id=0Bw56g3tFwIuWOXNHUDZmRmQ3ZlE) and submit proposed changes to our Sketch wireframes [here](https://drive.google.com/open?id=0B1oWObk56wa5cE5iY2JWNmI2djg).
## Documentation
Our workshop meeting notes are kept in this [Google Drive folder](https://drive.google.com/open?id=0B1oWObk56wa5eklBNGFlWFRFOHM). Our meeting notes are kept in this [Google Drive folder](https://drive.google.com/open?id=0B1oWObk56wa5N1VzZjhZWWpDTTQ).
## Development
If you would like to contribute to the project, the recommended way to setup is to
insure that you have docker installed, and optionally have a triton account and profile
setup using the triton tool.
Currently requires [yarn](https://yarnpkg.com/en/docs/install) for installing dependencies,
as well as `docker` and `docker-compose` are installed correctly, this can be done by
running `make`, make continues without any errors, then you are good to go. [node-triton](https://github.com/joyent/node-triton)
is also needed if deployment to Triton is required.
```
make && make install
```
Then to run each individual component locally (subject to change).
## Setup
```sh
make
```
## Run services
To run the stack locally:
```sh
docker-compose -f local-compose.yml up -d
```
This will run the front-end at [http://127.0.0.1:8000](http://127.0.0.1:8000),
the UI framework at [http://127.0.0.1:8001](http://127.0.0.1:8001),
## Project Management
This project is using [Github Projects](https://www.youtube.com/watch?v=C6MGKHkNtxU) for organisation and development of the Joyent Dashboard.
## Repository Layout
Currently we are using this repository as a monolithic catch-all for all project communication, development and designs.
We will also include multiple PoC's of various bits of functionality from UI's prototypes to API development.
```
.
├── cloudapi-graphql
├── docs
├── frontend
├── nginx
├── ui
└── spikes
```
### cloudapi-graphql
An implementation of the [Joyent CloudAPI](https://apidocs.joyent.com/cloudapi/) in GraphQL.
### Docs
Documentation about the project, mainly focused on information for the technical runnings of this project.
Can be view online at the [documentation website](http://docs.svc.f4b20699-b323-4452-9091-977895896da6.eu-ams-1.triton.zone/)
### frontend
The client side code with a dev-server, this also includes the production server for the meantime, however we are looking at moving towards a deployment of the build artifacts to manta, and another server to host these assets.
### nginx
Nginx will be sitting in-front of the `ui` service, allowing the `ui` to scale out.
### ui
Code for the reusable UI framework.
### spikes
Implementation examples from spikes, this directory is experimental and is likely broken.
### Git LFS
- We are using Git LFS to track large files, such as design files in Sketch.
- Make sure you have this downloaded locally
`brew install git-lfs`
#### Helpful tips
- If there is an error cloning to a new machine, or there is an error cloning in the Circle CI process run `git lfs push origin master --all` from a machine that has it already checked out.
### Sketch Pre Commit
A pre-commit hook has been added to generate a PNG shot of each .sketch file "page".
To use, make sure the following are installed:
- [Sketch Toolbox](http://sketchtoolbox.com/)
- [Sketch Measure Plugin](https://github.com/utom/sketch-measure)
Then add following to your `.git/config`
```
[diff "sketchtool"]
textconv = "sketchtool dump"
cachetextconv = true
```

30
bin/deploy Executable file
View File

@ -0,0 +1,30 @@
#! /usr/bin/env bash
#
# Prelude - make bash behave sanely
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
#
set -euo pipefail
# Beware of CDPATH gotchas causing cd not to work correctly when a user
# has set this in their environment
# https://bosker.wordpress.com/2012/02/12/bash-scripters-beware-of-the-cdpath/
unset CDPATH
readonly INCLUDE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# shellcheck source=bin/setup-tools
. "${INCLUDE}"/setup-tools
echo ">> running triton deploy with docker-compose"
ensure_command triton
ensure_triton_cns_is_enabled
get_triton_details
write_env_file
export DOCKER_HOST=$_DOCKER_HOST
export DOCKER_CERT_PATH=$_DOCKER_CERT_PATH
# Do not TLS verify for now, incompatibilities between circleci and joyent
export DOCKER_TLS_VERIFY=
docker-compose pull
COMPOSE_PROJECT_NAME=${CIRCLE_BRANCH} docker-compose up -d

10
bin/docker-login Executable file
View File

@ -0,0 +1,10 @@
#! /usr/bin/env bash
#
# Prelude - make bash behave sanely
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
#
set -euo pipefail
echo ">> Logging into $_DOCKER_REGISTRY"
docker login -e="." -u="$_DOCKER_LOGIN_USERNAME" -p="$_DOCKER_LOGIN_PASSWORD" "$_DOCKER_REGISTRY"

203
bin/history-check Executable file
View File

@ -0,0 +1,203 @@
#! /usr/bin/env bash
#
# Prelude - make bash behave sanely
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
#
set -euo pipefail
IFS=$'\n\t'
#
# Globals
#
remember_git_start_and_end() {
HEAD="$(git rev-parse HEAD)"
ROOT="$(git log --pretty=format:%H | tail -n 1)"
}
#
# Utilities
#
die() {
local msg="$@"
[[ -z "${msg}" ]] || {
tput setaf 1 # red
tput bold
echo "${msg}"
tput sgr0 # reset
}
exit 1
}
error() {
local msg="$@"
echo -n '| '
tput setaf 1 # red
echo -n ' ✖'
tput sgr0 # reset
echo " ${msg}"
}
success() {
local msg="$@"
echo -n '| '
tput setaf 2 # green
echo -n ' ✓'
tput sgr0 # reset
echo " ${msg}"
}
log_commit() {
echo "○ $@"
}
# Check a command is present
ensure_command() {
local cmd="$1"
command -v "${cmd}" > /dev/null 2>&1 || {
die "Couldn't find required command: ${cmd}"
}
}
#
# Signal handling
#
cleanup() {
git reset --hard "${HEAD}" > /dev/null 2>&1
rm -f $$_commit_message
}
trap cleanup SIGHUP SIGINT SIGTERM
#
# Git helpers
#
# Go back one commit in history (first parent for merges)
step_back_one_commit() {
git reset --hard HEAD^ > /dev/null
log_commit "$(git rev-parse HEAD)"
}
current_commit_message() {
GIT_PAGER= git log --format=%B -n 1
}
current_commit_sha() {
git rev-parse HEAD
}
exit_if_not_git_repo() {
local gitroot="$(git rev-parse --show-toplevel 2> /dev/null)"
[[ "${gitroot}" == "" ]] && die 'Current directory is not in a repository'
return 0
}
#
# Checks
#
check_commit_message() {
local lineno=0
local length=0
local succeded=1
while read -r line ; do
let succeded=1
let lineno+=1
length=${#line}
[[ "${lineno}" -eq "1" ]] && {
[[ "${length}" -gt 50 ]] && {
error "Commit message: Subject line longer than 50 characters";
succeded=0
};
[[ ! "${line}" =~ ^[A-Z].*$ ]] && {
error "Commit message: Subject line not capitalised";
succeded=0
};
[[ "${line}" == *. ]] && {
error "Commit message: Subject line ended with a full stop";
succeded=0
};
}
[[ "${lineno}" -eq "2" ]] && [[ -n "${line}" ]] && {
error "Commit message: Subject line not separated by a blank line";
succeded=0;
};
[[ "${lineno}" -gt "1" ]] && [[ "${length}" -gt "72" ]] && {
error "Commit message: Body not wrapped at 72 characters";
succeded=0
};
done < $$_commit_message
[[ "${succeded}" -eq "1" ]] && success "Commit message"
return 0
}
run_checks() {
current_commit_message > $$_commit_message
check_commit_message
rm -f $$_commit_message
set +e
npm run lint > /dev/null 2>&1
if [[ "$?" -eq 0 ]]; then
success 'Lint'
else
error 'Lint: script did not exit successfully'
fi
npm test > /dev/null 2>&1
if [[ "$?" -eq 0 ]]; then
success 'Test'
else
error 'Test: script did not exit successfully'
fi
set -e
}
check_project() {
exit_if_not_git_repo
[[ -f './package.json' ]] || {
die 'This does not appear to be a node project'
}
[[ -z "$(json -f package.json 'scripts.lint')" ]] && {
die 'There is no lint script in the package.json'
}
[[ -z "$(json -f package.json 'scripts.test')" ]] && {
die 'There is no test script in the package.json'
}
return 0
}
traverse_history() {
while [[ "${ROOT}" != "$(current_commit_sha)" ]] ; do
run_checks
step_back_one_commit
done
}
#
# Main
#
ensure_command git
ensure_command tail
ensure_command npm
ensure_command json
check_project
remember_git_start_and_end
log_commit "HEAD: $(current_commit_sha)"
traverse_history
run_checks
log_commit "ROOT: $(current_commit_sha)"
cleanup
# vim: syntax=sh et ts=2 sts=2 sw=2

16
bin/on-changes-publish-ui Executable file
View File

@ -0,0 +1,16 @@
#! /usr/bin/env bash
#
# Prelude
#
set -euo pipefail
set -x
# Set internal field seperator to `/` to split up urls
IFS='/'
read -ra ADDR <<< "$CIRCLE_COMPARE_URL"
if [[ "$(git diff --name-only """${ADDR[-1]}""")" == *"ui/"* ]]
then
make -C ui publish | sed '/NPM_TOKEN/d'
fi

60
bin/pre-commit.hook Executable file
View File

@ -0,0 +1,60 @@
#! /usr/bin/env bash
#
# Prelude - make bash behave sanely
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
#
set -euo pipefail
# Make pushd & popd silent
pushd () {
command pushd "$@" > /dev/null
}
popd () {
command popd "$@" > /dev/null
}
export EXIT_CODE=0
function lint_changed() {
# Allow lint to be ran from outside of the root directory
local git_root
git_root=$(git rev-parse --show-cdup)
git_root=${git_root:-./}
local subdirs
subdirs=$(find "$git_root" -maxdepth 2 -mindepth 2 -name 'Makefile' -printf '%h\n')
for directory in $subdirs
do
pushd "$directory"
local npm_bin="node_modules/.bin"
local eslint="$npm_bin/eslint"
function lint() {
local to_lint
to_lint=$(git diff --staged --diff-filter=ACMTUXB --name-only -- '*.j'{s,sx})
echo $to_lint
echo $eslint
if [ "$to_lint" ]; then
$eslint "$to_lint" -c ".eslintrc" || EXIT_CODE=$?
fi
}
lint
popd
done
}
lint_changed
if [[ "$EXIT_CODE" -gt "0" ]]; then
exit $EXIT_CODE
fi
make test
echo "⚡️ changed files pass eslint! ⚡️"

23
bin/setup Executable file
View File

@ -0,0 +1,23 @@
#! /usr/bin/env bash
#
# Prelude
#
set -euo pipefail
IFS=$'\n\t'
# Beware of CDPATH gotchas causing cd not to work correctly when a user
# has set this in their environment
# https://bosker.wordpress.com/2012/02/12/bash-scripters-beware-of-the-cdpath/
unset CDPATH
readonly INCLUDE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# shellcheck source=bin/setup-tools
. "${INCLUDE}"/setup-tools
#
# Main
#
ensure_prerequisites
get_triton_details
check_docker_config
write_env_file

105
bin/setup-tools Normal file
View File

@ -0,0 +1,105 @@
#! /usr/bin/env bash
# setup.sh - Checks that all the required tools are present and that they are
# appropriately configured for deploying to Triton.
#
# Adapted from https://github.com/autopilotpattern/mysql/blob/master/setup.sh
#
#
# Prelude
#
set -euo pipefail
IFS=$'\n\t'
#
# Utilities
#
die() {
local msg="$*"
[[ -z "${msg}" ]] || {
echo
tput setaf 1 # red
tput bold
echo "${msg}"
tput sgr0 # reset
}
exit 1
}
#
# Check functions
#
ensure_command() {
local cmd="$1"
command -v "${cmd}" > /dev/null 2>&1 || {
die "Couldn't find required command: ${cmd}"
}
}
get_triton_details() {
TRITON_USER=$(triton profile get | awk -F": " '/account:/{print $2}')
TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}')
TRITON_URL=$(triton profile get | awk -F' ' '/url:/{print $2}')
TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}')
TRITON_KEY=$(triton profile get | awk -F' ' '/keyId:/{print $2}')
}
check_docker_config() {
[[ "${DOCKER_HOST:=unset}" == "unset" ]] && {
echo "Run \"docker-compose -f local-compose.yml up\" to run locally"
return 0
}
return 0
}
ensure_docker_config_matches_triton_config_and_capture_triton_details() {
local docker_user
docker_user=$(docker info 2>&1 | awk -F": " '/SDCAccount:/{print $2}')
local docker_dc
docker_dc="$(echo "${DOCKER_HOST}" | awk -F"/" '{print $3}' | awk -F'.' '{print $1}')"
get_triton_details
[[ ! "$docker_user" = "$TRITON_USER" ]] || [[ ! "$docker_dc" = "$TRITON_DC" ]] && {
echo "Docker user: ${docker_user}"
echo "Triton user: ${TRITON_USER}"
echo "Docker data center: ${docker_dc}"
echo "Triton data center: ${TRITON_DC}"
die "Your Triton config does not match your Docker configuration."
}
return 0;
}
ensure_triton_cns_is_enabled() {
local triton_cns_enabled
triton_cns_enabled=$(triton account get | awk -F": " '/cns/{print $2}')
[[ "$triton_cns_enabled" == "true" ]] || {
die "Triton CNS is required and not enabled."
}
}
write_env_file() {
if [[ -f .env ]] ; then
echo "Env file already exists, not overwriting"
else
echo '# Consul discovery via Triton CNS' >> .env
[[ "${DOCKER_HOST:=unset}" == "*docker.joyent.com*" ]] || {
echo CONSUL="consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com" \
>> .env
}
echo SDC_KEY_ID=${TRITON_KEY} >> .env
echo SDC_ACCOUNT=${TRITON_ACCOUNT} >> .env
echo SDC_URL=${TRITON_URL} >> .env
echo >> .env
fi
}
ensure_prerequisites() {
ensure_command docker
ensure_command docker-compose
ensure_command triton
}
# vim: syntax=sh et ts=2 sts=2 sw=2

57
bin/sketch-previews.rb Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env ruby
require 'fileutils'
PROGNAME = 'pre-commit (auto-generate sketch previews)'
def main
progress "Installing sketchtool"
system!(%W[/Applications/Sketch.app/Contents/Resources/sketchtool/install.sh])
progress "Looking for changed or added .sketch files"
diff_output = capture!(%W[git diff --name-only --cached --pretty=format:])
sketch_files = diff_output.split("\n").grep(/\.sketch\z/)
if sketch_files.empty?
progress "No sketch files to create preview images for in this commit!"
end
puts sketch_files
sketch_files.each do |f|
unless File.exist?(f)
progress "#{f} does not exist (anymore?)"
next
end
png_output_dir = f.sub(/\.sketch\z/, '') + '-sketch-previews'
progress "deleting old previews"
FileUtils.rm_rf(png_output_dir)
progress "exporting pages"
cmd = %W[sketchtool --overwriting=YES --output=#{png_output_dir} export pages #{f}]
system!(cmd)
progress "adding to git"
system!(%W[git add #{png_output_dir}])
end
end
def system!(cmd)
puts "running: #{cmd.join(' ')}"
abort failure_message(cmd) unless system(*cmd)
end
def capture!(cmd)
puts "capturing: #{cmd.join(' ')}"
result = IO.popen(cmd) { |io| io.read }
abort failure_message(cmd) unless $?.success?
result
end
def failure_message(cmd)
"#{PROGNAME}: command failed: #{cmd.join(' ')}"
end
def progress(msg)
puts "#{PROGNAME}: #{msg}"
end
main

View File

@ -1,8 +0,0 @@
'use strict';
const { homedir } = require('os');
const { join } = require('path');
const { SDC_KEY_PATH } = process.env;
process.env.SDC_KEY_PATH = SDC_KEY_PATH || join(homedir(), './.ssh/id_rsa');

View File

@ -1,20 +0,0 @@
module.exports = [
{
name: 'Logout',
slug: 'logout',
description: 'Do the daggum logout',
url: '/logout'
},
{
name: 'Change Password',
slug: 'change-password',
description: 'Change yer own password',
url: '/password'
},
{
name: 'Account',
slug: 'account',
description: 'Your account information',
url: '/account'
}
];

View File

@ -1,89 +0,0 @@
module.exports = [
{
name: 'Compute',
services: [
{
name: 'VMs & Containers',
slug: 'instances',
description: 'Run VMs and bare metal containers'
}
]
},
{
name: 'Network',
services: [
{
name: 'VLANs',
slug: 'vlans',
description: 'Wire your application your way'
},
{
name: 'Subnets',
slug: 'subnets',
description: 'A network for everything'
},
{
name: 'Firewall Rules',
slug: 'firewall',
description: 'Control the bits coming and going'
}
]
},
{
name: 'Storage',
services: [
{
name: 'Triton Object Storage',
slug: 'object-storage',
description: 'Modern cloud object storage',
tags: ["'note'='was Manta'"]
},
{
name: 'S3 Compatibility Bridge',
slug: 's3-bridge',
description: 'Modern storage, legacy compability'
},
{
name: 'Triton Volumes',
slug: 'volumes',
description: 'Network filesystems for your apps',
tags: ["'is-new'='true'"]
}
]
},
{
name: 'Access',
services: [
{
name: 'Role Based Access Control',
slug: 'rbac',
description: 'Manage users within your account'
}
]
},
{
name: 'Help & Support',
services: [
{
name: 'Service Status',
slug: 'status',
description: 'Find out about the status of our services'
},
{
name: 'Contact Support',
slug: 'contact-support',
description: 'Chat to us via phone or email'
},
{
name: 'Support Plans',
slug: 'support-plans',
description: 'Write here about Support Plans'
},
{
name: 'Getting Started',
slug: 'getting-started',
description: 'Write here about Getting Started'
}
]
}
];

View File

@ -1,86 +0,0 @@
module.exports = [
{
name: 'Ashburn, Virginia, USA',
continent: 'NORTH_AMERICA',
datacenters: [
{
name: 'us-east-1',
url: 'http://localhost'
},
{
name: 'us-east-2',
url: 'http://localhost'
},
{
name: 'us-east-3',
url: 'http://localhost'
}
]
},
{
name: 'Las Vegas, Nevada, USA',
continent: 'NORTH_AMERICA',
datacenters: [
{
name: 'us-sw-1',
url: 'http://localhost'
}
]
},
{
name: 'Emeryville, California, USA',
continent: 'NORTH_AMERICA',
datacenters: [
{
name: 'us-west-1',
url: 'http://localhost'
}
]
},
{
name: 'Amsterdam, Netherlands',
continent: 'EUROPE',
datacenters: [
{
name: 'us-ams-1',
url: 'http://localhost'
}
]
},
{
name: 'Singapore',
continent: 'ASIA',
datacenters: [
{
name: 'ap-sg-1',
url: 'http://localhost'
},
{
name: 'ap-sg-2',
url: 'http://localhost'
},
{
name: 'ap-sg-3',
url: 'http://localhost'
}
]
},
{
name: 'Seoul, South Korea',
continent: 'ASIA',
datacenters: [
{
name: 'ap-kr-1',
url: 'http://localhost'
},
{
name: 'ap-kr-2',
url: 'http://localhost'
},
{
name: 'ap-kr-3',
url: 'http://localhost'
}
]
}
];

View File

@ -1,72 +0,0 @@
// Requires .env.js file with the following exports:
// SDC_URL, SDC_KEY_ID, SDC_KEY_PATH
require('./.env.js');
const Main = require('apr-main');
const Hapi = require('hapi');
const H2O2 = require('h2o2');
const Execa = require('execa');
const Path = require('path');
const Fs = require('fs');
const { PORT = 4000 } = process.env;
const ROOT = Path.join(__dirname, 'src');
const calcPort = i => Number(PORT) + Number(i) + 1;
const namespaces = Fs.readdirSync(ROOT)
.filter(filename => /.js$/.test(filename))
.map(filename => filename.replace(/.js$/, ''))
.filter(filename => !['index', 'server'].includes(filename));
const routes = namespaces.map((namespace, i) => ({
method: '*',
path: `/${namespace}/{params*}`,
handler: {
proxy: {
uri: `{protocol}://0.0.0.0:${calcPort(i)}/${namespace}/{params}`
}
}
}));
namespaces.forEach((namespace, i) => {
const child = Execa('node', [namespace], {
cwd: ROOT,
cleanup: true,
env: Object.assign({}, process.env, {
PORT: calcPort(i),
PREFIX: namespace
})
});
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
});
Main(async () => {
const server = Hapi.server({
port: PORT,
routes: {
cors: {
origin: ['*'],
credentials: true,
additionalHeaders: ['Cookie', 'X-CSRF-Token']
}
},
debug: {
log: ['error'],
request: ['error']
}
});
await server.register({
plugin: H2O2
});
routes.map(route => server.route(route));
await server.start();
// eslint-disable-next-line no-console
console.log(`server started at http://0.0.0.0:${server.info.port}`);
});

View File

@ -1,31 +0,0 @@
{
"name": "joyent-portal-bundle",
"version": "1.0.0",
"private": true,
"license": "MPL-2.0",
"scripts": {
"dev": "NODE_ENV=development PORT=4000 node index.js",
"build:test": "echo 0",
"build:lib": "echo 0",
"build:bundle": "echo 0",
"prepublish": "echo 0",
"test": "echo 0",
"test:ci": "echo 0"
},
"dependencies": {
"apr-main": "^4.0.3",
"cloudapi-gql": "^8.0.0",
"execa": "^0.10.0",
"graphi": "^5.7.0",
"h2o2": "^8.1.2",
"hapi": "^17.5.0",
"hapi-triton-auth": "^3.0.0",
"hapi-webconsole-nav": "^2.1.0",
"my-joy-images": "*",
"my-joy-instances": "*",
"my-joy-navigation": "*",
"my-joy-service-groups": "*",
"my-joy-templates": "*",
"tsg-graphql": "^1.0.0"
}
}

View File

@ -1,64 +0,0 @@
require('../.env.js');
const Main = require('apr-main');
const CloudApiGql = require('cloudapi-gql');
const Graphi = require('graphi');
const Url = require('url');
const Server = require('./server');
const Ui = require('my-joy-images');
const {
PORT = 4003,
BASE_URL = `http://0.0.0.0:${PORT}`,
PREFIX = 'images',
DC_NAME,
SDC_URL,
SDC_KEY_PATH,
SDC_ACCOUNT,
SDC_KEY_ID
} = process.env;
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
const keyPath = SDC_KEY_PATH;
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
const apiBaseUrl = SDC_URL;
Main(async () => {
const server = await Server({
PORT,
BASE_URL
});
await server.register([
{
plugin: Graphi,
options: {
graphqlPath: '/graphql',
graphiqlPath: '/graphiql',
authStrategy: 'sso'
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: CloudApiGql,
options: {
authStrategy: 'sso',
keyPath,
keyId,
apiBaseUrl,
dcName
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Ui
}
]);
await server.start();
});

View File

@ -1,64 +0,0 @@
require('../.env.js');
const Main = require('apr-main');
const CloudApiGql = require('cloudapi-gql');
const Graphi = require('graphi');
const Url = require('url');
const Server = require('./server');
const Ui = require('my-joy-instances');
const {
PORT = 4002,
BASE_URL = `http://0.0.0.0:${PORT}`,
PREFIX = 'instances',
DC_NAME,
SDC_URL,
SDC_KEY_PATH,
SDC_ACCOUNT,
SDC_KEY_ID
} = process.env;
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
const keyPath = SDC_KEY_PATH;
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
const apiBaseUrl = SDC_URL;
Main(async () => {
const server = await Server({
PORT,
BASE_URL
});
await server.register([
{
plugin: Graphi,
options: {
graphqlPath: '/graphql',
graphiqlPath: '/graphiql',
authStrategy: 'sso'
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: CloudApiGql,
options: {
authStrategy: 'sso',
keyPath,
keyId,
apiBaseUrl,
dcName
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Ui
}
]);
await server.start();
});

View File

@ -1,72 +0,0 @@
require('../.env.js');
const Main = require('apr-main');
const Nav = require('hapi-webconsole-nav');
const Graphi = require('graphi');
const Url = require('url');
const Server = require('./server');
const Ui = require('my-joy-navigation');
const Regions = require('../data/regions');
const Categories = require('../data/categories');
const Account = require('../data/account');
const {
PORT = 4001,
BASE_URL = `http://0.0.0.0:${PORT}`,
PREFIX = 'navigation',
DC_NAME,
SDC_URL,
SDC_KEY_PATH,
SDC_ACCOUNT,
SDC_KEY_ID
} = process.env;
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
const keyPath = SDC_KEY_PATH;
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
const apiBaseUrl = SDC_URL;
const baseUrl = BASE_URL;
Main(async () => {
const server = await Server({
PORT,
BASE_URL
});
await server.register([
{
plugin: Graphi,
options: {
graphqlPath: '/graphql',
graphiqlPath: '/graphiql',
authStrategy: 'sso'
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Nav,
options: {
keyPath,
keyId,
apiBaseUrl,
dcName,
baseUrl,
regions: Regions,
accountServices: Account,
categories: Categories
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Ui
}
]);
await server.start();
});

View File

@ -1,84 +0,0 @@
require('../.env.js');
const Hapi = require('hapi');
const Sso = require('hapi-triton-auth');
const {
COOKIE_PASSWORD,
COOKIE_DOMAIN,
SDC_KEY_PATH,
SDC_ACCOUNT,
SDC_KEY_ID,
SDC_URL
} = process.env;
module.exports = async ({ PORT, BASE_URL }) => {
const keyPath = SDC_KEY_PATH;
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
const apiBaseUrl = SDC_URL;
const ssoUrl = 'https://sso.joyent.com/login';
const baseUrl = BASE_URL;
const isDev = true;
const permissions = {
cloudapi: ['/my/*']
};
const cookie = {
password: COOKIE_PASSWORD,
domain: COOKIE_DOMAIN,
isSecure: false,
isHttpOnly: true,
ttl: 1000 * 60 * 60 // 1 hour
};
const server = Hapi.server({
port: PORT,
routes: {
cors: {
origin: ['*'],
credentials: true,
additionalHeaders: ['Cookie', 'X-CSRF-Token']
}
},
debug: {
log: ['error'],
request: ['error']
}
});
server.events.on('log', (event, tags) => {
if (tags.error) {
// eslint-disable-next-line no-console
console.log(event);
}
});
server.events.on('request', (request, event) => {
const { tags } = event;
if (tags.includes('error') && event.data && event.data.errors) {
event.data.errors.forEach(error => {
// eslint-disable-next-line no-console
console.log(error);
});
}
});
await server.register({
plugin: Sso,
options: {
keyPath,
keyId,
apiBaseUrl,
ssoUrl,
permissions,
baseUrl,
isDev,
cookie
}
});
server.auth.default('sso');
return server;
};

View File

@ -1,78 +0,0 @@
require('../.env.js');
const Main = require('apr-main');
const CloudApiGql = require('cloudapi-gql');
const Tsg = require('tsg-graphql');
const Graphi = require('graphi');
const Url = require('url');
const Server = require('./server');
const Ui = require('my-joy-service-groups');
const {
PORT = 4004,
BASE_URL = `http://0.0.0.0:${PORT}`,
PREFIX = 'service-groups',
DC_NAME,
TSG_URL = 'http://0.0.0.0:3000',
SDC_URL,
SDC_KEY_PATH,
SDC_ACCOUNT,
SDC_KEY_ID
} = process.env;
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
const keyPath = SDC_KEY_PATH;
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
Main(async () => {
const server = await Server({
PORT,
BASE_URL
});
await server.register([
{
plugin: Graphi,
options: {
graphqlPath: '/graphql',
graphiqlPath: '/graphiql',
authStrategy: 'sso'
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Tsg,
options: {
authStrategy: 'sso',
keyPath,
keyId,
apiBaseUrl: TSG_URL,
dcName
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: CloudApiGql,
options: {
authStrategy: 'sso',
keyPath,
keyId,
apiBaseUrl: SDC_URL,
dcName
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Ui
}
]);
await server.start();
});

View File

@ -1,78 +0,0 @@
require('../.env.js');
const Main = require('apr-main');
const CloudApiGql = require('cloudapi-gql');
const Tsg = require('tsg-graphql');
const Graphi = require('graphi');
const Url = require('url');
const Server = require('./server');
const Ui = require('my-joy-templates');
const {
PORT = 4005,
BASE_URL = `http://0.0.0.0:${PORT}`,
PREFIX = 'templates',
DC_NAME,
TSG_URL = 'http://0.0.0.0:3000',
SDC_URL,
SDC_KEY_PATH,
SDC_ACCOUNT,
SDC_KEY_ID
} = process.env;
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
const keyPath = SDC_KEY_PATH;
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
Main(async () => {
const server = await Server({
PORT,
BASE_URL
});
await server.register([
{
plugin: Graphi,
options: {
graphqlPath: '/graphql',
graphiqlPath: '/graphiql',
authStrategy: 'sso'
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Tsg,
options: {
authStrategy: 'sso',
keyPath,
keyId,
apiBaseUrl: TSG_URL,
dcName
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: CloudApiGql,
options: {
authStrategy: 'sso',
keyPath,
keyId,
apiBaseUrl: SDC_URL,
dcName
},
routes: {
prefix: `/${PREFIX}`
}
},
{
plugin: Ui
}
]);
await server.start();
});

51
circle.yml Normal file
View File

@ -0,0 +1,51 @@
## Customize the test machine
machine:
pre:
- git config --global user.email "circleci@joyent.zone"
- git config --global user.name "circlebot"
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
services:
- docker
node:
version: 7.7.3
dependencies:
pre:
- sudo curl -L https://github.com/docker/compose/releases/download/1.8.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
- sudo chmod +x /usr/local/bin/docker-compose
- yarn global add triton@4.15.0 || cat /home/ubuntu/.yarn-config/global/yarn-error.log
- echo '{"url":"https://eu-ams-1.api.joyent.com","account":"'$SDC_ACCOUNT'","keyId":"c3:30:35:9b:85:48:73:44:31:cc:4b:2e:6a:00:16:e2","name":"eu-ams-1","curr":true}' | triton profile create -f -
- triton env --docker eu-ams-1
- mkdir -p ${CIRCLE_TEST_REPORTS}/tap-xunit/
- echo -e "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
override:
- make && make install
# Install git-lfs - TODO: Move to make task
- curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
- sudo apt-get install git-lfs
- ssh git@github.com git-lfs-authenticate yldio/joyent-portal.git download
- git config credential.helper manager
- git lfs pull
test:
override:
- make -j2 lint-ci test-ci
deployment:
development:
branch: master
commands:
- ./bin/docker-login
- ./bin/on-changes-publish-ui
- make -j2 build
- make -j2 push
- ./bin/deploy
staging:
tag: /release.*/
commands:
- CIRCLE_BRANCH=staging ./bin/docker-login
- CIRCLE_BRANCH=staging ./bin/on-changes-publish-ui
- CIRCLE_BRANCH=staging make -j2 build
- CIRCLE_BRANCH=staging make -j2 push
- CIRCLE_BRANCH=staging ./bin/deploy

View File

@ -0,0 +1,27 @@
{
"extends": [
"eslint:recommended",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"no-console": 0,
"prettier/prettier": ["error", {
"useTabs": false,
"printWidth": 80,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"parser": "flow",
"semi": true
}]
},
"env": {
"es6": true,
"node": true
}
}

50
cloudapi-graphql/.gitignore vendored Normal file
View File

@ -0,0 +1,50 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Vim files:
*.sw*
# Mac OS dirty files
.DS_Store

View File

@ -0,0 +1 @@
FROM quay.io/yldio/alpine-node-containerpilot:6.9.4-3

54
cloudapi-graphql/Makefile Normal file
View File

@ -0,0 +1,54 @@
NAME := $(lastword $(subst /, ,$(CURDIR)))
bindir := $(shell yarn bin)
AVA := $(bindir)/ava
NYC := $(bindir)/nyc
.PHONY: install
install:
yarn install --prefer-offline
.PHONY: install-production
install-production:
yarn install --production --pure-lockfile --prefer-offline
.PHONY: clean
clean:
@rm -rf node_modules
.PHONY: test
test:
$(AVA) $(TEST_ARGS)
XUNIT_DIR := ${CIRCLE_TEST_REPORTS}/tap-xunit
XUNIT := $(bindir)/tap-xunit
XUNIT_OUTPUT := >> ${CIRCLE_TEST_REPORTS}/tap-xunit/xunit-$(NAME)
.PHONY: test-ci
test-ci:
mkdir -p $(XUNIT_DIR)
$(NYC) $(AVA) -t | $(XUNIT) $(XUNIT_OUTPUT).xml
.PHONY: start
start:
yarn run start
.PHONY: build
build:
docker build -t quay.io/yldio/joyent-dashboard-$(NAME):$(CIRCLE_BRANCH) .
.PHONY: push
push:
docker push quay.io/yldio/joyent-dashboard-$(NAME)
.PHONY: lint
lint:
$(bindir)/eslint .
.PHONY: lint-ci
lint-ci:
mkdir -p $(XUNIT_DIR)
-$(bindir)/eslint . --format tap | $(XUNIT) $(XUNIT_OUTPUT)-lint.xml
.PHONY: licence-check
licence-check:
../node_modules/.bin/license-to-fail ../licence.js

166
cloudapi-graphql/README.md Normal file
View File

@ -0,0 +1,166 @@
[![Docker Repository on Quay](https://quay.io/repository/yldio/joyent-dashboard-cloudapi-graphql/status?token=bddd694a-a913-4b66-b7bc-fb71992672c4 "Docker Repository on Quay")](https://quay.io/repository/yldio/joyent-dashboard-cloudapi-graphql)
# cloudapi-graphql
Proof-of-Concept of the [Joyent Cloud API](https://apidocs.joyent.com/cloudapi/) running on GraphQL.
## Setup
### Setup Credentials
Create `credentials.json`:
```json5
{
"url": "https://us-sw-1.api.joyentcloud.com",
"keyId": "", //public key fingerprint ex: 35:jh:42:56...
"account": "", // account ex: raoulmillais
"user": "" // sub-account ex: ramitos
}
```
Alternatively you can just use ENV variables:
```
SDC_URL
SDC_ACCOUNT
SDC_USER
SDC_KEY_ID
```
As a third option you can use a `.env` file.
### Install Dependencies and run
```bash
yarn install
yarn start
```
### Visit GraphiQL
Go-to http://0.0.0.0:4000/graphql to use the REPL with interactive documentation.
![GraphiQL](https://cloud.githubusercontent.com/assets/524382/19242455/1e371978-8f0b-11e6-9563-d6f5b93fa63c.png)
## API
- [x] Account
- [x] GetAccount
- [x] UpdateAccount
- [x] Keys
- [x] ListKeys
- [x] GetKey
- [x] CreateKey
- [x] DeleteKey
- [x] Users
- [x] ListUsers
- [x] GetUser
- [x] CreateUser
- [x] UpdateUser
- [ ] ChangeUserPassword
- [x] DeleteUser
- [x] Roles
- [x] ListRoles
- [x] GetRole
- [x] CreateRole
- [x] UpdateRole
- [x] DeleteRole
- [x] Role Tags
- [x] SetRoleTags
- [x] Policies
- [x] ListPolicies
- [x] GetPolicy
- [x] CreatePolicy
- [x] UpdatePolicy
- [x] DeletePolicy
- [x] User SSH Keys
- [x] ListUserKeys
- [x] GetUserKey
- [x] CreateUserKey
- [x] DeleteUserKey
- [ ] Config
- [ ] GetConfig
- [ ] UpdateConfig
- [x] Datacenters
- [x] ListDatacenters
- [x] GetDatacenter
- [x] Services
- [x] ListServices
- [x] Images
- [x] ListImages
- [x] GetImage
- [x] DeleteImage
- [x] ExportImage
- [x] CreateImageFromMachine
- [ ] UpdateImage
- [x] Packages
- [x] ListPackages
- [x] GetPackage
- [x] Instances
- [x] ListMachines
- [x] GetMachine
- [x] CreateMachine
- [x] StopMachine
- [x] StartMachine
- [x] RebootMachine
- [ ] ResizeMachine
- [ ] RenameMachine
- [x] EnableMachineFirewall
- [x] DisableMachineFirewall
- [x] CreateMachineSnapshot
- [x] StartMachineFromSnapshot
- [x] ListMachineSnapshots
- [x] GetMachineSnapshot
- [x] DeleteMachineSnapshot
- [ ] UpdateMachineMetadata
- [ ] ListMachineMetadata
- [ ] GetMachineMetadata
- [ ] DeleteMachineMetadata
- [ ] DeleteAllMachineMetadata
- [x] AddMachineTags
- [x] ReplaceMachineTags
- [ ] ListMachineTags
- [x] GetMachineTag
- [x] DeleteMachineTag
- [x] DeleteMachineTags
- [x] DeleteMachine
- [x] MachineAudit
- [ ] Analytics
- [ ] DescribeAnalytics
- [ ] ListInstrumentations
- [ ] GetInstrumentation
- [ ] GetInstrumentationValue
- [ ] GetInstrumentationHeatmap
- [ ] GetInstrumentationHeatmapDetails
- [ ] CreateInstrumentation
- [ ] DeleteInstrumentation
- [x] FirewallRules
- [x] Firewall Rule Syntax
- [x] ListFirewallRules
- [x] GetFirewallRule
- [x] CreateFirewallRule
- [x] UpdateFirewallRule
- [x] EnableFirewallRule
- [x] DisableFirewallRule
- [x] DeleteFirewallRule
- [x] ListMachineFirewallRules
- [x] ListFirewallRuleMachines
- [ ] Fabrics
- [ ] ListFabricVLANs
- [ ] CreateFabricVLAN
- [ ] GetFabricVLAN
- [ ] UpdateFabricVLAN
- [ ] DeleteFabricVLAN
- [ ] ListFabricNetworks
- [ ] CreateFabricNetwork
- [ ] GetFabricNetwork
- [ ] DeleteFabricNetwork
- [x] Networks
- [x] ListNetworks
- [x] GetNetwork
- [ ] Nics
- [ ] ListNics
- [ ] GetNic
- [ ] AddNic
- [ ] RemoveNic

View File

@ -0,0 +1,38 @@
{
"consul": "{{ .CONSUL }}:8500",
"services": [{
"name": "joyent-portal-cloudapi-graphql",
"port": 4000,
"health": "/usr/bin/curl -o /dev/null --fail -s http://localhost:4000/graphql",
"poll": 3,
"ttl": 10
}],
"telemetry": {
"port": 9090,
"sensors": [{
"name": "graphql_memory_percent",
"help": "percentage of memory used",
"type": "gauge",
"poll": 5,
"check": ["/bin/sensors", "memory"]
}, {
"name": "graphql_cpu_load",
"help": "cpu load",
"type": "gauge",
"poll": 5,
"check": ["/bin/sensors", "cpu"]
}, {
"name": "graphql_disk_capacity",
"help": "disk capacity",
"type": "gauge",
"poll": 60,
"check": ["/bin/sensors", "diskcapacity"]
}, {
"name": "graphql_disk_usage",
"help": "disk usage",
"type": "gauge",
"poll": 60,
"check": ["/bin/sensors", "diskusage"]
}]
}
}

View File

@ -0,0 +1,31 @@
{
"name": "cloudapi-graphql",
"private": true,
"license": "MPL-2.0",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"lint": "eslint src --fix",
"start": "node src/index.js",
"test": "make test"
},
"dependencies": {
"bunyan": "^1.8.10",
"dotenv": "^4.0.0",
"express": "^4.15.2",
"express-graphql": "^0.6.4",
"got": "^6.7.1",
"graphql": "^0.9.3",
"smartdc-auth": "^2.5.2",
"triton": "^5.2.0"
},
"devDependencies": {
"ava": "^0.19.1",
"eslint": "^3.19.0",
"eslint-config-prettier": "^1.7.0",
"eslint-plugin-prettier": "^2.0.1",
"nyc": "^10.2.0",
"prettier": "^1.2.2",
"tap-xunit": "^1.7.0"
}
}

View File

@ -0,0 +1,9 @@
const request = require('./request');
module.exports.get = () => {
return request('getAccount');
};
module.exports.update = ctx => {
return request('updateAccount', ctx);
};

View File

@ -0,0 +1,5 @@
// const request = require('./request');
module.exports.get = () => {
// return request('', ctx);
};

View File

@ -0,0 +1,5 @@
const request = require('./request');
module.exports = () => {
return request('listDatacenters');
};

View File

@ -0,0 +1,9 @@
const request = require('./request');
module.exports.list = () => {
return request('listFirewallRules', {});
};
module.exports.get = ctx => {
return request('getFirewallRule', ctx);
};

View File

@ -0,0 +1,37 @@
const request = require('./request');
module.exports.list = () => {
return request('listFirewallRules', {});
};
module.exports.listByMachine = ctx => {
return request('listMachineFirewallRules', ctx);
};
module.exports.listMachines = ctx => {
return request('listFirewallRuleMachines', ctx);
};
module.exports.get = ctx => {
return request('getFirewallRule', ctx);
};
module.exports.create = ctx => {
return request('createFirewallRule', ctx);
};
module.exports.update = ctx => {
return request('updateFirewallRule', ctx);
};
module.exports.enable = ctx => {
return request('enableFirewallRule', ctx);
};
module.exports.disable = ctx => {
return request('disableFirewallRule', ctx);
};
module.exports.destroy = ctx => {
return request('deleteFirewallRule', ctx);
};

View File

@ -0,0 +1,25 @@
const request = require('./request');
module.exports.list = ctx => {
return request('listImages', ctx);
};
module.exports.get = ctx => {
return request('getImage', ctx);
};
module.exports.create = ctx => {
return request('createImageFromMachine', ctx);
};
// module.exports.update = (ctx) => {
// return request('UpdateImage', ctx);
// };
module.exports.destroy = uuid => {
return request('deleteImage', uuid);
};
// module.exports.xport = (uuid) => {
// return request('deleteImage', uuid);
// };

View File

@ -0,0 +1,16 @@
module.exports = {
account: require('./account'),
users: require('./users'),
policies: require('./policies'),
roles: require('./roles'),
keys: require('./keys'),
datacenters: require('./datacenters'),
services: require('./services'),
images: require('./images'),
packages: require('./packages'),
machines: require('./machines'),
firewallRules: require('./firewall-rules'),
// fabrics: require('./fabrics'),
networks: require('./networks'),
nics: require('./nics')
};

View File

@ -0,0 +1,35 @@
const request = require('./request');
module.exports = {
user: {
list: ctx => {
return request('listUserKeys', ctx);
},
get: ctx => {
return request('getUserKey', ctx);
},
create: ctx => {
return request('createUserKey', ctx);
},
destroy: ctx => {
return request('deleteUserKey', ctx);
}
},
account: {
list: () => {
return request('listKeys', {});
},
get: ctx => {
return request('getKey', ctx);
},
create: ctx => {
return request('createKey', ctx);
},
destroy: ctx => {
return request('deleteKey', ctx);
}
}
};

View File

@ -0,0 +1,108 @@
const request = require('./request');
const snapshots = {
list: ctx => {
return request('listMachineSnapshots', ctx);
},
get: ctx => {
return request('getMachineSnapshot', ctx);
},
create: ctx => {
return request('createMachineSnapshot', ctx);
},
destroy: ctx => {
return request('deleteMachineSnapshot', ctx);
}
};
const metadata = {
list: ctx => {
return request('', ctx);
},
get: ctx => {
return request('', ctx);
},
update: ctx => {
return request('', ctx);
},
destroy: ctx => {
return request('', ctx);
}
};
const firewall = {
enable: ctx => {
return request('enableMachineFirewall', ctx);
},
disable: ctx => {
return request('disableMachineFirewall', ctx);
}
};
const tags = {
list: ctx => {
return request('listMachineTags', ctx);
},
get: ctx => {
return request('getMachineTag', ctx);
},
add: ctx => {
return request('addMachineTags', ctx);
},
replace: ctx => {
return request('replaceMachineTags', ctx);
},
destroy: ctx => {
const method = ctx.tag ? 'deleteMachineTag' : 'deleteMachineTags';
return request(method, ctx);
}
};
module.exports.list = ctx => {
return request('listMachines', ctx);
};
module.exports.get = ctx => {
return request('getMachine', ctx);
};
module.exports.create = ctx => {
return request('createMachine', ctx);
};
module.exports.stop = ctx => {
return request('stopMachine', ctx);
};
module.exports.start = uuid => {
return request('startMachine', uuid);
};
module.exports.startFromSnapshot = ctx => {
return request('startMachineFromSnapshot', ctx);
};
module.exports.reboot = ctx => {
return request('rebootMachine', ctx);
};
module.exports.resize = ctx => {
return request('', ctx);
};
module.exports.rename = ctx => {
return request('', ctx);
};
module.exports.destroy = ctx => {
return request('deleteMachine', ctx);
};
module.exports.audit = ctx => {
return request('machineAudit', ctx);
};
module.exports.snapshots = snapshots;
module.exports.metadata = metadata;
module.exports.firewall = firewall;
module.exports.tags = tags;

View File

@ -0,0 +1,9 @@
const request = require('./request');
module.exports.list = () => {
return request('listNetworks');
};
module.exports.get = ctx => {
return request('getNetwork', ctx);
};

View File

@ -0,0 +1,9 @@
const request = require('./request');
module.exports.list = () => {
return request('listNics');
};
module.exports.get = ctx => {
return request('getNic', ctx);
};

View File

@ -0,0 +1,9 @@
const request = require('./request');
module.exports.list = ctx => {
return request('listPackages', ctx);
};
module.exports.get = ctx => {
return request('getPackage', ctx);
};

View File

@ -0,0 +1,21 @@
const request = require('./request');
module.exports.list = () => {
return request('listPolicies');
};
module.exports.get = ctx => {
return request('getPolicy', ctx);
};
module.exports.create = ctx => {
return request('createPolicy', ctx);
};
module.exports.update = ctx => {
return request('updatePolicy', ctx);
};
module.exports.destroy = ctx => {
return request('deletePolicy', ctx);
};

View File

@ -0,0 +1,40 @@
const credentials = require('../credentials');
const auth = require('smartdc-auth');
const cloudapi = require('triton/lib/cloudapi2');
const bunyan = require('bunyan');
const pkg = require('../../package.json');
var log = bunyan.createLogger({
name: pkg.name
});
var client = cloudapi.createClient({
log: log,
url: credentials.url,
account: credentials.account,
user: credentials.user,
sign: auth.cliSigner({
log: log,
keyId: credentials.keyId,
user: credentials.account,
subuser: credentials.user
})
});
module.exports = (method, args) => {
return new Promise((resolve, reject) => {
const fn = client[method].bind(client);
const cb = (err, res) => {
if (err) {
return reject(err);
}
resolve(res);
};
return args ? fn(args, cb) : fn(cb);
});
};
module.exports.client = client;

View File

@ -0,0 +1,31 @@
const request = require('./request');
module.exports.list = () => {
return request('listRoles');
};
module.exports.get = ctx => {
return request('getRole', ctx);
};
module.exports.create = ctx => {
return request('createRole', ctx);
};
module.exports.set = ctx => {
const id = ctx.id ? `/${ctx.id}` : '';
const resource = `/${request.client.account}/${ctx.resource}${id}`;
return request('setRoleTags', {
roleTags: ctx.role,
resource
});
};
module.exports.update = ctx => {
return request('updateRole', ctx);
};
module.exports.destroy = ctx => {
return request('deleteRole', ctx);
};

View File

@ -0,0 +1,5 @@
const request = require('./request');
module.exports = () => {
return request('listServices');
};

View File

@ -0,0 +1,21 @@
const request = require('./request');
module.exports.list = () => {
return request('listUsers');
};
module.exports.get = ctx => {
return request('getUser', ctx);
};
module.exports.create = ctx => {
return request('createUser', ctx);
};
module.exports.destroy = ctx => {
return request('deleteUser', ctx);
};
module.exports.update = ctx => {
return request('updateUser', ctx);
};

View File

@ -0,0 +1,27 @@
const json = (() => {
try {
const res = require('dotenv').config({
path: '../.env',
silent: true
});
if (res.error) {
throw res.error;
}
} catch (err) {
try {
return require('../credentials.json');
} catch (err) {
return {};
}
}
return {};
})();
module.exports = {
url: process.env.SDC_URL || json.SDC_URL || json.url || '',
account: process.env.SDC_ACCOUNT || json.SDC_ACCOUNT || json.account || '',
user: process.env.SDC_USER || json.SDC_USER || json.user || '',
keyId: process.env.SDC_KEY_ID || json.SDC_KEY_ID || json.keyId || ''
};

View File

@ -0,0 +1,8 @@
const graphqlHTTP = require('express-graphql');
const schema = require('./schema');
module.exports = graphqlHTTP(() => ({
schema: schema,
graphiql: true,
pretty: true
}));

View File

@ -0,0 +1,14 @@
const express = require('express');
const app = express();
app.use('/graphql', require('./endpoint'));
const server = app.listen(4000, err => {
if (err) {
console.error(err);
throw err;
}
console.log(`Listening at http://0.0.0.0:${server.address().port}/graphql`);
});

View File

@ -0,0 +1,10 @@
const graphql = require('graphql');
const mutation = require('./mutations');
const query = require('./queries');
const { GraphQLSchema } = graphql;
module.exports = new GraphQLSchema({
query,
mutation
});

View File

@ -0,0 +1,56 @@
const AccountType = require('../types/login');
const api = require('../../api');
const { GraphQLBoolean, GraphQLString } = require('graphql');
module.exports.updateAccount = {
type: AccountType,
description: 'Update your account details',
args: {
email: {
type: GraphQLString
},
company_name: {
type: GraphQLString
},
first_name: {
type: GraphQLString
},
last_name: {
type: GraphQLString
},
address: {
type: GraphQLString
},
postal_code: {
type: GraphQLString
},
city: {
type: GraphQLString
},
state: {
type: GraphQLString
},
country: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
cns_enabled: {
type: GraphQLBoolean
}
},
resolve: (root, args) => {
return api.account.get().then(account => {
return api.account.update(
Object.assign(account, args, {
firstName: args.first_name || account.firstName,
lastName: args.first_name || account.lastName,
companyName: args.company_name || account.companyName,
postalCode: args.postal_code || account.postalCode
})
);
});
}
};

View File

@ -0,0 +1,98 @@
const FirewallRuleType = require('../types/firewall-rule');
const api = require('../../api');
const { GraphQLID, GraphQLBoolean, GraphQLString } = require('graphql');
module.exports.createFirewallRule = {
type: FirewallRuleType,
description: "Adds a new firewall rule for the specified account. This rule will be added to all the account's instances where it may be necessary",
args: {
enabled: {
type: GraphQLBoolean,
description: 'Indicates if the rule is enabled (optional, false by default)'
},
rule: {
type: GraphQLString,
description: 'Firewall rule text'
},
description: {
type: GraphQLString,
description: 'Human-readable description for the rule (optional)'
}
},
resolve: (root, args) => {
return api.firewallRules.create({
rule: args.rule,
description: args.description,
enabled: !!args.enabled
});
}
};
module.exports.updateFirewallRule = {
type: FirewallRuleType,
description: 'Updates the given rule record and -- depending on rule contents -- adds/removes/updates the rule on all the required instances',
args: {
id: {
type: GraphQLID,
description: 'Firewall rule id'
},
enabled: {
type: GraphQLBoolean,
description: 'Indicates if the rule is enabled (optional, false by default)'
},
rule: {
type: GraphQLString,
description: 'Firewall rule text'
},
description: {
type: GraphQLString,
description: 'Human-readable description for the rule (optional)'
}
},
resolve: (root, args) => {
return api.firewallRules.update(args);
}
};
module.exports.enableFirewallRule = {
type: FirewallRuleType,
description: 'Enables the given firewall rule if it is disabled',
args: {
id: {
type: GraphQLID,
description: 'Firewall rule id'
}
},
resolve: (root, args) => {
return api.firewallRules.enable(args);
}
};
module.exports.disableFirewallRule = {
type: FirewallRuleType,
description: 'Disables the given firewall rule if it is enabled',
args: {
id: {
type: GraphQLID,
description: 'Firewall rule id'
}
},
resolve: (root, args) => {
return api.firewallRules.disable(args);
}
};
module.exports.deleteFirewallRule = {
type: FirewallRuleType,
description: 'Removes the given firewall rule from all the required instances',
args: {
id: {
type: GraphQLID,
description: 'Firewall rule id'
}
},
resolve: (root, args) => {
return api.firewallRules.destroy(args);
}
};

View File

@ -0,0 +1,54 @@
const AccountType = require('../types/login');
const DynamicObjectType = require('../types/dynamic-object');
const api = require('../../api');
const {
GraphQLString,
GraphQLList,
GraphQLNonNull,
GraphQLID
} = require('graphql');
module.exports.createImage = {
type: AccountType,
description: 'Create a new custom image from an instance',
args: {
machine: {
type: new GraphQLNonNull(GraphQLID),
description: 'The prepared and stopped instance UUID from which the image is to be created'
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'The name of the custom image, e.g. "my-image". Maximum 512 characters. However, typical names should be much shorter, e.g. 5-20 characters'
},
version: {
type: new GraphQLNonNull(GraphQLString),
description: 'The version of the custom image, e.g. "1.0.0". Maximum 128 characters'
},
description: {
type: GraphQLString,
description: 'A short prose description of this image. Maximum 512 characters'
},
homepage: {
type: GraphQLString,
description: 'Homepage URL where users can find more information about the image. Maximum 128 characters'
},
eula: {
type: GraphQLString,
description: 'URL of the End User License Agreement (EULA) for the image. Maximum 128 characters'
},
acl: {
type: new GraphQLList(GraphQLID),
description: 'An array of user/account UUIDs to which to give read access to a private image. I.e. this is only relevant for images with public === false'
},
tags: {
type: DynamicObjectType,
description: 'An object of key/value pairs that allows clients to categorize images by any given criteria'
}
},
resolve: (root, args) => {
const { create } = api.images;
return create(args);
}
};

View File

@ -0,0 +1,16 @@
const { GraphQLObjectType } = require('graphql');
module.exports = new GraphQLObjectType({
name: 'RootMutationType',
fields: Object.assign(
require('./account'),
require('./keys'),
require('./users'),
require('./roles'),
require('./policies'),
require('./machines'),
require('./images'),
require('./firewall-rules'),
require('./snapshots')
)
});

View File

@ -0,0 +1,49 @@
const KeyType = require('../types/key');
const api = require('../../api');
const { GraphQLNonNull, GraphQLString, GraphQLID } = require('graphql');
module.exports.createKey = {
type: KeyType,
description: 'Uploads a new OpenSSH key to Triton for use in HTTP signing and SSH',
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
key: {
type: new GraphQLNonNull(GraphQLString)
},
userId: {
type: GraphQLID,
description: 'UserId to add this key to. Leaving this in blank will add the key to the account'
}
},
resolve: (root, args) => {
const _api = args.userId ? api.keys.user : api.keys.account;
return _api.create(args);
}
};
module.exports.deleteKey = {
type: GraphQLID,
description: 'Deletes a single SSH key, by name or fingerprint',
args: {
name: {
type: GraphQLString
},
fingerprint: {
type: GraphQLString
},
userId: {
type: GraphQLID,
description: 'UserId who this key belongs to. Leaving this in blank will delete an account key'
}
},
resolve: (root, args) => {
const _api = args.userId ? api.keys.user : api.keys.account;
return _api.destroy(args).then(() => {
return args.name || args.fingerprint;
});
}
};

View File

@ -0,0 +1,320 @@
const MachineType = require('../types/machine');
const DynamicObjectType = require('../types/dynamic-object');
const api = require('../../api');
const {
GraphQLNonNull,
GraphQLString,
GraphQLBoolean,
GraphQLID,
GraphQLList
} = require('graphql');
module.exports.createMachine = {
type: MachineType,
description: 'Allows you to provision an instance',
args: {
name: {
type: GraphQLString,
description: 'Friendly name for this instance; default is the first 8 characters of the machine id'
},
package: {
type: new GraphQLNonNull(GraphQLString),
description: 'Id of the package to use on provisioning, obtained from ListPackages'
},
image: {
type: new GraphQLNonNull(GraphQLString),
description: 'The image UUID (from images { id })'
},
networks: {
type: new GraphQLList(GraphQLString),
description: 'Desired networks ids (from networks { id })'
},
locality: {
type: MachineType.locality,
description: 'Optionally specify which instances the new instance should be near or far from'
},
metadata: {
type: DynamicObjectType,
description: 'An arbitrary set of metadata key/value pairs can be set at provision time'
},
tags: {
type: DynamicObjectType,
description: 'An arbitrary set of tags can be set at provision time'
},
firewall_enabled: {
type: GraphQLBoolean,
description: 'Completely enable or disable firewall for this instance. Default is false'
}
},
resolve: (root, args) => {
const resolveNames = (obj = {}, namespace) => {
return Object.keys(obj).reduce((all, name) => {
return Object.assign(all, {
[`${namespace}.${name}`]: obj[name]
});
}, {});
};
const tags = resolveNames(args.tags, 'tag');
const metadata = resolveNames(args.tags, 'metadata');
const machine = Object.assign(
{
name: args.name,
package: args['package'],
image: args.image,
networks: args.networks,
locality: args.locality,
firewall_enabled: args.firewall_enabled
},
tags,
metadata
);
return api.machines.create(machine);
}
};
module.exports.startMachine = {
type: MachineType,
description: 'Allows you to boot up an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
return api.machines.start(args.id).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.startMachineFromSnapshot = {
type: MachineType,
description: 'If an instance is in the "stopped" state, you can choose to start the instance from the referenced snapshot',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
name: {
type: new GraphQLNonNull(GraphQLID),
description: 'The snapshot id'
}
},
resolve: (root, args) => {
return api.machines.startFromSnapshot(args).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.stopMachine = {
type: MachineType,
description: 'Allows you to shut down an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
return api.machines.stop(args.id).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.rebootMachine = {
type: MachineType,
description: 'Allows you to reboot an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
return api.machines.reboot(args.id).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.deleteMachine = {
type: DynamicObjectType,
description: 'Allows you to completely destroy an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
return api.machines.destroy(args.id);
}
};
module.exports.auditMachine = {
type: new GraphQLList(DynamicObjectType),
description: "Provides a list of an instance's accomplished actions. Results are sorted from newest to oldest action",
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
return api.machines.destroy(args.id);
}
};
module.exports.setMachineFirewall = {
type: MachineType,
description: 'Allows you to set the firewall state for an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
enabled: {
type: new GraphQLNonNull(GraphQLBoolean)
}
},
resolve: (root, args) => {
const { firewall } = api.machines;
const fn = args.enabled ? firewall.enable : firewall.disable;
return fn(args.id).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.enableMachineFirewall = {
type: MachineType,
description: 'Allows you to enable the firewall for an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
const { firewall } = api.machines;
return firewall.enable(args.id).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.disableMachineFirewall = {
type: MachineType,
description: 'Allows you to completely disable the firewall of an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
}
},
resolve: (root, args) => {
const { firewall } = api.machines;
return firewall.disable(args.id).then(machine => {
if (machine) {
return machine;
}
return api.machines.get(args);
});
}
};
module.exports.addMachineTags = {
type: DynamicObjectType,
description: 'Set tags on the given instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
tags: {
type: new GraphQLNonNull(DynamicObjectType),
description: 'Tag name/value pairs'
}
},
resolve: (root, args) => {
const { tags } = api.machines;
return tags.add(args);
}
};
module.exports.replaceMachineTags = {
type: DynamicObjectType,
description: 'Fully replace all tags on an instance with the given tags',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
tags: {
type: new GraphQLNonNull(DynamicObjectType),
description: 'Tag name/value pairs'
}
},
resolve: (root, args) => {
const { tags } = api.machines;
return tags.replace(args);
}
};
module.exports.deleteMachineTags = {
type: DynamicObjectType,
description: 'Deletes tags from an instance',
args: {
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
tag: {
type: GraphQLString,
description: 'Tag name to remove. If value is not supplied, all machine tags are removed'
}
},
resolve: (root, args) => {
const { tags } = api.machines;
return tags.destroy(args);
}
};

View File

@ -0,0 +1,71 @@
const PolicyType = require('../types/policy');
const api = require('../../api');
const {
GraphQLNonNull,
GraphQLString,
GraphQLID,
GraphQLList
} = require('graphql');
module.exports.createPolicy = {
type: PolicyType,
description: 'Creates a new account policy',
args: {
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'The policy name'
},
rules: {
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
description: 'One or more Aperture sentences to be added to the current policy'
},
description: {
type: GraphQLString,
description: 'A description for this policy (Optional)'
}
},
resolve: (root, args) => {
return api.policies.create(args);
}
};
module.exports.updatePolicy = {
type: PolicyType,
description: 'Upgrades an existing account policy. Everything but id can be modified',
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'The policy name'
},
rules: {
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
description: 'One or more Aperture sentences to be added to the current policy'
},
description: {
type: GraphQLString,
description: 'A description for this policy (Optional)'
}
},
resolve: (root, args) => {
return api.policies.update(args);
}
};
module.exports.deletePolicy = {
type: GraphQLID,
description: 'Delete an RBAC policy',
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolve: (root, args) => {
return api.policies.destroy(args).then(() => {
return args.id;
});
}
};

View File

@ -0,0 +1,103 @@
const RoleType = require('../types/role');
const api = require('../../api');
const {
GraphQLNonNull,
GraphQLString,
GraphQLID,
GraphQLList
} = require('graphql');
module.exports.createRole = {
type: RoleType,
description: 'Create a new role for your account',
args: {
name: {
type: new GraphQLNonNull(GraphQLString),
description: "The role's name"
},
policies: {
type: new GraphQLList(GraphQLString),
description: "This account's policies to be given to this role (Optional)"
},
members: {
type: new GraphQLList(GraphQLString),
description: "This account's user logins to be added to this role (Optional)"
},
default_members: {
type: new GraphQLList(GraphQLString),
description: "This account's user logins to be added to this role and have it enabled by default (Optional)"
}
},
resolve: (root, args) => {
return api.roles.create(args);
}
};
module.exports.updateRole = {
type: RoleType,
description: 'Modifies an account role. Anything but id can be modified',
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
},
name: {
type: GraphQLString,
description: "The role's name"
},
policies: {
type: new GraphQLList(GraphQLString),
description: "This account's policies to be given to this role (Optional)"
},
members: {
type: new GraphQLList(GraphQLString),
description: "This account's user logins to be added to this role (Optional)"
},
default_members: {
type: new GraphQLList(GraphQLString),
description: "This account's user logins to be added to this role and have it enabled by default (Optional)"
}
},
resolve: (root, args) => {
return api.roles.update(args);
}
};
module.exports.deleteRole = {
type: GraphQLID,
description: 'Remove a role',
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolve: (root, args) => {
return api.roles.destroy(args).then(() => {
return args.id;
});
}
};
module.exports.setRoleTags = {
type: RoleType.tag,
description: "Sets the given role tags to the provided resource path. resource_path can be the path to any of the CloudAPI resources described in this document: account, keys, users, roles, policies, user's ssh keys, datacenters, images, packages, instances, analytics, instrumentations, firewall rules and networks.",
args: {
resource: {
type: new GraphQLNonNull(GraphQLString),
description: 'The resource type e.g. `machines`, `policies`...'
},
id: {
type: GraphQLID,
description: 'The resource id'
},
role: {
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
description: 'The list role-tags to be added to this resource'
}
},
resolve: (root, args) => {
const { set } = api.roles;
return set(args);
}
};

View File

@ -0,0 +1,60 @@
const SnapshotType = require('../types/snapshot');
const api = require('../../api');
const { GraphQLNonNull, GraphQLString, GraphQLID } = require('graphql');
module.exports.createSnapshot = {
type: SnapshotType,
description: 'Allows you to take a snapshot of a machine instance',
args: {
machine: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
name: {
type: GraphQLString,
description: 'The name to assign to the new snapshot'
}
},
resolve: (root, args) => {
const { snapshot: { create, get } } = api.machines;
const newArgs = {
id: args.machine,
name: args.name
};
return create(newArgs).then(snapshot => {
if (snapshot) {
return snapshot;
}
return get(newArgs);
});
}
};
module.exports.deleteSnapshot = {
type: GraphQLID,
description: 'Deletes the specified snapshot of an instance',
args: {
machine: {
type: new GraphQLNonNull(GraphQLID),
description: 'The machine id'
},
name: {
type: GraphQLString,
description: 'The name to assign to the new snapshot'
}
},
resolve: (root, args) => {
const { snapshot: { destroy } } = api.machines;
const newArgs = {
id: args.machine,
name: args.name
};
return destroy(newArgs).then(() => args.name);
}
};

View File

@ -0,0 +1,144 @@
const UserType = require('../types/login');
const api = require('../../api');
const { GraphQLNonNull, GraphQLString, GraphQLID } = require('graphql');
module.exports.createUser = {
type: UserType,
description: 'Creates a new user under an account',
args: {
login: {
type: new GraphQLNonNull(GraphQLString)
},
email: {
type: new GraphQLNonNull(GraphQLString)
},
password: {
type: new GraphQLNonNull(GraphQLString)
},
company_name: {
type: GraphQLString
},
first_name: {
type: GraphQLString
},
last_name: {
type: GraphQLString
},
address: {
type: GraphQLString
},
postal_code: {
type: GraphQLString
},
city: {
type: GraphQLString
},
state: {
type: GraphQLString
},
country: {
type: GraphQLString
},
phone: {
type: GraphQLString
}
},
resolve: (root, args) => {
return api.users.create(
Object.assign(args, {
firstName: args.first_name,
lastName: args.first_name,
companyName: args.company_name,
postalCode: args.postal_code
})
);
}
};
module.exports.deleteUser = {
type: GraphQLID,
description: 'Remove a user',
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolve: (root, args) => {
return api.users.destroy(args).then(() => {
return args.id;
});
}
};
module.exports.updateUser = {
type: UserType,
description: "Update a user's modifiable properties",
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
},
login: {
type: GraphQLString
},
email: {
type: GraphQLString
},
company_name: {
type: GraphQLString
},
first_name: {
type: GraphQLString
},
last_name: {
type: GraphQLString
},
address: {
type: GraphQLString
},
postal_code: {
type: GraphQLString
},
city: {
type: GraphQLString
},
state: {
type: GraphQLString
},
country: {
type: GraphQLString
},
phone: {
type: GraphQLString
}
},
resolve: (root, args) => {
return api.users.update(
Object.assign(args, {
firstName: args.first_name,
lastName: args.first_name,
companyName: args.company_name,
postalCode: args.postal_code
})
);
}
};
// module.exports.changeUserPassword = {
// type: UserType,
// description: 'This is a separate rule for password changes, so different policies can be used for an user trying to modify other data, or only their own password',
// args: {
// id: {
// type: new GraphQLNonNull(GraphQLID)
// },
// password: {
// type: GraphQLString
// },
// password_confirmation: {
// type: GraphQLString
// }
// },
// resolve: (root, args) => {
// return api.users.updatePassword(args);
// }
// };

View File

@ -0,0 +1,13 @@
const AccountType = require('../types/login');
const api = require('../../api');
module.exports = {
type: AccountType,
resolve() {
return api.account.get().then(account => {
return Object.assign(account, {
isUser: false
});
});
}
};

View File

@ -0,0 +1,19 @@
const DatacenterType = require('../types/datacenter');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList } = graphql;
module.exports = {
type: new GraphQLList(DatacenterType),
resolve() {
return api.datacenters().then(datacenters => {
return Object.keys(datacenters).map(name => {
return {
url: datacenters[name],
name
};
});
});
}
};

View File

@ -0,0 +1,12 @@
const FabricType = require('../types/fabrics');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList } = graphql;
module.exports = {
type: new GraphQLList(FabricType),
resolve() {
return api.fabrics.list();
}
};

View File

@ -0,0 +1,20 @@
const FirewallRuleType = require('../types/firewall-rule');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(FirewallRuleType),
args: {
id: {
type: GraphQLID,
description: 'Filter on id'
}
},
resolve(root, args) {
const { list, get } = api.firewallRules;
return !args.id ? list() : get(args.id).then(rule => [rule]);
}
};

View File

@ -0,0 +1,52 @@
const ImageType = require('../types/image');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLBoolean, GraphQLString, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(ImageType),
args: {
id: {
type: GraphQLID,
description: 'Filter on id'
},
name: {
type: GraphQLString,
description: 'Filter on "friendly" name'
},
os: {
type: GraphQLString,
description: 'Filter on the underlying operating system'
},
version: {
type: GraphQLString,
description: 'Filter on the version'
},
public: {
type: GraphQLBoolean,
description: 'Filter public/private images'
},
state: {
type: GraphQLString,
description: 'Filter on image state. By default only active images are shown. Use "all" to list all images'
},
owner: {
type: GraphQLString,
description: 'Filter on owner UUID'
},
type: {
type: GraphQLString,
description: 'Filter on image type'
}
},
resolve(root, args) {
const { list, get } = api.images;
return args.id
? get({
id: args.id
}).then(img => [img])
: list(args);
}
};

View File

@ -0,0 +1,20 @@
const { GraphQLObjectType } = require('graphql');
module.exports = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
account: require('./account'),
users: require('./users'),
policies: require('./policies'),
roles: require('./roles'),
datacenters: require('./datacenters'),
services: require('./services'),
images: require('./images'),
packages: require('./packages'),
machines: require('./machines'),
firewallRules: require('./firewall-rules'),
// fabrics: require('./fabrics')
networks: require('./networks')
// nics: require('./nics')
}
});

View File

@ -0,0 +1,74 @@
const MachineType = require('../types/machine');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLInt, GraphQLList, GraphQLString, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(MachineType),
args: {
id: {
type: GraphQLID
},
brand: {
type: GraphQLString,
description: 'Filter on the type of instance (e.g. lx)'
},
name: {
type: GraphQLString,
description: 'Machine name to find (will make your list size 1, or 0 if nothing found)'
},
image: {
type: GraphQLString,
description: 'Image id; returns instances provisioned with that image'
},
state: {
type: GraphQLString,
description: 'Filter on the current state (e.g. running)'
},
memory: {
type: GraphQLInt,
description: 'Filter on the current size of the RAM deployed (in MiB)'
},
tombstone: {
type: GraphQLInt,
description: 'Filter on instances destroyed in the last N minutes'
},
first: {
type: GraphQLInt,
description: 'Return a max of N instances; default is 1000 (which is also the maximum allowable result set size)'
},
after: {
type: GraphQLInt,
description: 'Get a `first` number of instances starting at this offset'
},
tags: {
type: new GraphQLList(GraphQLString),
description: 'Filter on existing tags'
},
docker: {
type: GraphQLString,
description: 'Whether to only list Docker instances, or only non-Docker instances, if present. Defaults to showing all instances.'
},
credentials: {
type: GraphQLString,
description: 'Whether to include the generated credentials for instances, if present. Defaults to false'
}
},
resolve(root, args) {
const { list, get } = api.machines;
const { after, first } = args;
const newArgs = Object.assign(args, {
limit: first,
offset: after
});
return args.id
? get({
id: args.id
}).then(machine => [machine])
: list(newArgs);
}
};

View File

@ -0,0 +1,19 @@
const NetworkType = require('../types/network');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(NetworkType),
args: {
id: {
type: GraphQLID
}
},
resolve(root, args) {
const { list, get } = api.networks;
return !args.id ? list() : get(args).then(network => [network]);
}
};

View File

@ -0,0 +1,19 @@
const NicType = require('../types/nic');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLString } = graphql;
module.exports = {
type: new GraphQLList(NicType),
args: {
mac: {
type: GraphQLString
}
},
resolve(root, args) {
const { list, get } = api.nics;
return !args.id ? list() : get(args).then(nic => [nic]);
}
};

View File

@ -0,0 +1,56 @@
const PackageType = require('../types/package');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLInt, GraphQLList, GraphQLString, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(PackageType),
args: {
id: {
type: GraphQLID,
description: 'Filter on package id'
},
name: {
type: GraphQLString,
description: 'Filter on the "friendly" name'
},
memory: {
type: GraphQLInt,
description: 'Filter on how much memory will by available (in MiB)'
},
disk: {
type: GraphQLInt,
description: 'Filter on how much disk space will be available (in MiB)'
},
swap: {
type: GraphQLInt,
description: 'Filter on how much swap space will be available (in MiB)'
},
lwps: {
type: GraphQLInt,
description: 'Filter on maximum number of light-weight processes (threads) allowed'
},
vcpus: {
type: GraphQLInt,
description: 'Filter on number of vCPUs'
},
version: {
type: GraphQLString,
description: 'Filter on the version'
},
group: {
type: GraphQLString,
description: 'Filter on the group belonging to'
}
},
resolve(root, args) {
const { list, get } = api.packages;
return args.id
? get({
id: args.id
}).then(pkg => [pkg])
: list(args);
}
};

View File

@ -0,0 +1,20 @@
const PolicyType = require('../types/policy');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(PolicyType),
args: {
id: {
type: GraphQLID,
description: '`id` of the `PolicyType` to filter'
}
},
resolve(root, args) {
const { list, get } = api.policies;
return !args.id ? list() : get(args).then(policy => [policy]);
}
};

View File

@ -0,0 +1,20 @@
const RoleType = require('../types/role');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(RoleType),
args: {
id: {
type: GraphQLID,
description: '`id` or `name` of the `RoleType` to filter'
}
},
resolve(root, args) {
const { list, get } = api.roles;
return !args.id ? list() : get(args).then(role => [role]);
}
};

View File

@ -0,0 +1,19 @@
const ServiceType = require('../types/service');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList } = graphql;
module.exports = {
type: new GraphQLList(ServiceType),
resolve() {
return api.services().then(services => {
return Object.keys(services).map(name => {
return {
url: services[name],
name
};
});
});
}
};

View File

@ -0,0 +1,26 @@
const UserType = require('../types/login');
const graphql = require('graphql');
const api = require('../../api');
const { GraphQLList, GraphQLID } = graphql;
module.exports = {
type: new GraphQLList(UserType),
args: {
id: {
type: GraphQLID,
description: '`id` or `login` of the `UserType` to filter'
}
},
resolve(root, args) {
const { list, get } = api.users;
return !args.id
? list()
: get(args).then(user => [user]).then(user => {
return Object.assign(user, {
isUser: true
});
});
}
};

View File

@ -0,0 +1,54 @@
const DynamicObjectType = require('./dynamic-object');
const { GraphQLString, GraphQLObjectType, GraphQLBoolean } = require('graphql');
const CallerType = new GraphQLObjectType({
name: 'CallerType',
fields: {
type: {
type: GraphQLString,
description: 'Authentication type for the action request. One of "basic", "operator", "signature" or "token"'
},
user: {
type: GraphQLString,
description: 'When the authentication type is "basic", this member will be present and include user login'
},
ip: {
type: GraphQLString,
description: 'The IP addresses this from which the action was requested. Not present if type is "operator"'
},
keyId: {
type: GraphQLString,
description: 'When authentication type is either "signature" or "token", SSH key identifier'
}
}
});
module.exports = new GraphQLObjectType({
name: 'AuditType',
fields: {
action: {
type: GraphQLString,
description: 'The name of the action'
},
parameters: {
type: DynamicObjectType,
description: 'The original set of parameters sent when the action was requested'
},
success: {
type: GraphQLBoolean,
description: "`true` or `false`, depending on the action's success",
resolve: root => {
return root.success === 'yes';
}
},
caller: {
type: CallerType,
description: 'Account requesting the action'
},
time: {
type: GraphQLString,
description: 'When the action finished'
}
}
});

View File

@ -0,0 +1,14 @@
const { GraphQLString, GraphQLObjectType } = require('graphql');
module.exports = new GraphQLObjectType({
name: 'DatacenterType',
fields: {
name: {
type: GraphQLString,
description: 'location of the datacenter'
},
url: {
type: GraphQLString
}
}
});

View File

@ -0,0 +1,41 @@
const { GraphQLScalarType, Kind } = require('graphql');
const kinds = {
[Kind.STRING]: ast => {
return ast.value;
},
[Kind.BOOLEAN]: ast => {
return kinds[Kind.STRING](ast);
},
[Kind.INT]: ast => {
return Number(ast.value);
},
[Kind.FLOAT]: ast => {
return kinds[Kind.INT](ast);
},
[Kind.OBJECT]: ast => {
const value = Object.create(null);
ast.fields.forEach(field => {
value[field.name.value] = parseLiteral(field.value);
});
return value;
},
[Kind.LIST]: ast => {
return ast.values.map(parseLiteral);
}
};
// https://github.com/taion/graphql-type-json/blob/master/src/index.js
const parseLiteral = ast => {
const kind = kinds[ast.kind];
return kind ? kinds[ast.kind](ast) : null;
};
// from http://stackoverflow.com/a/34229603
module.exports = new GraphQLScalarType({
name: 'DynamicObjectType',
serialize: v => v,
parseValue: v => v,
parseLiteral: parseLiteral
});

View File

@ -0,0 +1,19 @@
const { GraphQLString, GraphQLObjectType, GraphQLInt } = require('graphql');
module.exports = new GraphQLObjectType({
name: 'FabricsType',
fields: {
name: {
type: GraphQLString,
description: 'A unique name to identify the VLAN'
},
vlan_id: {
type: GraphQLInt,
description: "A number from 0-4095 that indicates the VLAN's id"
},
description: {
type: GraphQLString,
description: 'An optional description of the VLAN'
}
}
});

View File

@ -0,0 +1,90 @@
const api = require('../../api');
const {
GraphQLString,
GraphQLBoolean,
GraphQLObjectType,
GraphQLList,
GraphQLID,
GraphQLInt
} = require('graphql');
const FirewallRuleSyntaxType = new GraphQLObjectType({
name: 'FirewallRuleSyntaxType',
fields: {
text: {
type: GraphQLString
},
from: {
type: GraphQLString
},
to: {
type: GraphQLString
},
action: {
type: GraphQLString
},
protocol: {
type: GraphQLString
},
port: {
type: GraphQLInt
}
}
});
module.exports = new GraphQLObjectType({
name: 'FirewallRuleType',
// function to allow circular dependencies
fields: () => ({
id: {
type: GraphQLID,
description: 'Unique identifier for this rule'
},
enabled: {
type: GraphQLBoolean,
description: 'Indicates if the rule is enabled',
resolve: root => {
return !!root.enabled;
}
},
rule: {
type: FirewallRuleSyntaxType,
description: 'Firewall rule',
resolve: ({ rule }) => {
const regex = /from (.*?) to (.*?) (allow|deny) (.*?) port (\d*)/i;
const tokens = rule.match(regex);
return {
from: tokens[1],
to: tokens[2],
action: tokens[3],
protocol: tokens[4],
port: tokens[5],
text: rule
};
}
},
global: {
type: GraphQLBoolean,
description: 'Indicates if the rule is global',
resolve: root => {
return !!root.global;
}
},
description: {
type: GraphQLString,
description: 'Human-readable description for the rule'
},
machines: {
// circular dependency
type: new GraphQLList(require('./machine')),
description: 'Lists all instances a firewall rule is applied to',
resolve: root => {
return api.firewallRules.listMachines({
id: root.id
});
}
}
})
});

View File

@ -0,0 +1,116 @@
const DynamicObjectType = require('./dynamic-object');
const {
GraphQLBoolean,
GraphQLString,
GraphQLObjectType,
GraphQLInt,
GraphQLList,
GraphQLID
} = require('graphql');
const ErrorType = new GraphQLObjectType({
name: 'ErrorType',
fields: {
code: {
type: GraphQLString,
description: 'A CamelCase string code for this error, e.g. "PrepareImageDidNotRun". See GetImage docs for a table of error.code values'
},
message: {
type: GraphQLString,
description: 'A short description of the image creation failure'
}
}
});
const ImageFileType = new GraphQLObjectType({
name: 'ImageFileType',
fields: {
compression: {
type: GraphQLString,
description: 'The type of file compression used for the image file. One of "bzip2", "gzip", "none"'
},
sha1: {
type: GraphQLString,
description: 'SHA-1 hex digest of the file content. Used for corruption checking'
},
size: {
type: GraphQLInt,
description: 'File size in bytes'
}
}
});
module.exports = new GraphQLObjectType({
name: 'ImageType',
description: 'An image contains the software packages that will be available on newly-provisioned instance. In the case of hardware virtual machines, the image also includes the operating system',
fields: {
id: {
type: GraphQLID,
description: 'Unique id for this image'
},
name: {
type: GraphQLString,
description: 'The "friendly" name for this image'
},
os: {
type: GraphQLString,
description: 'The underlying operating system for this image'
},
version: {
type: GraphQLString,
description: 'The version for this image'
},
type: {
type: GraphQLString,
description: 'What kind of image this is. The values differ after v8.0.0+'
},
requirements: {
type: DynamicObjectType,
description: 'Contains a grouping of various minimum requirements for provisioning an instance with this image. For example "password" indicates that a password must be provided'
},
homepage: {
type: GraphQLString,
description: 'The URL for a web page with more detailed information for this image'
},
files: {
type: new GraphQLList(ImageFileType),
description: 'An array of image files that make up each image. Currently only a single file per image is supported'
},
published_at: {
type: GraphQLString,
description: 'The time this image has been made publicly available'
},
owner: {
type: GraphQLString,
description: 'The UUID of the user who owns this image'
},
public: {
type: GraphQLBoolean,
description: 'Indicates if this image is publicly available',
resolve: root => {
return !!root['public'];
}
},
state: {
type: GraphQLString,
description: 'The current state of the image. One of "active", "unactivated", "disabled", "creating", "failed"'
},
tags: {
type: DynamicObjectType,
description: 'An object of key/value pairs that allows clients to categorize images by any given criteria'
},
eula: {
type: GraphQLString,
description: 'URL of the End User License Agreement (EULA) for the image'
},
acl: {
type: new GraphQLList(GraphQLString),
description: 'Access Control List. An array of account UUIDs given access to a private image. The field is only relevant to private images'
},
error: {
type: ErrorType,
description: 'If state=="failed", resulting from CreateImageFromMachine failure, then there may be an error object of the form {"code": "<string error code>", "message": "<string desc>"}'
}
}
});

View File

@ -0,0 +1,16 @@
const { GraphQLString, GraphQLObjectType } = require('graphql');
module.exports = new GraphQLObjectType({
name: 'KeyType',
fields: {
name: {
type: GraphQLString
},
fingerprint: {
type: GraphQLString
},
key: {
type: GraphQLString
}
}
});

View File

@ -0,0 +1,108 @@
const KeyType = require('./key');
const api = require('../../api');
const {
GraphQLBoolean,
GraphQLString,
GraphQLList,
GraphQLObjectType,
GraphQLID
} = require('graphql');
module.exports = new GraphQLObjectType({
name: 'LoginType',
fields: {
id: {
type: GraphQLID,
description: 'Unique id for this user/account'
},
login: {
type: GraphQLString,
description: 'Account/Sub-user login name'
},
email: {
type: GraphQLString,
description: 'Email address'
},
company_name: {
type: GraphQLString,
resolve: root => {
return !!root.company_name || root.companyName;
}
},
first_name: {
type: GraphQLString,
resolve: root => {
return !!root.first_name || root.firstName;
}
},
last_name: {
type: GraphQLString,
resolve: root => {
return !!root.last_name || root.lastName;
}
},
address: {
type: GraphQLString
},
postal_code: {
type: GraphQLString,
resolve: root => {
return !!root.postal_code || root.postalCode;
}
},
city: {
type: GraphQLString
},
state: {
type: GraphQLString
},
country: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
cns_enabled: {
type: GraphQLBoolean,
description: 'true if Triton CNS is enabled for account',
resolve: root => {
return root.isUser ? null : !!root.triton_cns_enabled;
}
},
keys: {
type: new GraphQLList(KeyType),
description: 'Get keys for user/account',
args: {
name: {
type: GraphQLString,
description: 'Filter on key name'
},
fingerprint: {
type: GraphQLString,
description: 'Filter on key fingerprint'
}
},
resolve(root, args) {
const _api = root.isUser ? api.keys.user : api.keys.account;
const { list, get } = _api;
const newArgs = Object.assign(args, {
userId: root.id
});
const filtered = args.name || args.fingerprint;
return !filtered ? list(newArgs) : get(newArgs).then(key => [key]);
}
},
updated: {
type: GraphQLString,
description: "When this user/account's details was last updated"
},
created: {
type: GraphQLString,
description: 'When this user/account was created'
}
}
});

View File

@ -0,0 +1,162 @@
const DynamicObjectType = require('./dynamic-object');
const SnapshotType = require('./snapshot');
const api = require('../../api');
const {
GraphQLBoolean,
GraphQLString,
GraphQLInputObjectType,
GraphQLObjectType,
GraphQLInt,
GraphQLList,
GraphQLID
} = require('graphql');
module.exports = new GraphQLObjectType({
name: 'MachineType',
description: 'An image contains the software packages that will be available on newly-provisioned instance. In the case of hardware virtual machines, the image also includes the operating system',
// function to allow circular dependencies
fields: () => ({
id: {
type: GraphQLID,
description: 'Unique id for this instance'
},
name: {
type: GraphQLString,
description: 'The "friendly" name for this instance'
},
brand: {
type: GraphQLString,
description: 'The type of instance (e.g. lx)'
},
state: {
type: GraphQLString,
description: 'The current state of this instance (e.g. running)'
},
image: {
type: GraphQLString,
description: 'The image id this instance was provisioned with'
},
memory: {
type: GraphQLInt,
description: 'The amount of RAM this instance has (in MiB)'
},
disk: {
type: GraphQLInt,
description: 'The amount of disk this instance has (in MiB)'
},
metadata: {
type: DynamicObjectType,
description: 'Any additional metadata this instance has'
},
tags: {
type: DynamicObjectType,
description: 'Any tags this instance has',
args: {
name: {
type: GraphQLString,
description: 'Filter on the name of the tag'
}
},
resolve: (root, args) => {
const { tags: { get } } = api.machines;
return !args.name
? root.tags
: get({
id: root.id,
tag: args.name
}).then(value => {
return {
[args.name]: value
};
});
}
},
created: {
type: GraphQLString,
description: 'When this instance was created'
},
updated: {
type: GraphQLString,
description: "When this instance's details was last updated"
},
docker: {
type: GraphQLBoolean,
description: 'Whether this instance is a Docker container, if present',
resolve: root => {
return !!root.docker;
}
},
ips: {
type: new GraphQLList(GraphQLString),
description: 'The IP addresses this instance has'
},
networks: {
type: new GraphQLList(GraphQLString),
description: 'The network UUIDs of the nics this instance has'
},
primaryIp: {
type: GraphQLString,
description: 'IP address of the primary nic of this instance'
},
firewall_enabled: {
type: GraphQLBoolean,
description: 'Whether firewall rules are enforced on this instance',
resolve: root => {
return !!root.firewall_enabled;
}
},
firewall_rules: {
// circular dependency
type: new GraphQLList(require('./firewall-rule')),
description: 'List of FirewallRules affecting this machine',
resolve: root => {
return api.firewallRules.listByMachine(root.id);
}
},
compute_node: {
type: GraphQLString,
description: 'UUID of the server on which the instance is located'
},
package: {
type: GraphQLString,
description: 'The id or name of the package used to create this instance'
},
snapshots: {
type: new GraphQLList(SnapshotType),
description: 'The snapshots based on this instance',
args: {
name: {
type: GraphQLString,
description: 'Filter on the name of the snapshot'
}
},
resolve: (root, args) => {
const { snapshot: { list, get } } = api.machines;
return !args.id
? list(root)
: get({
id: root.id,
name: args.name
});
}
}
})
});
module.exports.locality = new GraphQLInputObjectType({
name: 'LocalityType',
fields: {
strict: {
type: GraphQLBoolean
},
near: {
type: new GraphQLList(GraphQLID)
},
far: {
type: new GraphQLList(GraphQLID)
}
}
});

Some files were not shown because too many files have changed in this diff Show More