├── .gitignore ├── rudder ├── third-party └── swagger │ ├── images │ ├── expand.gif │ ├── favicon.ico │ ├── collapse.gif │ ├── throbber.gif │ ├── logo_small.png │ ├── wordnik_api.png │ ├── explorer_icons.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── pet_store_api.png │ ├── fonts │ ├── DroidSans.ttf │ └── DroidSans-Bold.ttf │ ├── lib │ ├── jquery.slideto.min.js │ ├── jquery.wiggle.min.js │ ├── jquery.ba-bbq.min.js │ ├── highlight.7.3.pack.js │ ├── swagger-oauth.js │ ├── underscore-min.js │ ├── backbone-min.js │ └── underscore-min.map │ ├── css │ ├── typography.css │ ├── reset.css │ └── style.css │ ├── o2c.html │ ├── lang │ ├── translator.js │ ├── zh-cn.js │ ├── ja.js │ ├── tr.js │ ├── pl.js │ ├── pt.js │ ├── en.js │ ├── ru.js │ ├── es.js │ ├── fr.js │ └── it.js │ └── index.html ├── internal ├── util │ ├── str-util.go │ ├── http-util.go │ ├── time-util.go │ ├── encoding-util.go │ └── file-util.go ├── resource │ ├── common.go │ ├── repo.go │ └── release.go ├── filter │ └── debug-filter.go ├── client │ └── tiller-client.go └── controller │ ├── release.go │ └── repo.go ├── Dockerfile ├── Makefile ├── glide.yaml ├── kube └── manifest.yaml ├── glide.lock ├── README.md ├── cmd └── rudder.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /build 3 | -------------------------------------------------------------------------------- /rudder: -------------------------------------------------------------------------------- 1 | /Users/peterbroadhurst/dev/photic/rudder -------------------------------------------------------------------------------- /third-party/swagger/images/expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/expand.gif -------------------------------------------------------------------------------- /third-party/swagger/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/favicon.ico -------------------------------------------------------------------------------- /third-party/swagger/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /third-party/swagger/images/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/collapse.gif -------------------------------------------------------------------------------- /third-party/swagger/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/throbber.gif -------------------------------------------------------------------------------- /third-party/swagger/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/logo_small.png -------------------------------------------------------------------------------- /third-party/swagger/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/wordnik_api.png -------------------------------------------------------------------------------- /third-party/swagger/fonts/DroidSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/fonts/DroidSans-Bold.ttf -------------------------------------------------------------------------------- /third-party/swagger/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/explorer_icons.png -------------------------------------------------------------------------------- /third-party/swagger/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/favicon-16x16.png -------------------------------------------------------------------------------- /third-party/swagger/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/favicon-32x32.png -------------------------------------------------------------------------------- /third-party/swagger/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcalephStorage/rudder/HEAD/third-party/swagger/images/pet_store_api.png -------------------------------------------------------------------------------- /internal/util/str-util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // ToInt32 is a convenience function to convert a string to int32 8 | func ToInt32(in string) (out int32) { 9 | val, _ := strconv.ParseInt(in, 10, 32) 10 | return int32(val) 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | MAINTAINER admin@acale.ph 3 | 4 | RUN apk add --update ca-certificates 5 | 6 | ADD build/bin/rudder /usr/local/bin/rudder 7 | ADD third-party/swagger /opt/rudder/swagger 8 | 9 | VOLUME /opt/rudder/cache 10 | 11 | EXPOSE 5000 12 | ENTRYPOINT ["/usr/local/bin/rudder"] 13 | -------------------------------------------------------------------------------- /internal/util/http-util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | ) 7 | 8 | // HTTPGet is a convenience method for quicking GETting an HTTP resource to a []byte 9 | func HTTPGet(url string) (out []byte, err error) { 10 | res, err := http.Get(url) 11 | if err == nil { 12 | defer res.Body.Close() 13 | out, err = ioutil.ReadAll(res.Body) 14 | } 15 | return 16 | } 17 | -------------------------------------------------------------------------------- /third-party/swagger/lib/jquery.slideto.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery); 2 | -------------------------------------------------------------------------------- /internal/util/time-util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // IsOutdated returns true if the timestamp is less than the timestamp + lifetime 8 | func IsOutdated(timestamp time.Time, lifetime time.Duration) bool { 9 | elapsed := time.Now().Sub(timestamp) 10 | return elapsed >= lifetime 11 | } 12 | 13 | // IsExpired returns true if the timestamp is before the current datetime 14 | func IsExpired(timestamp time.Time) bool { 15 | return time.Now().After(timestamp) 16 | } 17 | -------------------------------------------------------------------------------- /third-party/swagger/css/typography.css: -------------------------------------------------------------------------------- 1 | /* Google Font's Droid Sans */ 2 | @font-face { 3 | font-family: 'Droid Sans'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf') format('truetype'); 7 | } 8 | /* Google Font's Droid Sans Bold */ 9 | @font-face { 10 | font-family: 'Droid Sans'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf') format('truetype'); 14 | } 15 | -------------------------------------------------------------------------------- /third-party/swagger/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/util/encoding-util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "github.com/ghodss/yaml" 8 | ) 9 | 10 | // EncodeMD5Hex encodes in to md5 then hex 11 | func EncodeMD5Hex(in string) string { 12 | hasher := md5.New() 13 | hasher.Write([]byte(in)) 14 | return hex.EncodeToString(hasher.Sum(nil)) 15 | 16 | } 17 | 18 | // YAMLtoJSON converts the YAML in to JSON out 19 | func YAMLtoJSON(in []byte, out interface{}) (err error) { 20 | jsn, err := yaml.YAMLToJSON(in) 21 | if err != nil { 22 | return 23 | } 24 | err = json.Unmarshal(jsn, out) 25 | if err != nil { 26 | } 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /internal/resource/common.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "net/http" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | "github.com/emicklei/go-restful" 8 | ) 9 | 10 | var ( 11 | errFailToReadResponse = restful.NewError(http.StatusBadRequest, "unable to read request body") 12 | errFailToWriteResponse = restful.NewError(http.StatusInternalServerError, "unable to write response") 13 | ) 14 | 15 | // errorResponse creates an error response from the given error 16 | func errorResponse(origErr error, res *restful.Response, err restful.ServiceError) { 17 | log.WithError(origErr).Error(err.Message) 18 | if err := res.WriteServiceError(err.Code, err); err != nil { 19 | log.WithError(origErr).Error("unable to write error") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /third-party/swagger/lib/jquery.wiggle.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Wiggle 3 | Author: WonderGroup, Jordan Thomas 4 | URL: http://labs.wondergroup.com/demos/mini-ui/index.html 5 | License: MIT (http://en.wikipedia.org/wiki/MIT_License) 6 | */ 7 | jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('
').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);} 8 | if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});}; -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APP=rudder 2 | VERSION=latest 3 | LDFLAGS=-ldflags "-X github.com/AcalephStorage/rudder/cmd.version=${VERSION}" 4 | 5 | all: deps build 6 | 7 | clean: 8 | @echo "--> cleaning..." 9 | @rm -rf build 10 | @rm -rf vendor 11 | @go clean ./... 12 | 13 | prereq: 14 | @mkdir -p build/{bin,tar} 15 | @go get -u github.com/Masterminds/glide 16 | 17 | deps: prereq 18 | @glide install 19 | 20 | build: prereq 21 | @echo '--> building...' 22 | @go fmt ./... 23 | go build -o build/bin/${APP} ${LDFLAGS} ./cmd 24 | 25 | package: 26 | @echo '--> packaging...' 27 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -o build/bin/${APP} ${LDFLAGS} ./cmd 28 | @docker build -t quay.io/acaleph/rudder:${VERSION} . 29 | 30 | deploy: package 31 | @echo '--> deploying...' 32 | @docker push quay.io/acaleph/rudder:${VERSION} 33 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/AcalephStorage/rudder 2 | import: 3 | - package: github.com/emicklei/go-restful 4 | version: ~1.2.0 5 | subpackages: 6 | - swagger 7 | - log 8 | - package: github.com/Sirupsen/logrus 9 | version: ~0.10.0 10 | - package: k8s.io/helm 11 | version: 2.8.1 12 | subpackages: 13 | - pkg/chartutil 14 | - pkg/helm 15 | - pkg/version 16 | - pkg/repo 17 | - pkg/proto/hapi/services 18 | - pkg/proto/hapi/release 19 | - package: github.com/urfave/cli 20 | version: ~1.18.1 21 | - package: github.com/ghodss/yaml 22 | - package: google.golang.org/grpc 23 | - package: github.com/coreos/go-oidc 24 | - package: golang.org/x/net 25 | subpackages: 26 | - context 27 | - package: golang.org/x/text/secure/bidirule 28 | - package: gopkg.in/square/go-jose.v2 29 | version: ~2.0.1 30 | - package: github.com/AcalephStorage/go-auth 31 | -------------------------------------------------------------------------------- /internal/filter/debug-filter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "time" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | "github.com/emicklei/go-restful" 8 | ) 9 | 10 | // DebugFilter provides a go-restful filter for logging debugging information 11 | type DebugFilter struct{} 12 | 13 | // NewDebugFilter returns a debug filter that logs some info about the request and response 14 | func NewDebugFilter() *DebugFilter { 15 | return &DebugFilter{} 16 | } 17 | 18 | // Debug is a filter for logging the request method, URL, response time and code 19 | func (df *DebugFilter) Debug(req *restful.Request, res *restful.Response, chain *restful.FilterChain) { 20 | log.Debugf("Request: Method=%v URL=%v", req.Request.Method, req.Request.URL) 21 | reqTime := time.Now() 22 | chain.ProcessFilter(req, res) 23 | resTime := time.Now() 24 | dur := resTime.Sub(reqTime) 25 | log.Debugf("Response: time=%v code=%v", dur.String(), res.StatusCode()) 26 | } 27 | -------------------------------------------------------------------------------- /internal/util/file-util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "archive/tar" 8 | "compress/gzip" 9 | "io/ioutil" 10 | ) 11 | 12 | // ReadFile reads the file from the given filePath 13 | func ReadFile(filePath string) (out []byte, err error) { 14 | return ioutil.ReadFile(filePath) 15 | } 16 | 17 | // WriteFile writes the data to the given filePath 18 | func WriteFile(filePath string, data []byte) error { 19 | return ioutil.WriteFile(filePath, data, 0644) 20 | } 21 | 22 | // TarballToMap converts a tarball to map[string][]byte 23 | func TarballToMap(in []byte) (out map[string][]byte, err error) { 24 | byteReader := bytes.NewReader(in) 25 | gzipReader, err := gzip.NewReader(byteReader) 26 | defer gzipReader.Close() 27 | if err != nil { 28 | return 29 | } 30 | tarReader := tar.NewReader(gzipReader) 31 | out = make(map[string][]byte) 32 | for { 33 | header, tarErr := tarReader.Next() 34 | if tarErr == io.EOF { 35 | // eof 36 | break 37 | } 38 | if tarErr != nil { 39 | // something went wrong 40 | err = tarErr 41 | return 42 | } 43 | // only regular files 44 | info := header.FileInfo() 45 | if info.IsDir() { 46 | continue 47 | } 48 | data, readErr := ioutil.ReadAll(tarReader) 49 | if readErr != nil { 50 | err = readErr 51 | return 52 | } 53 | out[header.Name] = data 54 | 55 | } 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /third-party/swagger/lang/translator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Translator for documentation pages. 5 | * 6 | * To enable translation you should include one of language-files in your index.html 7 | * after . 8 | * For example - 9 | * 10 | * If you wish to translate some new texsts you should do two things: 11 | * 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too. 12 | * 2. Mark that text it templates this way New Phrase or . 13 | * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate. 14 | * 15 | */ 16 | window.SwaggerTranslator = { 17 | 18 | _words:[], 19 | 20 | translate: function(sel) { 21 | var $this = this; 22 | sel = sel || '[data-sw-translate]'; 23 | 24 | $(sel).each(function() { 25 | $(this).html($this._tryTranslate($(this).html())); 26 | 27 | $(this).val($this._tryTranslate($(this).val())); 28 | $(this).attr('title', $this._tryTranslate($(this).attr('title'))); 29 | }); 30 | }, 31 | 32 | _tryTranslate: function(word) { 33 | return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; 34 | }, 35 | 36 | learn: function(wordsMap) { 37 | this._words = wordsMap; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /third-party/swagger/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /third-party/swagger/lang/zh-cn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告:已过时", 6 | "Implementation Notes":"实现备注", 7 | "Response Class":"响应类", 8 | "Status":"状态", 9 | "Parameters":"参数", 10 | "Parameter":"参数", 11 | "Value":"值", 12 | "Description":"描述", 13 | "Parameter Type":"参数类型", 14 | "Data Type":"数据类型", 15 | "Response Messages":"响应消息", 16 | "HTTP Status Code":"HTTP状态码", 17 | "Reason":"原因", 18 | "Response Model":"响应模型", 19 | "Request URL":"请求URL", 20 | "Response Body":"响应体", 21 | "Response Code":"响应码", 22 | "Response Headers":"响应头", 23 | "Hide Response":"隐藏响应", 24 | "Headers":"头", 25 | "Try it out!":"试一下!", 26 | "Show/Hide":"显示/隐藏", 27 | "List Operations":"显示操作", 28 | "Expand Operations":"展开操作", 29 | "Raw":"原始", 30 | "can't parse JSON. Raw result":"无法解析JSON. 原始结果", 31 | "Model Schema":"模型架构", 32 | "Model":"模型", 33 | "apply":"应用", 34 | "Username":"用户名", 35 | "Password":"密码", 36 | "Terms of service":"服务条款", 37 | "Created by":"创建者", 38 | "See more at":"查看更多:", 39 | "Contact the developer":"联系开发者", 40 | "api version":"api版本", 41 | "Response Content Type":"响应Content Type", 42 | "fetching resource":"正在获取资源", 43 | "fetching resource list":"正在获取资源列表", 44 | "Explore":"浏览", 45 | "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", 47 | "Please specify the protocol for":"请指定协议:", 48 | "Can't read swagger JSON from":"无法读取swagger JSON于", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", 50 | "Unable to read api":"无法读取api", 51 | "from path":"从路径", 52 | "server returned":"服务器返回" 53 | }); 54 | -------------------------------------------------------------------------------- /third-party/swagger/lang/ja.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告: 廃止予定", 6 | "Implementation Notes":"実装メモ", 7 | "Response Class":"レスポンスクラス", 8 | "Status":"ステータス", 9 | "Parameters":"パラメータ群", 10 | "Parameter":"パラメータ", 11 | "Value":"値", 12 | "Description":"説明", 13 | "Parameter Type":"パラメータタイプ", 14 | "Data Type":"データタイプ", 15 | "Response Messages":"レスポンスメッセージ", 16 | "HTTP Status Code":"HTTPステータスコード", 17 | "Reason":"理由", 18 | "Response Model":"レスポンスモデル", 19 | "Request URL":"リクエストURL", 20 | "Response Body":"レスポンスボディ", 21 | "Response Code":"レスポンスコード", 22 | "Response Headers":"レスポンスヘッダ", 23 | "Hide Response":"レスポンスを隠す", 24 | "Headers":"ヘッダ", 25 | "Try it out!":"実際に実行!", 26 | "Show/Hide":"表示/非表示", 27 | "List Operations":"操作一覧", 28 | "Expand Operations":"操作の展開", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果", 31 | "Model Schema":"モデルスキーマ", 32 | "Model":"モデル", 33 | "apply":"実行", 34 | "Username":"ユーザ名", 35 | "Password":"パスワード", 36 | "Terms of service":"サービス利用規約", 37 | "Created by":"Created by", 38 | "See more at":"See more at", 39 | "Contact the developer":"開発者に連絡", 40 | "api version":"APIバージョン", 41 | "Response Content Type":"レスポンス コンテンツタイプ", 42 | "fetching resource":"リソースの取得", 43 | "fetching resource list":"リソース一覧の取得", 44 | "Explore":"Explore", 45 | "Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.", 47 | "Please specify the protocol for":"プロトコルを指定してください", 48 | "Can't read swagger JSON from":"次からswagger JSONを読み込めません", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています", 50 | "Unable to read api":"APIを読み込めません", 51 | "from path":"次のパスから", 52 | "server returned":"サーバからの返答" 53 | }); 54 | -------------------------------------------------------------------------------- /kube/manifest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # exposing tiller as a service 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | labels: 7 | app: helm 8 | name: tiller 9 | name: tiller 10 | namespace: kube-system 11 | spec: 12 | ports: 13 | - name: api 14 | port: 44134 15 | protocol: TCP 16 | targetPort: 44134 17 | selector: 18 | app: helm 19 | name: tiller 20 | 21 | --- 22 | # add repo file as config map 23 | apiVersion: v1 24 | data: 25 | repo: | 26 | apiVersion: v1 27 | repositories: 28 | - cache: stable-index.yaml 29 | name: stable 30 | url: http://storage.googleapis.com/kubernetes-charts 31 | kind: ConfigMap 32 | metadata: 33 | name: ruddercfg 34 | namespace: default 35 | 36 | --- 37 | apiVersion: extensions/v1beta1 38 | kind: Deployment 39 | metadata: 40 | labels: 41 | app: rudder 42 | type: api 43 | name: rudder 44 | namespace: default 45 | spec: 46 | replicas: 1 47 | selector: 48 | matchLabels: 49 | app: rudder 50 | type: api 51 | template: 52 | metadata: 53 | labels: 54 | app: rudder 55 | type: api 56 | name: rudder 57 | spec: 58 | containers: 59 | - name: rudder 60 | image: quay.io/acaleph/rudder:v0.1.3 61 | env: 62 | - name: RUDDER_TILLER_ADDRESS 63 | value: tiller.kube-system:44134 64 | # - name: RUDDER_BASIC_AUTH_USERNAME 65 | # valueFrom: 66 | # secretKeyRef: 67 | # key: username 68 | # name: rudder-creds 69 | # - name: RUDDER_BASIC_AUTH_PASSWORD 70 | # valueFrom: 71 | # secretKeyRef: 72 | # key: password 73 | # name: rudder-creds 74 | - name: RUDDER_HELM_REPO_FILE 75 | value: /opt/rudder/helm/repo.yaml 76 | 77 | 78 | ports: 79 | - containerPort: 5000 80 | name: api 81 | protocol: TCP 82 | volumeMounts: 83 | - mountPath: /opt/rudder/helm 84 | name: repo 85 | volumes: 86 | - configMap: 87 | defaultMode: 420 88 | items: 89 | - key: repo 90 | path: repo.yaml 91 | name: ruddercfg 92 | name: repo 93 | -------------------------------------------------------------------------------- /third-party/swagger/lang/tr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uyarı: Deprecated", 6 | "Implementation Notes":"Gerçekleştirim Notları", 7 | "Response Class":"Dönen Sınıf", 8 | "Status":"Statü", 9 | "Parameters":"Parametreler", 10 | "Parameter":"Parametre", 11 | "Value":"Değer", 12 | "Description":"Açıklama", 13 | "Parameter Type":"Parametre Tipi", 14 | "Data Type":"Veri Tipi", 15 | "Response Messages":"Dönüş Mesajı", 16 | "HTTP Status Code":"HTTP Statü Kodu", 17 | "Reason":"Gerekçe", 18 | "Response Model":"Dönüş Modeli", 19 | "Request URL":"İstek URL", 20 | "Response Body":"Dönüş İçeriği", 21 | "Response Code":"Dönüş Kodu", 22 | "Response Headers":"Dönüş Üst Bilgileri", 23 | "Hide Response":"Dönüşü Gizle", 24 | "Headers":"Üst Bilgiler", 25 | "Try it out!":"Dene!", 26 | "Show/Hide":"Göster/Gizle", 27 | "List Operations":"Operasyonları Listele", 28 | "Expand Operations":"Operasyonları Aç", 29 | "Raw":"Ham", 30 | "can't parse JSON. Raw result":"JSON çözümlenemiyor. Ham sonuç", 31 | "Model Schema":"Model Şema", 32 | "Model":"Model", 33 | "apply":"uygula", 34 | "Username":"Kullanıcı Adı", 35 | "Password":"Parola", 36 | "Terms of service":"Servis şartları", 37 | "Created by":"Oluşturan", 38 | "See more at":"Daha fazlası için", 39 | "Contact the developer":"Geliştirici ile İletişime Geçin", 40 | "api version":"api versiyon", 41 | "Response Content Type":"Dönüş İçerik Tipi", 42 | "fetching resource":"kaynak getiriliyor", 43 | "fetching resource list":"kaynak listesi getiriliyor", 44 | "Explore":"Keşfet", 45 | "Show Swagger Petstore Example Apis":"Swagger Petstore Örnek Api'yi Gör", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.", 47 | "Please specify the protocol for":"Lütfen istenen adres için protokol belirtiniz", 48 | "Can't read swagger JSON from":"Swagger JSON bu kaynaktan okunamıyor", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor", 50 | "Unable to read api":"api okunamadı", 51 | "from path":"yoldan", 52 | "server returned":"sunucuya dönüldü" 53 | }); 54 | -------------------------------------------------------------------------------- /third-party/swagger/lang/pl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uwaga: Wycofane", 6 | "Implementation Notes":"Uwagi Implementacji", 7 | "Response Class":"Klasa Odpowiedzi", 8 | "Status":"Status", 9 | "Parameters":"Parametry", 10 | "Parameter":"Parametr", 11 | "Value":"Wartość", 12 | "Description":"Opis", 13 | "Parameter Type":"Typ Parametru", 14 | "Data Type":"Typ Danych", 15 | "Response Messages":"Wiadomości Odpowiedzi", 16 | "HTTP Status Code":"Kod Statusu HTTP", 17 | "Reason":"Przyczyna", 18 | "Response Model":"Model Odpowiedzi", 19 | "Request URL":"URL Wywołania", 20 | "Response Body":"Treść Odpowiedzi", 21 | "Response Code":"Kod Odpowiedzi", 22 | "Response Headers":"Nagłówki Odpowiedzi", 23 | "Hide Response":"Ukryj Odpowiedź", 24 | "Headers":"Nagłówki", 25 | "Try it out!":"Wypróbuj!", 26 | "Show/Hide":"Pokaż/Ukryj", 27 | "List Operations":"Lista Operacji", 28 | "Expand Operations":"Rozwiń Operacje", 29 | "Raw":"Nieprzetworzone", 30 | "can't parse JSON. Raw result":"nie można przetworzyć pliku JSON. Nieprzetworzone dane", 31 | "Model Schema":"Schemat Modelu", 32 | "Model":"Model", 33 | "apply":"użyj", 34 | "Username":"Nazwa użytkownika", 35 | "Password":"Hasło", 36 | "Terms of service":"Warunki używania", 37 | "Created by":"Utworzone przez", 38 | "See more at":"Zobacz więcej na", 39 | "Contact the developer":"Kontakt z deweloperem", 40 | "api version":"wersja api", 41 | "Response Content Type":"Typ Zasobu Odpowiedzi", 42 | "fetching resource":"ładowanie zasobu", 43 | "fetching resource list":"ładowanie listy zasobów", 44 | "Explore":"Eksploruj", 45 | "Show Swagger Petstore Example Apis":"Pokaż Przykładowe Api Swagger Petstore", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.", 47 | "Please specify the protocol for":"Proszę podać protokół dla", 48 | "Can't read swagger JSON from":"Nie można odczytać swagger JSON z", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI", 50 | "Unable to read api":"Nie można odczytać api", 51 | "from path":"ze ścieżki", 52 | "server returned":"serwer zwrócił" 53 | }); 54 | -------------------------------------------------------------------------------- /third-party/swagger/lang/pt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Aviso: Depreciado", 6 | "Implementation Notes":"Notas de Implementação", 7 | "Response Class":"Classe de resposta", 8 | "Status":"Status", 9 | "Parameters":"Parâmetros", 10 | "Parameter":"Parâmetro", 11 | "Value":"Valor", 12 | "Description":"Descrição", 13 | "Parameter Type":"Tipo de parâmetro", 14 | "Data Type":"Tipo de dados", 15 | "Response Messages":"Mensagens de resposta", 16 | "HTTP Status Code":"Código de status HTTP", 17 | "Reason":"Razão", 18 | "Response Model":"Modelo resposta", 19 | "Request URL":"URL requisição", 20 | "Response Body":"Corpo da resposta", 21 | "Response Code":"Código da resposta", 22 | "Response Headers":"Cabeçalho da resposta", 23 | "Headers":"Cabeçalhos", 24 | "Hide Response":"Esconder resposta", 25 | "Try it out!":"Tente agora!", 26 | "Show/Hide":"Mostrar/Esconder", 27 | "List Operations":"Listar operações", 28 | "Expand Operations":"Expandir operações", 29 | "Raw":"Cru", 30 | "can't parse JSON. Raw result":"Falha ao analisar JSON. Resulto cru", 31 | "Model Schema":"Modelo esquema", 32 | "Model":"Modelo", 33 | "apply":"Aplicar", 34 | "Username":"Usuário", 35 | "Password":"Senha", 36 | "Terms of service":"Termos do serviço", 37 | "Created by":"Criado por", 38 | "See more at":"Veja mais em", 39 | "Contact the developer":"Contate o desenvolvedor", 40 | "api version":"Versão api", 41 | "Response Content Type":"Tipo de conteúdo da resposta", 42 | "fetching resource":"busca recurso", 43 | "fetching resource list":"buscando lista de recursos", 44 | "Explore":"Explorar", 45 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin", 47 | "Please specify the protocol for":"Por favor especifique o protocolo", 48 | "Can't read swagger JSON from":"Não é possível ler o JSON Swagger de", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Carregar informação de recurso finalizada. Renderizando Swagger UI", 50 | "Unable to read api":"Não foi possível ler api", 51 | "from path":"do caminho", 52 | "server returned":"servidor retornou" 53 | }); 54 | -------------------------------------------------------------------------------- /third-party/swagger/lang/en.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Warning: Deprecated", 6 | "Implementation Notes":"Implementation Notes", 7 | "Response Class":"Response Class", 8 | "Status":"Status", 9 | "Parameters":"Parameters", 10 | "Parameter":"Parameter", 11 | "Value":"Value", 12 | "Description":"Description", 13 | "Parameter Type":"Parameter Type", 14 | "Data Type":"Data Type", 15 | "Response Messages":"Response Messages", 16 | "HTTP Status Code":"HTTP Status Code", 17 | "Reason":"Reason", 18 | "Response Model":"Response Model", 19 | "Request URL":"Request URL", 20 | "Response Body":"Response Body", 21 | "Response Code":"Response Code", 22 | "Response Headers":"Response Headers", 23 | "Hide Response":"Hide Response", 24 | "Headers":"Headers", 25 | "Try it out!":"Try it out!", 26 | "Show/Hide":"Show/Hide", 27 | "List Operations":"List Operations", 28 | "Expand Operations":"Expand Operations", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"can't parse JSON. Raw result", 31 | "Model Schema":"Model Schema", 32 | "Model":"Model", 33 | "Click to set as parameter value":"Click to set as parameter value", 34 | "apply":"apply", 35 | "Username":"Username", 36 | "Password":"Password", 37 | "Terms of service":"Terms of service", 38 | "Created by":"Created by", 39 | "See more at":"See more at", 40 | "Contact the developer":"Contact the developer", 41 | "api version":"api version", 42 | "Response Content Type":"Response Content Type", 43 | "Parameter content type:":"Parameter content type:", 44 | "fetching resource":"fetching resource", 45 | "fetching resource list":"fetching resource list", 46 | "Explore":"Explore", 47 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 48 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.", 49 | "Please specify the protocol for":"Please specify the protocol for", 50 | "Can't read swagger JSON from":"Can't read swagger JSON from", 51 | "Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI", 52 | "Unable to read api":"Unable to read api", 53 | "from path":"from path", 54 | "server returned":"server returned" 55 | }); 56 | -------------------------------------------------------------------------------- /third-party/swagger/lang/ru.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Предупреждение: Устарело", 6 | "Implementation Notes":"Заметки", 7 | "Response Class":"Пример ответа", 8 | "Status":"Статус", 9 | "Parameters":"Параметры", 10 | "Parameter":"Параметр", 11 | "Value":"Значение", 12 | "Description":"Описание", 13 | "Parameter Type":"Тип параметра", 14 | "Data Type":"Тип данных", 15 | "HTTP Status Code":"HTTP код", 16 | "Reason":"Причина", 17 | "Response Model":"Структура ответа", 18 | "Request URL":"URL запроса", 19 | "Response Body":"Тело ответа", 20 | "Response Code":"HTTP код ответа", 21 | "Response Headers":"Заголовки ответа", 22 | "Hide Response":"Спрятать ответ", 23 | "Headers":"Заголовки", 24 | "Response Messages":"Что может прийти в ответ", 25 | "Try it out!":"Попробовать!", 26 | "Show/Hide":"Показать/Скрыть", 27 | "List Operations":"Операции кратко", 28 | "Expand Operations":"Операции подробно", 29 | "Raw":"В сыром виде", 30 | "can't parse JSON. Raw result":"Не удается распарсить ответ:", 31 | "Model Schema":"Структура", 32 | "Model":"Описание", 33 | "Click to set as parameter value":"Нажмите, чтобы испльзовать в качестве значения параметра", 34 | "apply":"применить", 35 | "Username":"Имя пользователя", 36 | "Password":"Пароль", 37 | "Terms of service":"Условия использования", 38 | "Created by":"Разработано", 39 | "See more at":"Еще тут", 40 | "Contact the developer":"Связаться с разработчиком", 41 | "api version":"Версия API", 42 | "Response Content Type":"Content Type ответа", 43 | "Parameter content type:":"Content Type параметра:", 44 | "fetching resource":"Получение ресурса", 45 | "fetching resource list":"Получение ресурсов", 46 | "Explore":"Показать", 47 | "Show Swagger Petstore Example Apis":"Показать примеры АПИ", 48 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа", 49 | "Please specify the protocol for":"Пожалуйста, укажите протокол для", 50 | "Can't read swagger JSON from":"Не получается прочитать swagger json из", 51 | "Finished Loading Resource Information. Rendering Swagger UI":"Загрузка информации о ресурсах завершена. Рендерим", 52 | "Unable to read api":"Не удалось прочитать api", 53 | "from path":"по адресу", 54 | "server returned":"сервер сказал" 55 | }); 56 | -------------------------------------------------------------------------------- /third-party/swagger/lang/es.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Advertencia: Obsoleto", 6 | "Implementation Notes":"Notas de implementación", 7 | "Response Class":"Clase de la Respuesta", 8 | "Status":"Status", 9 | "Parameters":"Parámetros", 10 | "Parameter":"Parámetro", 11 | "Value":"Valor", 12 | "Description":"Descripción", 13 | "Parameter Type":"Tipo del Parámetro", 14 | "Data Type":"Tipo del Dato", 15 | "Response Messages":"Mensajes de la Respuesta", 16 | "HTTP Status Code":"Código de Status HTTP", 17 | "Reason":"Razón", 18 | "Response Model":"Modelo de la Respuesta", 19 | "Request URL":"URL de la Solicitud", 20 | "Response Body":"Cuerpo de la Respuesta", 21 | "Response Code":"Código de la Respuesta", 22 | "Response Headers":"Encabezados de la Respuesta", 23 | "Hide Response":"Ocultar Respuesta", 24 | "Try it out!":"Pruébalo!", 25 | "Show/Hide":"Mostrar/Ocultar", 26 | "List Operations":"Listar Operaciones", 27 | "Expand Operations":"Expandir Operaciones", 28 | "Raw":"Crudo", 29 | "can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo", 30 | "Model Schema":"Esquema del Modelo", 31 | "Model":"Modelo", 32 | "apply":"aplicar", 33 | "Username":"Nombre de usuario", 34 | "Password":"Contraseña", 35 | "Terms of service":"Términos de Servicio", 36 | "Created by":"Creado por", 37 | "See more at":"Ver más en", 38 | "Contact the developer":"Contactar al desarrollador", 39 | "api version":"versión de la api", 40 | "Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta", 41 | "fetching resource":"buscando recurso", 42 | "fetching resource list":"buscando lista del recurso", 43 | "Explore":"Explorar", 44 | "Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore", 45 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.", 46 | "Please specify the protocol for":"Por favor, especificar el protocola para", 47 | "Can't read swagger JSON from":"No se puede leer el JSON de swagger desde", 48 | "Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI", 49 | "Unable to read api":"No se puede leer la api", 50 | "from path":"desde ruta", 51 | "server returned":"el servidor retornó" 52 | }); 53 | -------------------------------------------------------------------------------- /third-party/swagger/lang/fr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Avertissement : Obsolète", 6 | "Implementation Notes":"Notes d'implementation", 7 | "Response Class":"Classe de la réponse", 8 | "Status":"Statut", 9 | "Parameters":"Paramètres", 10 | "Parameter":"Paramètre", 11 | "Value":"Valeur", 12 | "Description":"Description", 13 | "Parameter Type":"Type du paramètre", 14 | "Data Type":"Type de données", 15 | "Response Messages":"Messages de la réponse", 16 | "HTTP Status Code":"Code de statut HTTP", 17 | "Reason":"Raison", 18 | "Response Model":"Modèle de réponse", 19 | "Request URL":"URL appelée", 20 | "Response Body":"Corps de la réponse", 21 | "Response Code":"Code de la réponse", 22 | "Response Headers":"En-têtes de la réponse", 23 | "Hide Response":"Cacher la réponse", 24 | "Headers":"En-têtes", 25 | "Try it out!":"Testez !", 26 | "Show/Hide":"Afficher/Masquer", 27 | "List Operations":"Liste des opérations", 28 | "Expand Operations":"Développer les opérations", 29 | "Raw":"Brut", 30 | "can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut", 31 | "Model Schema":"Définition du modèle", 32 | "Model":"Modèle", 33 | "apply":"appliquer", 34 | "Username":"Nom d'utilisateur", 35 | "Password":"Mot de passe", 36 | "Terms of service":"Conditions de service", 37 | "Created by":"Créé par", 38 | "See more at":"Voir plus sur", 39 | "Contact the developer":"Contacter le développeur", 40 | "api version":"version de l'api", 41 | "Response Content Type":"Content Type de la réponse", 42 | "fetching resource":"récupération de la ressource", 43 | "fetching resource list":"récupération de la liste de ressources", 44 | "Explore":"Explorer", 45 | "Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.", 47 | "Please specify the protocol for":"Veuillez spécifier un protocole pour", 48 | "Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI", 50 | "Unable to read api":"Impossible de lire l'api", 51 | "from path":"à partir du chemin", 52 | "server returned":"réponse du serveur" 53 | }); 54 | -------------------------------------------------------------------------------- /third-party/swagger/lang/it.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Attenzione: Deprecato", 6 | "Implementation Notes":"Note di implementazione", 7 | "Response Class":"Classe della risposta", 8 | "Status":"Stato", 9 | "Parameters":"Parametri", 10 | "Parameter":"Parametro", 11 | "Value":"Valore", 12 | "Description":"Descrizione", 13 | "Parameter Type":"Tipo di parametro", 14 | "Data Type":"Tipo di dato", 15 | "Response Messages":"Messaggi della risposta", 16 | "HTTP Status Code":"Codice stato HTTP", 17 | "Reason":"Motivo", 18 | "Response Model":"Modello di risposta", 19 | "Request URL":"URL della richiesta", 20 | "Response Body":"Corpo della risposta", 21 | "Response Code":"Oggetto della risposta", 22 | "Response Headers":"Intestazioni della risposta", 23 | "Hide Response":"Nascondi risposta", 24 | "Try it out!":"Provalo!", 25 | "Show/Hide":"Mostra/Nascondi", 26 | "List Operations":"Mostra operazioni", 27 | "Expand Operations":"Espandi operazioni", 28 | "Raw":"Grezzo (raw)", 29 | "can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).", 30 | "Model Schema":"Schema del modello", 31 | "Model":"Modello", 32 | "apply":"applica", 33 | "Username":"Nome utente", 34 | "Password":"Password", 35 | "Terms of service":"Condizioni del servizio", 36 | "Created by":"Creato da", 37 | "See more at":"Informazioni aggiuntive:", 38 | "Contact the developer":"Contatta lo sviluppatore", 39 | "api version":"versione api", 40 | "Response Content Type":"Tipo di contenuto (content type) della risposta", 41 | "fetching resource":"recuperando la risorsa", 42 | "fetching resource list":"recuperando lista risorse", 43 | "Explore":"Esplora", 44 | "Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore", 45 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.", 46 | "Please specify the protocol for":"Si prega di specificare il protocollo per", 47 | "Can't read swagger JSON from":"Impossibile leggere JSON swagger da:", 48 | "Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata", 49 | "Unable to read api":"Impossibile leggere la api", 50 | "from path":"da cartella", 51 | "server returned":"il server ha restituito" 52 | }); 53 | -------------------------------------------------------------------------------- /internal/client/tiller-client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | log "github.com/Sirupsen/logrus" 5 | "golang.org/x/net/context" 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/metadata" 8 | tiller "k8s.io/helm/pkg/proto/hapi/services" 9 | "k8s.io/helm/pkg/version" 10 | ) 11 | 12 | // TillerClient is a wrapper for accessing Tiller's gRPC 13 | type TillerClient struct { 14 | address string 15 | context context.Context 16 | } 17 | 18 | // NewTillerClient creates a new TillerClient instance 19 | func NewTillerClient(address string) *TillerClient { 20 | md := metadata.Pairs("x-helm-api-client", version.Version) 21 | ctx := metadata.NewOutgoingContext(context.TODO(), md) 22 | return &TillerClient{address: address, context: ctx} 23 | } 24 | 25 | func (tc *TillerClient) execute(request func(tiller.ReleaseServiceClient)) error { 26 | conn, err := grpc.Dial(tc.address, grpc.WithInsecure()) 27 | if err != nil { 28 | log.Debug("unable to dial tiller") 29 | return err 30 | } 31 | defer conn.Close() 32 | rsc := tiller.NewReleaseServiceClient(conn) 33 | request(rsc) 34 | return nil 35 | } 36 | 37 | // ListReleases returns a list of release from tiller 38 | func (tc *TillerClient) ListReleases(req *tiller.ListReleasesRequest) (res *tiller.ListReleasesResponse, err error) { 39 | log.Info(req) 40 | tc.execute(func(rsc tiller.ReleaseServiceClient) { 41 | lrc, err := rsc.ListReleases(tc.context, req) 42 | if err != nil { 43 | log.Debug("unable to list all releases") 44 | return 45 | } 46 | res, err = lrc.Recv() 47 | }) 48 | return 49 | } 50 | 51 | // InstallRelease installs a new release 52 | func (tc *TillerClient) InstallRelease(req *tiller.InstallReleaseRequest) (res *tiller.InstallReleaseResponse, err error) { 53 | tc.execute(func(rsc tiller.ReleaseServiceClient) { 54 | res, err = rsc.InstallRelease(tc.context, req) 55 | if err != nil { 56 | log.Debug("unable to install release") 57 | } 58 | }) 59 | return 60 | } 61 | 62 | // UpdateRelease updates an existing release 63 | func (tc *TillerClient) UpdateRelease(req *tiller.UpdateReleaseRequest) (res *tiller.UpdateReleaseResponse, err error) { 64 | tc.execute(func(rsc tiller.ReleaseServiceClient) { 65 | res, err = rsc.UpdateRelease(tc.context, req) 66 | if err != nil { 67 | log.Debug("unable to install release") 68 | } 69 | }) 70 | return 71 | } 72 | 73 | // UninstallRelease uninstalls a release 74 | func (tc *TillerClient) UninstallRelease(req *tiller.UninstallReleaseRequest) (res *tiller.UninstallReleaseResponse, err error) { 75 | tc.execute(func(rsc tiller.ReleaseServiceClient) { 76 | res, err = rsc.UninstallRelease(tc.context, req) 77 | if err != nil { 78 | log.Debug("unable to uninstall release") 79 | } 80 | }) 81 | return 82 | } 83 | 84 | // GetReleaseContent returns the contents of a release 85 | func (tc *TillerClient) GetReleaseContent(req *tiller.GetReleaseContentRequest) (res *tiller.GetReleaseContentResponse, err error) { 86 | tc.execute(func(rsc tiller.ReleaseServiceClient) { 87 | res, err = rsc.GetReleaseContent(tc.context, req) 88 | if err != nil { 89 | log.Debug("unable to get release content") 90 | } 91 | }) 92 | return 93 | } 94 | 95 | // GetReleaseStatus returns the status of a release 96 | func (tc *TillerClient) GetReleaseStatus(req *tiller.GetReleaseStatusRequest) (res *tiller.GetReleaseStatusResponse, err error) { 97 | tc.execute(func(rsc tiller.ReleaseServiceClient) { 98 | res, err = rsc.GetReleaseStatus(tc.context, req) 99 | if err != nil { 100 | log.Debug("unable to get release status") 101 | } 102 | }) 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /third-party/swagger/lib/jquery.ba-bbq.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 3 | * http://benalman.com/projects/jquery-bbq-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 6192beab0887a4287fa923ec44dd8efd53ea9edb3f07aeb9ac7ba9d375201756 2 | updated: 2018-02-26T11:31:08.834851571Z 3 | imports: 4 | - name: github.com/AcalephStorage/go-auth 5 | version: a94da01afb9889ea6537e78aa6a7c0bfac1f197d 6 | - name: github.com/BurntSushi/toml 7 | version: b26d9c308763d68093482582cea63d69be07a0f0 8 | - name: github.com/coreos/go-oidc 9 | version: 8472879a4fc0182e34ed1a1edf711625a9c9fab2 10 | - name: github.com/emicklei/go-restful 11 | version: 777bb3f19bcafe2575ffb2a3e46af92509ae9594 12 | subpackages: 13 | - log 14 | - swagger 15 | - name: github.com/ghodss/yaml 16 | version: a54de18a07046d8c4b26e9327698a2ebb9285b36 17 | - name: github.com/gobwas/glob 18 | version: 5ccd90ef52e1e632236f7326478d4faa74f99438 19 | subpackages: 20 | - compiler 21 | - match 22 | - syntax 23 | - syntax/ast 24 | - syntax/lexer 25 | - util/runes 26 | - util/strings 27 | - name: github.com/golang/protobuf 28 | version: 4bd1920723d7b7c925de087aa32e2187708897f7 29 | subpackages: 30 | - proto 31 | - ptypes 32 | - ptypes/any 33 | - ptypes/duration 34 | - ptypes/timestamp 35 | - name: github.com/Masterminds/semver 36 | version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd 37 | - name: github.com/pquerna/cachecontrol 38 | version: c97913dcbd76de40b051a9b4cd827f7eaeb7a868 39 | subpackages: 40 | - cacheobject 41 | - name: github.com/Sirupsen/logrus 42 | version: 4b6ea7319e214d98c938f12692336f7ca9348d6b 43 | - name: github.com/spf13/pflag 44 | version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 45 | - name: github.com/urfave/cli 46 | version: a14d7d367bc02b1f57d88de97926727f2d936387 47 | - name: golang.org/x/crypto 48 | version: ede567c8e044a5913dad1d1af3696d9da953104c 49 | subpackages: 50 | - cast5 51 | - openpgp 52 | - openpgp/armor 53 | - openpgp/clearsign 54 | - openpgp/elgamal 55 | - openpgp/errors 56 | - openpgp/packet 57 | - openpgp/s2k 58 | - name: golang.org/x/net 59 | version: 1c05540f6879653db88113bc4a2b70aec4bd491f 60 | subpackages: 61 | - context 62 | - context/ctxhttp 63 | - http2 64 | - http2/hpack 65 | - idna 66 | - internal/timeseries 67 | - lex/httplex 68 | - trace 69 | - name: golang.org/x/oauth2 70 | version: d5040cddfc0da40b408c9a1da4728662435176a9 71 | subpackages: 72 | - internal 73 | - name: golang.org/x/sys 74 | version: c200b10b5d5e122be351b67af224adc6128af5bf 75 | subpackages: 76 | - unix 77 | - name: golang.org/x/text 78 | version: b19bf474d317b857955b12035d2c5acb57ce8b01 79 | subpackages: 80 | - secure/bidirule 81 | - transform 82 | - unicode/bidi 83 | - unicode/norm 84 | - name: google.golang.org/appengine 85 | version: ca59ef35f409df61fa4a5f8290ff289b37eccfb8 86 | subpackages: 87 | - internal 88 | - internal/base 89 | - internal/datastore 90 | - internal/log 91 | - internal/remote_api 92 | - internal/urlfetch 93 | - urlfetch 94 | - name: google.golang.org/genproto 95 | version: 2b5a72b8730b0b16380010cfe5286c42108d88e7 96 | subpackages: 97 | - googleapis/rpc/status 98 | - name: google.golang.org/grpc 99 | version: 5ffe3083946d5603a0578721101dc8165b1d5b5f 100 | subpackages: 101 | - codes 102 | - credentials 103 | - grpclog 104 | - internal 105 | - keepalive 106 | - metadata 107 | - naming 108 | - peer 109 | - stats 110 | - status 111 | - tap 112 | - transport 113 | - name: gopkg.in/square/go-jose.v2 114 | version: 296c7f1463ec9b712176dc804dea0173d06dc728 115 | subpackages: 116 | - cipher 117 | - json 118 | - name: gopkg.in/yaml.v2 119 | version: a5b47d31c556af34a302ce5d659e6fea44d90de0 120 | - name: k8s.io/apimachinery 121 | version: cced8e64b6ca92a8b6afcbfea3353ca016694a45 122 | subpackages: 123 | - pkg/version 124 | - name: k8s.io/client-go 125 | version: e00c8f23c00e0935e9abf285596fe76f0b63c040 126 | subpackages: 127 | - util/homedir 128 | - name: k8s.io/helm 129 | version: 6af75a8fd72e2aa18a2b278cfe5c7a1c5feca7f2 130 | subpackages: 131 | - pkg/chartutil 132 | - pkg/getter 133 | - pkg/helm 134 | - pkg/helm/environment 135 | - pkg/helm/helmpath 136 | - pkg/ignore 137 | - pkg/plugin 138 | - pkg/proto/hapi/chart 139 | - pkg/proto/hapi/release 140 | - pkg/proto/hapi/services 141 | - pkg/proto/hapi/version 142 | - pkg/provenance 143 | - pkg/repo 144 | - pkg/sympath 145 | - pkg/tlsutil 146 | - pkg/urlutil 147 | - pkg/version 148 | testImports: [] 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rudder 2 | ------ 3 | 4 | RESTful API for Helm Repositories and the Tiller service. 5 | 6 | Requirements 7 | ------------ 8 | 9 | - Tiller v2.0.0 10 | 11 | Installation 12 | ------------ 13 | 14 | ### Binary 15 | 16 | Binaries can be downloaded [here](https://github.com/AcalephStorage/rudder/releases). 17 | 18 | Running 19 | ------- 20 | 21 | Launch in Kubernetes: 22 | 23 | ``` 24 | $ kubectl create -f kube/manifest.yaml 25 | ``` 26 | 27 | This will create a Service for Tiller in the `kube-system` namespace, along with the Deployment and Configmap to run Rudder (in the `default` namespace). Customize as needed with Services, Secrets, etc (until the Rudder helm chart is ready) 28 | 29 | Alternatively: 30 | 31 | ``` 32 | $ docker run quay.io/acaleph/rudder 33 | ``` 34 | 35 | or 36 | 37 | ``` 38 | $ rudder {{flags}} 39 | ``` 40 | 41 | Configuration 42 | ------------- 43 | 44 | Configuration can be provided via cli flags or through environment variables: 45 | 46 | | Configuration | Flag | Environment Variable | Default | 47 | |-----------------------|--------------------------------|---------------------------------|--------------------------------------| 48 | | Rudder address | --address | RUDDER_ADDRESS | 0.0.0.0:5000 | 49 | | Tiller address | --tiller-address | RUDDER_TILLER_ADDRESS | localhost:44134 | 50 | | Repo File | --helm-repo-file | RUDDER_HELM_REPO_FILE | ~/.helm/repository/repositories.yaml | 51 | | Cache Directory | --helm-cache-dir | RUDDER_HELM_CACHE_DIR | /opt/rudder/cache | 52 | | Cache Lifetime | --helm-repo-cache-lifetime | RUDDER_HELM_REPO_CACHE_LIFETIME | 10m | 53 | | Swagger UI Path | --swagger-ui-path | RUDDER_SWAGGER_UI_PATH | /opt/rudder/swagger | 54 | | Basic Auth Username | --basic-auth-username | RUDDER_BASIC_AUTH_USERNAME | | 55 | | Basic Auth Password | --basic-auth-password | RUDDER_BASIC_AUTH_PASSWORD | | 56 | | OIDC Issuer URL | --oidc-issuer-url | RUDDER_OIDC_ISSUER_URL | | 57 | | Client ID | --client-id | RUDDER_CLIENT_ID | | 58 | | Client Secret | --client-secret | RUDDER_CLIENT_SECRET | | 59 | | Client Secret Encoded | --client-secret-base64-encoded | RUDDER_CLIENT_BASE64_ENCODED | | 60 | | Debug Mode | --debug | | | 61 | 62 | API 63 | --- 64 | 65 | API docs is provided via swagger. This is available at: `http://{rudder-url}/swagger`. 66 | 67 | Using the docker image already has this enabled by default. When using the binary, copy the [swagger files](https://github.com/AcalephStorage/rudder/tree/develop/third-party/swagger) to `/opt/rudder/swagger` or a different directory and set `--swagger-ui-path`. 68 | 69 | Currently there are read-only Helm Repository endpoints for fetching charts from repositories and Basic Release endpoints (tiller), `install` and `uninstall`. The rest is still WIP. 70 | 71 | Notes 72 | ----- 73 | 74 | ### Helm Repositories 75 | 76 | At the moment, repositories are provided via a repo file. The format should be the same as what helm uses (`~/.helm/repository/repositories.yaml`). This may change in the future when a repo manager is implemented. 77 | 78 | ### Charts cache 79 | 80 | Charts are downloaded from the helm repository and are cached at the location defined by `--helm-cache-dir` (default: ./opt/rudder/cache). This directory should exist and be writable. 81 | 82 | ### Authentication 83 | 84 | Authentication can be enabled by providing authentication details. 85 | 86 | #### Basic Auth 87 | 88 | Providing `--basic-auth-username` and `--basic-auth-password` will enable Basic Authentication. 89 | 90 | #### OIDC 91 | 92 | Providing `--oidc-issuer-url` or `--client-secret` will enable OIDC. 93 | 94 | TODO 95 | ---- 96 | 97 | This is still WIP. Some immediate TODOs are: 98 | 99 | - [ ] implement a repo manager 100 | - [ ] implement missing tiller functions 101 | -------------------------------------------------------------------------------- /internal/resource/repo.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "net/http" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | "github.com/emicklei/go-restful" 8 | "k8s.io/helm/pkg/repo" 9 | 10 | "github.com/AcalephStorage/rudder/internal/controller" 11 | ) 12 | 13 | var ( 14 | errFailToGetCharts = restful.NewError(http.StatusBadRequest, "unable to fetch charts") 15 | errFailToListVersions = restful.NewError(http.StatusBadRequest, "unable to fetch chart versions") 16 | errFailToGetChartDetail = restful.NewError(http.StatusBadRequest, "unable to fetch chart details") 17 | ) 18 | 19 | // RepoResource represents helm repositories 20 | type RepoResource struct { 21 | controller *controller.RepoController 22 | } 23 | 24 | // NewRepoResource creates a new RepoResource 25 | func NewRepoResource(controller *controller.RepoController) *RepoResource { 26 | return &RepoResource{controller: controller} 27 | } 28 | 29 | // Register registers this resource to the provided container 30 | func (rr *RepoResource) Register(container *restful.Container) { 31 | 32 | ws := new(restful.WebService) 33 | 34 | ws.Path("/api/v1/repo"). 35 | Doc("Helm repositories"). 36 | Consumes(restful.MIME_JSON). 37 | Produces(restful.MIME_JSON) 38 | 39 | // GET /api/v1/repo 40 | ws.Route(ws.GET("").To(rr.listRepos). 41 | Doc("list repos"). 42 | Operation("listRepos"). 43 | Writes([]repo.Entry{})) 44 | 45 | // GET /api/v1/repo/{repo}/charts 46 | ws.Route(ws.GET("{repo}/charts").To(rr.listCharts). 47 | Doc("list charts"). 48 | Operation("listCharts"). 49 | Param(ws.PathParameter("repo", "the helm repository")). 50 | Param(ws.QueryParameter("filter", "filter for the charts")). 51 | Writes(map[string][]repo.ChartVersion{})) 52 | 53 | // GET /api/v1/repo/{repo}/charts/{chart} 54 | ws.Route(ws.GET("{repo}/charts/{chart}").To(rr.listVersions). 55 | Doc("list chart versions"). 56 | Operation("listVersions"). 57 | Param(ws.PathParameter("repo", "the helm repository")). 58 | Param(ws.PathParameter("chart", "the helm chart")). 59 | Writes([]repo.ChartVersion{})) 60 | 61 | // GET /api/v1/repo/{repo}/charts/{chart}/{version} 62 | ws.Route(ws.GET("{repo}/charts/{chart}/{version}").To(rr.getChart). 63 | Doc("get chart details. specifying version=latest will return the chart tagged latest, or the top version if none is found"). 64 | Operation("getChart"). 65 | Param(ws.PathParameter("repo", "the helm repository")). 66 | Param(ws.PathParameter("chart", "the helm chart")). 67 | Param(ws.PathParameter("version", "the helm chart version")). 68 | Writes(controller.ChartDetail{})) 69 | 70 | container.Add(ws) 71 | } 72 | 73 | // listRepos returns a list of helm repositories taken from the repo file 74 | func (rr *RepoResource) listRepos(req *restful.Request, res *restful.Response) { 75 | log.Info("Getting list of helm repositories...") 76 | repos := rr.controller.ListRepos() 77 | if err := res.WriteEntity(repos); err != nil { 78 | errorResponse(err, res, errFailToWriteResponse) 79 | } 80 | } 81 | 82 | // listCharts returns a list of charts from a repository 83 | func (rr *RepoResource) listCharts(req *restful.Request, res *restful.Response) { 84 | repoName := req.PathParameter("repo") 85 | filter := req.QueryParameter("filter") 86 | 87 | charts, err := rr.controller.ListCharts(repoName, filter) 88 | if err != nil { 89 | errorResponse(err, res, errFailToGetCharts) 90 | return 91 | } 92 | // output 93 | if err := res.WriteEntity(charts); err != nil { 94 | errorResponse(err, res, errFailToWriteResponse) 95 | } 96 | } 97 | 98 | // listVersions returns a list of chart versions 99 | func (rr *RepoResource) listVersions(req *restful.Request, res *restful.Response) { 100 | repoName := req.PathParameter("repo") 101 | chartName := req.PathParameter("chart") 102 | 103 | charts, err := rr.controller.ListCharts(repoName, chartName) 104 | if err != nil { 105 | errorResponse(err, res, errFailToListVersions) 106 | return 107 | } 108 | 109 | versions := charts[chartName] 110 | if err := res.WriteEntity(versions); err != nil { 111 | errorResponse(err, res, errFailToWriteResponse) 112 | } 113 | 114 | } 115 | 116 | // getChart returns the chart details 117 | func (rr *RepoResource) getChart(req *restful.Request, res *restful.Response) { 118 | repoName := req.PathParameter("repo") 119 | chartName := req.PathParameter("chart") 120 | chartVersion := req.PathParameter("version") 121 | 122 | chartDetail, err := rr.controller.ChartDetails(repoName, chartName, chartVersion) 123 | if err != nil { 124 | errorResponse(err, res, errFailToGetChartDetail) 125 | return 126 | } 127 | 128 | if err := res.WriteEntity(chartDetail); err != nil { 129 | errorResponse(err, res, errFailToWriteResponse) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /third-party/swagger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 107 | 108 | 109 | 110 | 120 | 121 |
 
