diff --git a/pkg/internal/tests/protocolspec/bridge.go b/pkg/internal/tests/protocolspec/bridge.go new file mode 100644 index 000000000..7c6601e44 --- /dev/null +++ b/pkg/internal/tests/protocolspec/bridge.go @@ -0,0 +1,167 @@ +package protocolspec + +// CPS stands for Current Pulumi Schema +// PPS stands for Previous Pulumi Schema +// CTFS stands for Current TF Schema +// PTFS stands for Previous TF Schema +type Bridge[CPS PulumiSchema, PPS PulumiSchema, CTFS TFSchema, PTFS TFSchema] struct { + TFProvider TFProviderWithUpgradeState[CTFS, PTFS] +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) getCurrentBridge() Bridge[CPS, CPS, CTFS, CTFS] { + return Bridge[CPS, CPS, CTFS, CTFS]{ + TFProvider: b.TFProvider.GetCurrentProvider(), + } +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) tfStateToPulumiOutput(TFState[CTFS]) PulumiOutput[CPS] { + return PulumiOutput[CPS]{} +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) tfPlannedStateToPulumiPreviewOutput( + TFPlannedState[CTFS], +) PulumiPreviewOutput[CPS] { + return PulumiPreviewOutput[CPS]{} +} + +// This was recently replaced by tfRawStateFromPulumiOutput +func (b *Bridge[CPS, PPS, CTFS, PTFS]) pulumiOutputToTFState(PulumiOutput[CPS]) TFState[CTFS] { + return TFState[CTFS]{} +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) pulumiCheckedInputToTFConfig(PulumiCheckedInput[CPS]) TFConfig[CTFS] { + return TFConfig[CTFS]{} +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) tfRawStateFromPulumiOutput(PulumiOutput[PPS]) TFState[PTFS] { + return TFState[PTFS]{} +} + +// extractInputsFromOutputs is not precise and has some assumptions and approximations +// Especially when there are no past inputs +func (b *Bridge[CPS, PPS, CTFS, PTFS]) extractInputsFromOutputs( + oldInputs PulumiCheckedInput[PPS], + outputs PulumiOutput[CPS], +) PulumiInput[CPS] { + return PulumiInput[CPS]{} +} + +var _ PulumiProvider[PulumiSchema, PulumiSchema] = &Bridge[PulumiSchema, PulumiSchema, TFSchema, TFSchema]{} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) Check( + news PulumiInput[CPS], olds PulumiCheckedInput[PPS], +) PulumiCheckedInput[CPS] { + return PulumiCheckedInput[CPS]{} +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) CreatePreview(checkedInputs PulumiCheckedInput[CPS]) PulumiPreviewOutput[CPS] { + tfConfig := b.pulumiCheckedInputToTFConfig(checkedInputs) + emptyState := TFState[CTFS]{} + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, emptyState) + return b.tfPlannedStateToPulumiPreviewOutput(tfPlannedState) +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) Create(checkedInputs PulumiCheckedInput[CPS]) PulumiOutput[CPS] { + tfConfig := b.pulumiCheckedInputToTFConfig(checkedInputs) + emptyState := TFState[CTFS]{} + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, emptyState) + tfState := b.TFProvider.ApplyResourceChange(tfConfig, emptyState, tfPlannedState) + return b.tfStateToPulumiOutput(tfState) +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) UpdatePreview( + checkedInputs PulumiCheckedInput[CPS], + state PulumiOutput[PPS], + // oldInputs is unused + _oldInputs PulumiCheckedInput[PPS], +) PulumiPreviewOutput[CPS] { + tfConfig := b.pulumiCheckedInputToTFConfig(checkedInputs) + tfState := b.tfRawStateFromPulumiOutput(state) + tfUpgradedState := b.TFProvider.UpgradeState(tfState) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfUpgradedState) + return b.tfPlannedStateToPulumiPreviewOutput(tfPlannedState) +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) Update( + checkedInputs PulumiCheckedInput[CPS], + state PulumiOutput[PPS], + // oldInputs is unused + _oldInputs PulumiCheckedInput[PPS], +) PulumiOutput[CPS] { + tfConfig := b.pulumiCheckedInputToTFConfig(checkedInputs) + tfState := b.tfRawStateFromPulumiOutput(state) + tfUpgradedState := b.TFProvider.UpgradeState(tfState) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfUpgradedState) + tfNewState := b.TFProvider.ApplyResourceChange(tfConfig, tfUpgradedState, tfPlannedState) + return b.tfStateToPulumiOutput(tfNewState) +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) DeletePreview(state PulumiOutput[PPS]) { + tfState := b.tfRawStateFromPulumiOutput(state) + upgradedState := b.TFProvider.UpgradeState(tfState) + emptyConfig := TFConfig[CTFS]{} + b.TFProvider.PlanResourceChange(emptyConfig, upgradedState) +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) Delete(state PulumiOutput[PPS]) { + tfState := b.tfRawStateFromPulumiOutput(state) + upgradedState := b.TFProvider.UpgradeState(tfState) + emptyConfig := TFConfig[CTFS]{} + b.TFProvider.ApplyResourceChange(emptyConfig, upgradedState, TFPlannedState[CTFS]{}) +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) Diff( + oldState PulumiOutput[PPS], + oldInputs PulumiCheckedInput[PPS], + newCheckedInputs PulumiCheckedInput[CPS], +) (PulumiDiff[CPS], PulumiDetailedDiff[CPS, PPS]) { + tfState := b.tfRawStateFromPulumiOutput(oldState) + tfUpgradedState := b.TFProvider.UpgradeState(tfState) + tfConfig := b.pulumiCheckedInputToTFConfig(newCheckedInputs) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfUpgradedState) + plannedState := b.tfPlannedStateToPulumiPreviewOutput(tfPlannedState) + + tfDiff := b.TFProvider.Diff(tfUpgradedState, tfPlannedState) + + translateTFDiffToPulumiDiff := func(TFDiff[CTFS]) PulumiDiff[CPS] { + return PulumiDiff[CPS]{} + } + + producePulumiDetailedDiff := func( + _ TFDiff[CTFS], + oldState PulumiOutput[PPS], + newCheckedInputs PulumiCheckedInput[CPS], + _ PulumiPreviewOutput[CPS], + ) PulumiDetailedDiff[CPS, PPS] { + // This is not easy as we first need to diff PulumiOutput[PPS] against PulumiPreviewOutput[CPS] + // and then we need to map this back to the PulumiCheckedInput[CPS] + // PulumiPreviewOutput and PulumiCheckedInput are not directly comparable + return PulumiDetailedDiff[CPS, PPS]{ + oldState: oldState, + newCheckedInputs: newCheckedInputs, + } + } + + pulumiDiff := translateTFDiffToPulumiDiff(tfDiff) + pulumiDetailedDiff := producePulumiDetailedDiff(tfDiff, oldState, newCheckedInputs, plannedState) + + return pulumiDiff, pulumiDetailedDiff +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) ReadForImport(id string) (PulumiOutput[CPS], PulumiInput[CPS]) { + tfState := b.TFProvider.Importer(id) + state := b.tfStateToPulumiOutput(tfState) + inputs := b.extractInputsFromOutputs(PulumiCheckedInput[PPS]{}, state) + return state, inputs +} + +func (b *Bridge[CPS, PPS, CTFS, PTFS]) ReadForRefresh( + inputs PulumiCheckedInput[PPS], state PulumiOutput[PPS], +) (PulumiInput[CPS], PulumiOutput[CPS]) { + tfState := b.tfRawStateFromPulumiOutput(state) + tfUpgradedState := b.TFProvider.UpgradeState(tfState) + tfNewState := b.TFProvider.ReadResource(tfUpgradedState) + newPulumiState := b.tfStateToPulumiOutput(tfNewState) + newPulumiInputs := b.extractInputsFromOutputs(inputs, newPulumiState) + return newPulumiInputs, newPulumiState +} diff --git a/pkg/internal/tests/protocolspec/bridgev2.go b/pkg/internal/tests/protocolspec/bridgev2.go new file mode 100644 index 000000000..dc7da22db --- /dev/null +++ b/pkg/internal/tests/protocolspec/bridgev2.go @@ -0,0 +1,140 @@ +package protocolspec + +type BridgeV2[CPS PulumiSchema, CTFS TFSchema] struct { + TFProvider TFProvider[CTFS] +} + +var _ PulumiProviderV2[PulumiSchema] = &BridgeV2[PulumiSchema, TFSchema]{} + +func (b *BridgeV2[CPS, CTFS]) tfStateToPulumiOutput(TFState[CTFS]) PulumiOutput[CPS] { + return PulumiOutput[CPS]{} +} + +func (b *BridgeV2[CPS, CTFS]) tfPlannedStateToPulumiPreviewOutput( + TFPlannedState[CTFS], +) PulumiPreviewOutput[CPS] { + return PulumiPreviewOutput[CPS]{} +} + +// This is new. +func (b *BridgeV2[CPS, CTFS]) pulumiPreviewOutputToTFPlannedState(PulumiPreviewOutput[CPS]) TFPlannedState[CTFS] { + return TFPlannedState[CTFS]{} +} + +func (b *BridgeV2[CPS, CTFS]) pulumiInputToTFConfig(PulumiInput[CPS]) TFConfig[CTFS] { + return TFConfig[CTFS]{} +} + +func (b *BridgeV2[CPS, CTFS]) tfRawStateFromPulumiOutput(PulumiOutput[CPS]) TFState[CTFS] { + return TFState[CTFS]{} +} + +func (b *BridgeV2[CPS, CTFS]) Check(news PulumiInput[CPS]) PulumiInput[CPS] { + return PulumiInput[CPS]{} +} + +func (b *BridgeV2[CPS, CTFS]) CreatePreview(inputs PulumiInput[CPS]) PulumiPreviewOutput[CPS] { + tfConfig := b.pulumiInputToTFConfig(inputs) + emptyState := TFState[CTFS]{} + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, emptyState) + return b.tfPlannedStateToPulumiPreviewOutput(tfPlannedState) +} + +func (b *BridgeV2[CPS, CTFS]) Create(inputs PulumiInput[CPS]) PulumiOutput[CPS] { + tfConfig := b.pulumiInputToTFConfig(inputs) + emptyState := TFState[CTFS]{} + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, emptyState) + tfState := b.TFProvider.ApplyResourceChange(tfConfig, emptyState, tfPlannedState) + return b.tfStateToPulumiOutput(tfState) +} + +func (b *BridgeV2[CPS, CTFS]) UpdatePreview(inputs PulumiInput[CPS], state PulumiOutput[CPS]) PulumiPreviewOutput[CPS] { + tfConfig := b.pulumiInputToTFConfig(inputs) + tfState := b.tfRawStateFromPulumiOutput(state) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfState) + return b.tfPlannedStateToPulumiPreviewOutput(tfPlannedState) +} + +func (b *BridgeV2[CPS, CTFS]) Update(inputs PulumiInput[CPS], state PulumiOutput[CPS]) PulumiOutput[CPS] { + tfConfig := b.pulumiInputToTFConfig(inputs) + tfState := b.tfRawStateFromPulumiOutput(state) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfState) + tfState = b.TFProvider.ApplyResourceChange(tfConfig, tfState, tfPlannedState) + return b.tfStateToPulumiOutput(tfState) +} + +func (b *BridgeV2[CPS, CTFS]) DeletePreview(state PulumiOutput[CPS]) PulumiPreviewOutput[CPS] { + tfConfig := TFConfig[CTFS]{} + tfState := b.tfRawStateFromPulumiOutput(state) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfState) + return b.tfPlannedStateToPulumiPreviewOutput(tfPlannedState) +} + +func (b *BridgeV2[CPS, CTFS]) Delete(state PulumiOutput[CPS]) PulumiOutput[CPS] { + tfConfig := TFConfig[CTFS]{} + tfState := b.tfRawStateFromPulumiOutput(state) + tfPlannedState := b.TFProvider.PlanResourceChange(tfConfig, tfState) + tfState = b.TFProvider.ApplyResourceChange(tfConfig, tfState, tfPlannedState) + return b.tfStateToPulumiOutput(tfState) +} + +func (b *BridgeV2[CPS, CTFS]) Diff(oldState PulumiOutput[CPS], plannedState PulumiPreviewOutput[CPS]) (PulumiDiff[CPS], PulumiDetailedDiffV2[CPS]) { + tfState := b.tfRawStateFromPulumiOutput(oldState) + tfPlannedState := b.pulumiPreviewOutputToTFPlannedState(plannedState) + tfDiff := b.TFProvider.Diff(tfState, tfPlannedState) + + translateTFDiffToPulumiDiff := func(TFDiff[CTFS]) PulumiDiff[CPS] { + return PulumiDiff[CPS]{} + } + + producePulumiDetailedDiff := func( + oldState PulumiOutput[CPS], + plannedState PulumiPreviewOutput[CPS], + ) PulumiDetailedDiffV2[CPS] { + // The values here are in the same plane - much easier to produce a diff. + return PulumiDetailedDiffV2[CPS]{ + OldState: oldState, + plannedState: plannedState, + } + } + + pulumiDiff := translateTFDiffToPulumiDiff(tfDiff) + pulumiDetailedDiff := producePulumiDetailedDiff(oldState, plannedState) + + return pulumiDiff, pulumiDetailedDiff +} + +// extractInputsFromOutputs is not precise and has some assumptions and approximations +// Especially when there are no past inputs +func (b *BridgeV2[CPS, CTFS]) extractInputsFromOutputsNoInputs( + outputs PulumiOutput[CPS], +) PulumiInput[CPS] { + // guess + return PulumiInput[CPS]{} +} + +func (b *BridgeV2[CPS, CTFS]) ReadForImport(id string) (PulumiOutput[CPS], PulumiInput[CPS]) { + tfState := b.TFProvider.Importer(id) + state := b.tfStateToPulumiOutput(tfState) + inputs := b.extractInputsFromOutputsNoInputs(state) + return state, inputs +} + +func (b *BridgeV2[CPS, CTFS]) ReadForRefresh(state PulumiOutput[CPS]) (PulumiOutput[CPS]) { + tfState := b.tfRawStateFromPulumiOutput(state) + tfNewState := b.TFProvider.ReadResource(tfState) + newState := b.tfStateToPulumiOutput(tfNewState) + return newState +} + +// The only method which needs to deal with old schema is UpgradeState. +type BridgeV2WithUpgrade[CPS PulumiSchema, PS PulumiSchema, CTFS TFSchema, PTS TFSchema] struct { + BridgeV2[CPS, CTFS] + TFProvider TFProviderWithUpgradeState[CTFS, PTS] +} + +var _ PulumiProviderV2WithUpgrade[PulumiSchema, PulumiSchema] = &BridgeV2WithUpgrade[PulumiSchema, PulumiSchema, TFSchema, TFSchema]{} + +func (b *BridgeV2WithUpgrade[CPS, PS, CTFS, PTS]) UpgradeState(state PulumiOutput[PS]) PulumiOutput[CPS] { + return PulumiOutput[CPS]{} +} diff --git a/pkg/internal/tests/protocolspec/current_engine.go b/pkg/internal/tests/protocolspec/current_engine.go new file mode 100644 index 000000000..bad0579c1 --- /dev/null +++ b/pkg/internal/tests/protocolspec/current_engine.go @@ -0,0 +1,167 @@ +package protocolspec + +func getUserInputs[S PulumiSchema]() PulumiInput[S] { + return PulumiInput[S]{} +} + +func getOldInputs[S PulumiSchema]() PulumiCheckedInput[S] { + return PulumiCheckedInput[S]{} +} + +func getOldState[S PulumiSchema]() PulumiOutput[S] { + return PulumiOutput[S]{} +} + +func CreateResource() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + userInputs := getUserInputs[PS]() + oldInputs := getOldInputs[PS]() + + checkedInputs := prov.Check(userInputs, oldInputs) + previewOutputs := prov.CreatePreview(checkedInputs) + + // previewOutputs is discarded? + _ = previewOutputs + + // Note the engine does not display unknowns in the preview + displayPreview(checkedInputs, PulumiOutput[PS]{}, PulumiDetailedDiff[PS, PS]{}) + + prov.Create(checkedInputs) +} + +func UpdateResourceNoUpgrade() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + userInputs := getUserInputs[PS]() + oldState := getOldState[PS]() + oldInputs := getOldInputs[PS]() + + checkedInputs := prov.Check(userInputs, oldInputs) + + diff, detailedDiff := prov.Diff(oldState, oldInputs, checkedInputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(checkedInputs, oldState, oldInputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(checkedInputs, oldState, detailedDiff) + + prov.Update(checkedInputs, oldState, oldInputs) +} + +func UpdateResourceWithUpgrade() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + prov := &Bridge[CPS, PPS, CTFS, PTFS]{} + userInputs := getUserInputs[CPS]() + oldState := getOldState[PPS]() + oldInputs := getOldInputs[PPS]() + + checkedInputs := prov.Check(userInputs, oldInputs) + + diff, detailedDiff := prov.Diff(oldState, oldInputs, checkedInputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(checkedInputs, oldState, oldInputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(checkedInputs, oldState, detailedDiff) + + prov.Update(checkedInputs, oldState, oldInputs) +} + +func DeleteResourceNoUpgrade() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + oldState := getOldState[PS]() + + prov.Delete(oldState) +} + +func DeleteResourceWithUpgrade() { + type CPS struct{} + _ = CPS{} // unused + type CTFS struct{} + _ = CTFS{} // unused + type PPS struct{} + type PTFS struct{} + + // Note the engine currently uses the old provider for delete + // TODO: Is this always the case? + prov := &Bridge[PPS, PPS, PTFS, PTFS]{} + oldState := getOldState[PPS]() + + prov.Delete(oldState) +} + +func ImportResource() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + id := "imported-resource" + + prov.ReadForImport(id) +} + +func RefreshNoUpgrade() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + + inputs := getOldInputs[PS]() + state := getOldState[PS]() + + readInputs, readState := prov.ReadForRefresh(inputs, state) + + readCheckedInputs := prov.Check(readInputs, inputs) + + //nolint:lll + // parameter inversion here: https://pulumi-developer-docs.readthedocs.io/latest/developer-docs/providers/implementers-guide.html#refresh + diff, detailedDiff := prov.Diff(readState, readCheckedInputs, inputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(readCheckedInputs, state, inputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(readCheckedInputs, state, detailedDiff) + // state is replaced here. +} + +func RefreshWithUpgrade() { + type CPS struct{} + _ = CPS{} // unused + type CTFS struct{} + _ = CTFS{} // unused + type PPS struct{} + type PTFS struct{} + + // Note the engine currently uses the old provider for refresh + // TODO: Is this always the case? + prov := &Bridge[PPS, PPS, PTFS, PTFS]{} + state := getOldState[PPS]() + inputs := getOldInputs[PPS]() + + readInputs, readState := prov.ReadForRefresh(inputs, state) + + readCheckedInputs := prov.Check(readInputs, inputs) + + //nolint:lll + // parameter inversion here: https://pulumi-developer-docs.readthedocs.io/latest/developer-docs/providers/implementers-guide.html#refresh + diff, detailedDiff := prov.Diff(readState, readCheckedInputs, inputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(readCheckedInputs, state, inputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(readCheckedInputs, state, detailedDiff) +} diff --git a/pkg/internal/tests/protocolspec/engine_with_run_program.go b/pkg/internal/tests/protocolspec/engine_with_run_program.go new file mode 100644 index 000000000..5b06b65be --- /dev/null +++ b/pkg/internal/tests/protocolspec/engine_with_run_program.go @@ -0,0 +1,156 @@ +package protocolspec + +func RPCreateResource() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + userInputs := getUserInputs[PS]() + oldInputs := getOldInputs[PS]() + + checkedInputs := prov.Check(userInputs, oldInputs) + previewOutputs := prov.CreatePreview(checkedInputs) + + // previewOutputs is discarded? + _ = previewOutputs + + // Note the engine does not display unknowns in the preview + displayPreview(checkedInputs, PulumiOutput[PS]{}, PulumiDetailedDiff[PS, PS]{}) + + prov.Create(checkedInputs) +} + +func RPUpdateResourceNoUpgrade() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + userInputs := getUserInputs[PS]() + oldState := getOldState[PS]() + oldInputs := getOldInputs[PS]() + + checkedInputs := prov.Check(userInputs, oldInputs) + + diff, detailedDiff := prov.Diff(oldState, oldInputs, checkedInputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(checkedInputs, oldState, oldInputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(checkedInputs, oldState, detailedDiff) + + prov.Update(checkedInputs, oldState, oldInputs) +} + +func RPUpdateResourceWithUpgrade() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + prov := &Bridge[CPS, PPS, CTFS, PTFS]{} + userInputs := getUserInputs[CPS]() + oldState := getOldState[PPS]() + oldInputs := getOldInputs[PPS]() + + checkedInputs := prov.Check(userInputs, oldInputs) + + diff, detailedDiff := prov.Diff(oldState, oldInputs, checkedInputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(checkedInputs, oldState, oldInputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(checkedInputs, oldState, detailedDiff) + + prov.Update(checkedInputs, oldState, oldInputs) +} + +func RPDeleteResourceNoUpgrade() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + oldState := getOldState[PS]() + + prov.Delete(oldState) +} + +func RPDeleteResourceWithUpgrade() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + + prov := &Bridge[CPS, PPS, CTFS, PTFS]{} + oldState := getOldState[PPS]() + + prov.Delete(oldState) +} + +func RPImportResource() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + id := "imported-resource" + + prov.ReadForImport(id) +} + +func RPRefreshNoUpgrade() { + type PS struct{} + type TFS struct{} + prov := &Bridge[PS, PS, TFS, TFS]{} + + inputs := getOldInputs[PS]() + state := getOldState[PS]() + + readInputs, readState := prov.ReadForRefresh(inputs, state) + + readCheckedInputs := prov.Check(readInputs, inputs) + + //nolint:lll + // parameter inversion here: https://pulumi-developer-docs.readthedocs.io/latest/developer-docs/providers/implementers-guide.html#refresh + diff, detailedDiff := prov.Diff(readState, readCheckedInputs, inputs) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(readCheckedInputs, state, inputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(readCheckedInputs, state, detailedDiff) + // state is replaced here. +} + +func RPRefreshWithUpgrade() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + + prov := &Bridge[CPS, PPS, CTFS, PTFS]{} + state := getOldState[PPS]() + inputs := getOldInputs[PPS]() + + readInputs, readState := prov.ReadForRefresh(inputs, state) + + readCheckedInputs := prov.Check(readInputs, inputs) + + // fudge the types here - this is fine as as a provider can upgrade from current to current. + prov2 := prov.getCurrentBridge() + + //nolint:lll + // parameter inversion here: https://pulumi-developer-docs.readthedocs.io/latest/developer-docs/providers/implementers-guide.html#refresh + diff, detailedDiff := prov2.Diff( + readState, + readCheckedInputs, + // We are passing old inputs to the current provider. This is very likely to be a problem. + // https://github.com/pulumi/pulumi-terraform-bridge/issues/3027 + inputs, // cannot use inputs (variable of struct type PulumiCheckedInput[PPS]) as PulumiCheckedInput[CPS] value in argument to prov.Diff + ) + if diff.HasChanges { + previewOutputs := prov.UpdatePreview(readCheckedInputs, state, inputs) + // previewOutputs is discarded? + _ = previewOutputs + } + + displayPreview(readCheckedInputs, state, detailedDiff) +} diff --git a/pkg/internal/tests/protocolspec/enginev2.go b/pkg/internal/tests/protocolspec/enginev2.go new file mode 100644 index 000000000..a59583bd1 --- /dev/null +++ b/pkg/internal/tests/protocolspec/enginev2.go @@ -0,0 +1,87 @@ +package protocolspec + +func getOldInputsV2[S PulumiSchema]() PulumiInput[S] { + return PulumiInput[S]{} +} + +func CreateResourceV2() { + type PS struct{} + type TFS struct{} + prov := &BridgeV2[PS, TFS]{} + userInputs := getUserInputs[PS]() + + checkedInputs := prov.Check(userInputs) + previewOutputs := prov.CreatePreview(checkedInputs) + + displayPreviewV2(previewOutputs, PulumiOutput[PS]{}, PulumiDetailedDiffV2[PS]{}) +} + + +func UpdateResourceWithUpgradeV2() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + prov := &BridgeV2WithUpgrade[CPS, PPS, CTFS, PTFS]{} + userInputs := getUserInputs[CPS]() + oldState := getOldState[PPS]() + // oldInputs := getOldInputs[PPS]() not used anymore + + checkedInputs := prov.Check(userInputs) + upgradedState := prov.UpgradeState(oldState) + + proposedState := prov.UpdatePreview(checkedInputs, upgradedState) + + diff, detailedDiff := prov.Diff(upgradedState, proposedState) + + displayPreviewV2(proposedState, upgradedState, detailedDiff) + + if diff.HasChanges { + prov.Update(checkedInputs, upgradedState) + } +} + +// Assumes run-program +func DeleteResourceWithUpgradeV2() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + + prov := &BridgeV2WithUpgrade[CPS, PPS, CTFS, PTFS]{} + oldState := getOldState[PPS]() + upgradedState := prov.UpgradeState(oldState) + + prov.Delete(upgradedState) +} + +func RefreshWithUpgradeV2() { + type CPS struct{} + type CTFS struct{} + type PPS struct{} + type PTFS struct{} + + prov := &BridgeV2WithUpgrade[CPS, PPS, CTFS, PTFS]{} + state := getOldState[PPS]() + + upgradedState := prov.UpgradeState(state) + readState := prov.ReadForRefresh(upgradedState) + + promoteToPreviewOutput := func(state PulumiOutput[CPS]) PulumiPreviewOutput[CPS] { + return PulumiPreviewOutput[CPS]{} + } + + promotedUpgradedState := promoteToPreviewOutput(upgradedState) + + //nolint:lll + // parameter inversion here: https://pulumi-developer-docs.readthedocs.io/latest/developer-docs/providers/implementers-guide.html#refresh + diff, detailedDiff := prov.Diff( + readState, + promotedUpgradedState, + ) + + displayPreviewV2(promotedUpgradedState, readState, detailedDiff) + if diff.HasChanges { + // save the state + } +} \ No newline at end of file diff --git a/pkg/internal/tests/protocolspec/pulumi_protocol_types.go b/pkg/internal/tests/protocolspec/pulumi_protocol_types.go new file mode 100644 index 000000000..61eb256dc --- /dev/null +++ b/pkg/internal/tests/protocolspec/pulumi_protocol_types.go @@ -0,0 +1,62 @@ +package protocolspec + +type PulumiSchema interface{} + +type PulumiInput[S PulumiSchema] struct{} + +type PulumiCheckedInput[S PulumiSchema] struct{} + +type PulumiOutput[S PulumiSchema] struct{} + +type PulumiPreviewOutput[S PulumiSchema] struct{} + +type PulumiDiff[S PulumiSchema] struct { + HasChanges bool +} + +type PulumiDetailedDiff[CS PulumiSchema, PS PulumiSchema] struct { + oldState PulumiOutput[PS] + // Note that the PulumiDetailedDiff is expressed in terms of old state and new checked inputs. + // This creates a lot of complexity in the provider. + newCheckedInputs PulumiCheckedInput[CS] +} + +// CS stands for Current Schema +// PS stands for Previous Schema +type PulumiProvider[CS PulumiSchema, PS PulumiSchema] interface { + Check(news PulumiInput[CS], olds PulumiCheckedInput[PS]) PulumiCheckedInput[CS] + + CreatePreview(checkedInputs PulumiCheckedInput[CS]) PulumiPreviewOutput[CS] + Create(checkedInputs PulumiCheckedInput[CS]) PulumiOutput[CS] + + UpdatePreview( + checkedInputs PulumiCheckedInput[CS], + state PulumiOutput[PS], + oldInputs PulumiCheckedInput[PS], + ) PulumiPreviewOutput[CS] + Update( + checkedInputs PulumiCheckedInput[CS], + state PulumiOutput[PS], + oldInputs PulumiCheckedInput[PS], + ) PulumiOutput[CS] + + DeletePreview(state PulumiOutput[PS]) + Delete(state PulumiOutput[PS]) + + Diff( + oldState PulumiOutput[PS], + oldInputs PulumiCheckedInput[PS], + newInputs PulumiCheckedInput[CS], + ) (PulumiDiff[CS], PulumiDetailedDiff[CS, PS]) + + ReadForImport(id string) (PulumiOutput[CS], PulumiInput[CS]) + + ReadForRefresh(inputs PulumiCheckedInput[CS], state PulumiOutput[CS]) (PulumiInput[CS], PulumiOutput[CS]) + + // TODO Read for .get +} + +func displayPreview[CS PulumiSchema, PS PulumiSchema]( + inputs PulumiCheckedInput[CS], oldState PulumiOutput[PS], dd PulumiDetailedDiff[CS, PS], +) { +} diff --git a/pkg/internal/tests/protocolspec/pulumi_types_v2.go b/pkg/internal/tests/protocolspec/pulumi_types_v2.go new file mode 100644 index 000000000..05f2225d8 --- /dev/null +++ b/pkg/internal/tests/protocolspec/pulumi_types_v2.go @@ -0,0 +1,41 @@ +package protocolspec + +type PulumiDetailedDiffV2[CS PulumiSchema] struct { + OldState PulumiOutput[CS] + plannedState PulumiPreviewOutput[CS] +} + +// Note that the only provider method that needs to deal with old schema is UpgradeState. +// Note also that old inputs are not needed for any method. +type PulumiProviderV2[CS PulumiSchema] interface { + // Check shouldn't apply defaults, so old Inputs are not needed. Checked input then becomes the same as input but validated. + Check(news PulumiInput[CS]) PulumiInput[CS] + + CreatePreview(inputs PulumiInput[CS]) PulumiPreviewOutput[CS] + // Should we go further here and avoid replanning? What is needed to achieve this? + Create(inputs PulumiInput[CS]) PulumiOutput[CS] + + // Old inputs are not needed for Update and are currently unused. + UpdatePreview(inputs PulumiInput[CS], upgradedState PulumiOutput[CS]) PulumiPreviewOutput[CS] + // Avoid replanning? + Update(inputs PulumiInput[CS], upgradedState PulumiOutput[CS]) PulumiOutput[CS] + + DeletePreview(upgradedState PulumiOutput[CS]) PulumiPreviewOutput[CS] + // Avoid replanning? + Delete(upgradedState PulumiOutput[CS]) PulumiOutput[CS] + + Diff(oldUpgradedState PulumiOutput[CS], plannedState PulumiPreviewOutput[CS]) (PulumiDiff[CS], PulumiDetailedDiffV2[CS]) + + ReadForImport(id string) (PulumiOutput[CS], PulumiInput[CS]) + ReadForRefresh(state PulumiOutput[CS]) (PulumiOutput[CS]) +} + +type PulumiProviderV2WithUpgrade[CS PulumiSchema, PS PulumiSchema] interface { + PulumiProviderV2[CS] + UpgradeState(state PulumiOutput[PS]) PulumiOutput[CS] +} + +func displayPreviewV2[CS PulumiSchema]( + proposedState PulumiPreviewOutput[CS], upgradedOldState PulumiOutput[CS], dd PulumiDetailedDiffV2[CS], +) { +} diff --git a/pkg/internal/tests/protocolspec/tf_protocol_types.go b/pkg/internal/tests/protocolspec/tf_protocol_types.go new file mode 100644 index 000000000..c4fdb874a --- /dev/null +++ b/pkg/internal/tests/protocolspec/tf_protocol_types.go @@ -0,0 +1,37 @@ +package protocolspec + +type TFSchema interface{} + +// TFConfig is the user provided config +type TFConfig[S TFSchema] struct{} + +// TFState is the current state of the resource +type TFState[S TFSchema] struct{} + +// TFPlannedState is the planned state of the resource. This applies defaults and can contain unknowns. +type TFPlannedState[S TFSchema] struct{} + +// TFDiff contains the diff/ no_diff decision as well as the replacement decision +type TFDiff[S TFSchema] struct{} + +// CS stands for Current Schema +type TFProvider[CS TFSchema] interface { + // PlanResourceChange also takes a proposed state but that is just a function of the config and the state + PlanResourceChange(config TFConfig[CS], state TFState[CS]) TFPlannedState[CS] + + ApplyResourceChange(config TFConfig[CS], state TFState[CS], planned TFPlannedState[CS]) TFState[CS] + + Diff(state TFState[CS], planned TFPlannedState[CS]) TFDiff[CS] + + Importer(id string) TFState[CS] + + ReadResource(TFState[CS]) TFState[CS] +} + +// CS stands for Current Schema +// PS stands for Past Schema +type TFProviderWithUpgradeState[CS TFSchema, PS TFSchema] interface { + TFProvider[CS] + UpgradeState(state TFState[PS]) TFState[CS] + GetCurrentProvider() TFProviderWithUpgradeState[CS, CS] +}