mirror of
https://github.com/kittywitch/nixfiles.git
synced 2026-02-09 20:39:18 -08:00
340 lines
9.1 KiB
Go
340 lines
9.1 KiB
Go
// Copyright 2022, Pulumi Corporation.
|
|
//
|
|
// 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.
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/blang/semver"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
|
presource "github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
p "github.com/pulumi/pulumi-go-provider"
|
|
)
|
|
|
|
type Server interface {
|
|
GetSchema(p.GetSchemaRequest) (p.GetSchemaResponse, error)
|
|
Cancel() error
|
|
CheckConfig(p.CheckRequest) (p.CheckResponse, error)
|
|
DiffConfig(p.DiffRequest) (p.DiffResponse, error)
|
|
Configure(p.ConfigureRequest) error
|
|
Invoke(p.InvokeRequest) (p.InvokeResponse, error)
|
|
Check(p.CheckRequest) (p.CheckResponse, error)
|
|
Diff(p.DiffRequest) (p.DiffResponse, error)
|
|
Create(p.CreateRequest) (p.CreateResponse, error)
|
|
Read(p.ReadRequest) (p.ReadResponse, error)
|
|
Update(p.UpdateRequest) (p.UpdateResponse, error)
|
|
Delete(p.DeleteRequest) error
|
|
Construct(p.ConstructRequest) (p.ConstructResponse, error)
|
|
}
|
|
|
|
func NewServer(pkg string, version semver.Version, provider p.Provider) Server {
|
|
return &server{p.RunInfo{
|
|
PackageName: pkg,
|
|
Version: version.String(),
|
|
}, provider.WithDefaults(),
|
|
context.Background()}
|
|
}
|
|
|
|
type server struct {
|
|
runInfo p.RunInfo
|
|
p p.Provider
|
|
context context.Context
|
|
}
|
|
|
|
type ctx struct {
|
|
context.Context
|
|
runInfo p.RunInfo
|
|
urn presource.URN
|
|
}
|
|
|
|
func (c *ctx) Log(severity diag.Severity, msg string) {
|
|
if c.urn != "" {
|
|
fmt.Printf("Log(%s): %s", severity, msg)
|
|
return
|
|
}
|
|
fmt.Printf("%s Log(%s): %s", c.urn, severity, msg)
|
|
}
|
|
func (c *ctx) Logf(severity diag.Severity, msg string, args ...any) {
|
|
c.Log(severity, fmt.Sprintf(msg, args...))
|
|
}
|
|
func (c *ctx) LogStatus(severity diag.Severity, msg string) {
|
|
if c.urn != "" {
|
|
fmt.Printf("LogStatus(%s): %s", severity, msg)
|
|
return
|
|
}
|
|
fmt.Printf("%s LogStatus(%s): %s", c.urn, severity, msg)
|
|
|
|
}
|
|
func (c *ctx) LogStatusf(severity diag.Severity, msg string, args ...any) {
|
|
c.LogStatus(severity, fmt.Sprintf(msg, args...))
|
|
}
|
|
|
|
func (c *ctx) RuntimeInformation() p.RunInfo { return c.runInfo }
|
|
|
|
func (s *server) ctx(urn presource.URN) p.Context {
|
|
return &ctx{s.context, s.runInfo, urn}
|
|
}
|
|
|
|
func (s *server) GetSchema(req p.GetSchemaRequest) (p.GetSchemaResponse, error) {
|
|
return s.p.GetSchema(s.ctx(""), req)
|
|
}
|
|
|
|
func (s *server) Cancel() error {
|
|
return s.p.Cancel(s.ctx(""))
|
|
}
|
|
|
|
func (s *server) CheckConfig(req p.CheckRequest) (p.CheckResponse, error) {
|
|
return s.p.CheckConfig(s.ctx(""), req)
|
|
}
|
|
|
|
func (s *server) DiffConfig(req p.DiffRequest) (p.DiffResponse, error) {
|
|
return s.p.DiffConfig(s.ctx(""), req)
|
|
}
|
|
|
|
func (s *server) Configure(req p.ConfigureRequest) error {
|
|
return s.p.Configure(s.ctx(""), req)
|
|
}
|
|
|
|
func (s *server) Invoke(req p.InvokeRequest) (p.InvokeResponse, error) {
|
|
return s.p.Invoke(s.ctx(presource.URN(req.Token)), req)
|
|
}
|
|
|
|
func (s *server) Check(req p.CheckRequest) (p.CheckResponse, error) {
|
|
return s.p.Check(s.ctx(req.Urn), req)
|
|
}
|
|
|
|
func (s *server) Diff(req p.DiffRequest) (p.DiffResponse, error) {
|
|
return s.p.Diff(s.ctx(req.Urn), req)
|
|
}
|
|
|
|
func (s *server) Create(req p.CreateRequest) (p.CreateResponse, error) {
|
|
return s.p.Create(s.ctx(req.Urn), req)
|
|
}
|
|
|
|
func (s *server) Read(req p.ReadRequest) (p.ReadResponse, error) {
|
|
return s.p.Read(s.ctx(req.Urn), req)
|
|
}
|
|
|
|
func (s *server) Update(req p.UpdateRequest) (p.UpdateResponse, error) {
|
|
return s.p.Update(s.ctx(req.Urn), req)
|
|
}
|
|
|
|
func (s *server) Delete(req p.DeleteRequest) error {
|
|
return s.p.Delete(s.ctx(req.Urn), req)
|
|
}
|
|
|
|
func (s *server) Construct(req p.ConstructRequest) (p.ConstructResponse, error) {
|
|
return s.p.Construct(s.ctx(req.URN), req)
|
|
}
|
|
|
|
// TODO: Add support for diff verification
|
|
type Operation struct {
|
|
// The inputs for the operation
|
|
Inputs presource.PropertyMap
|
|
// The expected output for the operation. If ExpectedOutput is nil, no check will be made.
|
|
ExpectedOutput presource.PropertyMap
|
|
// A function called on the output of this operation.
|
|
Hook func(inputs, output presource.PropertyMap)
|
|
// If the test should expect the operation to signal an error.
|
|
ExpectFailure bool
|
|
// If CheckFailures is non-nil, expect the check step to fail with the provided output.
|
|
CheckFailures []p.CheckFailure
|
|
}
|
|
|
|
// Steps describing the lifecycle of a resource.
|
|
type LifeCycleTest struct {
|
|
Resource tokens.Type
|
|
Create Operation
|
|
Updates []Operation
|
|
}
|
|
|
|
// Run a resource through it's lifecycle asserting that its output is as expected.
|
|
// The resource is
|
|
// 1. Previewed
|
|
// 2. Created
|
|
// 2. Previewed and Updated for each update in the Updates list.
|
|
// 3. Deleted
|
|
func (l LifeCycleTest) Run(t *testing.T, server Server) {
|
|
urn := presource.NewURN("test", "provider", "", l.Resource, "test")
|
|
|
|
runCreate := func(op Operation) (p.CreateResponse, bool) {
|
|
// Here we do the create and the initial setup
|
|
checkResponse, err := server.Check(p.CheckRequest{
|
|
Urn: urn,
|
|
Olds: nil,
|
|
News: op.Inputs,
|
|
})
|
|
assert.NoError(t, err, "resource check errored")
|
|
if len(op.CheckFailures) > 0 || len(checkResponse.Failures) > 0 {
|
|
assert.ElementsMatch(t, op.CheckFailures, checkResponse.Failures,
|
|
"check failures mismatch on create")
|
|
return p.CreateResponse{}, false
|
|
}
|
|
|
|
_, err = server.Create(p.CreateRequest{
|
|
Urn: urn,
|
|
Properties: checkResponse.Inputs.Copy(),
|
|
Preview: true,
|
|
})
|
|
// We allow the failure from ExpectFailure to hit at either the preview or the Create.
|
|
if op.ExpectFailure && err != nil {
|
|
return p.CreateResponse{}, false
|
|
}
|
|
createResponse, err := server.Create(p.CreateRequest{
|
|
Urn: urn,
|
|
Properties: checkResponse.Inputs.Copy(),
|
|
})
|
|
if op.ExpectFailure {
|
|
assert.Error(t, err, "expected an error on create")
|
|
return p.CreateResponse{}, false
|
|
}
|
|
assert.NoError(t, err, "failed to run the create")
|
|
if err != nil {
|
|
return p.CreateResponse{}, false
|
|
}
|
|
if op.Hook != nil {
|
|
op.Hook(checkResponse.Inputs, createResponse.Properties.Copy())
|
|
}
|
|
if op.ExpectedOutput != nil {
|
|
assert.EqualValues(t, op.ExpectedOutput, createResponse.Properties, "create outputs")
|
|
}
|
|
return createResponse, true
|
|
}
|
|
|
|
createResponse, keepGoing := runCreate(l.Create)
|
|
if !keepGoing {
|
|
return
|
|
}
|
|
|
|
id := createResponse.ID
|
|
olds := createResponse.Properties
|
|
for i, update := range l.Updates {
|
|
// Perform the check
|
|
check, err := server.Check(p.CheckRequest{
|
|
Urn: urn,
|
|
Olds: olds,
|
|
News: update.Inputs,
|
|
})
|
|
|
|
assert.NoErrorf(t, err, "check returned an error on update %d", i)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(update.CheckFailures) > 0 || len(check.Failures) > 0 {
|
|
assert.ElementsMatchf(t, update.CheckFailures, check.Failures,
|
|
"check failures mismatch on update %d", i)
|
|
continue
|
|
}
|
|
|
|
diff, err := server.Diff(p.DiffRequest{
|
|
ID: id,
|
|
Urn: urn,
|
|
Olds: olds,
|
|
News: check.Inputs.Copy(),
|
|
})
|
|
assert.NoErrorf(t, err, "diff failed on update %d", i)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !diff.HasChanges {
|
|
// We don't have any changes, so we can just do nothing
|
|
continue
|
|
}
|
|
isDelete := false
|
|
for _, v := range diff.DetailedDiff {
|
|
switch v.Kind {
|
|
case p.AddReplace:
|
|
fallthrough
|
|
case p.DeleteReplace:
|
|
fallthrough
|
|
case p.UpdateReplace:
|
|
isDelete = true
|
|
}
|
|
}
|
|
if isDelete {
|
|
runDelete := func() {
|
|
err = server.Delete(p.DeleteRequest{
|
|
ID: id,
|
|
Urn: urn,
|
|
Properties: olds,
|
|
})
|
|
assert.NoError(t, err, "failed to delete the resource")
|
|
}
|
|
if diff.DeleteBeforeReplace {
|
|
runDelete()
|
|
result, keepGoing := runCreate(update)
|
|
if !keepGoing {
|
|
continue
|
|
}
|
|
id = result.ID
|
|
olds = result.Properties
|
|
} else {
|
|
result, keepGoing := runCreate(update)
|
|
if !keepGoing {
|
|
continue
|
|
}
|
|
|
|
runDelete()
|
|
// Set the new block
|
|
id = result.ID
|
|
olds = result.Properties
|
|
}
|
|
} else {
|
|
|
|
// Now perform the preview
|
|
_, err = server.Update(p.UpdateRequest{
|
|
ID: id,
|
|
Urn: urn,
|
|
Olds: olds,
|
|
News: check.Inputs.Copy(),
|
|
Preview: true,
|
|
})
|
|
|
|
if update.ExpectFailure && err != nil {
|
|
continue
|
|
}
|
|
|
|
result, err := server.Update(p.UpdateRequest{
|
|
ID: id,
|
|
Urn: urn,
|
|
Olds: olds,
|
|
News: check.Inputs.Copy(),
|
|
})
|
|
if update.ExpectFailure {
|
|
assert.Errorf(t, err, "expected failure on update %d", i)
|
|
continue
|
|
}
|
|
if update.Hook != nil {
|
|
update.Hook(check.Inputs, result.Properties.Copy())
|
|
}
|
|
if update.ExpectedOutput != nil {
|
|
assert.EqualValues(t, update.ExpectedOutput, result.Properties.Copy(), "expected output on update %d", i)
|
|
}
|
|
olds = result.Properties
|
|
}
|
|
}
|
|
err := server.Delete(p.DeleteRequest{
|
|
ID: id,
|
|
Urn: urn,
|
|
Properties: olds,
|
|
})
|
|
assert.NoError(t, err, "failed to delete the resource")
|
|
|
|
}
|