122 |
123 | 124 | 125 | -------------------------------------------------------------------------------- /internal/controller/release.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | log "github.com/Sirupsen/logrus" 5 | "github.com/ghodss/yaml" 6 | "k8s.io/helm/pkg/chartutil" 7 | hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" 8 | tiller "k8s.io/helm/pkg/proto/hapi/services" 9 | 10 | "fmt" 11 | 12 | "github.com/AcalephStorage/rudder/internal/client" 13 | ) 14 | 15 | // GetReleaseResponse contains the response for requesting Release information 16 | type GetReleaseResponse struct { 17 | Content *tiller.GetReleaseContentResponse `json:"content"` 18 | Status *tiller.GetReleaseStatusResponse `json:"status"` 19 | } 20 | 21 | // ReleaseController handles helm release related operations 22 | type ReleaseController struct { 23 | tillerClient *client.TillerClient 24 | repoController *RepoController 25 | } 26 | 27 | // NewReleaseController creates a new Release controller 28 | func NewReleaseController(tillerClient *client.TillerClient, repoController *RepoController) *ReleaseController { 29 | return &ReleaseController{ 30 | tillerClient: tillerClient, 31 | repoController: repoController, 32 | } 33 | } 34 | 35 | // ListReleases returns a list of releases 36 | func (rc *ReleaseController) ListReleases(req *tiller.ListReleasesRequest) (*tiller.ListReleasesResponse, error) { 37 | res, err := rc.tillerClient.ListReleases(req) 38 | if err != nil { 39 | log.WithError(err).Error("unable to get list of releases from tiller") 40 | return nil, err 41 | } 42 | return res, nil 43 | } 44 | 45 | // InstallRelease installs a new release of the provided chart 46 | func (rc *ReleaseController) InstallRelease(name, namespace, repo, chart, version string, values map[string]interface{}) (*tiller.InstallReleaseResponse, error) { 47 | chartDetails, err := rc.repoController.ChartDetails(repo, chart, version) 48 | if err != nil { 49 | log.WithError(err).Error("unable to get chart details") 50 | return nil, err 51 | } 52 | tarball := chartDetails.ChartFile 53 | 54 | inChart, err := chartutil.LoadFile(tarball) 55 | if err != nil { 56 | log.WithError(err).Error("unable to load chart details") 57 | return nil, err 58 | } 59 | raw, _ := yaml.Marshal(values) 60 | 61 | inValues := make(map[string]*hapi_chart.Value) 62 | for k, v := range values { 63 | inValues[k] = &hapi_chart.Value{Value: fmt.Sprintf("%v", v)} 64 | } 65 | 66 | config := &hapi_chart.Config{ 67 | Raw: string(raw), 68 | Values: inValues, 69 | } 70 | 71 | req := &tiller.InstallReleaseRequest{ 72 | Name: name, 73 | Namespace: namespace, 74 | Chart: inChart, 75 | Values: config, 76 | } 77 | 78 | res, err := rc.tillerClient.InstallRelease(req) 79 | if err != nil { 80 | log.WithError(err).Error("unable to install new release") 81 | return nil, err 82 | } 83 | return res, nil 84 | } 85 | 86 | // UpdateRelease updates an existing release of the provided chart 87 | func (rc *ReleaseController) UpdateRelease(name, repo, chart, version string, values map[string]interface{}) (*tiller.UpdateReleaseResponse, error) { 88 | chartDetails, err := rc.repoController.ChartDetails(repo, chart, version) 89 | if err != nil { 90 | log.WithError(err).Error("unable to get chart details") 91 | return nil, err 92 | } 93 | tarball := chartDetails.ChartFile 94 | 95 | inChart, err := chartutil.LoadFile(tarball) 96 | if err != nil { 97 | log.WithError(err).Error("unable to load chart details") 98 | return nil, err 99 | } 100 | raw, _ := yaml.Marshal(values) 101 | 102 | inValues := make(map[string]*hapi_chart.Value) 103 | for k, v := range values { 104 | inValues[k] = &hapi_chart.Value{Value: fmt.Sprintf("%v", v)} 105 | } 106 | 107 | config := &hapi_chart.Config{ 108 | Raw: string(raw), 109 | Values: inValues, 110 | } 111 | 112 | req := &tiller.UpdateReleaseRequest{ 113 | Name: name, 114 | Chart: inChart, 115 | Values: config, 116 | } 117 | 118 | res, err := rc.tillerClient.UpdateRelease(req) 119 | if err != nil { 120 | log.WithError(err).Error("unable to update release") 121 | return nil, err 122 | } 123 | return res, nil 124 | } 125 | 126 | // UninstallRelease uninstall a release 127 | func (rc *ReleaseController) UninstallRelease(releaseName string, purge bool) (*tiller.UninstallReleaseResponse, error) { 128 | req := &tiller.UninstallReleaseRequest{ 129 | Name: releaseName, 130 | Purge: purge, 131 | } 132 | 133 | res, err := rc.tillerClient.UninstallRelease(req) 134 | if err != nil { 135 | log.WithError(err).Error("unable to uninstall release") 136 | return nil, err 137 | } 138 | return res, nil 139 | } 140 | 141 | // GetRelease returns the release details 142 | func (rc *ReleaseController) GetRelease(name string, version int32) (*GetReleaseResponse, error) { 143 | req := &tiller.GetReleaseContentRequest{ 144 | Name: name, 145 | Version: version, 146 | } 147 | content, err := rc.tillerClient.GetReleaseContent(req) 148 | if err != nil { 149 | log.WithError(err).Error("unable to get release content") 150 | return nil, err 151 | } 152 | 153 | req2 := &tiller.GetReleaseStatusRequest{ 154 | Name: name, 155 | Version: version, 156 | } 157 | status, err := rc.tillerClient.GetReleaseStatus(req2) 158 | if err != nil { 159 | log.WithError(err).Error("unable to get release status") 160 | return nil, err 161 | } 162 | return &GetReleaseResponse{ 163 | Content: content, 164 | Status: status, 165 | }, nil 166 | } 167 | -------------------------------------------------------------------------------- /third-party/swagger/css/style.css: -------------------------------------------------------------------------------- 1 | .swagger-section #header a#logo { 2 | font-size: 1.5em; 3 | font-weight: bold; 4 | text-decoration: none; 5 | background: transparent url(../images/logo.png) no-repeat left center; 6 | padding: 20px 0 20px 40px; 7 | } 8 | #text-head { 9 | font-size: 80px; 10 | font-family: 'Roboto', sans-serif; 11 | color: #ffffff; 12 | float: right; 13 | margin-right: 20%; 14 | } 15 | .navbar-fixed-top .navbar-nav { 16 | height: auto; 17 | } 18 | .navbar-fixed-top .navbar-brand { 19 | height: auto; 20 | } 21 | .navbar-header { 22 | height: auto; 23 | } 24 | .navbar-inverse { 25 | background-color: #000; 26 | border-color: #000; 27 | } 28 | #navbar-brand { 29 | margin-left: 20%; 30 | } 31 | .navtext { 32 | font-size: 10px; 33 | } 34 | .h1, 35 | h1 { 36 | font-size: 60px; 37 | } 38 | .navbar-default .navbar-header .navbar-brand { 39 | color: #a2dfee; 40 | } 41 | /* tag titles */ 42 | .swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { 43 | color: #393939; 44 | font-family: 'Arvo', serif; 45 | font-size: 1.5em; 46 | } 47 | .swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { 48 | color: black; 49 | } 50 | .swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { 51 | color: #525252; 52 | padding-left: 0px; 53 | display: block; 54 | clear: none; 55 | float: left; 56 | font-family: 'Arvo', serif; 57 | font-weight: bold; 58 | } 59 | .navbar-default .navbar-collapse, 60 | .navbar-default .navbar-form { 61 | border-color: #0A0A0A; 62 | } 63 | .container1 { 64 | width: 1500px; 65 | margin: auto; 66 | margin-top: 0; 67 | background-image: url('../images/shield.png'); 68 | background-repeat: no-repeat; 69 | background-position: -40px -20px; 70 | margin-bottom: 210px; 71 | } 72 | .container-inner { 73 | width: 1200px; 74 | margin: auto; 75 | background-color: rgba(223, 227, 228, 0.75); 76 | padding-bottom: 40px; 77 | padding-top: 40px; 78 | border-radius: 15px; 79 | } 80 | .header-content { 81 | padding: 0; 82 | width: 1000px; 83 | } 84 | .title1 { 85 | font-size: 80px; 86 | font-family: 'Vollkorn', serif; 87 | color: #404040; 88 | text-align: center; 89 | padding-top: 40px; 90 | padding-bottom: 100px; 91 | } 92 | #icon { 93 | margin-top: -18px; 94 | } 95 | .subtext { 96 | font-size: 25px; 97 | font-style: italic; 98 | color: #08b; 99 | text-align: right; 100 | padding-right: 250px; 101 | } 102 | .bg-primary { 103 | background-color: #00468b; 104 | } 105 | .navbar-default .nav > li > a, 106 | .navbar-default .nav > li > a:focus { 107 | color: #08b; 108 | } 109 | .navbar-default .nav > li > a, 110 | .navbar-default .nav > li > a:hover { 111 | color: #08b; 112 | } 113 | .navbar-default .nav > li > a, 114 | .navbar-default .nav > li > a:focus:hover { 115 | color: #08b; 116 | } 117 | .text-faded { 118 | font-size: 25px; 119 | font-family: 'Vollkorn', serif; 120 | } 121 | .section-heading { 122 | font-family: 'Vollkorn', serif; 123 | font-size: 45px; 124 | padding-bottom: 10px; 125 | } 126 | hr { 127 | border-color: #00468b; 128 | padding-bottom: 10px; 129 | } 130 | .description { 131 | margin-top: 20px; 132 | padding-bottom: 200px; 133 | } 134 | .description li { 135 | font-family: 'Vollkorn', serif; 136 | font-size: 25px; 137 | color: #525252; 138 | margin-left: 28%; 139 | padding-top: 5px; 140 | } 141 | .gap { 142 | margin-top: 200px; 143 | } 144 | .troubleshootingtext { 145 | color: rgba(255, 255, 255, 0.7); 146 | padding-left: 30%; 147 | } 148 | .troubleshootingtext li { 149 | list-style-type: circle; 150 | font-size: 25px; 151 | padding-bottom: 5px; 152 | } 153 | .overlay { 154 | position: absolute; 155 | top: 0; 156 | left: 0; 157 | width: 100%; 158 | height: 100%; 159 | z-index: 1000; 160 | } 161 | .block.response_body.json:hover { 162 | cursor: pointer; 163 | } 164 | .backdrop { 165 | color: blue; 166 | } 167 | #myModal { 168 | height: 100%; 169 | } 170 | .modal-backdrop { 171 | bottom: 0; 172 | position: fixed; 173 | } 174 | .curl { 175 | padding: 10px; 176 | font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; 177 | font-size: 0.9em; 178 | max-height: 400px; 179 | margin-top: 5px; 180 | overflow-y: auto; 181 | background-color: #fcf6db; 182 | border: 1px solid #e5e0c6; 183 | border-radius: 4px; 184 | } 185 | .curl_title { 186 | font-size: 1.1em; 187 | margin: 0; 188 | padding: 15px 0 5px; 189 | font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif; 190 | font-weight: 500; 191 | line-height: 1.1; 192 | } 193 | .footer { 194 | display: none; 195 | } 196 | .swagger-section .swagger-ui-wrap h2 { 197 | padding: 0; 198 | } 199 | h2 { 200 | margin: 0; 201 | margin-bottom: 5px; 202 | } 203 | .markdown p { 204 | font-size: 15px; 205 | font-family: 'Arvo', serif; 206 | } 207 | .swagger-section .swagger-ui-wrap .code { 208 | font-size: 15px; 209 | font-family: 'Arvo', serif; 210 | } 211 | .swagger-section .swagger-ui-wrap b { 212 | font-family: 'Arvo', serif; 213 | } 214 | #signin:hover { 215 | cursor: pointer; 216 | } 217 | .dropdown-menu { 218 | padding: 15px; 219 | } 220 | .navbar-right .dropdown-menu { 221 | left: 0; 222 | right: auto; 223 | } 224 | #signinbutton { 225 | width: 100%; 226 | height: 32px; 227 | font-size: 13px; 228 | font-weight: bold; 229 | color: #08b; 230 | } 231 | .navbar-default .nav > li .details { 232 | color: #000000; 233 | text-transform: none; 234 | font-size: 15px; 235 | font-weight: normal; 236 | font-family: 'Open Sans', sans-serif; 237 | font-style: italic; 238 | line-height: 20px; 239 | top: -2px; 240 | } 241 | .navbar-default .nav > li .details:hover { 242 | color: black; 243 | } 244 | #signout { 245 | width: 100%; 246 | height: 32px; 247 | font-size: 13px; 248 | font-weight: bold; 249 | color: #08b; 250 | } 251 | -------------------------------------------------------------------------------- /third-party/swagger/lib/highlight.7.3.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs);hljs.LANGUAGES.json=function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}}(hljs); -------------------------------------------------------------------------------- /internal/controller/repo.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "regexp" 7 | "strings" 8 | "time" 9 | 10 | "encoding/base64" 11 | 12 | log "github.com/Sirupsen/logrus" 13 | "k8s.io/helm/pkg/proto/hapi/chart" 14 | "k8s.io/helm/pkg/repo" 15 | 16 | "github.com/AcalephStorage/rudder/internal/util" 17 | ) 18 | 19 | // RepoController handles helm repository related operations 20 | type RepoController struct { 21 | repos []*repo.Entry 22 | cacheDir string 23 | cacheLifetime time.Duration 24 | } 25 | 26 | // ChartDetail defines the details of a chart 27 | type ChartDetail struct { 28 | Metadata chart.Metadata `json:"metadata"` 29 | ValuesRaw string `json:"values_raw"` 30 | Values map[string]interface{} `json:"values"` 31 | Templates map[string]string `json:"templates"` 32 | ChartURL string `json:"-"` 33 | ChartFile string `json:"-"` 34 | } 35 | 36 | // NewRepoController creates a new repo controller. 37 | func NewRepoController(repos []*repo.Entry, cacheDir string, cacheLifetime time.Duration) *RepoController { 38 | 39 | if _, err := os.Stat(cacheDir); os.IsNotExist(err) { 40 | os.MkdirAll(cacheDir, 0766) 41 | } 42 | 43 | return &RepoController{ 44 | repos: repos, 45 | cacheDir: cacheDir, 46 | cacheLifetime: cacheLifetime, 47 | } 48 | } 49 | 50 | // ListRepos returns a list of repositories 51 | func (rc *RepoController) ListRepos() []*repo.Entry { 52 | return rc.repos 53 | } 54 | 55 | func (rc *RepoController) findRepo(repoName string) (r *repo.Entry, err error) { 56 | for _, rr := range rc.repos { 57 | if rr.Name == repoName { 58 | r = rr 59 | return 60 | } 61 | } 62 | err = errors.New("repository not found") 63 | return 64 | } 65 | 66 | // ListCharts returns the charts contained in the provided repo 67 | func (rc *RepoController) ListCharts(repoName, filter string) (charts map[string]repo.ChartVersions, err error) { 68 | r, err := rc.findRepo(repoName) 69 | if err != nil { 70 | log.WithError(err).Errorf("unable to find repo %s", repoName) 71 | return 72 | } 73 | repoURL := r.URL 74 | indexURL := repoURL + "/index.yaml" 75 | data, err := rc.readFromCacheOrURL(indexURL) 76 | if err != nil { 77 | log.WithError(err).Error("Unable to get index.yaml from cache or %s", indexURL) 78 | return 79 | } 80 | var index repo.IndexFile 81 | err = util.YAMLtoJSON(data, &index) 82 | if err != nil { 83 | log.WithError(err).Error("Unable to parse index.yaml") 84 | } 85 | charts = index.Entries 86 | filterCharts(charts, filter) 87 | return 88 | } 89 | 90 | // ChartDetail returns the details of the provided chart 91 | func (rc *RepoController) ChartDetails(repoName, chartName, chartVersion string) (chartDetail *ChartDetail, err error) { 92 | // update charts if needed 93 | charts, err := rc.ListCharts(repoName, "") 94 | if err != nil { 95 | log.WithError(err).Errorf("unable to get list of charts for %s", repoName) 96 | return 97 | } 98 | versions := charts[chartName] 99 | version, found := findVersion(versions, chartVersion) 100 | if !found { 101 | log.Errorf("%s:%s not found", chartName, chartVersion) 102 | return 103 | } 104 | // get the first URL 105 | chartURL := version.URLs[0] 106 | data, err := rc.readFromCacheOrURL(chartURL) 107 | if err != nil { 108 | log.WithError(err).Errorf("Unable to get chart from cache or %s", chartURL) 109 | return 110 | } 111 | fileMap, err := util.TarballToMap(data) 112 | if err != nil { 113 | log.WithError(err).Errorf("Unable to read tarball") 114 | return 115 | } 116 | 117 | var m chart.Metadata 118 | chartYAML := fileMap[chartName+"/Chart.yaml"] 119 | err = util.YAMLtoJSON(chartYAML, &m) 120 | if err != nil { 121 | log.WithError(err).Errorf("Unable to unmarshal chart") 122 | return 123 | } 124 | 125 | var v map[string]interface{} 126 | valuesYAML := fileMap[chartName+"/values.yaml"] 127 | // vrxp := regexp.MustCompile("# ") 128 | // valuesYAML = vrxp.ReplaceAll(valuesYAML, []byte("")) 129 | util.YAMLtoJSON(valuesYAML, &v) 130 | if err != nil { 131 | log.WithError(err).Errorf("Unable to unmarshal values") 132 | return 133 | } 134 | valuesRaw := base64.StdEncoding.EncodeToString(valuesYAML) 135 | 136 | regex := regexp.MustCompile(".+/templates/(.+)") 137 | templates := make(map[string]string) 138 | for k, v := range fileMap { 139 | if strings.Contains(k, "templates/") { 140 | file := regex.FindStringSubmatch(k)[1] 141 | templates[file] = base64.StdEncoding.EncodeToString(v) 142 | } 143 | } 144 | 145 | chartFile := rc.cacheDir + "/" + util.EncodeMD5Hex(chartURL) 146 | chartDetail = &ChartDetail{ 147 | Metadata: m, 148 | ValuesRaw: valuesRaw, 149 | Values: v, 150 | Templates: templates, 151 | ChartURL: chartURL, 152 | ChartFile: chartFile, 153 | } 154 | return 155 | } 156 | 157 | // readFromCacheOrURL handles reading of the charts. Charts are stored locally for faster access 158 | // but expires at a set time. 159 | func (rc *RepoController) readFromCacheOrURL(url string) ([]byte, error) { 160 | log.Debugf("Fetching resource from cache or %s...", url) 161 | mustReload := false 162 | 163 | cacheFile := util.EncodeMD5Hex(url) 164 | 165 | filePath := rc.cacheDir + "/" + cacheFile 166 | log.Debugf("checking cache: %s", filePath) 167 | fi, err := os.Stat(filePath) 168 | if err != nil { 169 | // file may not exist : needs debug log 170 | log.Debug("cache not found") 171 | mustReload = true 172 | } else { 173 | // outdated 174 | log.Debug("cache found") 175 | mustReload = util.IsOutdated(fi.ModTime(), rc.cacheLifetime) 176 | } 177 | 178 | if mustReload { 179 | log.Debug("cache not found or outdated. getting from URL") 180 | // get from url 181 | out, err := util.HTTPGet(url) 182 | if err != nil { 183 | // unable to download 184 | log.Debugf("unable to download from %s", url) 185 | return nil, err 186 | } 187 | // save to file 188 | if err := util.WriteFile(filePath, out); err != nil { 189 | log.Debugf("unable to save to file %s", filePath) 190 | return nil, err 191 | } 192 | } 193 | 194 | data, err := util.ReadFile(filePath) 195 | if err != nil { 196 | log.Debugf("unable to read from %s", filePath) 197 | return nil, err 198 | } 199 | return data, nil 200 | } 201 | 202 | func findVersion(versions repo.ChartVersions, version string) (ver *repo.ChartVersion, found bool) { 203 | for _, v := range versions { 204 | if v.Version == version { 205 | ver = v 206 | found = true 207 | break 208 | } 209 | } 210 | // if not found but version is latest, return the first item 211 | if version == "latest" { 212 | ver = versions[0] 213 | found = true 214 | } 215 | return 216 | } 217 | 218 | func filterCharts(charts map[string]repo.ChartVersions, filter string) { 219 | if filter == "" { 220 | return 221 | } 222 | for key, val := range charts { 223 | // if key and filter match, skip 224 | if key == filter { 225 | continue 226 | } 227 | 228 | var keywordMatched bool 229 | for _, versions := range val { 230 | name := versions.Name 231 | keywords := versions.Keywords 232 | // if name matches, skip 233 | if name == filter { 234 | continue 235 | } 236 | for _, keyword := range keywords { 237 | if keyword == filter { 238 | // if keyword matches, skip 239 | keywordMatched = true 240 | break 241 | } 242 | } 243 | } 244 | // if keyword matches, skip (not using labels) 245 | if keywordMatched { 246 | continue 247 | } 248 | delete(charts, key) 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /internal/resource/release.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "net/http" 8 | 9 | log "github.com/Sirupsen/logrus" 10 | restful "github.com/emicklei/go-restful" 11 | "k8s.io/helm/pkg/proto/hapi/release" 12 | tiller "k8s.io/helm/pkg/proto/hapi/services" 13 | 14 | "github.com/AcalephStorage/rudder/internal/controller" 15 | "github.com/AcalephStorage/rudder/internal/util" 16 | ) 17 | 18 | var ( 19 | sortByMap = map[string]tiller.ListSort_SortBy{ 20 | "unknown": tiller.ListSort_UNKNOWN, 21 | "name": tiller.ListSort_NAME, 22 | "last-released": tiller.ListSort_LAST_RELEASED, 23 | } 24 | sortOrderMap = map[string]tiller.ListSort_SortOrder{ 25 | "asc": tiller.ListSort_ASC, 26 | "desc": tiller.ListSort_DESC, 27 | } 28 | statusCodeMap = map[string]release.Status_Code{ 29 | "unknown": release.Status_UNKNOWN, 30 | "deployed": release.Status_DEPLOYED, 31 | "deleted": release.Status_DELETED, 32 | "superseded": release.Status_SUPERSEDED, 33 | "failed": release.Status_FAILED, 34 | } 35 | ) 36 | 37 | var ( 38 | errFailToListReleases = restful.NewError(http.StatusBadRequest, "unable to get list of releases") 39 | errFailToInstallRelease = restful.NewError(http.StatusInternalServerError, "unable to install releases") 40 | errFailToUpdateRelease = restful.NewError(http.StatusInternalServerError, "unable to update releases") 41 | errFailtToUninstallRelease = restful.NewError(http.StatusInternalServerError, "unable to uninstall releases") 42 | errFailToGetRelease = restful.NewError(http.StatusInternalServerError, "unable to get release content and status") 43 | ) 44 | 45 | // InstallReleaseRequest is the request body needed for installing a new release 46 | type InstallReleaseRequest struct { 47 | Name string `json:"name"` 48 | Namespace string `json:"namespace"` 49 | Repo string `json:"repo"` 50 | Chart string `json:"chart"` 51 | Version string `json:"version"` 52 | Values map[string]interface{} `json:"values"` 53 | } 54 | 55 | // UpdateReleaseRequest is the request body needed for updating a release 56 | type UpdateReleaseRequest struct { 57 | Repo string `json:"repo"` 58 | Chart string `json:"chart"` 59 | Version string `json:"version"` 60 | Values map[string]interface{} `json:"values"` 61 | } 62 | 63 | // ReleaseResource represents helm releases 64 | type ReleaseResource struct { 65 | controller *controller.ReleaseController 66 | } 67 | 68 | // NewReleaseResource creates a new ReleaseResource instance 69 | func NewReleaseResource(controller *controller.ReleaseController) *ReleaseResource { 70 | return &ReleaseResource{controller: controller} 71 | } 72 | 73 | // Register registers this to the provided container 74 | func (rr *ReleaseResource) Register(container *restful.Container) { 75 | 76 | ws := new(restful.WebService) 77 | ws.Path("/api/v1/releases"). 78 | Doc("Helm releases"). 79 | Consumes(restful.MIME_JSON). 80 | Produces(restful.MIME_JSON) 81 | 82 | // GET /api/v1/releases 83 | ws.Route(ws.GET("").To(rr.listReleases). 84 | Doc("list releases"). 85 | Operation("listReleases"). 86 | Param(ws.QueryParameter("limit", "max number of releases to return")). 87 | Param(ws.QueryParameter("offset", "last release name that was seen")). 88 | Param(ws.QueryParameter("sort-by", "sort by: unknown, name, last-released")). 89 | Param(ws.QueryParameter("filter", "regex to filter releases")). 90 | Param(ws.QueryParameter("sort-order", "sort order: asc, desc")). 91 | Param(ws.QueryParameter("status-code", "comma-separated status codes: unknown, deployed, deleted, superseded, failed")). 92 | Writes(tiller.ListReleasesResponse{})) 93 | 94 | // POST /api/v1/releases 95 | ws.Route(ws.POST("").To(rr.installRelease). 96 | Doc("install release. defaults: namespace=default, version=latest."). 97 | Operation("installRelease"). 98 | Reads(InstallReleaseRequest{}). 99 | Writes(tiller.InstallReleaseResponse{})) 100 | 101 | // PUT /api/v1/releases 102 | ws.Route(ws.PUT("/{release}").To(rr.updateRelease). 103 | Doc("update release. defaults: namespace=default, version=latest."). 104 | Operation("updateRelease"). 105 | Reads(UpdateReleaseRequest{}). 106 | Writes(tiller.UpdateReleaseResponse{})) 107 | 108 | // DELETE /api/v1/releases/{release} 109 | ws.Route(ws.DELETE("/{release}").To(rr.uninstallRelease). 110 | Doc("uninstall release"). 111 | Operation("uninstallRelease"). 112 | Param(ws.PathParameter("release", "the release name to be deleted")). 113 | Param(ws.QueryParameter("purge", "purge the release"))) 114 | 115 | // GET /api/v1/releases/{release}/{version} 116 | ws.Route(ws.GET("/{release}/{version}").To(rr.getRelease). 117 | Doc("get release"). 118 | Operation("getRelease"). 119 | Param(ws.PathParameter("release", "the release name")). 120 | Param(ws.PathParameter("version", "the release version")). 121 | Writes(controller.GetReleaseResponse{})) 122 | 123 | container.Add(ws) 124 | 125 | } 126 | 127 | // listReleases returns a list of installed releases 128 | func (rr *ReleaseResource) listReleases(req *restful.Request, res *restful.Response) { 129 | log.Info("Getting list of releases...") 130 | 131 | limit, _ := strconv.ParseInt(req.QueryParameter("limit"), 10, 64) 132 | offset := req.QueryParameter("offset") 133 | sortBy := sortByMap[req.QueryParameter("sort-by")] 134 | filter := req.QueryParameter("filter") 135 | sortOrder := sortOrderMap[req.QueryParameter("sort-order")] 136 | statusCodesRaw := req.QueryParameter("status-code") 137 | var statusCodes []release.Status_Code 138 | if len(statusCodesRaw) > 0 { 139 | scs := strings.Split(statusCodesRaw, ",") 140 | if len(scs) > 0 { 141 | statusCodes = make([]release.Status_Code, len(scs)) 142 | for i, s := range scs { 143 | sc, ok := statusCodeMap[s] 144 | if ok { 145 | statusCodes[i] = sc 146 | } 147 | } 148 | } 149 | } 150 | 151 | request := &tiller.ListReleasesRequest{ 152 | Limit: limit, 153 | Offset: offset, 154 | SortBy: sortBy, 155 | Filter: filter, 156 | SortOrder: sortOrder, 157 | StatusCodes: statusCodes, 158 | } 159 | 160 | response, err := rr.controller.ListReleases(request) 161 | if err != nil { 162 | errorResponse(err, res, errFailToListReleases) 163 | return 164 | } 165 | if err := res.WriteEntity(response); err != nil { 166 | errorResponse(err, res, errFailToWriteResponse) 167 | } 168 | } 169 | 170 | // installRelease installs the provided release and version to the given namespace 171 | func (rr *ReleaseResource) installRelease(req *restful.Request, res *restful.Response) { 172 | in := InstallReleaseRequest{ 173 | Namespace: "default", 174 | Version: "latest", 175 | } 176 | if err := req.ReadEntity(&in); err != nil { 177 | errorResponse(err, res, errFailToReadResponse) 178 | return 179 | } 180 | out, err := rr.controller.InstallRelease(in.Name, in.Namespace, in.Repo, in.Chart, in.Version, in.Values) 181 | if err != nil { 182 | errorResponse(err, res, errFailToInstallRelease) 183 | return 184 | } 185 | if err := res.WriteEntity(out); err != nil { 186 | errorResponse(err, res, errFailToWriteResponse) 187 | } 188 | } 189 | 190 | // updateRelease updates the provided release 191 | func (rr *ReleaseResource) updateRelease(req *restful.Request, res *restful.Response) { 192 | releaseName := req.PathParameter("release") 193 | in := UpdateReleaseRequest{ 194 | Version: "latest", 195 | } 196 | if err := req.ReadEntity(&in); err != nil { 197 | errorResponse(err, res, errFailToReadResponse) 198 | return 199 | } 200 | out, err := rr.controller.UpdateRelease(releaseName, in.Repo, in.Chart, in.Version, in.Values) 201 | if err != nil { 202 | errorResponse(err, res, errFailToUpdateRelease) 203 | return 204 | } 205 | if err := res.WriteEntity(out); err != nil { 206 | errorResponse(err, res, errFailToWriteResponse) 207 | } 208 | } 209 | 210 | // uninstallRelease removes the release from the list of releases 211 | func (rr *ReleaseResource) uninstallRelease(req *restful.Request, res *restful.Response) { 212 | releaseName := req.PathParameter("release") 213 | _, purge := req.Request.URL.Query()["purge"] 214 | out, err := rr.controller.UninstallRelease(releaseName, purge) 215 | if err != nil { 216 | errorResponse(err, res, errFailtToUninstallRelease) 217 | return 218 | } 219 | if err := res.WriteEntity(out); err != nil { 220 | errorResponse(err, res, errFailToWriteResponse) 221 | } 222 | } 223 | 224 | // getRelease returns the details of the provided release 225 | func (rr *ReleaseResource) getRelease(req *restful.Request, res *restful.Response) { 226 | name := req.PathParameter("release") 227 | versionRaw := req.PathParameter("version") 228 | version := util.ToInt32(versionRaw) 229 | 230 | out, err := rr.controller.GetRelease(name, version) 231 | if err != nil { 232 | errorResponse(err, res, errFailToGetRelease) 233 | return 234 | } 235 | 236 | if err := res.WriteEntity(out); err != nil { 237 | errorResponse(err, res, errFailToWriteResponse) 238 | } 239 | 240 | } 241 | 242 | // GET api/v1/releases/:name/:version/:status {create request body} 243 | func (rr *ReleaseResource) releaseStatus(req *restful.Request, res *restful.Response) { 244 | // TODO 245 | } 246 | 247 | // POST ??? I DUNNOT KNOW 248 | func (rr *ReleaseResource) rollbackRelease(req *restful.Request, res *restful.Response) { 249 | // TODO 250 | } 251 | 252 | // GET /api/v1/releases/:name/:version {create request body} 253 | func (rr *ReleaseResource) releaseContent(req *restful.Request, res *restful.Response) { 254 | // TODO 255 | } 256 | 257 | // ???? I DUNNOT KNOW 258 | func (rr *ReleaseResource) releaseHistory(req *restful.Request, res *restful.Response) { 259 | // TODO 260 | } 261 | 262 | // GET api/v1/version (do we need this?) 263 | func (rr *ReleaseResource) getVersion(req *restful.Request, res *restful.Response) { 264 | // TODO 265 | } 266 | -------------------------------------------------------------------------------- /cmd/rudder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "encoding/json" 8 | "io/ioutil" 9 | "net/http" 10 | 11 | auth "github.com/AcalephStorage/go-auth" 12 | log "github.com/Sirupsen/logrus" 13 | "github.com/emicklei/go-restful" 14 | restfullog "github.com/emicklei/go-restful/log" 15 | "github.com/emicklei/go-restful/swagger" 16 | "github.com/ghodss/yaml" 17 | "github.com/urfave/cli" 18 | "k8s.io/helm/pkg/repo" 19 | 20 | "github.com/AcalephStorage/rudder/internal/client" 21 | "github.com/AcalephStorage/rudder/internal/controller" 22 | "github.com/AcalephStorage/rudder/internal/filter" 23 | "github.com/AcalephStorage/rudder/internal/resource" 24 | ) 25 | 26 | const ( 27 | appName = "rudder" 28 | 29 | addressFlag = "address" 30 | tillerAddressFlag = "tiller-address" 31 | helmRepoFileFlag = "helm-repo-file" 32 | helmCacheDirFlag = "helm-cache-dir" 33 | helmRepoCacheLifetimeFlag = "helm-repo-cache-lifetime" 34 | swaggerUIPathFlag = "swagger-ui-path" 35 | basicAuthUsernameFlag = "basic-auth-username" 36 | basicAuthPasswordFlag = "basic-auth-password" 37 | oidcIssuerURLFlag = "oidc-issuer-url" 38 | clientIDFlag = "client-id" 39 | clientSecretFlag = "client-secret" 40 | clientSecretBase64EncodedFlag = "client-secret-base64-encoded" 41 | debugFlag = "debug" 42 | insecure = "insecure" 43 | 44 | swaggerAPIPath = "/apidocs.json" 45 | swaggerPath = "/swagger/" 46 | ) 47 | 48 | var ( 49 | version = "dev" 50 | 51 | corsAllowedMethods = []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"} 52 | corsAllowedHeaders = []string{"Authorization", "Accept", "Content-Type"} 53 | ) 54 | 55 | func main() { 56 | app := cli.NewApp() 57 | app.Name = appName 58 | app.Version = version 59 | app.Flags = []cli.Flag{ 60 | cli.StringFlag{ 61 | Name: addressFlag, 62 | Usage: "bind address", 63 | EnvVar: "RUDDER_ADDRESS", 64 | Value: "0.0.0.0:5000", 65 | }, 66 | cli.StringFlag{ 67 | Name: tillerAddressFlag, 68 | Usage: "tiller address", 69 | EnvVar: "RUDDER_TILLER_ADDRESS", 70 | Value: "localhost:44134", 71 | }, 72 | cli.StringFlag{ 73 | Name: helmRepoFileFlag, 74 | Usage: "helm repo file", 75 | EnvVar: "RUDDER_HELM_REPO_FILE", 76 | Value: os.Getenv("HOME") + "/.helm/repository/repositories.yaml", 77 | }, 78 | cli.StringFlag{ 79 | Name: helmCacheDirFlag, 80 | Usage: "helm cache dir", 81 | EnvVar: "RUDDER_HELM_CACHE_DIR", 82 | Value: "/opt/rudder/cache", 83 | }, 84 | cli.DurationFlag{ 85 | Name: helmRepoCacheLifetimeFlag, 86 | Usage: "cache lifetime. should be in duration format (eg. 10m). valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'", 87 | EnvVar: "RUDDER_HELM_REPO_CACHE_LIFETIME", 88 | Value: 10 * time.Minute, 89 | }, 90 | cli.StringFlag{ 91 | Name: swaggerUIPathFlag, 92 | Usage: "swagger ui path", 93 | EnvVar: "RUDDER_SWAGGER_UI_PATH", 94 | Value: "/opt/rudder/swagger", 95 | }, 96 | cli.StringFlag{ 97 | Name: basicAuthUsernameFlag, 98 | Usage: "basic auth username. will enable basic authentication if both username and password are provided", 99 | EnvVar: "RUDDER_BASIC_AUTH_USERNAME", 100 | }, 101 | cli.StringFlag{ 102 | Name: basicAuthPasswordFlag, 103 | Usage: "basic auth password. will enable basic authentication if both username and password are provided", 104 | EnvVar: "RUDDER_BASIC_AUTH_PASSWORD", 105 | }, 106 | cli.StringFlag{ 107 | Name: oidcIssuerURLFlag, 108 | Usage: "OIDC issuer url. will enable OIDC authentication", 109 | EnvVar: "RUDDER_OIDC_ISSUER_URL", 110 | }, 111 | cli.StringFlag{ 112 | Name: "client-id", 113 | Usage: "OAuth Client ID. if specified, oidc will verify 'aud'", 114 | EnvVar: "RUDDER_CLIENT_ID", 115 | }, 116 | cli.StringFlag{ 117 | Name: "client-secret", 118 | Usage: "OAuth Client Secret. if specified and JWT is signed with HMAC, this will be used for verification", 119 | EnvVar: "RUDDER_CLIENT_SECRET", 120 | }, 121 | cli.BoolFlag{ 122 | Name: clientSecretBase64EncodedFlag, 123 | Usage: "enable this flag to specify that the client-secret is base64 encoded", 124 | EnvVar: "RUDDER_CLIENT_BASE64_ENCODED", 125 | }, 126 | cli.BoolFlag{ 127 | Name: debugFlag, 128 | Hidden: true, 129 | }, 130 | cli.BoolFlag{ 131 | Name: insecure, 132 | Usage: "enable insecure interface", 133 | EnvVar: "RUDDER_INSECURE", 134 | }, 135 | } 136 | 137 | app.Action = startRudder 138 | app.Run(os.Args) 139 | } 140 | 141 | func startRudder(ctx *cli.Context) error { 142 | // unified logging format 143 | isDebug := ctx.Bool(debugFlag) 144 | initializeLogger(isDebug) 145 | log.Info("Starting Rudder...") 146 | 147 | // main container 148 | container := restful.NewContainer() 149 | 150 | // add debug, cors, and options filter 151 | createBasicFilters(container, isDebug) 152 | 153 | // add auth filter 154 | if !ctx.Bool(insecure) { 155 | username := ctx.String(basicAuthUsernameFlag) 156 | password := ctx.String(basicAuthPasswordFlag) 157 | oidcIssuerURL := ctx.String(oidcIssuerURLFlag) 158 | clientID := ctx.String(clientIDFlag) 159 | clientSecret := ctx.String(clientSecretFlag) 160 | secretIsBase64Encoded := ctx.Bool(clientSecretBase64EncodedFlag) 161 | createAuthFilter(container, username, password, oidcIssuerURL, clientID, clientSecret, secretIsBase64Encoded) 162 | } 163 | 164 | // add `repo` resource 165 | repoFile := ctx.String(helmRepoFileFlag) 166 | cacheDir := ctx.String(helmCacheDirFlag) 167 | cacheLifetime := ctx.Duration(helmRepoCacheLifetimeFlag) 168 | repoController := createRepoController(repoFile, cacheDir, cacheLifetime) 169 | registerRepoResource(container, repoController) 170 | 171 | // add `release` resource 172 | tillerAddress := ctx.String(tillerAddressFlag) 173 | registerReleaseResource(container, repoController, tillerAddress) 174 | 175 | // add swagger service 176 | swaggerUIPath := ctx.String(swaggerUIPathFlag) 177 | registerSwagger(container, swaggerUIPath) 178 | 179 | // start server 180 | address := ctx.String(addressFlag) 181 | log.Infof("Rudder listening at: %v", address) 182 | return http.ListenAndServe(address, container) 183 | } 184 | 185 | func initializeLogger(isDebug bool) { 186 | // use full timestamp 187 | logFormat := &log.TextFormatter{FullTimestamp: true} 188 | log.SetFormatter(logFormat) 189 | // use same format for restful logger 190 | restfulLogger := log.New() 191 | restfulLogger.Formatter = logFormat 192 | restfullog.SetLogger(restfulLogger) 193 | // debug mode 194 | if isDebug { 195 | log.SetLevel(log.DebugLevel) 196 | log.Debug("DEBUG Mode enabled.") 197 | } 198 | } 199 | 200 | func createBasicFilters(container *restful.Container, isDebug bool) { 201 | // debug filter 202 | if isDebug { 203 | debugFilter := filter.NewDebugFilter() 204 | container.Filter(debugFilter.Debug) 205 | log.Info("Debug filter added.") 206 | } 207 | // cors 208 | cors := restful.CrossOriginResourceSharing{ 209 | AllowedMethods: corsAllowedMethods, 210 | AllowedHeaders: corsAllowedHeaders, 211 | Container: container, 212 | } 213 | container.Filter(cors.Filter) 214 | log.Info("CORS filter added.") 215 | // options 216 | container.Filter(container.OPTIONSFilter) 217 | log.Info("OPTIONS filter added.") 218 | } 219 | 220 | func createAuthFilter(container *restful.Container, username, password, oidcIssuerURL, clientID, clientSecret string, isBase64Encoded bool) { 221 | // supported auth 222 | var supportedAuth []auth.Auth 223 | // enable basic auth of username and password are defined 224 | if username != "" && password != "" { 225 | supportedAuth = append(supportedAuth, auth.NewBasicAuth(username, password)) 226 | } 227 | // enable oidc auth if oidc issuer url or client secret is defined 228 | if oidcIssuerURL != "" || clientSecret != "" { 229 | oidcAuth := auth.NewOIDCAuth(oidcIssuerURL, clientID, clientSecret, isBase64Encoded) 230 | supportedAuth = append(supportedAuth, oidcAuth) 231 | } 232 | // don't include swagger to exceptions 233 | exceptions := []string{ 234 | "/apidocs.json", 235 | "/swagger", 236 | } 237 | 238 | authFilter := auth.NewAuthFilter(supportedAuth, exceptions) 239 | container.Filter(authFilter.Filter) 240 | log.Info("Auth filter added") 241 | } 242 | 243 | func createRepoController(repoFileURL, cacheDir string, cacheLife time.Duration) *controller.RepoController { 244 | repoFileYAML, err := ioutil.ReadFile(repoFileURL) 245 | if err != nil { 246 | log.Fatalf("unable to read repo file at %s", repoFileURL) 247 | } 248 | repoFileJSON, err := yaml.YAMLToJSON(repoFileYAML) 249 | if err != nil { 250 | log.Fatal("unable to convert yaml to json") 251 | } 252 | var repoFile repo.RepoFile 253 | if err := json.Unmarshal(repoFileJSON, &repoFile); err != nil { 254 | log.Fatal("unable to parse repoFile") 255 | } 256 | repoController := controller.NewRepoController(repoFile.Repositories, cacheDir, cacheLife) 257 | return repoController 258 | } 259 | 260 | func registerRepoResource(container *restful.Container, repoController *controller.RepoController) { 261 | repoResource := resource.NewRepoResource(repoController) 262 | repoResource.Register(container) 263 | log.Info("repo resource registered.") 264 | } 265 | 266 | func registerReleaseResource(container *restful.Container, repoController *controller.RepoController, tillerAddress string) { 267 | tillerClient := client.NewTillerClient(tillerAddress) 268 | releaseController := controller.NewReleaseController(tillerClient, repoController) 269 | releaseResource := resource.NewReleaseResource(releaseController) 270 | releaseResource.Register(container) 271 | log.Info("release resource registered.") 272 | } 273 | 274 | func registerSwagger(container *restful.Container, swaggerUIPath string) { 275 | swaggerConfig := swagger.Config{ 276 | WebServices: container.RegisteredWebServices(), 277 | ApiPath: swaggerAPIPath, 278 | ApiVersion: version, 279 | Info: swagger.Info{ 280 | Title: "Rudder", 281 | Description: "RESTful proxy for the Tiller service", 282 | }, 283 | SwaggerPath: swaggerPath, 284 | SwaggerFilePath: swaggerUIPath, 285 | } 286 | swagger.RegisterSwaggerService(swaggerConfig, container) 287 | log.Info("Swagger enabled.") 288 | } 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /third-party/swagger/lib/swagger-oauth.js: -------------------------------------------------------------------------------- 1 | var appName; 2 | var popupMask; 3 | var popupDialog; 4 | var clientId; 5 | var realm; 6 | var redirect_uri; 7 | var clientSecret; 8 | var scopeSeparator; 9 | var additionalQueryStringParams; 10 | 11 | function handleLogin() { 12 | var scopes = []; 13 | 14 | var auths = window.swaggerUi.api.authSchemes || window.swaggerUi.api.securityDefinitions; 15 | if(auths) { 16 | var key; 17 | var defs = auths; 18 | for(key in defs) { 19 | var auth = defs[key]; 20 | if(auth.type === 'oauth2' && auth.scopes) { 21 | var scope; 22 | if(Array.isArray(auth.scopes)) { 23 | // 1.2 support 24 | var i; 25 | for(i = 0; i < auth.scopes.length; i++) { 26 | scopes.push(auth.scopes[i]); 27 | } 28 | } 29 | else { 30 | // 2.0 support 31 | for(scope in auth.scopes) { 32 | scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key}); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | if(window.swaggerUi.api 40 | && window.swaggerUi.api.info) { 41 | appName = window.swaggerUi.api.info.title; 42 | } 43 | 44 | $('.api-popup-dialog').remove(); 45 | popupDialog = $( 46 | [ 47 | '
', 48 | '
Select OAuth2.0 Scopes
', 49 | '
', 50 | '

Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.', 51 | 'Learn how to use', 52 | '

