2019-05-02 21:22:50 +00:00
|
|
|
// Copyright 2016-2018, 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 deploy
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-12-12 12:19:42 +00:00
|
|
|
"errors"
|
2023-12-22 19:07:09 +00:00
|
|
|
"io"
|
|
|
|
"sync"
|
2019-05-02 21:22:50 +00:00
|
|
|
"testing"
|
|
|
|
|
2023-12-22 19:07:09 +00:00
|
|
|
"github.com/blang/semver"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
|
2024-04-25 17:30:30 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
2023-12-22 19:07:09 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
|
2023-12-22 19:07:09 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
2021-03-17 13:20:05 +00:00
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
2019-05-02 21:22:50 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2024-01-17 09:35:20 +00:00
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
2019-05-02 21:22:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestQuerySource_Trivial_Wait(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2019-05-02 21:22:50 +00:00
|
|
|
// Trivial querySource returns immediately with `Wait()`, even with multiple invocations.
|
|
|
|
|
|
|
|
// Success case.
|
2023-12-19 16:14:40 +00:00
|
|
|
var called1 bool
|
|
|
|
resmon1 := mockResmon{
|
|
|
|
CancelF: func() error {
|
|
|
|
called1 = true
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2023-09-20 15:43:46 +00:00
|
|
|
qs1, _ := newTestQuerySource(&resmon1, func(*querySource) error {
|
2019-05-02 21:22:50 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
qs1.forkRun()
|
|
|
|
|
2023-10-13 09:46:07 +00:00
|
|
|
err := qs1.Wait()
|
|
|
|
assert.NoError(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.False(t, called1)
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
// Can be called twice.
|
2023-10-13 09:46:07 +00:00
|
|
|
err = qs1.Wait()
|
|
|
|
assert.NoError(t, err)
|
2019-05-02 21:22:50 +00:00
|
|
|
|
|
|
|
// Failure case.
|
2023-12-19 16:14:40 +00:00
|
|
|
var called2 bool
|
|
|
|
resmon2 := mockResmon{
|
|
|
|
CancelF: func() error {
|
|
|
|
called2 = true
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2023-09-20 15:43:46 +00:00
|
|
|
qs2, _ := newTestQuerySource(&resmon2, func(*querySource) error {
|
2023-12-12 12:19:42 +00:00
|
|
|
return errors.New("failed")
|
2019-05-02 21:22:50 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
qs2.forkRun()
|
|
|
|
|
2023-10-13 09:46:07 +00:00
|
|
|
err = qs2.Wait()
|
|
|
|
assert.False(t, result.IsBail(err))
|
|
|
|
assert.Error(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.False(t, called2)
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
// Can be called twice.
|
2023-10-13 09:46:07 +00:00
|
|
|
err = qs2.Wait()
|
|
|
|
assert.False(t, result.IsBail(err))
|
|
|
|
assert.Error(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.False(t, called2)
|
2019-05-02 21:22:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestQuerySource_Async_Wait(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2019-05-02 21:22:50 +00:00
|
|
|
// `Wait()` executes asynchronously.
|
|
|
|
|
|
|
|
// Success case.
|
|
|
|
//
|
|
|
|
// test blocks until querySource signals execution has started
|
|
|
|
// -> querySource blocks until test acknowledges querySource's signal
|
|
|
|
// -> test blocks on `Wait()` until querySource completes.
|
2023-12-19 16:14:40 +00:00
|
|
|
var called1 bool
|
|
|
|
resmon1 := mockResmon{
|
|
|
|
CancelF: func() error {
|
|
|
|
called1 = true
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
qs1Start, qs1StartAck := make(chan interface{}), make(chan interface{})
|
2023-09-20 15:43:46 +00:00
|
|
|
qs1, _ := newTestQuerySource(&resmon1, func(*querySource) error {
|
2019-05-02 21:22:50 +00:00
|
|
|
qs1Start <- struct{}{}
|
|
|
|
<-qs1StartAck
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
qs1.forkRun()
|
|
|
|
|
|
|
|
// Wait until querySource starts, then acknowledge starting.
|
|
|
|
<-qs1Start
|
|
|
|
go func() {
|
|
|
|
qs1StartAck <- struct{}{}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for querySource to complete.
|
2023-10-13 09:46:07 +00:00
|
|
|
err := qs1.Wait()
|
|
|
|
assert.NoError(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.False(t, called1)
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-10-13 09:46:07 +00:00
|
|
|
err = qs1.Wait()
|
|
|
|
assert.NoError(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.False(t, called1)
|
|
|
|
|
|
|
|
var called2 bool
|
|
|
|
resmon2 := mockResmon{
|
|
|
|
CancelF: func() error {
|
|
|
|
called2 = true
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
// Cancellation case.
|
|
|
|
//
|
|
|
|
// test blocks until querySource signals execution has started
|
|
|
|
// -> querySource blocks until test acknowledges querySource's signal
|
|
|
|
// -> test blocks on `Wait()` until querySource completes.
|
|
|
|
qs2Start, qs2StartAck := make(chan interface{}), make(chan interface{})
|
2023-09-20 15:43:46 +00:00
|
|
|
qs2, cancelQs2 := newTestQuerySource(&resmon2, func(*querySource) error {
|
2019-05-02 21:22:50 +00:00
|
|
|
qs2Start <- struct{}{}
|
|
|
|
// Block forever.
|
|
|
|
<-qs2StartAck
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
qs2.forkRun()
|
|
|
|
|
|
|
|
// Wait until querySource starts, then cancel.
|
|
|
|
<-qs2Start
|
|
|
|
go func() {
|
|
|
|
cancelQs2()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for querySource to complete.
|
2023-10-13 09:46:07 +00:00
|
|
|
err = qs2.Wait()
|
|
|
|
assert.NoError(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.True(t, called2)
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-10-13 09:46:07 +00:00
|
|
|
err = qs2.Wait()
|
|
|
|
assert.NoError(t, err)
|
2023-12-19 16:14:40 +00:00
|
|
|
assert.True(t, called2)
|
2019-05-02 21:22:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestQueryResourceMonitor_UnsupportedOperations(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2019-05-02 21:22:50 +00:00
|
|
|
rm := &queryResmon{}
|
|
|
|
|
2023-09-25 12:25:26 +00:00
|
|
|
_, err := rm.ReadResource(context.Background(), nil)
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.EqualError(t, err, "Query mode does not support reading resources")
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-09-25 12:25:26 +00:00
|
|
|
_, err = rm.RegisterResource(context.Background(), nil)
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.EqualError(t, err, "Query mode does not support creating, updating, or deleting resources")
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-09-25 12:25:26 +00:00
|
|
|
_, err = rm.RegisterResourceOutputs(context.Background(), nil)
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.EqualError(t, err, "Query mode does not support registering resource operations")
|
2019-05-02 21:22:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 19:07:09 +00:00
|
|
|
func TestQueryResourceMonitor(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("newQueryResourceMonitor", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("bad decrypter", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
providerRegErrChan := make(chan error, 1)
|
|
|
|
expectedErr := errors.New("expected error")
|
|
|
|
resmon, err := newQueryResourceMonitor(
|
|
|
|
nil, nil, nil, nil, nil, providerRegErrChan, nil, &EvalRunInfo{
|
|
|
|
Proj: &workspace.Project{
|
|
|
|
Name: "expected-project",
|
|
|
|
},
|
|
|
|
Target: &Target{
|
|
|
|
Config: config.Map{
|
|
|
|
config.MustMakeKey("test", "secret"): config.NewSecureValue("secret-value"),
|
|
|
|
config.MustMakeKey("test", "regular"): config.NewValue("regular-value"),
|
|
|
|
},
|
|
|
|
Decrypter: &decrypterMock{
|
|
|
|
DecryptValueF: func(
|
|
|
|
ctx context.Context, ciphertext string,
|
|
|
|
) (string, error) {
|
|
|
|
return "", expectedErr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
_ = resmon
|
|
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
|
|
})
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
providerRegErrChan := make(chan error, 1)
|
|
|
|
resmon, err := newQueryResourceMonitor(
|
|
|
|
nil, nil, nil, nil, nil, providerRegErrChan, nil, &EvalRunInfo{
|
|
|
|
Proj: &workspace.Project{
|
|
|
|
Name: "expected-project",
|
|
|
|
},
|
|
|
|
Target: &Target{
|
|
|
|
Name: tokens.MustParseStackName("expected-name"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "expected-project", resmon.callInfo.Project)
|
|
|
|
assert.Equal(t, "expected-name", resmon.callInfo.Stack)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
t.Run("Cancel", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
expectedErr := errors.New("expected-error")
|
|
|
|
done := make(chan error, 1)
|
|
|
|
done <- expectedErr
|
|
|
|
rm := &queryResmon{
|
|
|
|
cancel: make(chan bool),
|
|
|
|
done: done,
|
|
|
|
}
|
|
|
|
assert.ErrorIs(t, rm.Cancel(), expectedErr)
|
|
|
|
})
|
|
|
|
t.Run("Invoke", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("bad provider request", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("bad version", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
rm := &queryResmon{}
|
|
|
|
_, err := rm.Invoke(context.Background(), &pulumirpc.ResourceInvokeRequest{
|
|
|
|
Tok: "pkgA:index:func",
|
|
|
|
Version: "bad-version",
|
|
|
|
})
|
|
|
|
assert.ErrorContains(t, err, "No Major.Minor.Patch elements found")
|
|
|
|
})
|
|
|
|
t.Run("default provider error", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
providerRegChan := make(chan *registerResourceEvent, 1)
|
|
|
|
requests := make(chan defaultProviderRequest, 1)
|
|
|
|
rm := &queryResmon{
|
|
|
|
reg: &providers.Registry{},
|
|
|
|
defaultProviders: &defaultProviders{
|
|
|
|
requests: requests,
|
|
|
|
providerRegChan: providerRegChan,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
wg := &sync.WaitGroup{}
|
|
|
|
wg.Add(1)
|
|
|
|
expectedErr := errors.New("expected error")
|
|
|
|
// Needed so defaultProviders.handleRequest() doesn't hang.
|
|
|
|
go func() {
|
|
|
|
req := <-rm.defaultProviders.requests
|
|
|
|
req.response <- defaultProviderResponse{
|
|
|
|
err: expectedErr,
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
_, err := rm.Invoke(context.Background(), &pulumirpc.ResourceInvokeRequest{
|
|
|
|
Tok: "pkgA:index:func",
|
|
|
|
Version: "1.0.0",
|
|
|
|
})
|
|
|
|
wg.Wait()
|
|
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-05-02 21:22:50 +00:00
|
|
|
//
|
2023-12-22 19:07:09 +00:00
|
|
|
// Test querySource constructor.
|
2019-05-02 21:22:50 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
func newTestQuerySource(mon SourceResourceMonitor,
|
2023-09-20 15:43:46 +00:00
|
|
|
runLangPlugin func(*querySource) error,
|
2023-03-03 16:36:39 +00:00
|
|
|
) (*querySource, context.CancelFunc) {
|
2019-05-02 21:22:50 +00:00
|
|
|
cancel, cancelFunc := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
return &querySource{
|
2019-08-27 17:10:51 +00:00
|
|
|
mon: mon,
|
|
|
|
runLangPlugin: runLangPlugin,
|
2023-09-20 15:43:46 +00:00
|
|
|
langPluginFinChan: make(chan error),
|
2019-08-27 17:10:51 +00:00
|
|
|
cancel: cancel,
|
2019-05-02 21:22:50 +00:00
|
|
|
}, cancelFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Mock resource monitor.
|
|
|
|
//
|
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
type mockResmon struct {
|
|
|
|
AddressF func() string
|
|
|
|
|
|
|
|
CancelF func() error
|
|
|
|
|
|
|
|
InvokeF func(ctx context.Context,
|
|
|
|
req *pulumirpc.ResourceInvokeRequest) (*pulumirpc.InvokeResponse, error)
|
|
|
|
|
|
|
|
CallF func(ctx context.Context,
|
2024-02-08 13:16:23 +00:00
|
|
|
req *pulumirpc.ResourceCallRequest) (*pulumirpc.CallResponse, error)
|
2023-12-19 16:14:40 +00:00
|
|
|
|
|
|
|
ReadResourceF func(ctx context.Context,
|
|
|
|
req *pulumirpc.ReadResourceRequest) (*pulumirpc.ReadResourceResponse, error)
|
|
|
|
|
|
|
|
RegisterResourceF func(ctx context.Context,
|
|
|
|
req *pulumirpc.RegisterResourceRequest) (*pulumirpc.RegisterResourceResponse, error)
|
|
|
|
|
|
|
|
RegisterResourceOutputsF func(ctx context.Context,
|
2024-01-17 09:35:20 +00:00
|
|
|
req *pulumirpc.RegisterResourceOutputsRequest) (*emptypb.Empty, error)
|
2019-05-02 21:22:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
var _ SourceResourceMonitor = (*mockResmon)(nil)
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) Address() string {
|
|
|
|
if rm.AddressF != nil {
|
|
|
|
return rm.AddressF()
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
2023-03-03 16:36:39 +00:00
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) Cancel() error {
|
|
|
|
if rm.CancelF != nil {
|
|
|
|
return rm.CancelF()
|
|
|
|
}
|
|
|
|
panic("not implemented")
|
2019-05-02 21:22:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) Invoke(ctx context.Context,
|
2023-03-03 16:36:39 +00:00
|
|
|
req *pulumirpc.ResourceInvokeRequest,
|
|
|
|
) (*pulumirpc.InvokeResponse, error) {
|
2023-12-19 16:14:40 +00:00
|
|
|
if rm.InvokeF != nil {
|
|
|
|
return rm.InvokeF(ctx, req)
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
2021-06-30 14:48:56 +00:00
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) Call(ctx context.Context,
|
2024-02-08 13:16:23 +00:00
|
|
|
req *pulumirpc.ResourceCallRequest,
|
2023-03-03 16:36:39 +00:00
|
|
|
) (*pulumirpc.CallResponse, error) {
|
2023-12-19 16:14:40 +00:00
|
|
|
if rm.CallF != nil {
|
|
|
|
return rm.CallF(ctx, req)
|
|
|
|
}
|
2021-06-30 14:48:56 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) ReadResource(ctx context.Context,
|
2023-03-03 16:36:39 +00:00
|
|
|
req *pulumirpc.ReadResourceRequest,
|
|
|
|
) (*pulumirpc.ReadResourceResponse, error) {
|
2023-12-19 16:14:40 +00:00
|
|
|
if rm.ReadResourceF != nil {
|
|
|
|
return rm.ReadResourceF(ctx, req)
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) RegisterResource(ctx context.Context,
|
2023-03-03 16:36:39 +00:00
|
|
|
req *pulumirpc.RegisterResourceRequest,
|
|
|
|
) (*pulumirpc.RegisterResourceResponse, error) {
|
2023-12-19 16:14:40 +00:00
|
|
|
if rm.RegisterResourceF != nil {
|
|
|
|
return rm.RegisterResourceF(ctx, req)
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2023-12-19 16:14:40 +00:00
|
|
|
func (rm *mockResmon) RegisterResourceOutputs(ctx context.Context,
|
2023-03-03 16:36:39 +00:00
|
|
|
req *pulumirpc.RegisterResourceOutputsRequest,
|
2024-01-17 09:35:20 +00:00
|
|
|
) (*emptypb.Empty, error) {
|
2023-12-19 16:14:40 +00:00
|
|
|
if rm.RegisterResourceOutputsF != nil {
|
|
|
|
return rm.RegisterResourceOutputsF(ctx, req)
|
|
|
|
}
|
2019-05-02 21:22:50 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
2023-12-22 19:07:09 +00:00
|
|
|
|
|
|
|
func TestQuerySource(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("Wait", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
var called bool
|
|
|
|
providerRegErrChan := make(chan error, 1)
|
|
|
|
expectedErr := errors.New("expected error")
|
|
|
|
providerRegErrChan <- expectedErr
|
|
|
|
src := &querySource{
|
|
|
|
providerRegErrChan: providerRegErrChan,
|
|
|
|
// Required to not nil ptr dereference.
|
|
|
|
cancel: context.Background(),
|
|
|
|
mon: &mockResmon{
|
|
|
|
CancelF: func() error {
|
|
|
|
called = true
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := src.Wait()
|
|
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
|
|
assert.True(t, called)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunLangPlugin(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("failed to launch language host", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
assert.ErrorContains(t, runLangPlugin(&querySource{
|
|
|
|
plugctx: &plugin.Context{
|
|
|
|
Host: &mockHost{
|
2024-01-25 23:28:58 +00:00
|
|
|
LanguageRuntimeF: func(runtime string, info plugin.ProgramInfo) (plugin.LanguageRuntime, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
return nil, errors.New("expected error")
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
runinfo: &EvalRunInfo{
|
2024-01-25 23:28:58 +00:00
|
|
|
ProjectRoot: "/",
|
|
|
|
Pwd: "/",
|
|
|
|
Program: ".",
|
2023-12-22 19:07:09 +00:00
|
|
|
Proj: &workspace.Project{
|
|
|
|
Runtime: workspace.NewProjectRuntimeInfo("stuff", map[string]interface{}{}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}), "failed to launch language host")
|
|
|
|
})
|
|
|
|
t.Run("bad decrypter", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
expectedErr := errors.New("expected error")
|
|
|
|
err := runLangPlugin(&querySource{
|
|
|
|
plugctx: &plugin.Context{
|
|
|
|
Host: &mockHost{
|
2024-01-25 23:28:58 +00:00
|
|
|
LanguageRuntimeF: func(runtime string, info plugin.ProgramInfo) (plugin.LanguageRuntime, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
return &mockLanguageRuntime{}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
runinfo: &EvalRunInfo{
|
2024-01-25 23:28:58 +00:00
|
|
|
ProjectRoot: "/",
|
|
|
|
Pwd: "/",
|
|
|
|
Program: ".",
|
2023-12-22 19:07:09 +00:00
|
|
|
Proj: &workspace.Project{
|
|
|
|
Runtime: workspace.NewProjectRuntimeInfo("stuff", map[string]interface{}{}),
|
|
|
|
},
|
|
|
|
Target: &Target{
|
|
|
|
Config: config.Map{
|
|
|
|
config.MustMakeKey("test", "secret"): config.NewSecureValue("secret-value"),
|
|
|
|
config.MustMakeKey("test", "regular"): config.NewValue("regular-value"),
|
|
|
|
},
|
|
|
|
Decrypter: &decrypterMock{
|
|
|
|
DecryptValueF: func(
|
|
|
|
ctx context.Context, ciphertext string,
|
|
|
|
) (string, error) {
|
|
|
|
return "", expectedErr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
|
|
})
|
|
|
|
t.Run("bail successfully", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
err := runLangPlugin(&querySource{
|
|
|
|
plugctx: &plugin.Context{
|
|
|
|
Host: &mockHost{
|
2024-01-25 23:28:58 +00:00
|
|
|
LanguageRuntimeF: func(runtime string, info plugin.ProgramInfo) (plugin.LanguageRuntime, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
return &mockLanguageRuntime{
|
|
|
|
RunF: func(info plugin.RunInfo) (string, bool, error) {
|
|
|
|
return "bail should override progerr", true /* bail */, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Prevent nilptr dereference.
|
|
|
|
mon: &mockResmon{
|
|
|
|
AddressF: func() string { return "" },
|
|
|
|
},
|
|
|
|
runinfo: &EvalRunInfo{
|
2024-01-25 23:28:58 +00:00
|
|
|
ProjectRoot: "/",
|
|
|
|
Pwd: "/",
|
|
|
|
Program: ".",
|
2023-12-22 19:07:09 +00:00
|
|
|
Proj: &workspace.Project{
|
|
|
|
Runtime: workspace.NewProjectRuntimeInfo("stuff", map[string]interface{}{}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.ErrorContains(t, err, "run bailed")
|
|
|
|
})
|
|
|
|
t.Run("progerr", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
err := runLangPlugin(&querySource{
|
|
|
|
plugctx: &plugin.Context{
|
|
|
|
Host: &mockHost{
|
2024-01-25 23:28:58 +00:00
|
|
|
LanguageRuntimeF: func(runtime string, info plugin.ProgramInfo) (plugin.LanguageRuntime, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
return &mockLanguageRuntime{
|
|
|
|
RunF: func(info plugin.RunInfo) (string, bool, error) {
|
|
|
|
return "expected progerr", false /* bail */, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Prevent nilptr dereference.
|
|
|
|
mon: &mockResmon{
|
|
|
|
AddressF: func() string { return "" },
|
|
|
|
},
|
|
|
|
runinfo: &EvalRunInfo{
|
2024-01-25 23:28:58 +00:00
|
|
|
ProjectRoot: "/",
|
|
|
|
Pwd: "/",
|
|
|
|
Program: ".",
|
2023-12-22 19:07:09 +00:00
|
|
|
Proj: &workspace.Project{
|
|
|
|
Runtime: workspace.NewProjectRuntimeInfo("stuff", map[string]interface{}{}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.ErrorContains(t, err, "expected progerr")
|
|
|
|
})
|
|
|
|
t.Run("langhost is run correctly", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
var runCalled bool
|
|
|
|
err := runLangPlugin(&querySource{
|
|
|
|
plugctx: &plugin.Context{
|
|
|
|
Host: &mockHost{
|
2024-01-25 23:28:58 +00:00
|
|
|
LanguageRuntimeF: func(runtime string, p plugin.ProgramInfo) (plugin.LanguageRuntime, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
return &mockLanguageRuntime{
|
|
|
|
RunF: func(info plugin.RunInfo) (string, bool, error) {
|
|
|
|
runCalled = true
|
|
|
|
assert.Equal(t, "expected-address", info.MonitorAddress)
|
|
|
|
assert.Equal(t, "expected-stack", info.Stack)
|
|
|
|
assert.Equal(t, "expected-project", info.Project)
|
2024-02-22 11:43:18 +00:00
|
|
|
assert.Equal(t, "/expected-pwd", info.Pwd)
|
|
|
|
assert.Equal(t, "/expected-pwd", info.Info.ProgramDirectory())
|
|
|
|
assert.Equal(t, "expected-program", info.Info.EntryPoint())
|
2023-12-22 19:07:09 +00:00
|
|
|
assert.Equal(t, []string{"expected", "args"}, info.Args)
|
|
|
|
assert.Equal(t, "secret-value", info.Config[config.MustMakeKey("test", "secret")])
|
|
|
|
assert.Equal(t, "regular-value", info.Config[config.MustMakeKey("test", "regular")])
|
|
|
|
assert.True(t, info.QueryMode)
|
|
|
|
assert.True(t, info.DryRun)
|
|
|
|
// Disregard Parallel argument.
|
|
|
|
assert.Equal(t, "expected-organization", info.Organization)
|
|
|
|
return "", false, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Prevent nilptr dereference.
|
|
|
|
mon: &mockResmon{
|
|
|
|
AddressF: func() string { return "expected-address" },
|
|
|
|
},
|
|
|
|
runinfo: &EvalRunInfo{
|
2024-01-25 23:28:58 +00:00
|
|
|
ProjectRoot: "/",
|
2023-12-22 19:07:09 +00:00
|
|
|
Proj: &workspace.Project{
|
|
|
|
Name: "expected-project",
|
|
|
|
Runtime: workspace.NewProjectRuntimeInfo("stuff", map[string]interface{}{}),
|
|
|
|
},
|
2024-02-22 11:43:18 +00:00
|
|
|
Pwd: "/expected-pwd",
|
2023-12-22 19:07:09 +00:00
|
|
|
Program: "expected-program",
|
|
|
|
Args: []string{"expected", "args"},
|
|
|
|
Target: &Target{
|
|
|
|
Config: config.Map{
|
|
|
|
config.MustMakeKey("test", "secret"): config.NewSecureValue("secret-value"),
|
|
|
|
config.MustMakeKey("test", "regular"): config.NewValue("regular-value"),
|
|
|
|
},
|
|
|
|
Name: tokens.MustParseStackName("expected-stack"),
|
|
|
|
Organization: "expected-organization",
|
|
|
|
Decrypter: &decrypterMock{
|
|
|
|
DecryptValueF: func(
|
|
|
|
ctx context.Context, ciphertext string,
|
|
|
|
) (string, error) {
|
|
|
|
return ciphertext, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.True(t, runCalled)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockHost struct {
|
|
|
|
ServerAddrF func() string
|
|
|
|
|
|
|
|
LogF func(sev diag.Severity, urn resource.URN, msg string, streamID int32)
|
|
|
|
|
|
|
|
LogStatusF func(sev diag.Severity, urn resource.URN, msg string, streamID int32)
|
|
|
|
|
|
|
|
AnalyzerF func(nm tokens.QName) (plugin.Analyzer, error)
|
|
|
|
|
|
|
|
PolicyAnalyzerF func(name tokens.QName, path string, opts *plugin.PolicyAnalyzerOptions) (plugin.Analyzer, error)
|
|
|
|
|
|
|
|
ListAnalyzersF func() []plugin.Analyzer
|
|
|
|
|
|
|
|
ProviderF func(pkg tokens.Package, version *semver.Version) (plugin.Provider, error)
|
|
|
|
|
|
|
|
CloseProviderF func(provider plugin.Provider) error
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
LanguageRuntimeF func(language string, info plugin.ProgramInfo) (plugin.LanguageRuntime, error)
|
2023-12-22 19:07:09 +00:00
|
|
|
|
|
|
|
EnsurePluginsF func(plugins []workspace.PluginSpec, kinds plugin.Flags) error
|
|
|
|
|
2024-04-25 17:30:30 +00:00
|
|
|
ResolvePluginF func(kind apitype.PluginKind, name string, version *semver.Version) (*workspace.PluginInfo, error)
|
2023-12-22 19:07:09 +00:00
|
|
|
|
|
|
|
GetProjectPluginsF func() []workspace.ProjectPlugin
|
|
|
|
|
|
|
|
SignalCancellationF func() error
|
|
|
|
|
|
|
|
CloseF func() error
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ plugin.Host = (*mockHost)(nil)
|
|
|
|
|
|
|
|
func (h *mockHost) ServerAddr() string {
|
|
|
|
if h.ServerAddrF != nil {
|
|
|
|
return h.ServerAddrF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) Log(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
|
|
|
|
if h.LogF != nil {
|
|
|
|
h.LogF(sev, urn, msg, streamID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) LogStatus(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
|
|
|
|
if h.LogStatusF != nil {
|
|
|
|
h.LogStatusF(sev, urn, msg, streamID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) Analyzer(nm tokens.QName) (plugin.Analyzer, error) {
|
|
|
|
if h.AnalyzerF != nil {
|
|
|
|
return h.Analyzer(nm)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) PolicyAnalyzer(
|
|
|
|
name tokens.QName, path string, opts *plugin.PolicyAnalyzerOptions,
|
|
|
|
) (plugin.Analyzer, error) {
|
|
|
|
if h.PolicyAnalyzerF != nil {
|
|
|
|
return h.PolicyAnalyzerF(name, path, opts)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) ListAnalyzers() []plugin.Analyzer {
|
|
|
|
if h.ListAnalyzersF != nil {
|
|
|
|
return h.ListAnalyzersF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) Provider(pkg tokens.Package, version *semver.Version) (plugin.Provider, error) {
|
|
|
|
if h.ProviderF != nil {
|
|
|
|
return h.ProviderF(pkg, version)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) CloseProvider(provider plugin.Provider) error {
|
|
|
|
if h.CloseProviderF != nil {
|
|
|
|
return h.CloseProviderF(provider)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
func (h *mockHost) LanguageRuntime(runtime string, info plugin.ProgramInfo) (plugin.LanguageRuntime, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
if h.LanguageRuntimeF != nil {
|
2024-01-25 23:28:58 +00:00
|
|
|
return h.LanguageRuntimeF(runtime, info)
|
2023-12-22 19:07:09 +00:00
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) EnsurePlugins(plugins []workspace.PluginSpec, kinds plugin.Flags) error {
|
|
|
|
if h.EnsurePluginsF != nil {
|
|
|
|
return h.EnsurePluginsF(plugins, kinds)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) ResolvePlugin(
|
2024-04-25 17:30:30 +00:00
|
|
|
kind apitype.PluginKind, name string, version *semver.Version,
|
2023-12-22 19:07:09 +00:00
|
|
|
) (*workspace.PluginInfo, error) {
|
|
|
|
if h.ResolvePluginF != nil {
|
|
|
|
return h.ResolvePluginF(kind, name, version)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) GetProjectPlugins() []workspace.ProjectPlugin {
|
|
|
|
if h.GetProjectPluginsF != nil {
|
|
|
|
return h.GetProjectPluginsF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mockHost) SignalCancellation() error {
|
|
|
|
if h.SignalCancellationF != nil {
|
|
|
|
return h.SignalCancellationF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close reclaims any resources associated with the host.
|
|
|
|
func (h *mockHost) Close() error {
|
|
|
|
if h.CloseF != nil {
|
|
|
|
return h.CloseF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockLanguageRuntime struct {
|
|
|
|
CloseF func() error
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
GetRequiredPluginsF func(info plugin.ProgramInfo) ([]workspace.PluginSpec, error)
|
2023-12-22 19:07:09 +00:00
|
|
|
|
|
|
|
RunF func(info plugin.RunInfo) (string, bool, error)
|
|
|
|
|
|
|
|
GetPluginInfoF func() (workspace.PluginInfo, error)
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
InstallDependenciesF func(info plugin.ProgramInfo) error
|
2023-12-22 19:07:09 +00:00
|
|
|
|
2024-06-17 17:10:55 +00:00
|
|
|
RuntimeOptionsPromptsF func(info plugin.ProgramInfo) ([]plugin.RuntimeOptionPrompt, error)
|
|
|
|
|
2024-06-06 08:21:46 +00:00
|
|
|
AboutF func(info plugin.ProgramInfo) (plugin.AboutInfo, error)
|
2023-12-22 19:07:09 +00:00
|
|
|
|
|
|
|
GetProgramDependenciesF func(
|
2024-01-25 23:28:58 +00:00
|
|
|
info plugin.ProgramInfo, transitiveDependencies bool,
|
2023-12-22 19:07:09 +00:00
|
|
|
) ([]plugin.DependencyInfo, error)
|
|
|
|
|
|
|
|
RunPluginF func(
|
|
|
|
info plugin.RunPluginInfo,
|
|
|
|
) (io.Reader, io.Reader, context.CancelFunc, error)
|
|
|
|
|
|
|
|
GenerateProjectF func(
|
|
|
|
sourceDirectory, targetDirectory, project string,
|
|
|
|
strict bool, loaderTarget string, localDependencies map[string]string,
|
|
|
|
) (hcl.Diagnostics, error)
|
|
|
|
|
|
|
|
GeneratePackageF func(
|
|
|
|
directory string, schema string,
|
2024-03-26 13:10:34 +00:00
|
|
|
extraFiles map[string][]byte, loaderTarget string, localDependencies map[string]string,
|
2024-08-07 08:16:37 +00:00
|
|
|
local bool,
|
2023-12-22 19:07:09 +00:00
|
|
|
) (hcl.Diagnostics, error)
|
|
|
|
|
|
|
|
GenerateProgramF func(
|
|
|
|
program map[string]string,
|
|
|
|
loaderTarget string,
|
|
|
|
) (map[string][]byte, hcl.Diagnostics, error)
|
|
|
|
|
|
|
|
PackF func(
|
|
|
|
packageDirectory string,
|
|
|
|
destinationDirectory string,
|
|
|
|
) (string, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ plugin.LanguageRuntime = (*mockLanguageRuntime)(nil)
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) Close() error {
|
|
|
|
if rt.CloseF != nil {
|
|
|
|
return rt.CloseF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
func (rt *mockLanguageRuntime) GetRequiredPlugins(info plugin.ProgramInfo) ([]workspace.PluginSpec, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
if rt.GetRequiredPluginsF != nil {
|
|
|
|
return rt.GetRequiredPluginsF(info)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) Run(info plugin.RunInfo) (string, bool, error) {
|
|
|
|
if rt.RunF != nil {
|
|
|
|
return rt.RunF(info)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) GetPluginInfo() (workspace.PluginInfo, error) {
|
|
|
|
if rt.GetPluginInfoF != nil {
|
|
|
|
return rt.GetPluginInfoF()
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
func (rt *mockLanguageRuntime) InstallDependencies(info plugin.ProgramInfo) error {
|
2023-12-22 19:07:09 +00:00
|
|
|
if rt.InstallDependenciesF != nil {
|
2024-01-25 23:28:58 +00:00
|
|
|
return rt.InstallDependenciesF(info)
|
2023-12-22 19:07:09 +00:00
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2024-06-17 17:10:55 +00:00
|
|
|
func (rt *mockLanguageRuntime) RuntimeOptionsPrompts(info plugin.ProgramInfo) ([]plugin.RuntimeOptionPrompt, error) {
|
|
|
|
if rt.RuntimeOptionsPromptsF != nil {
|
|
|
|
return rt.RuntimeOptionsPromptsF(info)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2024-06-06 08:21:46 +00:00
|
|
|
func (rt *mockLanguageRuntime) About(info plugin.ProgramInfo) (plugin.AboutInfo, error) {
|
2023-12-22 19:07:09 +00:00
|
|
|
if rt.AboutF != nil {
|
2024-06-06 08:21:46 +00:00
|
|
|
return rt.AboutF(info)
|
2023-12-22 19:07:09 +00:00
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) GetProgramDependencies(
|
2024-01-25 23:28:58 +00:00
|
|
|
info plugin.ProgramInfo, transitiveDependencies bool,
|
2023-12-22 19:07:09 +00:00
|
|
|
) ([]plugin.DependencyInfo, error) {
|
|
|
|
if rt.GetProgramDependenciesF != nil {
|
|
|
|
return rt.GetProgramDependenciesF(info, transitiveDependencies)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) RunPlugin(
|
|
|
|
info plugin.RunPluginInfo,
|
|
|
|
) (io.Reader, io.Reader, context.CancelFunc, error) {
|
|
|
|
if rt.RunPluginF != nil {
|
|
|
|
return rt.RunPluginF(info)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) GenerateProject(
|
|
|
|
sourceDirectory, targetDirectory, project string,
|
|
|
|
strict bool, loaderTarget string, localDependencies map[string]string,
|
|
|
|
) (hcl.Diagnostics, error) {
|
|
|
|
if rt.GenerateProjectF != nil {
|
|
|
|
return rt.GenerateProjectF(
|
|
|
|
sourceDirectory, targetDirectory, project, strict, loaderTarget, localDependencies)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) GeneratePackage(
|
2024-03-26 13:10:34 +00:00
|
|
|
directory string, schema string, extraFiles map[string][]byte,
|
|
|
|
loaderTarget string, localDependencies map[string]string,
|
2024-08-07 08:16:37 +00:00
|
|
|
local bool,
|
2023-12-22 19:07:09 +00:00
|
|
|
) (hcl.Diagnostics, error) {
|
|
|
|
if rt.GeneratePackageF != nil {
|
2024-08-07 08:16:37 +00:00
|
|
|
return rt.GeneratePackageF(directory, schema, extraFiles, loaderTarget, localDependencies, local)
|
2023-12-22 19:07:09 +00:00
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) GenerateProgram(
|
[cli/import] Fix undefined variable errors in code generation when imported resources use a parent or provider (#16786)
Fixes #15410 Fixes #13339
## Problem Context
When using `pulumi import` we generate code snippets for the resources
that were imported. Sometimes the user specifies `--parent
parentName=URN` or `--provider providerName=URN` which tweak the parent
or provider that the imported resources uses. When using `--parent` or
`--provider` the generated code emits a resource option `parent =
parentName` (in case of using `--parent`) where `parentName` is an
unbound variable.
Usually unbound variables would result in a _bind_ error such as `error:
undefined variable parentName` when type-checking the program however in
the import code generation we specify the bind option
`pcl.AllowMissingVariables` which turns that unbound variable errors
into warnings and code generation can continue to emit code.
This is all good and works as expected. However in the issues linked
above, we do get an _error_ for unbound variables in generated code even
though we specified `AllowMissingVariables`.
The problem as it turns out is when we are trying to generate code via
dynamically loaded `LangaugeRuntime` plugins. Specifically for NodeJS
and Python, we load `pulumi-language-nodejs` or `pulumi-language-python`
and call `GenerateProgram` to get the generated program. That function
`GenerateProgram` takes the text _SOURCE_ of the a bound program (one
that was bound using option `AllowMissingVariables`) and re-binds again
inside the implementation of the language plugin. The second time we
bind the program, we don't pass it the option `AllowMissingVariables`
and so it fails with `unboud variable` error.
I've verified that the issue above don't repro when doing an import for
dotnet (probably same for java/yaml) because we use the statically
linked function `codegen/{lang}/gen_program.go -> GenerateProgram`
## Solution
The problem can be solved by propagating the bind options from the CLI
to the language hosts during import so that they know how to bind the
program. I've extended the gRPC interface in `GenerateProgramRequest`
with a property `Strict` which follows the same logic from `pulumi
convert --strict` and made it such that the import command sends
`strict=false` to the language plugins when doing `GenerateProgram`.
This is consistent with `GenerateProject` that uses the same flag. When
`strict=false` we use `pcl.NonStrictBindOptions()` which includes
`AllowMissingVariables` .
## Repro
Once can test the before and after behaviour by running `pulumi up
--yes` on the following TypeScript program:
```ts
import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
export class MyComponent extends pulumi.ComponentResource {
public readonly randomPetId: pulumi.Output<string>;
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
super("example:index:MyComponent", name, {}, opts);
const randomPet = new random.RandomPet("randomPet", {}, {
parent: this
});
this.randomPetId = randomPet.id;
this.registerOutputs({
randomPetId: randomPet.id,
});
}
}
const example = new MyComponent("example");
export const randomPetId = example.randomPetId;
```
Then running `pulumi import -f import.json` where `import.json` contains
a resource to be imported under the created component (stack=`dev`,
project=`importerrors`)
```ts
{
"nameTable": {
"parentComponent": "urn:pulumi:dev::importerrors::example:index:MyComponent::example"
},
"resources": [
{
"type": "random:index/randomPassword:RandomPassword",
"name": "randomPassword",
"id": "supersecret",
"parent": "parentComponent"
}
]
}
```
Running this locally I get the following generated code (which
previously failed to generate)
```ts
import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
const randomPassword = new random.RandomPassword("randomPassword", {
length: 11,
lower: true,
number: true,
numeric: true,
special: true,
upper: true,
}, {
parent: parentComponent,
});
```
2024-07-25 13:53:44 +00:00
|
|
|
program map[string]string, loaderTarget string, strict bool,
|
2023-12-22 19:07:09 +00:00
|
|
|
) (map[string][]byte, hcl.Diagnostics, error) {
|
|
|
|
if rt.GenerateProgramF != nil {
|
|
|
|
return rt.GenerateProgramF(program, loaderTarget)
|
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *mockLanguageRuntime) Pack(
|
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
|
|
|
packageDirectory string, destinationDirectory string,
|
2023-12-22 19:07:09 +00:00
|
|
|
) (string, error) {
|
|
|
|
if rt.PackF != nil {
|
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
|
|
|
return rt.PackF(packageDirectory, destinationDirectory)
|
2023-12-22 19:07:09 +00:00
|
|
|
}
|
|
|
|
panic("unimplemented")
|
|
|
|
}
|