infrakit: Move the hyperkit instance plugin into the source directory

- The tools directory ideally should not contain source code
- Removes double vendoring of packagages
- Makes it easer to hook the build into the top-level Makefile

Eventually, the plugin should be moved to the infrakit repo.

Signed-off-by: Rolf Neugebauer <rolf.neugebauer@docker.com>
This commit is contained in:
Rolf Neugebauer
2017-03-25 12:41:22 +01:00
parent 30c39863c1
commit 48845bcfd9
418 changed files with 23393 additions and 2389 deletions

201
vendor/github.com/docker/infrakit/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

235
vendor/github.com/docker/infrakit/README.md generated vendored Normal file
View File

@@ -0,0 +1,235 @@
InfraKit
========
[![Circle CI](https://circleci.com/gh/docker/infrakit.png?style=shield&circle-token=50d2063f283f98b7d94746416c979af3102275b5)](https://circleci.com/gh/docker/infrakit)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/infrakit)](https://goreportcard.com/report/github.com/docker/infrakit)
[![codecov.io](https://codecov.io/github/docker/infrakit/coverage.svg?branch=master&token=z08ZKeIJfA)](https://codecov.io/github/docker/infrakit?branch=master)
_InfraKit_ is a toolkit for creating and managing declarative, self-healing infrastructure.
It breaks infrastructure automation down into simple, pluggable components. These components work together to actively
ensure the infrastructure state matches the user's specifications.
Although _InfraKit_ emphasizes primitives for building self-healing infrastructure, it also can be used passively like
conventional tools.
To get started, try the [tutorial](docs/tutorial.md).
### Who InfraKit is for
_InfraKit_ is designed to support setup and management of base infrastructure. For example, it can help you manage a
system like a cluster or container orchestrator, ideally relieving you of building custom release and maintenance tools.
As a result, it is a low-level tool intended to be used by infrastructure operators directly or indirectly
(as a toolkit) through a higher-level tool. Since _InfraKit_ is pluggable, it allows you to manage resources in diverse
environments while using shared components and consistent interfaces.
## Plugins
_InfraKit_ makes extensive use of _Plugins_ to manage arbitrary systems in diverse environments, which can be composed
to meet different needs.
See the [plugins](docs/plugins) documentation for more details.
## Building
### Your Environment
Make sure you check out the project following a convention for building Go projects. For example,
```shell
# Install Go - https://golang.org/dl/
# Assuming your go compiler is in /usr/local/go
export PATH=/usr/local/go/bin:$PATH
# Your dev environment
mkdir -p ~/go
export GOPATH=!$
export PATH=$GOPATH/bin:$PATH
mkdir -p ~/go/src/github.com/docker
cd !$
git clone git@github.com:docker/infrakit.git
cd infrakit
```
We recommended go version 1.7.1 or greater for all platforms.
Also install a few build tools:
```shell
make get-tools
```
### Running tests
```shell
$ make ci
```
### Binaries
```shell
$ make binaries
```
Executables will be placed in the `./build` directory.
This will produce binaries for tools and several reference Plugin implementations:
+ [`infrakit`](cmd/cli/README.md): a command line interface to interact with plugins
+ [`infrakit-group-default`](cmd/group/README.md): the default [Group plugin](./pkg/spi/group)
+ [`infrakit-instance-file`](examples/instance/file): an Instance plugin using dummy files to represent instances
+ [`infrakit-instance-terraform`](examples/instance/terraform):
an Instance plugin integrating [Terraform](https://www.terraform.io)
+ [`infrakit-instance-vagrant`](examples/instance/vagrant):
an Instance plugin using [Vagrant](https://www.vagrantup.com/)
+ [`infrakit-instance-maas`](examples/instance/maas):
an Instance plugin using [MaaS](https://maas.io)
+ [`infrakit-flavor-vanilla`](examples/flavor/vanilla):
a Flavor plugin for plain vanilla set up with user data and labels
+ [`infrakit-flavor-zookeeper`](examples/flavor/zookeeper):
a Flavor plugin for [Apache ZooKeeper](https://zookeeper.apache.org/) ensemble members
+ [`infrakit-flavor-swarm`](examples/flavor/swarm):
a Flavor plugin for Docker in [Swarm mode](https://docs.docker.com/engine/swarm/).
All provided binaries have a `help` sub-command to get usage and a `version` sub-command to identify the build revision.
# Design
## Configuration
_InfraKit_ uses JSON for configuration because it is composable and a widely accepted format for many
infrastructure SDKs and tools. Since the system is highly component-driven, our JSON format follows
simple patterns to support the composition of components.
A common pattern for a JSON object looks like this:
```json
{
"SomeKey": "ValueForTheKey",
"Properties": {
}
}
```
There is only one `Properties` field in this JSON and its value is a JSON object. The opaque
JSON value for `Properties` is decoded via the Go `Spec` struct defined within the package of the plugin --
for example -- [`vanilla.Spec`](pkg/plugin/flavor/vanilla/flavor.go).
The JSON above is a _value_, but the type of the value belongs outside the structure. For example, the
default Group [Spec](pkg/plugin/group/types/types.go) is composed of an Instance plugin, a Flavor plugin, and an
Allocation:
```json
{
"ID": "name-of-the-group",
"Properties": {
"Allocation": {
},
"Instance": {
"Plugin": "name-of-the-instance-plugin",
"Properties": {
}
},
"Flavor": {
"Plugin": "name-of-the-flavor-plugin",
"Properties": {
}
}
}
}
```
The group's Spec has `Instance` and `Flavor` fields which are used to indicate the type, and the value of the
fields follow the pattern of `<some_key>` and `Properties` as shown above.
The `Allocation` determines how the Group is managed. Allocation has two properties:
- `Size`: an integer for the number of instances to maintain in the Group
- `LogicalIDs`: a list of string identifiers, one will be associated with each Instance
Exactly one of these fields must be set, which defines whether the Group is treated as 'cattle' (`Size`) or 'pets'
(`LogicalIDs`). It is up to the Instance and Flavor plugins to determine how to use `LogicalID` values.
As an example, if you wanted to manage a Group of NGINX servers, you could
write a custom Group plugin for ultimate customization. The most concise configuration looks something like this:
```json
{
"ID": "nginx",
"Plugin": "my-nginx-group-plugin",
"Properties": {
"port": 8080
}
}
````
However, you would likely prefer to use the default Group plugin and implement a Flavor plugin to focus on
application-specific behavior. This gives you immediate support for any infrastructure that has an Instance plugin.
Your resulting configuration might look something like this:
```json
{
"ID": "nginx",
"Plugin": "group",
"Properties": {
"Allocation": {
"Size": 10
},
"Instance": {
"Plugin": "aws",
"Properties": {
"region": "us-west-2",
"ami": "ami-123456"
}
},
"Flavor": {
"Plugin": "nginx",
"Properties": {
"port": 8080
}
}
}
}
```
Once the configuration is ready, you will tell a Group plugin to
+ watch it
+ update it
+ destroy it
Watching the group as specified in the configuration means that the Group plugin will create
the instances if they don't already exist. New instances will be created if for any reason
existing instances have disappeared such that the state doesn't match your specifications.
Updating the group tells the Group plugin that your configuration may have changed. It will
then determine the changes necessary to ensure the state of the infrastructure matches the new
specification.
## Docs
Additional documentation can be found [here](docs).
## Reporting security issues
The maintainers take security seriously. If you discover a security issue,
please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[security@docker.com](mailto:security@docker.com).
Security reports are greatly appreciated and we will publicly thank you for it.
We also like to send gifts—if you're into Docker schwag, make sure to let
us know. We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
## Design goals
_InfraKit_ is currently focused on supporting setup and management of base infrastructure, such as a cluster
orchestrator. The image below illustrates an architecture we are working towards supporting - a Docker cluster in Swarm
mode.
![arch image](docs/images/arch.png)
This configuration co-locates _InfraKit_ with Swarm manager nodes and offers high availability of _InfraKit_ itself and
Swarm managers (using attached storage). _InfraKit_ is shown managing two groups - managers and workers that will be
continuously monitored, and may be modified with rolling updates.
Countless configurations are possible with _InfraKit_, but we believe achieving support for this configuration will
enable a large number of real-world use cases.
## Copyright and license
Copyright © 2016 Docker, Inc. All rights reserved. Released under the Apache 2.0
license. See [LICENSE](LICENSE) for the full license text.

View File

@@ -0,0 +1,74 @@
package server
import (
"fmt"
"net/http"
"strings"
log "github.com/Sirupsen/logrus"
)
// Interceptor implements http Handler and is used to intercept incoming requests to subscribe
// to topics and perform some validations before allowing the subscription to be established.
// Tasks that the interceptor can do include authn and authz, topic validation, etc.
type Interceptor struct {
// Do is the body of the http handler -- required
Do http.HandlerFunc
// Pre is called to before actual subscription to a topic happens.
// This is the hook where validation and authentication / authorization checks happen.
Pre func(topic string, headers map[string][]string) error
// Post is called when the client disconnects. This is optional.
Post func(topic string)
}
// ServeHTTP calls the before and after subscribe methods.
func (i *Interceptor) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
topic := clean(req.URL.Query().Get("topic"))
// need to strip out the / because it was added by the client
if strings.Index(topic, "/") == 0 {
topic = topic[1:]
}
err := i.Pre(topic, req.Header)
if err != nil {
log.Warningln("Error:", err)
http.Error(rw, err.Error(), getStatusCode(err))
return
}
i.Do.ServeHTTP(rw, req)
if i.Post != nil {
i.Post(topic)
}
}
func getStatusCode(e error) int {
switch e.(type) {
case ErrInvalidTopic:
return http.StatusNotFound
case ErrNotAuthorized:
return http.StatusUnauthorized
default:
return http.StatusInternalServerError
}
}
// ErrInvalidTopic is the error raised when topic is invalid
type ErrInvalidTopic string
func (e ErrInvalidTopic) Error() string {
return fmt.Sprintf("invalid topic: %s", string(e))
}
// ErrNotAuthorized is the error raised when the user isn't authorized
type ErrNotAuthorized string
func (e ErrNotAuthorized) Error() string {
return fmt.Sprintf("not authorized: %s", string(e))
}

View File

@@ -0,0 +1,27 @@
package server
import (
"net"
"net/http"
)
// ListenAndServeOnSocket starts a minimal server (mostly for testing) listening at the given unix socket path.
func ListenAndServeOnSocket(socketPath string, optionalURLPattern ...string) (*Broker, error) {
urlPattern := "/"
if len(optionalURLPattern) > 0 {
urlPattern = optionalURLPattern[0]
}
listener, err := net.Listen("unix", socketPath)
if err != nil {
return nil, err
}
broker := NewBroker()
mux := http.NewServeMux()
mux.Handle(urlPattern, broker)
httpServer := &http.Server{
Handler: mux,
}
go httpServer.Serve(listener)
return broker, nil
}

View File

@@ -0,0 +1,266 @@
package server
import (
"bytes"
"fmt"
"net/http"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/armon/go-radix"
"github.com/docker/infrakit/pkg/types"
)
// the amount of time to wait when pushing a message to
// a slow client or a client that closed after `range clients` started.
const patience time.Duration = time.Second * 1
type subscription struct {
topic string
exactMatch bool
ch chan []byte
}
type event struct {
topic string
data []byte
}
// Broker is the event message broker
type Broker struct {
// Close this to stop
stop chan struct{}
// Close this to stop the run loop
finish chan struct{}
// Events are pushed to this channel by the main events-gathering routine
notifier chan *event
// New client connections
newClients chan subscription
// Closed client connections
closingClients chan subscription
// Client connections registry
clients *radix.Tree
// how many clients
count int
}
// NewBroker returns an instance of the broker
func NewBroker() *Broker {
b := &Broker{
stop: make(chan struct{}),
finish: make(chan struct{}),
notifier: make(chan *event, 1),
newClients: make(chan subscription),
closingClients: make(chan subscription),
clients: radix.New(),
}
go b.run()
return b
}
// Stop stops the broker and exits the goroutine
func (b *Broker) Stop() {
close(b.stop)
}
func clean(topic string) string {
if len(topic) == 0 || topic == "." {
return "/"
}
if topic[0] != '/' {
return "/" + topic
}
return topic
}
//Check the topic ends with `/`
func checkExactMatch(topic string) bool {
return strings.LastIndex(topic, "/") != len(topic)-1
}
// Publish publishes a message at the topic
func (b *Broker) Publish(topic string, data interface{}, optionalTimeout ...time.Duration) error {
any, err := types.AnyValue(data)
if err != nil {
return err
}
topic = clean(topic)
if len(optionalTimeout) > 0 {
select {
case b.notifier <- &event{topic: topic, data: any.Bytes()}:
case <-time.After(optionalTimeout[0]):
return fmt.Errorf("timeout sending %v", topic)
}
} else {
b.notifier <- &event{topic: topic, data: any.Bytes()}
}
return nil
}
// ServerHTTP implements the HTTP handler
func (b *Broker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
defer func() {
if v := recover(); v != nil {
log.Warningln("broker.ServeHTTP recovered:", v)
}
}()
topic := clean(req.URL.Query().Get("topic"))
// flusher is required for streaming
flusher, ok := rw.(http.Flusher)
if !ok {
http.Error(rw, "Streaming unsupported!", http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "text/event-stream")
rw.Header().Set("Cache-Control", "no-cache")
rw.Header().Set("Connection", "keep-alive")
rw.Header().Set("Access-Control-Allow-Origin", "*")
// Each connection registers its own message channel with the Broker's connections registry
messageChan := make(chan []byte)
// Signal the broker that we have a new connection
b.newClients <- subscription{topic: topic, exactMatch: checkExactMatch(topic), ch: messageChan}
// Remove this client from the map of connected clients
// when this handler exits.
defer func() {
b.closingClients <- subscription{topic: topic, ch: messageChan}
}()
// Listen to connection close and un-register messageChan
notify := rw.(http.CloseNotifier).CloseNotify()
for {
select {
case <-b.stop:
close(b.finish)
return
case <-notify:
return
default:
// Write to the ResponseWriter
// Server Sent Events compatible
fmt.Fprintf(rw, "data: %s\n\n", <-messageChan)
// Flush the data immediatly instead of buffering it for later.
flusher.Flush()
}
}
}
func (b *Broker) run() {
for {
select {
case <-b.finish:
// Disconnect all clients
b.clients.Walk(
func(key string, value interface{}) bool {
chset, ok := value.(map[chan []byte]bool)
if !ok {
panic("assert-failed")
}
for ch := range chset {
log.Infoln("Closing client connection at", key, "ch=", ch)
close(ch)
}
return false
})
log.Infoln("Broker finished")
return
case subscription := <-b.newClients:
// A new client has connected.
// Register their message channel
subs := map[chan []byte]bool{subscription.ch: subscription.exactMatch}
v, has := b.clients.Get(subscription.topic)
if has {
if v, ok := v.(map[chan []byte]bool); !ok {
panic("assert-failed: not a map of channels")
} else {
v[subscription.ch] = subscription.exactMatch
subs = v
}
}
b.clients.Insert(subscription.topic, subs)
b.count++
log.Infof("Connected: topic=%s => %d registered clients, ch=%v", subscription.topic, b.count, subscription.ch)
case subscription := <-b.closingClients:
// A client has dettached and we want to stop sending messages
if v, has := b.clients.Get(subscription.topic); has {
if subs, ok := v.(map[chan []byte]bool); !ok {
panic("assert-failed: not a map of channels")
} else {
delete(subs, subscription.ch)
if len(subs) == 0 {
b.clients.Delete(subscription.topic)
} else {
b.clients.Insert(subscription.topic, subs)
}
b.count--
log.Infof("Disconnected: topic=%s => %d registered clients, ch=%v", subscription.topic, b.count, subscription.ch)
}
}
case event, open := <-b.notifier:
if !open {
log.Infoln("Stopping broker")
return
}
// Remove any \n because it's meaningful in SSE spec.
// We could use base64 encode, but it hurts interoperability with browser/ javascript clients.
data := bytes.Replace(event.data, []byte("\n"), nil, -1)
b.clients.WalkPath(event.topic,
func(key string, value interface{}) bool {
chset, ok := value.(map[chan []byte]bool)
if !ok {
panic("assert-failed")
}
for ch, exact := range chset {
if exact && event.topic != key {
return false
}
select {
case ch <- data:
case <-time.After(patience):
log.Print("Skipping client.")
}
}
return false
})
}
}
}

16
vendor/github.com/docker/infrakit/pkg/cli/logging.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
package cli
import log "github.com/Sirupsen/logrus"
// DefaultLogLevel is the default log level value.
var DefaultLogLevel = len(log.AllLevels) - 2
// SetLogLevel adjusts the logrus level.
func SetLogLevel(level int) {
if level > len(log.AllLevels)-1 {
level = len(log.AllLevels) - 1
} else if level < 0 {
level = 0
}
log.SetLevel(log.AllLevels[level])
}

View File

@@ -0,0 +1,47 @@
package cli
import (
"fmt"
"io/ioutil"
"os"
"path"
log "github.com/Sirupsen/logrus"
"github.com/docker/infrakit/pkg/discovery"
"github.com/docker/infrakit/pkg/rpc/server"
)
// EnsureDirExists makes sure the directory where the socket file will be placed exists.
func EnsureDirExists(dir string) {
os.MkdirAll(dir, 0700)
}
// RunPlugin runs a plugin server, advertising with the provided name for discovery.
// The plugin should conform to the rpc call convention as implemented in the rpc package.
func RunPlugin(name string, plugin server.VersionedInterface, more ...server.VersionedInterface) {
dir := discovery.Dir()
EnsureDirExists(dir)
socketPath := path.Join(dir, name)
pidPath := path.Join(dir, name+".pid")
stoppable, err := server.StartPluginAtPath(socketPath, plugin, more...)
if err != nil {
log.Error(err)
}
// write PID file
err = ioutil.WriteFile(pidPath, []byte(fmt.Sprintf("%v", os.Getpid())), 0644)
if err != nil {
log.Error(err)
}
log.Infoln("PID file at", pidPath)
if stoppable != nil {
stoppable.AwaitStopped()
}
// clean up
os.Remove(pidPath)
log.Infoln("Removed PID file at", pidPath)
}

45
vendor/github.com/docker/infrakit/pkg/cli/version.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
)
var (
// Version is the build release identifier.
Version = "Unspecified"
// Revision is the build source control revision.
Revision = "Unspecified"
)
var info = map[string]map[string]interface{}{}
// RegisterInfo allows any packages that use this register additional information to be displayed by the command.
// For example, a swarm flavor could register the docker api version. This allows us to selectively incorporate
// only required dependencies based on package registration (in their init()) without explicitly pulling unused
// dependencies.
func RegisterInfo(key string, data map[string]interface{}) {
info[key] = data
}
// VersionCommand creates a cobra Command that prints build version information.
func VersionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "print build version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("\n%-24s: %v", "Version", Version)
fmt.Printf("\n%-24s: %v", "Revision", Revision)
for k, m := range info {
fmt.Printf("\n\n%s", k)
for kk, vv := range m {
fmt.Printf("\n%-24s: %v", kk, vv)
}
fmt.Printf("\n")
}
fmt.Println()
},
}
}

106
vendor/github.com/docker/infrakit/pkg/discovery/dir.go generated vendored Normal file
View File

@@ -0,0 +1,106 @@
package discovery
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
log "github.com/Sirupsen/logrus"
"github.com/docker/infrakit/pkg/plugin"
)
type errNotUnixSocket string
func (e errNotUnixSocket) Error() string {
return string(e)
}
// IsErrNotUnixSocket returns true if the error is due to the file not being a valid unix socket.
func IsErrNotUnixSocket(e error) bool {
_, is := e.(errNotUnixSocket)
return is
}
type dirPluginDiscovery struct {
dir string
lock sync.Mutex
}
// Find returns a plugin by name
func (r *dirPluginDiscovery) Find(name plugin.Name) (*plugin.Endpoint, error) {
lookup, _ := name.GetLookupAndType()
plugins, err := r.List()
if err != nil {
return nil, err
}
p, exists := plugins[lookup]
if !exists {
return nil, fmt.Errorf("Plugin not found: %s (looked up using %s)", name, lookup)
}
return p, nil
}
// newDirPluginDiscovery creates a registry instance with the given file directory path.
func newDirPluginDiscovery(dir string) (*dirPluginDiscovery, error) {
d := &dirPluginDiscovery{dir: dir}
// Perform a dummy read to catch obvious issues early (such as the directory not existing).
_, err := d.List()
return d, err
}
func (r *dirPluginDiscovery) dirLookup(entry os.FileInfo) (*plugin.Endpoint, error) {
if entry.Mode()&os.ModeSocket != 0 {
socketPath := filepath.Join(r.dir, entry.Name())
return &plugin.Endpoint{
Protocol: "unix",
Address: socketPath,
Name: entry.Name(),
}, nil
}
return nil, errNotUnixSocket(fmt.Sprintf("File is not a socket: %s", entry))
}
// List returns a list of plugins known, keyed by the name
func (r *dirPluginDiscovery) List() (map[string]*plugin.Endpoint, error) {
r.lock.Lock()
defer r.lock.Unlock()
log.Debugln("Opening:", r.dir)
entries, err := ioutil.ReadDir(r.dir)
if err != nil {
return nil, err
}
plugins := map[string]*plugin.Endpoint{}
for _, entry := range entries {
if !entry.IsDir() {
instance, err := r.dirLookup(entry)
if err != nil {
if !IsErrNotUnixSocket(err) {
log.Warningln("Loading plugin err=", err)
}
continue
}
if instance == nil {
log.Warningln("Plugin in nil=")
continue
}
log.Debugln("Discovered plugin at", instance.Address)
plugins[instance.Name] = instance
}
}
return plugins, nil
}

View File

@@ -0,0 +1,60 @@
package discovery
import (
"fmt"
"os"
"os/user"
"path"
"github.com/docker/infrakit/pkg/plugin"
)
// Plugins provides access to plugin discovery.
type Plugins interface {
// Find looks up the plugin by name. The name can be of the form $lookup[/$subtype]. See GetLookupAndType().
Find(name plugin.Name) (*plugin.Endpoint, error)
List() (map[string]*plugin.Endpoint, error)
}
const (
// PluginDirEnvVar is the environment variable that may be used to customize the plugin discovery path.
PluginDirEnvVar = "INFRAKIT_PLUGINS_DIR"
)
// Dir returns the directory to use for plugin discovery, which may be customized by the environment.
func Dir() string {
if pluginDir := os.Getenv(PluginDirEnvVar); pluginDir != "" {
return pluginDir
}
home := os.Getenv("HOME")
if usr, err := user.Current(); err == nil {
home = usr.HomeDir
}
return path.Join(home, ".infrakit/plugins")
}
// NewPluginDiscovery creates a plugin discovery based on the environment configuration.
func NewPluginDiscovery() (Plugins, error) {
return NewPluginDiscoveryWithDirectory(Dir())
}
// NewPluginDiscoveryWithDirectory creates a plugin discovery based on the directory given.
func NewPluginDiscoveryWithDirectory(pluginDir string) (Plugins, error) {
stat, err := os.Stat(pluginDir)
if err == nil {
if !stat.IsDir() {
return nil, fmt.Errorf("Plugin dir %s is a file", pluginDir)
}
} else {
if os.IsNotExist(err) {
if err := os.MkdirAll(pluginDir, 0700); err != nil {
return nil, fmt.Errorf("Failed to create plugin dir %s: %s", pluginDir, err)
}
} else {
return nil, fmt.Errorf("Failed to access plugin dir %s: %s", pluginDir, err)
}
}
return newDirPluginDiscovery(pluginDir)
}

View File

@@ -0,0 +1,30 @@
package metadata
import (
"path/filepath"
"strings"
"github.com/docker/infrakit/pkg/spi/metadata"
)
// Path returns the path compoments of a / separated path
func Path(path string) metadata.Path {
return metadata.Path(strings.Split(filepath.Clean(path), "/"))
}
// PathFromStrings returns the path from a list of strings
func PathFromStrings(a string, b ...string) metadata.Path {
if a != "" {
return metadata.Path(append([]string{a}, b...))
}
return metadata.Path(b)
}
// String returns the string representation of path
func String(p metadata.Path) string {
s := strings.Join([]string(p), "/")
if len(s) == 0 {
return "."
}
return s
}

View File

@@ -0,0 +1,90 @@
package metadata
import (
log "github.com/Sirupsen/logrus"
"github.com/docker/infrakit/pkg/spi/metadata"
"github.com/docker/infrakit/pkg/types"
)
// NewPluginFromData creates a plugin out of a simple data map. Note the updates to the map
// is not guarded and synchronized with the reads.
func NewPluginFromData(data map[string]interface{}) metadata.Plugin {
return &plugin{data: data}
}
// NewPluginFromChannel returns a plugin implementation where reads and writes are serialized
// via channel of functions that have a view to the metadata. Closing the write channel stops
// the serialized read/writes and falls back to unserialized reads.
func NewPluginFromChannel(writes <-chan func(map[string]interface{})) metadata.Plugin {
readChan := make(chan func(map[string]interface{}))
p := &plugin{reads: readChan}
go func() {
defer func() {
if r := recover(); r != nil {
log.Warningln("Plugin stopped:", r)
}
}()
data := map[string]interface{}{}
for {
select {
case writer, open := <-writes:
if !open {
close(readChan)
p.reads = nil
return
}
writer(data)
case reader := <-p.reads:
copy := data
reader(copy)
}
}
}()
return p
}
type plugin struct {
data map[string]interface{}
reads chan func(data map[string]interface{})
}
// List returns a list of *child nodes* given a path, which is specified as a slice
// where for i > j path[i] is the parent of path[j]
func (p *plugin) List(path metadata.Path) ([]string, error) {
if p.reads == nil && p.data != nil {
return List(path, p.data), nil
}
children := make(chan []string)
p.reads <- func(data map[string]interface{}) {
children <- List(path, data)
return
}
return <-children, nil
}
// Get retrieves the value at path given.
func (p *plugin) Get(path metadata.Path) (*types.Any, error) {
if p.reads == nil && p.data != nil {
return types.AnyValue(Get(path, p.data))
}
value := make(chan *types.Any)
err := make(chan error)
p.reads <- func(data map[string]interface{}) {
v, e := types.AnyValue(Get(path, data))
value <- v
err <- e
return
}
return <-value, <-err
}

View File

@@ -0,0 +1,230 @@
package metadata
import (
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"github.com/docker/infrakit/pkg/types"
)
var (
indexRoot = "\\[(([+|-]*[0-9]+)|((.*)=(.*)))\\]$"
arrayIndexExp = regexp.MustCompile("(.*)" + indexRoot)
indexExp = regexp.MustCompile("^" + indexRoot)
)
// Put sets the attribute of an object at path to the given value
func Put(path []string, value interface{}, object map[string]interface{}) bool {
return put(path, value, object)
}
// Get returns the attribute of the object at path
func Get(path []string, object interface{}) interface{} {
return get(path, object)
}
// GetValue returns the attribute of the object at path, as serialized blob
func GetValue(path []string, object interface{}) (*types.Any, error) {
if any, is := object.(*types.Any); is {
return any, nil
}
return types.AnyValue(Get(path, object))
}
// List lists the members at the path
func List(path []string, object interface{}) []string {
list := []string{}
v := get(path, object)
if v == nil {
return list
}
val := reflect.Indirect(reflect.ValueOf(v))
if any, is := v.(*types.Any); is {
var temp interface{}
if err := any.Decode(&temp); err == nil {
val = reflect.ValueOf(temp)
}
}
switch val.Kind() {
case reflect.Slice:
// this is a slice, so return the name as '[%d]'
for i := 0; i < val.Len(); i++ {
list = append(list, fmt.Sprintf("[%d]", i))
}
case reflect.Map:
for _, k := range val.MapKeys() {
list = append(list, k.String())
}
case reflect.Struct:
vt := val.Type()
for i := 0; i < vt.NumField(); i++ {
if vt.Field(i).PkgPath == "" {
list = append(list, vt.Field(i).Name)
}
}
}
sort.Strings(list)
return list
}
func put(p []string, value interface{}, store map[string]interface{}) bool {
if len(p) == 0 {
return false
}
key := p[0]
if key == "" {
return put(p[1:], value, store)
}
// check if key is an array index of the form <1>[<2>]
matches := arrayIndexExp.FindStringSubmatch(key)
if len(matches) > 2 && matches[1] != "" {
key = matches[1]
p = append([]string{key, fmt.Sprintf("[%s]", matches[2])}, p[1:]...)
return put(p, value, store)
}
s := reflect.Indirect(reflect.ValueOf(store))
switch s.Kind() {
case reflect.Slice:
return false // not supported
case reflect.Map:
if reflect.TypeOf(p[0]).AssignableTo(s.Type().Key()) {
m := s.MapIndex(reflect.ValueOf(p[0]))
if !m.IsValid() {
m = reflect.ValueOf(map[string]interface{}{})
s.SetMapIndex(reflect.ValueOf(p[0]), m)
}
if len(p) > 1 {
return put(p[1:], value, m.Interface().(map[string]interface{}))
}
s.SetMapIndex(reflect.ValueOf(p[0]), reflect.ValueOf(value))
return true
}
}
return false
}
func get(path []string, object interface{}) (value interface{}) {
if f, is := object.(func() interface{}); is {
object = f()
}
if len(path) == 0 {
return object
}
if any, is := object.(*types.Any); is {
var temp interface{}
if err := any.Decode(&temp); err == nil {
return get(path, temp)
}
return nil
}
key := path[0]
switch key {
case ".":
return object
case "":
return get(path[1:], object)
}
// check if key is an array index of the form <1>[<2>]
matches := arrayIndexExp.FindStringSubmatch(key)
if len(matches) > 2 && matches[1] != "" {
key = matches[1]
path = append([]string{key, fmt.Sprintf("[%s]", matches[2])}, path[1:]...)
return get(path, object)
}
v := reflect.Indirect(reflect.ValueOf(object))
switch v.Kind() {
case reflect.Slice:
i := 0
matches = indexExp.FindStringSubmatch(key)
if len(matches) > 0 {
if matches[2] != "" {
// numeric index
if index, err := strconv.Atoi(matches[1]); err == nil {
switch {
case index >= 0 && v.Len() > index:
i = index
case index < 0 && v.Len() > -index: // negative index like python
i = v.Len() + index
}
}
return get(path[1:], v.Index(i).Interface())
} else if matches[3] != "" {
// equality search index for 'field=check'
lhs := matches[4] // supports another select expression for extracting deeply from the struct
rhs := matches[5]
// loop through the array looking for field that matches the check value
for j := 0; j < v.Len(); j++ {
if el := get(tokenize(lhs), v.Index(j).Interface()); el != nil {
if fmt.Sprintf("%v", el) == rhs {
return get(path[1:], v.Index(j).Interface())
}
}
}
}
}
case reflect.Map:
value := v.MapIndex(reflect.ValueOf(key))
if value.IsValid() {
return get(path[1:], value.Interface())
}
case reflect.Struct:
fv := v.FieldByName(key)
if !fv.IsValid() {
return nil
}
if !fv.CanInterface() {
return nil
}
return get(path[1:], fv.Interface())
}
return nil
}
// With quoting to support azure rm type names: e.g. Microsoft.Network/virtualNetworks
// This will split a sting like /Resources/'Microsoft.Network/virtualNetworks'/managerSubnet/Name" into
// [ , Resources, Microsoft.Network/virtualNetworks, managerSubnet, Name]
func tokenize(s string) []string {
if len(s) == 0 {
return []string{}
}
a := []string{}
start := 0
quoted := false
for i := 0; i < len(s); i++ {
switch s[i] {
case '/':
if !quoted {
a = append(a, strings.Replace(s[start:i], "'", "", -1))
start = i + 1
}
case '\'':
quoted = !quoted
}
}
if start < len(s)-1 {
a = append(a, strings.Replace(s[start:], "'", "", -1))
}
return a
}

45
vendor/github.com/docker/infrakit/pkg/plugin/name.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package plugin
import (
"fmt"
"strings"
)
// Name is a reference to the plugin. Places where it appears include JSON files as type of field `Plugin`.
type Name string
// GetLookupAndType returns the plugin name for lookup and sub-type supported by the plugin.
// The name follows a microformat of $plugin[/$subtype] where $plugin is used for the discovery / lookup by name.
// The $subtype is used for the Type parameter in the RPC requests.
// Example: instance-file/json means lookup socket file 'instance-file' and the type is 'json'.
func (r Name) GetLookupAndType() (string, string) {
name := string(r)
if first := strings.Index(name, "/"); first >= 0 {
return name[0:first], name[first+1:]
}
return name, ""
}
// String returns the string representation
func (r Name) String() string {
return string(r)
}
// MarshalJSON implements the JSON marshaler interface
func (r Name) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, r.String())), nil
}
// UnmarshalJSON implements the JSON unmarshaler interface
func (r *Name) UnmarshalJSON(data []byte) error {
str := string(data)
start := strings.Index(str, "\"")
last := strings.LastIndex(str, "\"")
if start == 0 && last == len(str)-1 {
str = str[start+1 : last]
} else {
return fmt.Errorf("bad-format-for-name:%v", string(data))
}
*r = Name(str)
return nil
}

97
vendor/github.com/docker/infrakit/pkg/plugin/plugin.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
package plugin
import (
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/template"
"github.com/docker/infrakit/pkg/types"
)
// Spec models a canonical pattern of fields that exist in a struct/ map / union that indicates the block is a plugin.
type Spec struct {
// Plugin is the name of the plugin
Plugin Name
// Properties is the configuration of the plugin
Properties *types.Any
}
// Informer is the interface that gives information about the plugin such as version and interface methods
type Informer interface {
// GetInfo returns metadata about the plugin
GetInfo() (Info, error)
// GetFunctions returns metadata about the plugin's template functions, if the plugin supports templating.
GetFunctions() (map[string][]template.Function, error)
}
// Info is metadata for the plugin
type Info struct {
// Vendor captures vendor-specific information about this plugin
Vendor *spi.VendorInfo
// Implements is a list of plugin interface and versions this plugin supports
Implements []spi.InterfaceSpec
// Interfaces (optional) is a slice of interface descriptions by the type and version
Interfaces []InterfaceDescription `json:",omitempty"`
}
// InterfaceDescription is a holder for RPC interface version and method descriptions
type InterfaceDescription struct {
spi.InterfaceSpec
Methods []MethodDescription
}
// MethodDescription contains information about the RPC method such as the request and response
// example structs. The request value can be used as an example input, possibly with example
// plugin-custom properties if the underlying plugin implements the InputExample interface.
// The response value gives an example of the example response.
type MethodDescription struct {
// Request is the RPC request example
Request Request
// Response is the RPC response example
Response Response
}
// Request models the RPC request payload
type Request struct {
// Version is the version of the JSON RPC protocol
Version string `json:"jsonrpc"`
// Method is the rpc method to use in the payload field 'method'
Method string `json:"method"`
// Params contains example inputs. This can be a zero value struct or one with defaults
Params interface{} `json:"params"`
// ID is the request is
ID string `json:"id"`
}
// Response is the RPC response struct
type Response struct {
// Result is the result of the call
Result interface{} `json:"result"`
// ID is id matching the request ID
ID string `json:"id"`
}
// Endpoint is the address of the plugin service
type Endpoint struct {
// Name is the key used to refer to this plugin in all JSON configs
Name string
// Protocol is the transport protocol -- unix, tcp, etc.
Protocol string
// Address is the how to connect - socket file, host:port, etc.
Address string
}

View File

@@ -0,0 +1,76 @@
package client
import (
"bytes"
log "github.com/Sirupsen/logrus"
"github.com/docker/infrakit/pkg/spi"
"github.com/gorilla/rpc/v2/json2"
"net"
"net/http"
"net/http/httputil"
"sync"
)
type client struct {
http http.Client
addr string
}
// New creates a new Client that communicates with a unix socket and validates the remote API.
func New(socketPath string, api spi.InterfaceSpec) (Client, error) {
dialUnix := func(proto, addr string) (conn net.Conn, err error) {
return net.Dial("unix", socketPath)
}
unvalidatedClient := &client{addr: socketPath, http: http.Client{Transport: &http.Transport{Dial: dialUnix}}}
cl := &handshakingClient{client: unvalidatedClient, iface: api, lock: &sync.Mutex{}}
// check handshake
if err := cl.handshake(); err != nil {
// Note - we still return the client with the possibility of doing a handshake later on
// if we provide an api for the plugin to recheck later. This way, individual components
// can stay running and recalibrate themselves after the user has corrected the problems.
return cl, err
}
return cl, nil
}
func (c client) Addr() string {
return c.addr
}
func (c client) Call(method string, arg interface{}, result interface{}) error {
message, err := json2.EncodeClientRequest(method, arg)
if err != nil {
return err
}
req, err := http.NewRequest("POST", "http://a/", bytes.NewReader(message))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
requestData, err := httputil.DumpRequest(req, true)
if err == nil {
log.Debugf("Sending request %s", string(requestData))
} else {
log.Error(err)
}
resp, err := c.http.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
responseData, err := httputil.DumpResponse(resp, true)
if err == nil {
log.Debugf("Received response %s", string(responseData))
} else {
log.Error(err)
}
return json2.DecodeClientResponse(resp.Body, result)
}

View File

@@ -0,0 +1,88 @@
package client
import (
"fmt"
"github.com/docker/infrakit/pkg/rpc"
"github.com/docker/infrakit/pkg/spi"
"sync"
)
type handshakingClient struct {
client Client
iface spi.InterfaceSpec
// handshakeResult handles the tri-state outcome of handshake state:
// - handshake has not yet completed (nil)
// - handshake completed successfully (non-nil result, nil error)
// - handshake failed (non-nil result, non-nil error)
handshakeResult *handshakeResult
// lock guards handshakeResult
lock *sync.Mutex
}
type handshakeResult struct {
err error
}
type errVersionMismatch string
// Error implements error interface
func (e errVersionMismatch) Error() string {
return string(e)
}
// IsErrVersionMismatch return true if the error is from mismatched api versions.
func IsErrVersionMismatch(e error) bool {
_, is := e.(errVersionMismatch)
return is
}
func (c *handshakingClient) handshake() error {
c.lock.Lock()
defer c.lock.Unlock()
if c.handshakeResult == nil {
req := rpc.ImplementsRequest{}
resp := rpc.ImplementsResponse{}
if err := c.client.Call("Handshake.Implements", req, &resp); err != nil {
return err
}
err := fmt.Errorf("Plugin does not support interface %v", c.iface)
if resp.APIs != nil {
for _, iface := range resp.APIs {
if iface.Name == c.iface.Name {
if iface.Version == c.iface.Version {
err = nil
break
} else {
err = errVersionMismatch(fmt.Sprintf(
"Plugin supports %s interface version %s, client requires %s",
iface.Name,
iface.Version,
c.iface.Version))
}
}
}
}
c.handshakeResult = &handshakeResult{err: err}
}
return c.handshakeResult.err
}
func (c *handshakingClient) Addr() string {
return c.client.Addr()
}
func (c *handshakingClient) Call(method string, arg interface{}, result interface{}) error {
if err := c.handshake(); err != nil {
return err
}
return c.client.Call(method, arg, result)
}

View File

@@ -0,0 +1,48 @@
package client
import (
"encoding/json"
"net"
"net/http"
"github.com/docker/infrakit/pkg/plugin"
"github.com/docker/infrakit/pkg/rpc"
"github.com/docker/infrakit/pkg/template"
)
// NewPluginInfoClient returns a plugin informer that can give metadata about a plugin
func NewPluginInfoClient(socketPath string) *InfoClient {
dialUnix := func(proto, addr string) (conn net.Conn, err error) {
return net.Dial("unix", socketPath)
}
return &InfoClient{client: &http.Client{Transport: &http.Transport{Dial: dialUnix}}}
}
// InfoClient is the client for retrieving plugin info
type InfoClient struct {
client *http.Client
}
// GetInfo implements the Info interface and returns the metadata about the plugin
func (i *InfoClient) GetInfo() (plugin.Info, error) {
meta := plugin.Info{}
resp, err := i.client.Get("http://d" + rpc.URLAPI)
if err != nil {
return meta, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&meta)
return meta, err
}
// GetFunctions returns metadata about the plugin's template functions, if the plugin supports templating.
func (i *InfoClient) GetFunctions() (map[string][]template.Function, error) {
meta := map[string][]template.Function{}
resp, err := i.client.Get("http://d" + rpc.URLFunctions)
if err != nil {
return meta, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&meta)
return meta, err
}

View File

@@ -0,0 +1,11 @@
package client
// Client allows execution of RPCs.
type Client interface {
// Addr returns the address -- e.g. unix socket path
Addr() string
// Call invokes an RPC method with an argument and a pointer to a result that will hold the return value.
Call(method string, arg interface{}, result interface{}) error
}

25
vendor/github.com/docker/infrakit/pkg/rpc/handshake.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package rpc
import (
"net/http"
"github.com/docker/infrakit/pkg/spi"
)
// ImplementsRequest is the rpc wrapper for the Implements method args.
type ImplementsRequest struct {
}
// ImplementsResponse is the rpc wrapper for the Implements return value.
type ImplementsResponse struct {
APIs []spi.InterfaceSpec
}
// Handshake is a simple RPC object for doing handshake
type Handshake []spi.InterfaceSpec
// Implements responds to a request for the supported plugin interfaces.
func (h Handshake) Implements(_ *http.Request, req *ImplementsRequest, resp *ImplementsResponse) error {
resp.APIs = []spi.InterfaceSpec(h)
return nil
}

21
vendor/github.com/docker/infrakit/pkg/rpc/info.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package rpc
const (
// URLAPI is the well-known HTTP GET endpoint that retrieves description of the plugin's interfaces.
URLAPI = "/info/api.json"
// URLFunctions exposes the templates functions that are available via this plugin
URLFunctions = "/info/functions.json"
// URLEventsPrefix is the prefix of the events endpoint
URLEventsPrefix = "/events"
)
// InputExample is the interface implemented by the rpc implementations for
// group, instance, and flavor to set example input using custom/ vendored data types.
type InputExample interface {
// SetExampleProperties updates the parameter with example properties.
// The request param must be a pointer
SetExampleProperties(request interface{})
}

View File

@@ -0,0 +1,75 @@
package instance
import (
"github.com/docker/infrakit/pkg/plugin"
rpc_client "github.com/docker/infrakit/pkg/rpc/client"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"
)
// NewClient returns a plugin interface implementation connected to a plugin
func NewClient(name plugin.Name, socketPath string) (instance.Plugin, error) {
rpcClient, err := rpc_client.New(socketPath, instance.InterfaceSpec)
if err != nil {
return nil, err
}
return &client{name: name, client: rpcClient}, nil
}
type client struct {
name plugin.Name
client rpc_client.Client
}
// Validate performs local validation on a provision request.
func (c client) Validate(properties *types.Any) error {
_, instanceType := c.name.GetLookupAndType()
req := ValidateRequest{Properties: properties, Type: instanceType}
resp := ValidateResponse{}
return c.client.Call("Instance.Validate", req, &resp)
}
// Provision creates a new instance based on the spec.
func (c client) Provision(spec instance.Spec) (*instance.ID, error) {
_, instanceType := c.name.GetLookupAndType()
req := ProvisionRequest{Spec: spec, Type: instanceType}
resp := ProvisionResponse{}
if err := c.client.Call("Instance.Provision", req, &resp); err != nil {
return nil, err
}
return resp.ID, nil
}
// Label labels the instance
func (c client) Label(instance instance.ID, labels map[string]string) error {
_, instanceType := c.name.GetLookupAndType()
req := LabelRequest{Type: instanceType, Instance: instance, Labels: labels}
resp := LabelResponse{}
return c.client.Call("Instance.Label", req, &resp)
}
// Destroy terminates an existing instance.
func (c client) Destroy(instance instance.ID) error {
_, instanceType := c.name.GetLookupAndType()
req := DestroyRequest{Instance: instance, Type: instanceType}
resp := DestroyResponse{}
return c.client.Call("Instance.Destroy", req, &resp)
}
// DescribeInstances returns descriptions of all instances matching all of the provided tags.
func (c client) DescribeInstances(tags map[string]string) ([]instance.Description, error) {
_, instanceType := c.name.GetLookupAndType()
req := DescribeInstancesRequest{Tags: tags, Type: instanceType}
resp := DescribeInstancesResponse{}
err := c.client.Call("Instance.DescribeInstances", req, &resp)
if err != nil {
return nil, err
}
return resp.Descriptions, nil
}

View File

@@ -0,0 +1,154 @@
package instance
import (
"fmt"
"net/http"
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/spi/instance"
)
// PluginServer returns a RPCService that conforms to the net/rpc rpc call convention.
func PluginServer(p instance.Plugin) *Instance {
return &Instance{plugin: p, typedPlugins: map[string]instance.Plugin{}}
}
// PluginServerWithTypes which supports multiple types of instance plugins. The de-multiplexing
// is done by the server's RPC method implementations.
func PluginServerWithTypes(typed map[string]instance.Plugin) *Instance {
return &Instance{typedPlugins: typed}
}
// Instance is the JSON RPC service representing the Instance Plugin. It must be exported in order to be
// registered by the rpc server package.
type Instance struct {
plugin instance.Plugin // the default plugin
typedPlugins map[string]instance.Plugin // by type, as qualified in the name of the plugin
}
// VendorInfo returns a metadata object about the plugin, if the plugin implements it.
func (p *Instance) VendorInfo() *spi.VendorInfo {
// TODO(chungers) - support typed plugins
if p.plugin == nil {
return nil
}
if m, is := p.plugin.(spi.Vendor); is {
return m.VendorInfo()
}
return nil
}
// SetExampleProperties sets the rpc request with any example properties/ custom type
func (p *Instance) SetExampleProperties(request interface{}) {
// TODO(chungers) - support typed plugins
if p.plugin == nil {
return
}
i, is := p.plugin.(spi.InputExample)
if !is {
return
}
example := i.ExampleProperties()
if example == nil {
return
}
switch request := request.(type) {
case *ValidateRequest:
request.Properties = example
case *ProvisionRequest:
request.Spec.Properties = example
}
}
// ImplementedInterface returns the interface implemented by this RPC service.
func (p *Instance) ImplementedInterface() spi.InterfaceSpec {
return instance.InterfaceSpec
}
func (p *Instance) getPlugin(instanceType string) instance.Plugin {
if instanceType == "" {
return p.plugin
}
if p, has := p.typedPlugins[instanceType]; has {
return p
}
return nil
}
// Validate performs local validation on a provision request.
func (p *Instance) Validate(_ *http.Request, req *ValidateRequest, resp *ValidateResponse) error {
c := p.getPlugin(req.Type)
if c == nil {
return fmt.Errorf("no-plugin:%s", req.Type)
}
resp.Type = req.Type
err := c.Validate(req.Properties)
if err != nil {
return err
}
resp.OK = true
return nil
}
// Provision creates a new instance based on the spec.
func (p *Instance) Provision(_ *http.Request, req *ProvisionRequest, resp *ProvisionResponse) error {
resp.Type = req.Type
c := p.getPlugin(req.Type)
if c == nil {
return fmt.Errorf("no-plugin:%s", req.Type)
}
id, err := c.Provision(req.Spec)
if err != nil {
return err
}
resp.ID = id
return nil
}
// Label labels the instance
func (p *Instance) Label(_ *http.Request, req *LabelRequest, resp *LabelResponse) error {
resp.Type = req.Type
c := p.getPlugin(req.Type)
if c == nil {
return fmt.Errorf("no-plugin:%s", req.Type)
}
err := c.Label(req.Instance, req.Labels)
if err != nil {
return err
}
resp.OK = true
return nil
}
// Destroy terminates an existing instance.
func (p *Instance) Destroy(_ *http.Request, req *DestroyRequest, resp *DestroyResponse) error {
resp.Type = req.Type
c := p.getPlugin(req.Type)
if c == nil {
return fmt.Errorf("no-plugin:%s", req.Type)
}
err := c.Destroy(req.Instance)
if err != nil {
return err
}
resp.OK = true
return nil
}
// DescribeInstances returns descriptions of all instances matching all of the provided tags.
func (p *Instance) DescribeInstances(_ *http.Request, req *DescribeInstancesRequest, resp *DescribeInstancesResponse) error {
resp.Type = req.Type
c := p.getPlugin(req.Type)
if c == nil {
return fmt.Errorf("no-plugin:%s", req.Type)
}
desc, err := c.DescribeInstances(req.Tags)
if err != nil {
return err
}
resp.Descriptions = desc
return nil
}

View File

@@ -0,0 +1,67 @@
package instance
import (
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"
)
// ValidateRequest is the rpc wrapper for the Validate method args
type ValidateRequest struct {
Type string
Properties *types.Any
}
// ValidateResponse is the rpc wrapper for the Validate response values
type ValidateResponse struct {
Type string
OK bool
}
// ProvisionRequest is the rpc wrapper for Provision request
type ProvisionRequest struct {
Type string
Spec instance.Spec
}
// ProvisionResponse is the rpc wrapper for Provision response
type ProvisionResponse struct {
Type string
ID *instance.ID
}
// LabelRequest is the rpc wrapper for Label request
type LabelRequest struct {
Type string
Instance instance.ID
Labels map[string]string
}
// LabelResponse is the rpc wrapper for Label response
type LabelResponse struct {
Type string
OK bool
}
// DestroyRequest is the rpc wrapper for Destroy request
type DestroyRequest struct {
Type string
Instance instance.ID
}
// DestroyResponse is the rpc wrapper for Destroy response
type DestroyResponse struct {
Type string
OK bool
}
// DescribeInstancesRequest is the rpc wrapper for DescribeInstances request
type DescribeInstancesRequest struct {
Type string
Tags map[string]string
}
// DescribeInstancesResponse is the rpc wrapper for the DescribeInstances response
type DescribeInstancesResponse struct {
Type string
Descriptions []instance.Description
}

View File

@@ -0,0 +1,44 @@
package metadata
import (
rpc_client "github.com/docker/infrakit/pkg/rpc/client"
"github.com/docker/infrakit/pkg/spi/metadata"
"github.com/docker/infrakit/pkg/types"
)
// NewClient returns a plugin interface implementation connected to a remote plugin
func NewClient(socketPath string) (metadata.Plugin, error) {
rpcClient, err := rpc_client.New(socketPath, metadata.InterfaceSpec)
if err != nil {
return nil, err
}
return &client{client: rpcClient}, nil
}
// Adapt converts a rpc client to a Metadata plugin object
func Adapt(rpcClient rpc_client.Client) metadata.Plugin {
return &client{client: rpcClient}
}
type client struct {
client rpc_client.Client
}
// List returns a list of nodes under path.
func (c client) List(path metadata.Path) ([]string, error) {
req := ListRequest{Path: path}
resp := ListResponse{}
err := c.client.Call("Metadata.List", req, &resp)
return resp.Nodes, err
}
// Get retrieves the metadata at path.
func (c client) Get(path metadata.Path) (*types.Any, error) {
req := GetRequest{Path: path}
resp := GetResponse{}
err := c.client.Call("Metadata.Get", req, &resp)
if err != nil {
return nil, err
}
return resp.Value, err
}

View File

@@ -0,0 +1,180 @@
package metadata
import (
"net/http"
"sort"
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/spi/metadata"
"github.com/docker/infrakit/pkg/template"
)
// PluginServer returns a Metadata that conforms to the net/rpc rpc call convention.
func PluginServer(p metadata.Plugin) *Metadata {
return &Metadata{plugin: p}
}
// PluginServerWithTypes which supports multiple types of metadata plugins. The de-multiplexing
// is done by the server's RPC method implementations.
func PluginServerWithTypes(typed map[string]metadata.Plugin) *Metadata {
return &Metadata{typedPlugins: typed}
}
// Metadata the exported type needed to conform to json-rpc call convention
type Metadata struct {
plugin metadata.Plugin
typedPlugins map[string]metadata.Plugin // by type, as qualified in the name of the plugin
}
// WithBase sets the base plugin to the given plugin object
func (p *Metadata) WithBase(m metadata.Plugin) *Metadata {
p.plugin = m
return p
}
// WithTypes sets the typed plugins to the given map of plugins (by type name)
func (p *Metadata) WithTypes(typed map[string]metadata.Plugin) *Metadata {
p.typedPlugins = typed
return p
}
// VendorInfo returns a metadata object about the plugin, if the plugin implements it. See spi.Vendor
func (p *Metadata) VendorInfo() *spi.VendorInfo {
// TODO(chungers) - support typed plugins
if p.plugin == nil {
return nil
}
if m, is := p.plugin.(spi.Vendor); is {
return m.VendorInfo()
}
return nil
}
// Funcs implements the template.FunctionExporter method to expose help for plugin's template functions
func (p *Metadata) Funcs() []template.Function {
f, is := p.plugin.(template.FunctionExporter)
if !is {
return []template.Function{}
}
return f.Funcs()
}
// Types implements server.TypedFunctionExporter
func (p *Metadata) Types() []string {
if p.typedPlugins == nil {
return nil
}
list := []string{}
for k := range p.typedPlugins {
list = append(list, k)
}
return list
}
// FuncsByType implements server.TypedFunctionExporter
func (p *Metadata) FuncsByType(t string) []template.Function {
if p.typedPlugins == nil {
return nil
}
fp, has := p.typedPlugins[t]
if !has {
return nil
}
exp, is := fp.(template.FunctionExporter)
if !is {
return nil
}
return exp.Funcs()
}
// ImplementedInterface returns the interface implemented by this RPC service.
func (p *Metadata) ImplementedInterface() spi.InterfaceSpec {
return metadata.InterfaceSpec
}
func (p *Metadata) getPlugin(metadataType string) metadata.Plugin {
if metadataType == "" {
return p.plugin
}
if p, has := p.typedPlugins[metadataType]; has {
return p
}
return nil
}
// List returns a list of child nodes given a path.
func (p *Metadata) List(_ *http.Request, req *ListRequest, resp *ListResponse) error {
nodes := []string{}
// the . case - list the typed plugins and the default's first level.
if len(req.Path) == 0 || req.Path[0] == "" || req.Path[0] == "." {
if p.plugin != nil {
n, err := p.plugin.List(req.Path)
if err != nil {
return err
}
nodes = append(nodes, n...)
}
for k := range p.typedPlugins {
nodes = append(nodes, k)
}
sort.Strings(nodes)
resp.Nodes = nodes
return nil
}
c, has := p.typedPlugins[req.Path[0]]
if !has {
if p.plugin == nil {
return nil
}
nodes, err := p.plugin.List(req.Path)
if err != nil {
return err
}
sort.Strings(nodes)
resp.Nodes = nodes
return nil
}
nodes, err := c.List(req.Path[1:])
if err != nil {
return err
}
sort.Strings(nodes)
resp.Nodes = nodes
return nil
}
// Get retrieves the value at path given.
func (p *Metadata) Get(_ *http.Request, req *GetRequest, resp *GetResponse) error {
if len(req.Path) == 0 {
return nil
}
c, has := p.typedPlugins[req.Path[0]]
if !has {
if p.plugin == nil {
return nil
}
value, err := p.plugin.Get(req.Path)
if err != nil {
return err
}
resp.Value = value
return nil
}
value, err := c.Get(req.Path[1:])
if err != nil {
return err
}
resp.Value = value
return nil
}

View File

@@ -0,0 +1,26 @@
package metadata
import (
"github.com/docker/infrakit/pkg/spi/metadata"
"github.com/docker/infrakit/pkg/types"
)
// ListRequest is the rpc wrapper for request parameters to List
type ListRequest struct {
Path metadata.Path
}
// ListResponse is the rpc wrapper for the results of List
type ListResponse struct {
Nodes []string
}
// GetRequest is the rpc wrapper of the params to Get
type GetRequest struct {
Path metadata.Path
}
// GetResponse is the rpc wrapper of the result of Get
type GetResponse struct {
Value *types.Any
}

View File

@@ -0,0 +1,134 @@
package server
import (
"encoding/json"
"net/http"
"github.com/docker/infrakit/pkg/plugin"
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/template"
)
// TypedFunctionExporter is an interface implemented by plugins that supports multiple types in a single RPC endpoint.
// Each typed plugin can export some functions and this interface provides metadata about them.
type TypedFunctionExporter interface {
// Types returns a list of types in this plugin
Types() []string
// FuncsByType returns the template functions exported by each typed plugin
FuncsByType(string) []template.Function
}
// PluginInfo is the service object for the RPC metadata service
type PluginInfo struct {
vendor spi.Vendor
reflectors []*reflector
receiver interface{}
}
// NewPluginInfo returns an instance of the metadata service
func NewPluginInfo(receiver interface{}) (*PluginInfo, error) {
m := &PluginInfo{
reflectors: []*reflector{},
receiver: receiver,
}
if v, is := receiver.(spi.Vendor); is {
m.vendor = v
}
return m, m.Register(receiver)
}
// Register registers an rpc-capable object for introspection and metadata service
func (m *PluginInfo) Register(receiver interface{}) error {
r := &reflector{target: receiver}
m.reflectors = append(m.reflectors, r)
return nil
}
// ShowAPI responds by returning information about the plugin.
func (m *PluginInfo) ShowAPI(resp http.ResponseWriter, req *http.Request) {
meta := m.getInfo()
buff, err := json.Marshal(meta)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
resp.Write([]byte(err.Error()))
return
}
resp.Write(buff)
return
}
// ShowTemplateFunctions responds by returning information about template functions the plugin expopses.
func (m *PluginInfo) ShowTemplateFunctions(resp http.ResponseWriter, req *http.Request) {
result := map[string][]template.Function{}
exporter, is := m.receiver.(template.FunctionExporter)
if is {
base := template.UpdateDocumentation(exporter.Funcs())
if len(base) > 0 {
result["base"] = base
}
}
texporter, is := m.receiver.(TypedFunctionExporter)
if is {
for _, t := range texporter.Types() {
typed := template.UpdateDocumentation(texporter.FuncsByType(t))
if len(typed) > 0 {
result[t] = typed
}
}
}
if t, err := template.NewTemplate("str://", template.Options{}); err == nil {
builtin := template.UpdateDocumentation(t.DefaultFuncs())
if len(builtin) > 0 {
result["builtin"] = builtin
}
}
buff, err := json.MarshalIndent(result, "", " ")
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
resp.Write([]byte(err.Error()))
return
}
resp.Write(buff)
return
}
func (m *PluginInfo) getInfo() *plugin.Info {
meta := &plugin.Info{}
myImplements := []spi.InterfaceSpec{}
myInterfaces := []plugin.InterfaceDescription{}
for _, r := range m.reflectors {
iface := r.Interface()
myImplements = append(myImplements, iface)
descriptions := []plugin.MethodDescription{}
for _, method := range r.pluginMethods() {
desc := r.toDescription(method)
descriptions = append(descriptions, desc)
// sets the example properties which are the custom types for the plugin
r.setExampleProperties(desc.Request.Params)
}
myInterfaces = append(myInterfaces,
plugin.InterfaceDescription{
InterfaceSpec: iface,
Methods: descriptions,
})
}
if m.vendor != nil {
meta.Vendor = m.vendor.VendorInfo()
}
meta.Implements = myImplements
meta.Interfaces = myInterfaces
return meta
}

View File

@@ -0,0 +1,145 @@
package server
import (
"fmt"
"net/http"
"reflect"
"time"
"unicode"
"unicode/utf8"
"github.com/docker/infrakit/pkg/plugin"
"github.com/docker/infrakit/pkg/rpc"
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/types"
)
var (
// Precompute the reflect.Type of error and http.Request -- from gorilla/rpc
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
typeOfHTTPRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
)
type reflector struct {
target interface{}
}
func (r *reflector) VendorInfo() *spi.VendorInfo {
if i, is := r.target.(spi.Vendor); is {
return i.VendorInfo()
}
return nil
}
func (r *reflector) exampleProperties() *types.Any {
if example, is := r.target.(spi.InputExample); is {
return example.ExampleProperties()
}
return nil
}
// Type returns the target's type, taking into account of pointer receiver
func (r *reflector) targetType() reflect.Type {
return reflect.Indirect(reflect.ValueOf(r.target)).Type()
}
// Interface returns the plugin type and version.
func (r *reflector) Interface() spi.InterfaceSpec {
if v, is := r.target.(VersionedInterface); is {
return v.ImplementedInterface()
}
return spi.InterfaceSpec{}
}
// isExported returns true of a string is an exported (upper case) name. -- from gorilla/rpc
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// isExportedOrBuiltin returns true if a type is exported or a builtin -- from gorilla/rpc
func isExportedOrBuiltin(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
func (r *reflector) getPluginTypeName() string {
return r.targetType().Name()
}
func (r *reflector) setExampleProperties(param interface{}) {
if example, is := r.target.(rpc.InputExample); is {
example.SetExampleProperties(param)
}
}
func (r *reflector) toDescription(m reflect.Method) plugin.MethodDescription {
method := fmt.Sprintf("%s.%s", r.getPluginTypeName(), m.Name)
input := reflect.New(m.Type.In(2).Elem())
ts := fmt.Sprintf("%v", time.Now().Unix())
d := plugin.MethodDescription{
Request: plugin.Request{
Version: "2.0",
Method: method,
Params: input.Interface(),
ID: ts,
},
Response: plugin.Response{
Result: reflect.Zero(m.Type.In(3).Elem()).Interface(),
ID: ts,
},
}
return d
}
// pluginMethods returns a slice of methods that match the criteria for exporting as RPC service
func (r *reflector) pluginMethods() []reflect.Method {
matches := []reflect.Method{}
receiverT := reflect.TypeOf(r.target)
for i := 0; i < receiverT.NumMethod(); i++ {
method := receiverT.Method(i)
mtype := method.Type
// Code from gorilla/rpc
// Method must be exported.
if method.PkgPath != "" {
continue
}
// Method needs four ins: receiver, *http.Request, *args, *reply.
if mtype.NumIn() != 4 {
continue
}
// First argument must be a pointer and must be http.Request.
reqType := mtype.In(1)
if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfHTTPRequest {
continue
}
// Second argument must be a pointer and must be exported.
args := mtype.In(2)
if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
continue
}
// Third argument must be a pointer and must be exported.
reply := mtype.In(3)
if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
continue
}
// Method needs one out: error.
if mtype.NumOut() != 1 {
continue
}
if returnType := mtype.Out(0); returnType != typeOfError {
continue
}
matches = append(matches, method)
}
return matches
}

View File

@@ -0,0 +1,177 @@
package server
import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/http/httputil"
"time"
log "github.com/Sirupsen/logrus"
broker "github.com/docker/infrakit/pkg/broker/server"
rpc_server "github.com/docker/infrakit/pkg/rpc"
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/spi/event"
"github.com/docker/infrakit/pkg/types"
"github.com/gorilla/mux"
"github.com/gorilla/rpc/v2"
"github.com/gorilla/rpc/v2/json2"
"gopkg.in/tylerb/graceful.v1"
)
// Stoppable support proactive stopping, and blocking until stopped.
type Stoppable interface {
Stop()
AwaitStopped()
}
type stoppableServer struct {
server *graceful.Server
}
func (s *stoppableServer) Stop() {
s.server.Stop(10 * time.Second)
}
func (s *stoppableServer) AwaitStopped() {
<-s.server.StopChan()
}
type loggingHandler struct {
handler http.Handler
}
func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
requestData, err := httputil.DumpRequest(req, true)
if err == nil {
log.Debugf("Received request %s", string(requestData))
} else {
log.Error(err)
}
recorder := httptest.NewRecorder()
h.handler.ServeHTTP(recorder, req)
responseData, err := httputil.DumpResponse(recorder.Result(), true)
if err == nil {
log.Debugf("Sending response %s", string(responseData))
} else {
log.Error(err)
}
w.WriteHeader(recorder.Code)
recorder.Body.WriteTo(w)
}
// A VersionedInterface identifies which Interfaces a plugin supports.
type VersionedInterface interface {
// ImplementedInterface returns the interface being provided.
ImplementedInterface() spi.InterfaceSpec
}
// StartPluginAtPath starts an HTTP server listening on a unix socket at the specified path.
// Returns a Stoppable that can be used to stop or block on the server.
func StartPluginAtPath(socketPath string, receiver VersionedInterface, more ...VersionedInterface) (Stoppable, error) {
server := rpc.NewServer()
server.RegisterCodec(json2.NewCodec(), "application/json")
targets := append([]VersionedInterface{receiver}, more...)
interfaces := []spi.InterfaceSpec{}
for _, t := range targets {
interfaces = append(interfaces, t.ImplementedInterface())
if err := server.RegisterService(t, ""); err != nil {
return nil, err
}
}
// handshake service that can exchange interface versions with client
if err := server.RegisterService(rpc_server.Handshake(interfaces), ""); err != nil {
return nil, err
}
// events handler
events := broker.NewBroker()
// wire up the publish event source channel to the plugin implementations
for _, t := range targets {
pub, is := t.(event.Publisher)
if !is {
continue
}
// We give one channel per source to provide some isolation. This we won't have the
// whole event bus stop just because one plugin closes the channel.
eventChan := make(chan *event.Event)
pub.PublishOn(eventChan)
go func() {
for {
event, ok := <-eventChan
if !ok {
return
}
events.Publish(event.Topic.String(), event, 1*time.Second)
}
}()
}
// info handler
info, err := NewPluginInfo(receiver)
if err != nil {
return nil, err
}
httpLog := log.New()
httpLog.Level = log.GetLevel()
router := mux.NewRouter()
router.HandleFunc(rpc_server.URLAPI, info.ShowAPI)
router.HandleFunc(rpc_server.URLFunctions, info.ShowTemplateFunctions)
intercept := broker.Interceptor{
Pre: func(topic string, headers map[string][]string) error {
for _, target := range targets {
if v, is := target.(event.Validator); is {
if err := v.Validate(types.PathFromString(topic)); err == nil {
return nil
}
}
}
return broker.ErrInvalidTopic(topic)
},
Do: events.ServeHTTP,
Post: func(topic string) {
log.Infoln("Client left", topic)
},
}
router.HandleFunc(rpc_server.URLEventsPrefix, intercept.ServeHTTP)
logger := loggingHandler{handler: server}
router.Handle("/", logger)
gracefulServer := graceful.Server{
Timeout: 10 * time.Second,
Server: &http.Server{Addr: fmt.Sprintf("unix://%s", socketPath), Handler: router},
}
listener, err := net.Listen("unix", socketPath)
if err != nil {
return nil, err
}
log.Infof("Listening at: %s", socketPath)
go func() {
err := gracefulServer.Serve(listener)
if err != nil {
log.Warn(err)
}
events.Stop()
}()
return &stoppableServer{server: &gracefulServer}, nil
}

42
vendor/github.com/docker/infrakit/pkg/spi/event/spi.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package event
import (
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/types"
)
// InterfaceSpec is the current name and version of the Flavor API.
var InterfaceSpec = spi.InterfaceSpec{
Name: "Event",
Version: "0.1.0",
}
// Plugin must be implemented for the object to be able to publish events.
type Plugin interface {
// List returns a list of *child nodes* given a path for a topic.
// A topic of "." is the top level
List(topic types.Path) (child []string, err error)
}
// Validator is the interface for validating the topic
type Validator interface {
// Validate validates the topic
Validate(topic types.Path) error
}
// Publisher is the interface that event sources also implement to be assigned
// a publish function.
type Publisher interface {
// PublishOn sets the channel to publish
PublishOn(chan<- *Event)
}
// Subscriber is the interface given to clients interested in events
type Subscriber interface {
// SubscribeOn returns the channel for the topic
SubscribeOn(topic types.Path) (<-chan *Event, error)
}

View File

@@ -0,0 +1,105 @@
package event
import (
"time"
"github.com/docker/infrakit/pkg/types"
)
// Type is the type of an event. This gives hint about what struct types to map to, etc.
// It also marks one instance of an event as of different nature from another.
type Type string
const (
// TypeError is the type to use for sending errors in the transport of the events.
TypeError = Type("error")
)
// Event holds information about when, what, etc.
type Event struct {
// Topic is the topic to which this event is published
Topic types.Path
// Type of the event. This is usually used as a hint to what struct types to use to unmarshal data
Type Type `json:",omitempty"`
// ID is unique id for the event.
ID string
// Timestamp is the time.UnixNano() value -- this is the timestamp when event occurred
Timestamp time.Time
// Received is the timestamp when the message is received.
Received time.Time `json:",omitempty"`
// Data contains some application specific payload
Data *types.Any `json:",omitempty"`
// Error contains any errors that occurred during delivery of the mesasge
Error error `json:",omitempty"`
}
// Init creates an instance with the value initialized to the state of receiver.
func (event Event) Init() *Event {
copy := event
return &copy
}
// WithError sets the error
func (event *Event) WithError(err error) *Event {
event.Error = err
return event
}
// WithTopic sets the topic from input string
func (event *Event) WithTopic(s string) *Event {
event.Topic = types.PathFromString(s)
return event
}
// WithType sets the type from a string
func (event *Event) WithType(s string) *Event {
event.Type = Type(s)
return event
}
// Now sets the timestamp of this event to now.
func (event *Event) Now() *Event {
event.Timestamp = time.Now()
return event
}
// ReceivedNow marks the receipt timestamp with now
func (event *Event) ReceivedNow() *Event {
event.Received = time.Now()
return event
}
// WithData converts the data into an any and sets this event's data to it.
func (event *Event) WithData(data interface{}) (*Event, error) {
any, err := types.AnyValue(data)
if err != nil {
return nil, err
}
event.Data = any
return event, nil
}
// WithDataMust does what WithData does but will panic on error
func (event *Event) WithDataMust(data interface{}) *Event {
e, err := event.WithData(data)
if err != nil {
panic(err)
}
return e
}
// FromAny sets the fields of this Event from the contents of Any
func (event *Event) FromAny(any *types.Any) *Event {
err := any.Decode(event)
if err != nil {
event.Error = err
}
return event
}

View File

@@ -0,0 +1,30 @@
package instance
import (
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/types"
)
// InterfaceSpec is the current name and version of the Instance API.
var InterfaceSpec = spi.InterfaceSpec{
Name: "Instance",
Version: "0.3.0",
}
// Plugin is a vendor-agnostic API used to create and manage resources with an infrastructure provider.
type Plugin interface {
// Validate performs local validation on a provision request.
Validate(req *types.Any) error
// Provision creates a new instance based on the spec.
Provision(spec Spec) (*ID, error)
// Label labels the instance
Label(instance ID, labels map[string]string) error
// Destroy terminates an existing instance.
Destroy(instance ID) error
// DescribeInstances returns descriptions of all instances matching all of the provided tags.
DescribeInstances(labels map[string]string) ([]Description, error)
}

View File

@@ -0,0 +1,46 @@
package instance
import (
"github.com/docker/infrakit/pkg/types"
)
// ID is the identifier for an instance.
type ID string
// Description contains details about an instance.
type Description struct {
ID ID
LogicalID *LogicalID
Tags map[string]string
}
// LogicalID is the logical identifier to associate with an instance.
type LogicalID string
// Attachment is an identifier for a resource to attach to an instance.
type Attachment struct {
// ID is the unique identifier for the attachment.
ID string
// Type is the kind of attachment. This allows multiple attachments of different types, with the supported
// types defined by the plugin.
Type string
}
// Spec is a specification of an instance to be provisioned
type Spec struct {
// Properties is the opaque instance plugin configuration.
Properties *types.Any
// Tags are metadata that describes an instance.
Tags map[string]string
// Init is the boot script to execute when the instance is created.
Init string
// LogicalID is the logical identifier assigned to this instance, which may be absent.
LogicalID *LogicalID
// Attachments are instructions for external entities that should be attached to the instance.
Attachments []Attachment
}

View File

@@ -0,0 +1,102 @@
package metadata
var (
// NullPath means no path
NullPath = Path([]string{})
)
// Path is used to identify a particle of metadata. The path can be strings separated by / as in a URL.
type Path []string
// Clean scrubs the path to remove any empty string or . or .. and collapse the path into a concise form.
// It's similar to path/filepath.Clean in the standard lib.
func (p Path) Clean() Path {
this := []string(p)
copy := []string{}
for _, v := range this {
switch v {
case "", ".":
case "..":
if len(copy) == 0 {
copy = append(copy, "..")
} else {
copy = copy[0 : len(copy)-1]
if len(copy) == 0 {
return NullPath
}
}
default:
copy = append(copy, v)
}
}
return Path(copy)
}
// Len returns the length of the path
func (p Path) Len() int {
return len([]string(p))
}
// Index returns the ith component in the path
func (p Path) Index(i int) *string {
if p.Len() <= i {
return nil
}
copy := []string(p)[i]
return &copy
}
// Shift returns a new path that's shifted i positions to the left -- ith child of the head at index=0
func (p Path) Shift(i int) Path {
len := p.Len() - i
if len <= 0 {
return Path([]string{})
}
new := make([]string, len)
copy(new, []string(p)[i:])
return Path(new)
}
// Dir returns the 'dir' of the path
func (p Path) Dir() Path {
pp := p.Clean()
if len(pp) > 1 {
return p[0 : len(pp)-1]
}
return Path([]string{"."})
}
// Base returns the base of the path
func (p Path) Base() string {
pp := p.Clean()
return pp[len(pp)-1]
}
// Join joins the input as a child of this path
func (p Path) Join(child string) Path {
return p.Sub(Path([]string{child}))
}
// Sub joins the child to the parent
func (p Path) Sub(child Path) Path {
pp := p.Clean()
return Path(append(pp, []string(child)...))
}
// Rel returns a new path that is a child of the input from this path.
// e.g. For a path a/b/c/d Rel(a/b/) returns c/d. NullPath is returned if
// the two are not relative to one another.
func (p Path) Rel(path Path) Path {
this := []string(p.Clean())
parent := []string(path.Clean())
if len(this) < len(parent) {
return NullPath
}
for i := 0; i < len(parent); i++ {
if parent[i] != this[i] {
return NullPath
}
}
return Path(this[len(parent):])
}

View File

@@ -0,0 +1,22 @@
package metadata
import (
"github.com/docker/infrakit/pkg/spi"
"github.com/docker/infrakit/pkg/types"
)
// InterfaceSpec is the current name and version of the Metadata API.
var InterfaceSpec = spi.InterfaceSpec{
Name: "Metadata",
Version: "0.1.0",
}
// Plugin is the interface for metadata-related operations.
type Plugin interface {
// List returns a list of *child nodes* given a path, which is specified as a slice
List(path Path) (child []string, err error)
// Get retrieves the value at path given.
Get(path Path) (value *types.Any, err error)
}

39
vendor/github.com/docker/infrakit/pkg/spi/plugin.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package spi
import (
"github.com/docker/infrakit/pkg/types"
)
// InterfaceSpec is metadata about an API.
type InterfaceSpec struct {
// Name of the interface.
Name string
// Version is the identifier for the API version.
Version string
}
// VendorInfo provides vendor-specific information
type VendorInfo struct {
InterfaceSpec // vendor-defined name / version
// URL is the informational url for the plugin. It can container help and docs, etc.
URL string
}
// Vendor is an optional interface that has vendor-specific information methods
type Vendor interface {
// VendorInfo returns a vendor-defined interface spec
VendorInfo() *VendorInfo
}
// InputExample interface is an optional interface implemented by the plugin that will provide
// example input struct to document the vendor-specific api of the plugin. An example of this
// is to provide a sample JSON for all the Properties field in the plugin API.
type InputExample interface {
// ExampleProperties returns an example JSON raw message that the vendor plugin understands.
// This is an example of what the user will configure and what will be used as the opaque
// blob in all the plugin methods where raw JSON messages are referenced.
ExampleProperties() *types.Any
}

View File

@@ -0,0 +1,16 @@
package template
import (
"os"
)
// defaultContextURL returns the default context URL if none is known.
func defaultContextURL() string {
pwd := "/"
if wd, err := os.Getwd(); err == nil {
pwd = wd
} else {
pwd = os.Getenv("PWD")
}
return "file://localhost" + pwd + "/"
}

View File

@@ -0,0 +1,64 @@
package template
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
)
// Fetch fetchs content from the given URL string. Supported schemes are http:// https:// file:// unix://
func Fetch(s string, opt Options) ([]byte, error) {
u, err := url.Parse(s)
if err != nil {
return nil, err
}
switch u.Scheme {
case "file":
return ioutil.ReadFile(u.Path)
case "http", "https":
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
case "unix":
// unix: will look for a socket that matches the host name at a
// directory path set by environment variable.
c, err := socketClient(u, opt.SocketDir)
if err != nil {
return nil, err
}
u.Scheme = "http"
resp, err := c.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
return nil, fmt.Errorf("unsupported url:%s", s)
}
func socketClient(u *url.URL, socketDir string) (*http.Client, error) {
socketPath := filepath.Join(socketDir, u.Host)
if f, err := os.Stat(socketPath); err != nil {
return nil, err
} else if f.Mode()&os.ModeSocket == 0 {
return nil, fmt.Errorf("not-a-socket:%v", socketPath)
}
return &http.Client{
Transport: &http.Transport{
Dial: func(proto, addr string) (conn net.Conn, err error) {
return net.Dial("unix", socketPath)
},
},
}, nil
}

361
vendor/github.com/docker/infrakit/pkg/template/funcs.go generated vendored Normal file
View File

@@ -0,0 +1,361 @@
package template
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/docker/infrakit/pkg/types"
"github.com/jmespath/go-jmespath"
)
// DeepCopyObject makes a deep copy of the argument, using encoding/gob encode/decode.
func DeepCopyObject(from interface{}) (interface{}, error) {
var mod bytes.Buffer
enc := json.NewEncoder(&mod)
dec := json.NewDecoder(&mod)
err := enc.Encode(from)
if err != nil {
return nil, err
}
copy := reflect.New(reflect.TypeOf(from))
err = dec.Decode(copy.Interface())
if err != nil {
return nil, err
}
return reflect.Indirect(copy).Interface(), nil
}
// QueryObject applies a JMESPath query specified by the expression, against the target object.
func QueryObject(exp string, target interface{}) (interface{}, error) {
query, err := jmespath.Compile(exp)
if err != nil {
return nil, err
}
return query.Search(target)
}
// SplitLines splits the input into a string slice.
func SplitLines(o interface{}) ([]string, error) {
ret := []string{}
switch o := o.(type) {
case string:
return strings.Split(o, "\n"), nil
case []byte:
return strings.Split(string(o), "\n"), nil
}
return ret, fmt.Errorf("not-supported-value-type")
}
// FromJSON decode the input JSON encoded as string or byte slice into a map.
func FromJSON(o interface{}) (interface{}, error) {
var ret interface{}
switch o := o.(type) {
case string:
err := json.Unmarshal([]byte(o), &ret)
return ret, err
case []byte:
err := json.Unmarshal(o, &ret)
return ret, err
case *types.Any:
err := json.Unmarshal(o.Bytes(), &ret)
return ret, err
}
return ret, fmt.Errorf("not-supported-value-type")
}
// ToJSON encodes the input struct into a JSON string.
func ToJSON(o interface{}) (string, error) {
buff, err := json.MarshalIndent(o, "", " ")
return string(buff), err
}
// ToJSONFormat encodes the input struct into a JSON string with format prefix, and indent.
func ToJSONFormat(prefix, indent string, o interface{}) (string, error) {
buff, err := json.MarshalIndent(o, prefix, indent)
return string(buff), err
}
// FromMap decodes map into raw struct
func FromMap(m map[string]interface{}, raw interface{}) error {
// The safest way, but the slowest, is to just marshal and unmarshal back
buff, err := ToJSON(m)
if err != nil {
return err
}
return json.Unmarshal([]byte(buff), raw)
}
// ToMap encodes the input as a map
func ToMap(raw interface{}) (map[string]interface{}, error) {
buff, err := ToJSON(raw)
if err != nil {
return nil, err
}
out, err := FromJSON(buff)
return out.(map[string]interface{}), err
}
// UnixTime returns a timestamp in unix time
func UnixTime() interface{} {
return time.Now().Unix()
}
// IndexOf returns the index of search in array. -1 if not found or array is not iterable. An optional true will
// turn on strict type check while by default string representations are used to compare values.
func IndexOf(srch interface{}, array interface{}, strictOptional ...bool) int {
strict := false
if len(strictOptional) > 0 {
strict = strictOptional[0]
}
switch reflect.TypeOf(array).Kind() {
case reflect.Slice:
s := reflect.ValueOf(array)
for i := 0; i < s.Len(); i++ {
if reflect.DeepEqual(srch, s.Index(i).Interface()) {
return i
}
if !strict {
// by string value which is useful for text based compares
search := reflect.Indirect(reflect.ValueOf(srch)).Interface()
value := reflect.Indirect(s.Index(i)).Interface()
searchStr := fmt.Sprintf("%v", search)
check := fmt.Sprintf("%v", value)
if searchStr == check {
return i
}
}
}
}
return -1
}
// DefaultFuncs returns a list of default functions for binding in the template
func (t *Template) DefaultFuncs() []Function {
return []Function{
{
Name: "source",
Description: []string{
"Source / evaluate the template at the input location (as URL).",
"This will make all of the global variables declared there visible in this template's context.",
"Similar to 'source' in bash, sourcing another template means applying it in the same context ",
"as the calling template. The context (e.g. variables) of the calling template as a result can be mutated.",
},
Func: func(p string, opt ...interface{}) (string, error) {
var o interface{}
if len(opt) > 0 {
o = opt[0]
}
loc := p
if strings.Index(loc, "str://") == -1 {
buff, err := getURL(t.url, p)
if err != nil {
return "", err
}
loc = buff
}
sourced, err := NewTemplate(loc, t.options)
if err != nil {
return "", err
}
// set this as the parent of the sourced template so its global can mutate the globals in this
sourced.parent = t
sourced.forkFrom(t)
sourced.context = t.context
if o == nil {
o = sourced.context
}
// TODO(chungers) -- let the sourced template define new functions that can be called in the parent.
return sourced.Render(o)
},
},
{
Name: "include",
Description: []string{
"Render content found at URL as template and include here.",
"The optional second parameter is the context to use when rendering the template.",
"Conceptually similar to exec in bash, where the template included is applied using a fork ",
"of current context in the calling template. Any mutations to the context via 'global' will not ",
"be visible in the calling template's context.",
},
Func: func(p string, opt ...interface{}) (string, error) {
var o interface{}
if len(opt) > 0 {
o = opt[0]
}
loc := p
if strings.Index(loc, "str://") == -1 {
buff, err := getURL(t.url, p)
if err != nil {
return "", err
}
loc = buff
}
included, err := NewTemplate(loc, t.options)
if err != nil {
return "", err
}
dotCopy, err := included.forkFrom(t)
if err != nil {
return "", err
}
included.context = dotCopy
if o == nil {
o = included.context
}
return included.Render(o)
},
},
{
Name: "loop",
Description: []string{
"Loop generates a slice of length specified by the input. For use like {{ range loop 5 }}...{{ end }}",
},
Func: func(c int) []struct{} {
return make([]struct{}, c)
},
},
{
Name: "global",
Description: []string{
"Sets a global variable named after the first argument, with the value as the second argument.",
"This is similar to def (which sets the default value).",
"Global variables are propagated to all templates that are rendered via the 'include' function.",
},
Func: func(n string, v interface{}) Void {
t.Global(n, v)
return voidValue
},
},
{
Name: "def",
Description: []string{
"Defines a variable with the first argument as name and last argument value as the default.",
"It's also ok to pass a third optional parameter, in the middle, as the documentation string.",
},
Func: func(name string, args ...interface{}) (Void, error) {
if _, has := t.defaults[name]; has {
// not sure if this is good, but should complain loudly
return voidValue, fmt.Errorf("already defined: %v", name)
}
var doc string
var value interface{}
switch len(args) {
case 1:
// just value, no docs
value = args[0]
case 2:
// docs and value
doc = fmt.Sprintf("%v", args[0])
value = args[1]
}
t.Def(name, value, doc)
return voidValue, nil
},
},
{
Name: "ref",
Description: []string{
"References / gets the variable named after the first argument.",
"The values must be set first by either def or global.",
},
Func: t.Ref,
},
{
Name: "q",
Description: []string{
"Runs a JMESPath (http://jmespath.org/) query (first arg) on the object (second arg).",
"The return value is an object which needs to be rendered properly for the format of the document.",
"Example: {{ include \"https://httpbin.org/get\" | from_json | q \"origin\" }} returns the origin of http request.",
},
Func: QueryObject,
},
{
Name: "to_json",
Description: []string{
"Encodes the input as a JSON string",
"This is useful for taking an object (interface{}) and render it inline as proper JSON.",
"Example: {{ include \"https://httpbin.org/get\" | from_json | to_json }}",
},
Func: ToJSON,
},
{
Name: "jsonEncode",
Description: []string{
"Encodes the input as a JSON string",
"This is useful for taking an object (interface{}) and render it inline as proper JSON.",
"Example: {{ include \"https://httpbin.org/get\" | from_json | to_json }}",
},
Func: ToJSON,
},
{
Name: "to_json_format",
Description: []string{
"Encodes the input as a JSON string with first arg as prefix, second arg the indentation, then the object",
},
Func: ToJSONFormat,
},
{
Name: "jsonEncodeIndent",
Description: []string{
"Encodes the input as a JSON string with first arg as prefix, second arg the indentation, then the object",
},
Func: ToJSONFormat,
},
{
Name: "from_json",
Description: []string{
"Decodes the input (first arg) into a structure (a map[string]interface{} or []interface{}).",
"This is useful for parsing arbitrary resources in JSON format as object. The object is the queryable via 'q'",
"For example: {{ include \"https://httpbin.org/get\" | from_json | q \"origin\" }} returns the origin of request.",
},
Func: FromJSON,
},
{
Name: "jsonDecode",
Description: []string{
"Decodes the input (first arg) into a structure (a map[string]interface{} or []interface{}).",
"This is useful for parsing arbitrary resources in JSON format as object. The object is the queryable via 'q'",
"For example: {{ include \"https://httpbin.org/get\" | from_json | q \"origin\" }} returns the origin of request.",
},
Func: FromJSON,
},
{
Name: "unixtime",
Description: []string{
"Returns the unix timestamp as the number of seconds elapsed since January 1, 1970 UTC.",
},
Func: UnixTime,
},
{
Name: "lines",
Description: []string{
"Splits the input string (first arg) into a slice by '\n'",
},
Func: SplitLines,
},
{
Name: "index_of",
Description: []string{
"Returns the index of first argument in the second argument which is a slice.",
"Example: {{ index_of \"foo\" (from_json \"[\"bar\",\"foo\",\"baz\"]\") }} returns 1 (int).",
},
Func: IndexOf,
},
{
Name: "indexOf",
Description: []string{
"Returns the index of first argument in the second argument which is a slice.",
"Example: {{ index_of \"foo\" (from_json \"[\"bar\",\"foo\",\"baz\"]\") }} returns 1 (int).",
},
Func: IndexOf,
},
}
}

83
vendor/github.com/docker/infrakit/pkg/template/help.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package template
import (
"fmt"
"reflect"
"strings"
)
// UpdateDocumentation uses reflection to generate documentation on usage and function signature.
func UpdateDocumentation(in []Function) []Function {
out := []Function{}
for _, f := range in {
copy := f
copy.Function = functionSignature(f.Name, f.Func)
copy.Usage = functionUsage(f.Name, f.Func)
if len(f.Description) == 0 {
copy.Description = []string{"None"}
}
out = append(out, copy)
}
return out
}
func isFunc(f interface{}) (string, bool) {
if f == nil {
return "no-function", false
}
ft := reflect.TypeOf(f)
if ft.Kind() != reflect.Func {
return "not-a-function", false
}
return ft.String(), true
}
func functionSignature(name string, f interface{}) string {
s, is := isFunc(f)
if !is {
return s
}
return s
}
func functionUsage(name string, f interface{}) string {
if s, is := isFunc(f); !is {
return s
}
ft := reflect.TypeOf(f)
if ft.Kind() != reflect.Func {
return "not-a-function"
}
args := make([]string, ft.NumIn())
for i := 0; i < len(args); i++ {
t := ft.In(i)
v := ""
switch {
case t == reflect.TypeOf(""):
v = fmt.Sprintf("\"%s\"", t.Name())
case t.Kind() == reflect.Slice && i == len(args)-1:
tt := t.Elem().Name()
if t.Elem() == reflect.TypeOf("") {
tt = fmt.Sprintf("\"%s\"", t.Name())
}
v = fmt.Sprintf("[ %s ... ]", tt)
case t.String() == "interface {}":
v = "any"
default:
v = strings.Replace(t.String(), " ", "", -1)
}
args[i] = v
}
arglist := strings.Join(args, " ")
if len(arglist) > 0 {
arglist = arglist + " "
}
return fmt.Sprintf("{{ %s %s}}", name, arglist)
}

View File

@@ -0,0 +1,385 @@
package template
import (
"bytes"
"fmt"
"io"
"reflect"
"strings"
"sync"
"text/template"
"github.com/Masterminds/sprig"
log "github.com/Sirupsen/logrus"
)
// Function contains the description of an exported template function
type Function struct {
// Name is the function name to bind in the template
Name string
// Description provides help for the function
Description []string `json:",omitempty"`
// Func is the reference to the actual function
Func interface{} `json:"-"`
// Function is the signature of the function
Function string
// Usage shows how to use it
Usage string `json:",omitempty"`
}
// FunctionExporter is implemented by any plugins wishing to show help on the function it exports.
type FunctionExporter interface {
// Funcs returns a list of special template functions of the form func(template.Context, arg1, arg2) interface{}
Funcs() []Function
}
// Context is a marker interface for a user-defined struct that is passed into the template engine (as context)
// and accessible in the exported template functions. Template functions can have the signature
// func(template.Context, arg1, arg2 ...) (string, error) and when functions like this are registered, the template
// engine will dynamically create and export a function of the form func(arg1, arg2...) (string, error) where
// the context instance becomes an out-of-band struct that can be mutated by functions. This in essence allows
// structured data as output of the template, in addition to a string from evaluating the template.
type Context interface {
// Funcs returns a list of special template functions of the form func(template.Context, arg1, arg2) interface{}
Funcs() []Function
}
// Options contains parameters for customizing the behavior of the engine
type Options struct {
// SocketDir is the directory for locating the socket file for
// a template URL of the form unix://socket_file/path/to/resource
SocketDir string
}
type defaultValue struct {
Name string
Value interface{}
Doc string
}
// Template is the templating engine
type Template struct {
options Options
url string
body []byte
parsed *template.Template
functions []func() []Function
funcs map[string]interface{}
globals map[string]interface{}
defaults map[string]defaultValue
context interface{}
registered []Function
lock sync.Mutex
parent *Template
}
// Void is used in the template functions return value type to indicate a void.
// Golang template does not allow functions with no return types to be bound.
type Void string
const voidValue Void = ""
// NewTemplate fetches the content at the url and returns a template. If the string begins
// with str:// as scheme, then the rest of the string is interpreted as the body of the template.
func NewTemplate(s string, opt Options) (*Template, error) {
var buff []byte
contextURL := s
// Special case of specifying the entire template as a string; otherwise treat as url
if strings.Index(s, "str://") == 0 {
buff = []byte(strings.Replace(s, "str://", "", 1))
contextURL = defaultContextURL()
} else {
b, err := Fetch(s, opt)
if err != nil {
return nil, err
}
buff = b
}
return NewTemplateFromBytes(buff, contextURL, opt)
}
// NewTemplateFromBytes builds the template from buffer with a contextURL which is used to deduce absolute
// path of any 'included' templates e.g. {{ include "./another.tpl" . }}
func NewTemplateFromBytes(buff []byte, contextURL string, opt Options) (*Template, error) {
if contextURL == "" {
log.Warningln("Context is not known. Included templates may not work properly.")
}
return &Template{
options: opt,
url: contextURL,
body: buff,
funcs: map[string]interface{}{},
globals: map[string]interface{}{},
defaults: map[string]defaultValue{},
functions: []func() []Function{},
}, nil
}
// SetOptions sets the runtime flags for the engine
func (t *Template) SetOptions(opt Options) *Template {
t.lock.Lock()
defer t.lock.Unlock()
t.options = opt
return t
}
// WithFunctions allows client code to extend the template by adding its own functions.
func (t *Template) WithFunctions(functions func() []Function) *Template {
t.lock.Lock()
defer t.lock.Unlock()
t.functions = append(t.functions, functions)
return t
}
// AddFunc adds a new function to support in template
func (t *Template) AddFunc(name string, f interface{}) *Template {
t.lock.Lock()
defer t.lock.Unlock()
t.funcs[name] = f
return t
}
// Ref returns the value keyed by name in the context of this template. See 'ref' template function.
func (t *Template) Ref(name string) interface{} {
if found, has := t.globals[name]; has {
return found
} else if v, has := t.defaults[name]; has {
return v.Value
}
return nil
}
// Dot returns the '.' in this template.
func (t *Template) Dot() interface{} {
return t.context
}
func (t *Template) forkFrom(parent *Template) (dotCopy interface{}, err error) {
t.lock.Lock()
defer t.lock.Unlock()
// copy the globals in the parent scope into the child
for k, v := range parent.globals {
t.globals[k] = v
}
// copy the defaults in the parent scope into the child
for k, v := range parent.defaults {
t.defaults[k] = v
}
// inherit the functions defined for this template
for k, v := range parent.funcs {
t.AddFunc(k, v)
}
// inherit other functions
for _, ff := range parent.functions {
t.functions = append(t.functions, ff)
}
if parent.context != nil {
return DeepCopyObject(parent.context)
}
return nil, nil
}
// Global sets the a key, value in the context of this template. It is visible to all the 'included'
// and 'sourced' templates by the calling template.
func (t *Template) Global(name string, value interface{}) *Template {
for here := t; here != nil; here = here.parent {
here.updateGlobal(name, value)
}
return t
}
func (t *Template) updateGlobal(name string, value interface{}) {
t.lock.Lock()
defer t.lock.Unlock()
t.globals[name] = value
}
// Def is equivalent to a {{ def "key" value "description" }} in defining a variable with a default value.
// The value is accessible via a {{ ref "key" }} in the template.
func (t *Template) Def(name string, value interface{}, doc string) *Template {
for here := t; here != nil; here = here.parent {
here.updateDef(name, value, doc)
}
return t
}
func (t *Template) updateDef(name string, val interface{}, doc ...string) *Template {
t.lock.Lock()
defer t.lock.Unlock()
t.defaults[name] = defaultValue{
Name: name,
Value: val,
Doc: strings.Join(doc, " "),
}
return t
}
// Validate parses the template and checks for validity.
func (t *Template) Validate() (*Template, error) {
t.lock.Lock()
t.parsed = nil
t.lock.Unlock()
return t, t.build(nil)
}
// Funcs returns a list of registered functions used by the template when it rendered the view.
func (t *Template) Funcs() []Function {
return t.registered
}
func (t *Template) build(context Context) error {
t.lock.Lock()
defer t.lock.Unlock()
if t.parsed != nil {
return nil
}
registered := []Function{}
fm := map[string]interface{}{}
for k, v := range sprig.TxtFuncMap() {
fm[k] = v
}
for k, v := range t.funcs {
if tf, err := makeTemplateFunc(context, v); err == nil {
fm[k] = tf
} else {
return err
}
}
// the default functions cannot be overriden
for _, f := range t.DefaultFuncs() {
tf, err := makeTemplateFunc(context, f.Func)
if err != nil {
return err
}
fm[f.Name] = tf
registered = append(registered, f)
}
// If there are any function sources that was set via WithFunctions()
for _, exp := range t.functions {
for _, f := range exp() {
tf, err := makeTemplateFunc(context, f.Func)
if err != nil {
return err
}
fm[f.Name] = tf
registered = append(registered, f)
}
}
// If the context implements the FunctionExporter interface, it can add more functions
// and potentially override existing.
if context != nil {
for _, f := range context.Funcs() {
if tf, err := makeTemplateFunc(context, f.Func); err == nil {
fm[f.Name] = tf
registered = append(registered, f)
} else {
return err
}
}
}
t.registered = registered
parsed, err := template.New(t.url).Funcs(fm).Parse(string(t.body))
if err != nil {
return err
}
t.parsed = parsed
return nil
}
// Execute is a drop-in replace of the execute method of template
func (t *Template) Execute(output io.Writer, context interface{}) error {
if err := t.build(toContext(context)); err != nil {
return err
}
t.context = context
return t.parsed.Execute(output, context)
}
// returns as Context if input implements the interface; otherwise nil
func toContext(in interface{}) Context {
var context Context
if in != nil {
if s, is := in.(Context); is {
context = s
}
}
return context
}
// Render renders the template given the context
func (t *Template) Render(context interface{}) (string, error) {
if err := t.build(toContext(context)); err != nil {
return "", err
}
var buff bytes.Buffer
err := t.Execute(&buff, context)
return buff.String(), err
}
// converts a function of f(Context, ags...) to a regular template function
func makeTemplateFunc(ctx Context, f interface{}) (interface{}, error) {
contextType := reflect.TypeOf((*Context)(nil)).Elem()
ff := reflect.Indirect(reflect.ValueOf(f))
// first we check to see if f has the special signature where the first
// parameter is the context parameter...
if ff.Kind() != reflect.Func {
return nil, fmt.Errorf("not a function:%v", f)
}
if ff.Type().NumIn() > 0 && ff.Type().In(0).AssignableTo(contextType) {
in := make([]reflect.Type, ff.Type().NumIn()-1) // exclude the context param
out := make([]reflect.Type, ff.Type().NumOut())
for i := 1; i < ff.Type().NumIn(); i++ {
in[i-1] = ff.Type().In(i)
}
variadic := false
if len(in) > 0 {
variadic = in[len(in)-1].Kind() == reflect.Slice
}
for i := 0; i < ff.Type().NumOut(); i++ {
out[i] = ff.Type().Out(i)
}
funcType := reflect.FuncOf(in, out, variadic)
funcImpl := func(in []reflect.Value) []reflect.Value {
if !variadic {
return ff.Call(append([]reflect.Value{reflect.ValueOf(ctx)}, in...))
}
variadicParam := in[len(in)-1]
last := make([]reflect.Value, variadicParam.Len())
for i := 0; i < variadicParam.Len(); i++ {
last[i] = variadicParam.Index(i)
}
return ff.Call(append(append([]reflect.Value{reflect.ValueOf(ctx)}, in[0:len(in)-1]...), last...))
}
newFunc := reflect.MakeFunc(funcType, funcImpl)
return newFunc.Interface(), nil
}
return ff.Interface(), nil
}

28
vendor/github.com/docker/infrakit/pkg/template/util.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package template
import (
"net/url"
"path/filepath"
"strings"
)
// returns a url string of the base and a relative path.
// e.g. http://host/foo/bar/baz, ./boo.tpl gives http://host/foo/bar/boo.tpl
func getURL(root, rel string) (string, error) {
// handle the case when rel is actually a full url
if strings.Index(rel, "://") > 0 {
u, err := url.Parse(rel)
if err != nil {
return "", err
}
return u.String(), nil
}
u, err := url.Parse(root)
if err != nil {
return "", err
}
u.Path = filepath.Clean(filepath.Join(filepath.Dir(u.Path), rel))
return u.String(), nil
}

92
vendor/github.com/docker/infrakit/pkg/types/any.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
package types
import (
"encoding/json"
)
// Any is the raw configuration for the plugin
type Any json.RawMessage
// AnyString returns an Any from a string that represents the marshaled/encoded data
func AnyString(s string) *Any {
return AnyBytes([]byte(s))
}
// AnyBytes returns an Any from the encoded message bytes
func AnyBytes(data []byte) *Any {
any := &Any{}
*any = data
return any
}
// AnyCopy makes a copy of the data in the given ptr.
func AnyCopy(any *Any) *Any {
if any == nil {
return &Any{}
}
return AnyBytes(any.Bytes())
}
// AnyValue returns an Any from a value by marshaling / encoding the input
func AnyValue(v interface{}) (*Any, error) {
if v == nil {
return nil, nil // So that any omitempty will see an empty/zero value
}
any := &Any{}
err := any.marshal(v)
return any, err
}
// AnyValueMust returns an Any from a value by marshaling / encoding the input. It panics if there's error.
func AnyValueMust(v interface{}) *Any {
any, err := AnyValue(v)
if err != nil {
panic(err)
}
return any
}
// Decode decodes the any into the input typed struct
func (c *Any) Decode(typed interface{}) error {
if c == nil || len([]byte(*c)) == 0 {
return nil // no effect on typed
}
return json.Unmarshal([]byte(*c), typed)
}
// marshal populates this raw message with a decoded form of the input struct.
func (c *Any) marshal(typed interface{}) error {
buff, err := json.MarshalIndent(typed, "", "")
if err != nil {
return err
}
*c = Any(json.RawMessage(buff))
return nil
}
// Bytes returns the encoded bytes
func (c *Any) Bytes() []byte {
if c == nil {
return nil
}
return []byte(*c)
}
// String returns the string representation.
func (c *Any) String() string {
return string([]byte(*c))
}
// MarshalJSON implements the json Marshaler interface
func (c *Any) MarshalJSON() ([]byte, error) {
if c == nil {
return nil, nil
}
return []byte(*c), nil
}
// UnmarshalJSON implements the json Unmarshaler interface
func (c *Any) UnmarshalJSON(data []byte) error {
*c = Any(json.RawMessage(data))
return nil
}

124
vendor/github.com/docker/infrakit/pkg/types/link.go generated vendored Normal file
View File

@@ -0,0 +1,124 @@
package types
import (
"fmt"
"math/rand"
"time"
)
const (
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
func init() {
rand.Seed(int64(time.Now().Nanosecond()))
}
// Link is a struct that represents an association between an infrakit managed resource
// and an entity in some other system. The mechanism of linkage is via labels or tags
// on both sides.
type Link struct {
value string
context string
}
// NewLink creates a link
func NewLink() *Link {
return &Link{
value: randomAlphaNumericString(16),
}
}
// NewLinkFromMap constructs a link from data in the map
func NewLinkFromMap(m map[string]string) *Link {
l := &Link{}
if v, has := m["infrakit-link"]; has {
l.value = v
}
if v, has := m["infrakit-link-context"]; has {
l.context = v
}
return l
}
// Valid returns true if the link value is set
func (l Link) Valid() bool {
return l.value != ""
}
// Value returns the value of the link
func (l Link) Value() string {
return l.value
}
// Label returns the label to look for the link
func (l Link) Label() string {
return "infrakit-link"
}
// Context returns the context of the link
func (l Link) Context() string {
return l.context
}
// WithContext sets a context for this link
func (l *Link) WithContext(s string) *Link {
l.context = s
return l
}
// KVPairs returns the link representation as a slice of Key=Value pairs
func (l *Link) KVPairs() []string {
out := []string{}
for k, v := range l.Map() {
out = append(out, fmt.Sprintf("%s=%s", k, v))
}
return out
}
// Map returns a representation that is easily converted to JSON or YAML
func (l *Link) Map() map[string]string {
return map[string]string{
"infrakit-link": l.value,
"infrakit-link-context": l.context,
}
}
// WriteMap writes to the target map. This will overwrite values of same key
func (l *Link) WriteMap(target map[string]string) {
for k, v := range l.Map() {
target[k] = v
}
}
// InMap returns true if the link is contained in the map
func (l *Link) InMap(m map[string]string) bool {
c, has := m["infrakit-link-context"]
if !has {
return false
}
if c != l.context {
return false
}
v, has := m["infrakit-link"]
if !has {
return false
}
return v == l.value
}
// Equal returns true if the links are the same - same value and context
func (l *Link) Equal(other Link) bool {
return l.value == other.value && l.context == other.context
}
// randomAlphaNumericString generates a non-secure random alpha-numeric string of a given length.
func randomAlphaNumericString(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

200
vendor/github.com/docker/infrakit/pkg/types/path.go generated vendored Normal file
View File

@@ -0,0 +1,200 @@
package types
import (
"sort"
"strings"
)
var (
// NullPath means no path
NullPath = Path([]string{})
)
// Path is used to identify a particle of metadata. The path can be strings separated by / as in a URL.
type Path []string
// PathFromString returns the path components of a / separated path
func PathFromString(path string) Path {
return Path(strings.Split(path, "/")).Clean()
}
// PathFrom return a single path of the given components
func PathFrom(a string, b ...string) Path {
p := Path(append([]string{a}, b...))
return p.Clean()
}
// PathFromStrings returns the path from a list of strings
func PathFromStrings(a string, b ...string) []Path {
list := []Path{PathFromString(a)}
for _, p := range b {
list = append(list, PathFromString(p))
}
return list
}
// String returns the string representation of path
func (p Path) String() string {
s := strings.Join([]string(p), "/")
if len(s) == 0 {
return "."
}
return s
}
// Valid returns true if is a valid path
func (p Path) Valid() bool {
return p.Len() > 0
}
// Dot returns true if this is a .
func (p Path) Dot() bool {
return len(p) == 1 && p[0] == "."
}
// Clean scrubs the path to remove any empty string or . or .. and collapse the path into a concise form.
// It's similar to path.Clean in the standard lib.
func (p Path) Clean() Path {
this := []string(p)
copy := []string{}
for _, v := range this {
switch v {
case "", ".":
case "..":
if len(copy) == 0 {
copy = append(copy, "..")
} else {
copy = copy[0 : len(copy)-1]
if len(copy) == 0 {
return NullPath
}
}
default:
copy = append(copy, v)
}
}
if len(copy) == 0 {
copy = []string{"."}
} else if this[len(this)-1] == "" || this[len(this)-1] == "." {
copy = append(copy, "")
}
return Path(copy)
}
// Len returns the length of the path
func (p Path) Len() int {
return len([]string(p))
}
// Index returns the ith component in the path
func (p Path) Index(i int) *string {
if p.Len() <= i {
return nil
}
copy := []string(p)[i]
return &copy
}
// Shift returns a new path that's shifted i positions to the left -- ith child of the head at index=0
func (p Path) Shift(i int) Path {
len := p.Len() - i
if len <= 0 {
return Path([]string{})
}
new := make([]string, len)
copy(new, []string(p)[i:])
return Path(new)
}
// Dir returns the 'dir' of the path
func (p Path) Dir() Path {
pp := p.Clean()
if len(pp) > 1 {
return p[0 : len(pp)-1]
}
return Path([]string{"."})
}
// Base returns the base of the path
func (p Path) Base() string {
pp := p.Clean()
return pp[len(pp)-1]
}
// JoinString joins the input as a child of this path
func (p Path) JoinString(child string) Path {
return p.Join(Path([]string{child}))
}
// Join joins the child to the parent
func (p Path) Join(child Path) Path {
pp := p.Clean()
this := []string(pp)
if this[len(this)-1] == "" {
pp = Path(this[:len(this)-1])
}
return Path(append(pp, []string(child)...))
}
// Rel returns a new path that is a child of the input from this path.
// e.g. For a path a/b/c/d Rel(a/b/) returns c/d. NullPath is returned if
// the two are not relative to one another.
func (p Path) Rel(path Path) Path {
if path.Equal(PathFromString(".")) {
return p
}
this := []string(p.Clean())
parent := []string(path.Clean())
if parent[len(parent)-1] == "" {
parent = parent[:len(parent)-1]
}
if len(this) < len(parent) {
return NullPath
}
for i := 0; i < len(parent); i++ {
if parent[i] != this[i] {
return NullPath
}
}
return Path(this[len(parent):])
}
// Equal returns true if the path is lexicographically equal to the other
func (p Path) Equal(other Path) bool {
if len(p) != len(other) {
return false
}
for i := 0; i < len(p); i++ {
if p[i] != other[i] {
return false
}
}
return true
}
// Less return true if the path is lexicographically less than the other
func (p Path) Less(other Path) bool {
min := len(p)
if len(other) < min {
min = len(other)
}
for i := 0; i < min; i++ {
if string(p[i]) != string(other[i]) {
return string(p[i]) < string(other[i])
}
}
return len(p) < len(other)
}
type pathSorter []Path
func (p pathSorter) Len() int { return len(p) }
func (p pathSorter) Less(i, j int) bool { return Path(p[i]).Less(Path(p[j])) }
func (p pathSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Sort sorts the paths
func Sort(p []Path) {
sort.Sort(pathSorter(p))
}

233
vendor/github.com/docker/infrakit/pkg/types/reflect.go generated vendored Normal file
View File

@@ -0,0 +1,233 @@
package types
import (
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
)
var (
indexRoot = "\\[(([+|-]*[0-9]+)|((.*)=(.*)))\\]$"
arrayIndexExp = regexp.MustCompile("(.*)" + indexRoot)
indexExp = regexp.MustCompile("^" + indexRoot)
)
// Put sets the attribute of an object at path to the given value
func Put(path []string, value interface{}, object map[string]interface{}) bool {
return put(path, value, object)
}
// Get returns the attribute of the object at path
func Get(path []string, object interface{}) interface{} {
return get(path, object)
}
// GetValue returns the attribute of the object at path, as serialized blob
func GetValue(path []string, object interface{}) (*Any, error) {
if any, is := object.(*Any); is {
return any, nil
}
return AnyValue(Get(path, object))
}
// List lists the members at the path
// If the value at the path is listable, then a slice of the children (unqualified) will be
// returned. If the value at the path doesn't exist, a nil will be returned.
// A value is listable if it is 1. a slice, 2. a map, 3. a struct (fields will be listed)
// or a 4. a types.Any where the any object will be unpacked and the same rules above will be
// applied to the decoded struct.
func List(path []string, object interface{}) []string {
list := []string{}
v := get(path, object)
if v == nil {
return nil
}
val := reflect.Indirect(reflect.ValueOf(v))
if any, is := v.(*Any); is {
var temp interface{}
if err := any.Decode(&temp); err == nil {
val = reflect.ValueOf(temp)
}
}
switch val.Kind() {
case reflect.Slice:
// this is a slice, so return the name as '[%d]'
for i := 0; i < val.Len(); i++ {
list = append(list, fmt.Sprintf("[%d]", i))
}
case reflect.Map:
for _, k := range val.MapKeys() {
list = append(list, k.String())
}
case reflect.Struct:
vt := val.Type()
for i := 0; i < vt.NumField(); i++ {
if vt.Field(i).PkgPath == "" {
list = append(list, vt.Field(i).Name)
}
}
}
sort.Strings(list)
return list
}
func put(p []string, value interface{}, store map[string]interface{}) bool {
if len(p) == 0 {
return false
}
key := p[0]
if key == "" {
return put(p[1:], value, store)
}
// check if key is an array index of the form <1>[<2>]
matches := arrayIndexExp.FindStringSubmatch(key)
if len(matches) > 2 && matches[1] != "" {
key = matches[1]
p = append([]string{key, fmt.Sprintf("[%s]", matches[2])}, p[1:]...)
return put(p, value, store)
}
s := reflect.Indirect(reflect.ValueOf(store))
switch s.Kind() {
case reflect.Slice:
return false // not supported
case reflect.Map:
if reflect.TypeOf(p[0]).AssignableTo(s.Type().Key()) {
m := s.MapIndex(reflect.ValueOf(p[0]))
if !m.IsValid() {
m = reflect.ValueOf(map[string]interface{}{})
s.SetMapIndex(reflect.ValueOf(p[0]), m)
}
if len(p) > 1 {
return put(p[1:], value, m.Interface().(map[string]interface{}))
}
s.SetMapIndex(reflect.ValueOf(p[0]), reflect.ValueOf(value))
return true
}
}
return false
}
func get(path []string, object interface{}) (value interface{}) {
if f, is := object.(func() interface{}); is {
object = f()
}
if len(path) == 0 {
return object
}
if any, is := object.(*Any); is {
var temp interface{}
if err := any.Decode(&temp); err == nil {
return get(path, temp)
}
return nil
}
key := path[0]
switch key {
case ".":
return object
case "":
return get(path[1:], object)
}
// check if key is an array index of the form <1>[<2>]
matches := arrayIndexExp.FindStringSubmatch(key)
if len(matches) > 2 && matches[1] != "" {
key = matches[1]
path = append([]string{key, fmt.Sprintf("[%s]", matches[2])}, path[1:]...)
return get(path, object)
}
v := reflect.Indirect(reflect.ValueOf(object))
switch v.Kind() {
case reflect.Slice:
i := 0
matches = indexExp.FindStringSubmatch(key)
if len(matches) > 0 {
if matches[2] != "" {
// numeric index
if index, err := strconv.Atoi(matches[1]); err == nil {
switch {
case index >= 0 && v.Len() > index:
i = index
case index < 0 && v.Len() > -index: // negative index like python
i = v.Len() + index
}
}
return get(path[1:], v.Index(i).Interface())
} else if matches[3] != "" {
// equality search index for 'field=check'
lhs := matches[4] // supports another select expression for extracting deeply from the struct
rhs := matches[5]
// loop through the array looking for field that matches the check value
for j := 0; j < v.Len(); j++ {
if el := get(tokenize(lhs), v.Index(j).Interface()); el != nil {
if fmt.Sprintf("%v", el) == rhs {
return get(path[1:], v.Index(j).Interface())
}
}
}
}
}
case reflect.Map:
value := v.MapIndex(reflect.ValueOf(key))
if value.IsValid() {
return get(path[1:], value.Interface())
}
case reflect.Struct:
fv := v.FieldByName(key)
if !fv.IsValid() {
return nil
}
if !fv.CanInterface() {
return nil
}
return get(path[1:], fv.Interface())
}
return nil
}
// With quoting to support azure rm type names: e.g. Microsoft.Network/virtualNetworks
// This will split a sting like /Resources/'Microsoft.Network/virtualNetworks'/managerSubnet/Name" into
// [ , Resources, Microsoft.Network/virtualNetworks, managerSubnet, Name]
func tokenize(s string) []string {
if len(s) == 0 {
return []string{}
}
a := []string{}
start := 0
quoted := false
for i := 0; i < len(s); i++ {
switch s[i] {
case '/':
if !quoted {
a = append(a, strings.Replace(s[start:i], "'", "", -1))
start = i + 1
}
case '\'':
quoted = !quoted
}
}
if start < len(s)-1 {
a = append(a, strings.Replace(s[start:], "'", "", -1))
}
return a
}