', 53 | '

' + appName + ' API requires the following scopes. Select which ones you want to grant to Swagger UI.

', 54 | '
    ', 55 | '
', 56 | '

', 57 | '
', 58 | '
', 59 | '
'].join('')); 60 | $(document.body).append(popupDialog); 61 | 62 | //TODO: only display applicable scopes (will need to pass them into handleLogin) 63 | popup = popupDialog.find('ul.api-popup-scopes').empty(); 64 | for (i = 0; i < scopes.length; i ++) { 65 | scope = scopes[i]; 66 | str = '
  • ' + '
  • '; 74 | popup.append(str); 75 | } 76 | 77 | var $win = $(window), 78 | dw = $win.width(), 79 | dh = $win.height(), 80 | st = $win.scrollTop(), 81 | dlgWd = popupDialog.outerWidth(), 82 | dlgHt = popupDialog.outerHeight(), 83 | top = (dh -dlgHt)/2 + st, 84 | left = (dw - dlgWd)/2; 85 | 86 | popupDialog.css({ 87 | top: (top < 0? 0 : top) + 'px', 88 | left: (left < 0? 0 : left) + 'px' 89 | }); 90 | 91 | popupDialog.find('button.api-popup-cancel').click(function() { 92 | popupMask.hide(); 93 | popupDialog.hide(); 94 | popupDialog.empty(); 95 | popupDialog = []; 96 | }); 97 | 98 | $('button.api-popup-authbtn').unbind(); 99 | popupDialog.find('button.api-popup-authbtn').click(function() { 100 | popupMask.hide(); 101 | popupDialog.hide(); 102 | 103 | var authSchemes = window.swaggerUi.api.authSchemes; 104 | var host = window.location; 105 | var pathname = location.pathname.substring(0, location.pathname.lastIndexOf("/")); 106 | var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html'; 107 | var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl; 108 | var url = null; 109 | var scopes = [] 110 | var o = popup.find('input:checked'); 111 | var OAuthSchemeKeys = []; 112 | var state; 113 | for(k =0; k < o.length; k++) { 114 | var scope = $(o[k]).attr('scope'); 115 | if (scopes.indexOf(scope) === -1) 116 | scopes.push(scope); 117 | var OAuthSchemeKey = $(o[k]).attr('oauthtype'); 118 | if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1) 119 | OAuthSchemeKeys.push(OAuthSchemeKey); 120 | } 121 | 122 | //TODO: merge not replace if scheme is different from any existing 123 | //(needs to be aware of schemes to do so correctly) 124 | window.enabledScopes=scopes; 125 | 126 | for (var key in authSchemes) { 127 | if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope. 128 | var flow = authSchemes[key].flow; 129 | 130 | if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) { 131 | var dets = authSchemes[key]; 132 | url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code'); 133 | window.swaggerUi.tokenName = dets.tokenName || 'access_token'; 134 | window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null); 135 | state = key; 136 | } 137 | else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) { 138 | var dets = authSchemes[key]; 139 | window.swaggerUi.tokenName = dets.tokenName || 'access_token'; 140 | clientCredentialsFlow(scopes, dets.tokenUrl, key); 141 | return; 142 | } 143 | else if(authSchemes[key].grantTypes) { 144 | // 1.2 support 145 | var o = authSchemes[key].grantTypes; 146 | for(var t in o) { 147 | if(o.hasOwnProperty(t) && t === 'implicit') { 148 | var dets = o[t]; 149 | var ep = dets.loginEndpoint.url; 150 | url = dets.loginEndpoint.url + '?response_type=token'; 151 | window.swaggerUi.tokenName = dets.tokenName; 152 | } 153 | else if (o.hasOwnProperty(t) && t === 'accessCode') { 154 | var dets = o[t]; 155 | var ep = dets.tokenRequestEndpoint.url; 156 | url = dets.tokenRequestEndpoint.url + '?response_type=code'; 157 | window.swaggerUi.tokenName = dets.tokenName; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | redirect_uri = redirectUrl; 165 | 166 | url += '&redirect_uri=' + encodeURIComponent(redirectUrl); 167 | url += '&realm=' + encodeURIComponent(realm); 168 | url += '&client_id=' + encodeURIComponent(clientId); 169 | url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator)); 170 | url += '&state=' + encodeURIComponent(state); 171 | for (var key in additionalQueryStringParams) { 172 | url += '&' + key + '=' + encodeURIComponent(additionalQueryStringParams[key]); 173 | } 174 | 175 | window.open(url); 176 | }); 177 | 178 | popupMask.show(); 179 | popupDialog.show(); 180 | return; 181 | } 182 | 183 | 184 | function handleLogout() { 185 | for(key in window.swaggerUi.api.clientAuthorizations.authz){ 186 | window.swaggerUi.api.clientAuthorizations.remove(key) 187 | } 188 | window.enabledScopes = null; 189 | $('.api-ic.ic-on').addClass('ic-off'); 190 | $('.api-ic.ic-on').removeClass('ic-on'); 191 | 192 | // set the info box 193 | $('.api-ic.ic-warning').addClass('ic-error'); 194 | $('.api-ic.ic-warning').removeClass('ic-warning'); 195 | } 196 | 197 | function initOAuth(opts) { 198 | var o = (opts||{}); 199 | var errors = []; 200 | 201 | appName = (o.appName||errors.push('missing appName')); 202 | popupMask = (o.popupMask||$('#api-common-mask')); 203 | popupDialog = (o.popupDialog||$('.api-popup-dialog')); 204 | clientId = (o.clientId||errors.push('missing client id')); 205 | clientSecret = (o.clientSecret||null); 206 | realm = (o.realm||errors.push('missing realm')); 207 | scopeSeparator = (o.scopeSeparator||' '); 208 | additionalQueryStringParams = (o.additionalQueryStringParams||{}); 209 | 210 | if(errors.length > 0){ 211 | log('auth unable initialize oauth: ' + errors); 212 | return; 213 | } 214 | 215 | $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); 216 | $('.api-ic').unbind(); 217 | $('.api-ic').click(function(s) { 218 | if($(s.target).hasClass('ic-off')) 219 | handleLogin(); 220 | else { 221 | handleLogout(); 222 | } 223 | false; 224 | }); 225 | } 226 | 227 | function clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) { 228 | var params = { 229 | 'client_id': clientId, 230 | 'client_secret': clientSecret, 231 | 'scope': scopes.join(' '), 232 | 'grant_type': 'client_credentials' 233 | } 234 | $.ajax( 235 | { 236 | url : tokenUrl, 237 | type: "POST", 238 | data: params, 239 | success:function(data, textStatus, jqXHR) 240 | { 241 | onOAuthComplete(data,OAuthSchemeKey); 242 | }, 243 | error: function(jqXHR, textStatus, errorThrown) 244 | { 245 | onOAuthComplete(""); 246 | } 247 | }); 248 | 249 | } 250 | 251 | window.processOAuthCode = function processOAuthCode(data) { 252 | var OAuthSchemeKey = data.state; 253 | var params = { 254 | 'client_id': clientId, 255 | 'code': data.code, 256 | 'grant_type': 'authorization_code', 257 | 'redirect_uri': redirect_uri 258 | }; 259 | 260 | if (clientSecret) { 261 | params.client_secret = clientSecret; 262 | } 263 | 264 | $.ajax( 265 | { 266 | url : window.swaggerUi.tokenUrl, 267 | type: "POST", 268 | data: params, 269 | success:function(data, textStatus, jqXHR) 270 | { 271 | onOAuthComplete(data, OAuthSchemeKey); 272 | }, 273 | error: function(jqXHR, textStatus, errorThrown) 274 | { 275 | onOAuthComplete(""); 276 | } 277 | }); 278 | }; 279 | 280 | window.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) { 281 | if(token) { 282 | if(token.error) { 283 | var checkbox = $('input[type=checkbox],.secured') 284 | checkbox.each(function(pos){ 285 | checkbox[pos].checked = false; 286 | }); 287 | alert(token.error); 288 | } 289 | else { 290 | var b = token[window.swaggerUi.tokenName]; 291 | if (!OAuthSchemeKey){ 292 | OAuthSchemeKey = token.state; 293 | } 294 | if(b){ 295 | // if all roles are satisfied 296 | var o = null; 297 | $.each($('.auth .api-ic .api_information_panel'), function(k, v) { 298 | var children = v; 299 | if(children && children.childNodes) { 300 | var requiredScopes = []; 301 | $.each((children.childNodes), function (k1, v1){ 302 | var inner = v1.innerHTML; 303 | if(inner) 304 | requiredScopes.push(inner); 305 | }); 306 | var diff = []; 307 | for(var i=0; i < requiredScopes.length; i++) { 308 | var s = requiredScopes[i]; 309 | if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) { 310 | diff.push(s); 311 | } 312 | } 313 | if(diff.length > 0){ 314 | o = v.parentNode.parentNode; 315 | $(o.parentNode).find('.api-ic.ic-on').addClass('ic-off'); 316 | $(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on'); 317 | 318 | // sorry, not all scopes are satisfied 319 | $(o).find('.api-ic').addClass('ic-warning'); 320 | $(o).find('.api-ic').removeClass('ic-error'); 321 | } 322 | else { 323 | o = v.parentNode.parentNode; 324 | $(o.parentNode).find('.api-ic.ic-off').addClass('ic-on'); 325 | $(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off'); 326 | 327 | // all scopes are satisfied 328 | $(o).find('.api-ic').addClass('ic-info'); 329 | $(o).find('.api-ic').removeClass('ic-warning'); 330 | $(o).find('.api-ic').removeClass('ic-error'); 331 | } 332 | } 333 | }); 334 | window.swaggerUi.api.clientAuthorizations.add(OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header')); 335 | } 336 | } 337 | } 338 | }; 339 | -------------------------------------------------------------------------------- /third-party/swagger/lib/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.7.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /third-party/swagger/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 1.1.2 2 | 3 | (function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('