Implement the Fission provider scaffolding; and Environments

This commit is contained in:
joeduffy 2017-04-19 14:23:01 -07:00
parent 936d3f45e6
commit 366e236853
4 changed files with 291 additions and 0 deletions

View file

@ -0,0 +1,21 @@
// Copyright 2017 Pulumi, Inc. All rights reserved.
package main
import (
"os"
"github.com/fission/fission/controller/client"
"github.com/pulumi/coconut/pkg/util/contract"
)
type Context struct {
Fission *client.Client // the Fission controller client.
}
func NewContext() *Context {
// TODO[pulumi/coconut#117]: fetch the client from config rather than environment variables.
url := os.Getenv("FISSION_URL")
contract.Assertf(url != "", "Missing FISSION_URL environment variable")
return &Context{Fission: client.MakeClient(url)}
}

View file

@ -0,0 +1,109 @@
// Copyright 2017 Pulumi, Inc. All rights reserved.
package main
import (
"errors"
"fmt"
"github.com/fission/fission"
pbempty "github.com/golang/protobuf/ptypes/empty"
pbstruct "github.com/golang/protobuf/ptypes/struct"
"github.com/pulumi/coconut/pkg/resource"
"github.com/pulumi/coconut/pkg/tokens"
"github.com/pulumi/coconut/pkg/util/contract"
"github.com/pulumi/coconut/pkg/util/mapper"
"github.com/pulumi/coconut/sdk/go/pkg/cocorpc"
"golang.org/x/net/context"
)
const Environment = tokens.Type("kube-fission:environment:Environment")
// NewEnvironmentProvider creates a provider that handles S3 environment operations.
func NewEnvironmentProvider(ctx *Context) cocorpc.ResourceProviderServer {
return &envProvider{ctx}
}
type envProvider struct {
ctx *Context
}
// Check validates that the given property bag is valid for a resource of the given type.
func (p *envProvider) Check(ctx context.Context, req *cocorpc.CheckRequest) (*cocorpc.CheckResponse, error) {
// Read in the properties, create and validate a new group, and return the failures (if any).
contract.Assert(req.GetType() == string(Environment))
_, _, result := unmarshalEnvironment(req.GetProperties())
return resource.NewCheckResponse(result), nil
}
// Name names a given resource. Sometimes this will be assigned by a developer, and so the provider
// simply fetches it from the property bag; other times, the provider will assign this based on its own algorithm.
// In any case, resources with the same name must be safe to use interchangeably with one another.
func (p *envProvider) Name(ctx context.Context, req *cocorpc.NameRequest) (*cocorpc.NameResponse, error) {
contract.Failf("Kube-Fission top-level dispatcher should have handled this RPC")
return nil, nil
}
// Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID
// must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational").
func (p *envProvider) Create(ctx context.Context, req *cocorpc.CreateRequest) (*cocorpc.CreateResponse, error) {
contract.Assert(req.GetType() == string(Environment))
// Read in the properties given by the request, validating as we go; if any fail, reject the request.
env, _, err := unmarshalEnvironment(req.GetProperties())
if err != nil {
return nil, err
}
// Perform the operation by contacting the controller.
fmt.Printf("Creating Fission environment '%v'\n", env.Metadata.Name)
meta, decerr := p.ctx.Fission.EnvironmentCreate(&env)
if decerr != nil {
return nil, decerr
}
fmt.Printf("Fission Environment '%v' created: version=%v\n", meta.Name, meta.Uid)
return &cocorpc.CreateResponse{Id: meta.Name}, nil
}
// Read reads the instance state identified by ID, returning a populated resource object, or an error if not found.
func (p *envProvider) Read(ctx context.Context, req *cocorpc.ReadRequest) (*cocorpc.ReadResponse, error) {
contract.Assert(req.GetType() == string(Environment))
return nil, errors.New("Not yet implemented")
}
// Update updates an existing resource with new values. Only those values in the provided property bag are updated
// to new values. The resource ID is returned and may be different if the resource had to be recreated.
func (p *envProvider) Update(ctx context.Context, req *cocorpc.UpdateRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(Environment))
return nil, errors.New("Not yet implemented")
}
// UpdateImpact checks what impacts a hypothetical update will have on the resource's properties.
func (p *envProvider) UpdateImpact(
ctx context.Context, req *cocorpc.UpdateRequest) (*cocorpc.UpdateImpactResponse, error) {
contract.Assert(req.GetType() == string(Environment))
return nil, errors.New("Not yet implemented")
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.
func (p *envProvider) Delete(ctx context.Context, req *cocorpc.DeleteRequest) (*pbempty.Empty, error) {
contract.Assert(req.GetType() == string(Environment))
id := req.GetId()
fmt.Printf("Deleting Fission Environment '%v'\n", id)
meta := &fission.Metadata{Name: id}
if err := p.ctx.Fission.EnvironmentDelete(meta); err != nil {
return nil, err
}
fmt.Printf("Fission Environment '%v' deleted\n", id)
return &pbempty.Empty{}, nil
}
// unmarshalEnvironment decodes and validates an environment property bag.
func unmarshalEnvironment(v *pbstruct.Struct) (fission.Environment, resource.PropertyMap, mapper.DecodeError) {
var env fission.Environment
props := resource.UnmarshalProperties(v)
err := mapper.MapIU(props.Mappable(), &env)
return env, props, err
}

View file

@ -0,0 +1,38 @@
// Copyright 2017 Pulumi, Inc. All rights reserved.
package main
import (
"fmt"
"os"
"github.com/pulumi/coconut/pkg/util/rpcutil"
"github.com/pulumi/coconut/sdk/go/pkg/cocorpc"
"google.golang.org/grpc"
)
func main() {
// Fire up a gRPC server, letting the kernel choose a free port for us.
port, done, err := rpcutil.Serve(0, []func(*grpc.Server) error{
func(srv *grpc.Server) error {
prov, err := NewProvider()
if err != nil {
return fmt.Errorf("failed to create Kube-Fission resource provider: %v", err)
}
cocorpc.RegisterResourceProviderServer(srv, prov)
return nil
},
})
if err != nil {
fmt.Fprintf(os.Stderr, "fatal: %v\n", err)
os.Exit(-1)
}
// The resource provider protocol requires that we now write out the port we have chosen to listen on.
fmt.Printf("%d\n", port)
// Finally, wait for the server to stop serving.
if err := <-done; err != nil {
fmt.Fprintf(os.Stderr, "fatal: %v\n", err)
}
}

View file

@ -0,0 +1,123 @@
// Copyright 2017 Pulumi, Inc. All rights reserved.
package main
import (
"fmt"
"github.com/fission/fission"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pulumi/coconut/pkg/resource"
"github.com/pulumi/coconut/pkg/tokens"
"github.com/pulumi/coconut/pkg/util/contract"
"github.com/pulumi/coconut/pkg/util/mapper"
"github.com/pulumi/coconut/sdk/go/pkg/cocorpc"
"golang.org/x/net/context"
)
// provider implements the Fission resource package functionality behind a gRPC interface.
type Provider struct {
impls map[tokens.Type]cocorpc.ResourceProviderServer
}
// NewProvider creates a new provider instance with server objects registered for every resource type.
func NewProvider() (*Provider, error) {
ctx := NewContext()
return &Provider{
impls: map[tokens.Type]cocorpc.ResourceProviderServer{
Environment: NewEnvironmentProvider(ctx),
},
}, nil
}
var _ cocorpc.ResourceProviderServer = (*Provider)(nil)
// Check validates that the given property bag is valid for a resource of the given type.
func (p *Provider) Check(ctx context.Context, req *cocorpc.CheckRequest) (*cocorpc.CheckResponse, error) {
t := tokens.Type(req.GetType())
if prov, has := p.impls[t]; has {
return prov.Check(ctx, req)
}
return nil, fmt.Errorf("Unrecognized resource type (Check): %v", t)
}
// Name names a given resource. Sometimes this will be assigned by a developer, and so the provider
// simply fetches it from the property bag; other times, the provider will assign this based on its own algorithm.
// In any case, resources with the same name must be safe to use interchangeably with one another.
func (p *Provider) Name(ctx context.Context, req *cocorpc.NameRequest) (*cocorpc.NameResponse, error) {
// First, validate that this is a type we understand.
t := tokens.Type(req.GetType())
if _, has := p.impls[t]; !has {
return nil, fmt.Errorf("Unrecognized resource type (Name): %v", t)
}
// All Fission names are taken from the Metadata header:
//
// {
// "metadata": {
// "name": "<NAME GOES HERE>"
//
// So, no need to ask the individual resource providers to do this; we can do it once and for all here.
var record struct {
Metadata fission.Metadata `json:"metadata"`
}
// Unmarshal the property bag into our struct.
props := req.GetProperties()
uprops := resource.UnmarshalProperties(props)
if err := mapper.MapIU(uprops.Mappable(), &record); err != nil {
return nil, err
}
// If we got here, the name is valid; return it.
contract.Assert(record.Metadata.Name != "")
return &cocorpc.NameResponse{Name: record.Metadata.Name}, nil
}
// Create allocates a new instance of the provided resource and returns its unique ID afterwards. (The input ID
// must be blank.) If this call fails, the resource must not have been created (i.e., it is "transacational").
func (p *Provider) Create(ctx context.Context, req *cocorpc.CreateRequest) (*cocorpc.CreateResponse, error) {
t := tokens.Type(req.GetType())
if prov, has := p.impls[t]; has {
return prov.Create(ctx, req)
}
return nil, fmt.Errorf("Unrecognized resource type (Create): %v", t)
}
// Read reads the instance state identified by ID, returning a populated resource object, or an error if not found.
func (p *Provider) Read(ctx context.Context, req *cocorpc.ReadRequest) (*cocorpc.ReadResponse, error) {
t := tokens.Type(req.GetType())
if prov, has := p.impls[t]; has {
return prov.Read(ctx, req)
}
return nil, fmt.Errorf("Unrecognized resource type (Read): %v", t)
}
// Update updates an existing resource with new values. Only those values in the provided property bag are updated
// to new values. The resource ID is returned and may be different if the resource had to be recreated.
func (p *Provider) Update(ctx context.Context, req *cocorpc.UpdateRequest) (*pbempty.Empty, error) {
t := tokens.Type(req.GetType())
if prov, has := p.impls[t]; has {
return prov.Update(ctx, req)
}
return nil, fmt.Errorf("Unrecognized resource type (Update): %v", t)
}
// UpdateImpact checks what impacts a hypothetical update will have on the resource's properties.
func (p *Provider) UpdateImpact(
ctx context.Context, req *cocorpc.UpdateRequest) (*cocorpc.UpdateImpactResponse, error) {
t := tokens.Type(req.GetType())
if prov, has := p.impls[t]; has {
return prov.UpdateImpact(ctx, req)
}
return nil, fmt.Errorf("Unrecognized resource type (UpdateImpact): %v", t)
}
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed to still exist.
func (p *Provider) Delete(ctx context.Context, req *cocorpc.DeleteRequest) (*pbempty.Empty, error) {
t := tokens.Type(req.GetType())
if prov, has := p.impls[t]; has {
return prov.Delete(ctx, req)
}
return nil, fmt.Errorf("Unrecognized resource type (Delete): %v", t)
}