Skip to content

Commit

Permalink
azapi_* - support payload (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
ms-henglu authored Apr 16, 2024
1 parent d706a85 commit 861bed6
Show file tree
Hide file tree
Showing 27 changed files with 1,117 additions and 83 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## v1.13.0 (unreleased)
ENHANCEMENTS:
- Support the new bicep types.
- `azapi_resource` resource: Support for the `payload` property.
- `azapi_update_resource` resource: Support for the `payload` property.
- `azapi_resource_action` resource: Support for the `payload` property.
- `azapi_resource_action` data source: Support for the `payload` property.

## v1.12.0
ENHANCEMENTS:
Expand Down
205 changes: 205 additions & 0 deletions internal/langserver/handlers/complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,47 @@ func TestCompletion_value(t *testing.T) {
}, string(expectRaw))
}

func TestCompletion_payload_value(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{}))
stop := ls.Start(t)
defer stop()

config, err := os.ReadFile(fmt.Sprintf("./testdata/%s/main.tf", t.Name()))
if err != nil {
t.Fatal(err)
}

expectRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s/expect.json", t.Name()))
if err != nil {
t.Fatal(err)
}

ls.Call(t, &langserver.CallRequest{
Method: "initialize",
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, tmpDir.URI()),
})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
ReqParams: "{}",
})
ls.Call(t, &langserver.CallRequest{
Method: "textDocument/didOpen",
ReqParams: buildReqParamsTextDocument(string(config), tmpDir.URI()),
})

ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "textDocument/completion",
ReqParams: buildReqParamsCompletion(7, 15, tmpDir.URI()),
}, string(expectRaw))
}

func TestCompletion_propWrappedByQuote(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())
Expand Down Expand Up @@ -194,6 +235,47 @@ func TestCompletion_propWrappedByQuote(t *testing.T) {
}, string(expectRaw))
}

func TestCompletion_payload_propWrappedByQuote(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{}))
stop := ls.Start(t)
defer stop()

config, err := os.ReadFile(fmt.Sprintf("./testdata/%s/main.tf", t.Name()))
if err != nil {
t.Fatal(err)
}

expectRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s/expect.json", t.Name()))
if err != nil {
t.Fatal(err)
}

ls.Call(t, &langserver.CallRequest{
Method: "initialize",
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, tmpDir.URI()),
})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
ReqParams: "{}",
})
ls.Call(t, &langserver.CallRequest{
Method: "textDocument/didOpen",
ReqParams: buildReqParamsTextDocument(string(config), tmpDir.URI()),
})

ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "textDocument/completion",
ReqParams: buildReqParamsCompletion(9, 11, tmpDir.URI()),
}, string(expectRaw))
}

func TestCompletion_action(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())
Expand Down Expand Up @@ -276,6 +358,47 @@ func TestCompletion_actionBody(t *testing.T) {
}, string(expectRaw))
}

func TestCompletion_payload_actionBody(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{}))
stop := ls.Start(t)
defer stop()

config, err := os.ReadFile(fmt.Sprintf("./testdata/%s/main.tf", t.Name()))
if err != nil {
t.Fatal(err)
}

expectRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s/expect.json", t.Name()))
if err != nil {
t.Fatal(err)
}

ls.Call(t, &langserver.CallRequest{
Method: "initialize",
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, tmpDir.URI()),
})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
ReqParams: "{}",
})
ls.Call(t, &langserver.CallRequest{
Method: "textDocument/didOpen",
ReqParams: buildReqParamsTextDocument(string(config), tmpDir.URI()),
})

ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "textDocument/completion",
ReqParams: buildReqParamsCompletion(5, 5, tmpDir.URI()),
}, string(expectRaw))
}

func TestCompletion_discriminated(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())
Expand Down Expand Up @@ -317,6 +440,47 @@ func TestCompletion_discriminated(t *testing.T) {
}, string(expectRaw))
}

func TestCompletion_payload_discriminated(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{}))
stop := ls.Start(t)
defer stop()

config, err := os.ReadFile(fmt.Sprintf("./testdata/%s/main.tf", t.Name()))
if err != nil {
t.Fatal(err)
}

expectRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s/expect.json", t.Name()))
if err != nil {
t.Fatal(err)
}

ls.Call(t, &langserver.CallRequest{
Method: "initialize",
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, tmpDir.URI()),
})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
ReqParams: "{}",
})
ls.Call(t, &langserver.CallRequest{
Method: "textDocument/didOpen",
ReqParams: buildReqParamsTextDocument(string(config), tmpDir.URI()),
})

ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "textDocument/completion",
ReqParams: buildReqParamsCompletion(11, 7, tmpDir.URI()),
}, string(expectRaw))
}

func TestCompletion_prop(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())
Expand Down Expand Up @@ -358,6 +522,47 @@ func TestCompletion_prop(t *testing.T) {
}, string(expectRaw))
}

func TestCompletion_payload_prop(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{}))
stop := ls.Start(t)
defer stop()

config, err := os.ReadFile(fmt.Sprintf("./testdata/%s/main.tf", t.Name()))
if err != nil {
t.Fatal(err)
}

expectRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s/expect.json", t.Name()))
if err != nil {
t.Fatal(err)
}

ls.Call(t, &langserver.CallRequest{
Method: "initialize",
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, tmpDir.URI()),
})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
ReqParams: "{}",
})
ls.Call(t, &langserver.CallRequest{
Method: "textDocument/didOpen",
ReqParams: buildReqParamsTextDocument(string(config), tmpDir.URI()),
})

ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "textDocument/completion",
ReqParams: buildReqParamsCompletion(12, 11, tmpDir.URI()),
}, string(expectRaw))
}

func TestCompletion_codeSample(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())
Expand Down
93 changes: 49 additions & 44 deletions internal/langserver/handlers/hover/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,64 +47,39 @@ func HoverAtPos(data []byte, filename string, pos hcl.Pos, logger *log.Logger) *
}
}
}
case "body":
case "payload":
if parser.ContainsPos(attribute.NameRange, pos) {
return Hover(property.Name, property.Modifier, property.Type, property.Description, attribute.NameRange)
}
typeValue := parser.ExtractAzureResourceType(block)
if typeValue == nil {

bodyDef := tfschema.BodyDefinitionFromBlock(block)
if bodyDef == nil {
return nil
}

tokens, _ := hclsyntax.LexExpression(data[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], filename, attribute.Expr.Range().Start)
hclNode := parser.BuildHclNode(tokens)
if hclNode == nil {
break
}

var bodyDef types.TypeBase
def, err := azure.GetResourceDefinitionByResourceType(*typeValue)
if err != nil || def == nil {
return nil
return hoverOnBody(hclNode, pos, bodyDef)
case "body":
if parser.ContainsPos(attribute.NameRange, pos) {
return Hover(property.Name, property.Modifier, property.Type, property.Description, attribute.NameRange)
}
bodyDef = def
if len(block.Labels) >= 2 && block.Labels[0] == "azapi_resource_action" {
parts := strings.Split(*typeValue, "@")
if len(parts) != 2 {
return nil
}
actionName := parser.ExtractAction(block)
if actionName != nil && len(*actionName) != 0 {
resourceFuncDef, err := azure.GetResourceFunction(parts[0], parts[1], *actionName)
if err != nil || resourceFuncDef == nil {
return nil
}
bodyDef = resourceFuncDef
}

bodyDef := tfschema.BodyDefinitionFromBlock(block)
if bodyDef == nil {
return nil
}

hclNode := parser.JsonEncodeExpressionToHclNode(data, attribute.Expr)
if hclNode == nil {
break
}
hclNodes := parser.HclNodeArraysOfPos(hclNode, pos)
if len(hclNodes) == 0 {
break
}
lastHclNode := hclNodes[len(hclNodes)-1]

if parser.ContainsPos(lastHclNode.KeyRange, pos) {
defs := schema.GetDef(bodyDef.AsTypeBase(), hclNodes[0:len(hclNodes)-1], 0)
props := make([]schema.Property, 0)
for _, def := range defs {
props = append(props, schema.GetAllowedProperties(def)...)
}
logger.Printf("received allowed keys: %#v", props)
if len(props) != 0 {
index := 0
for i := range props {
if props[i].Name == lastHclNode.Key {
index = i
break
}
}
return Hover(props[index].Name, string(props[index].Modifier), props[index].Type, props[index].Description, lastHclNode.KeyRange)
}
}
return hoverOnBody(hclNode, pos, bodyDef)
default:
if !parser.ContainsPos(attribute.NameRange, pos) {
return nil
Expand Down Expand Up @@ -132,6 +107,36 @@ func HoverAtPos(data []byte, filename string, pos hcl.Pos, logger *log.Logger) *
return nil
}

func hoverOnBody(hclNode *parser.HclNode, pos hcl.Pos, bodyDef types.TypeBase) *lsp.Hover {
hclNodes := parser.HclNodeArraysOfPos(hclNode, pos)
if len(hclNodes) == 0 {
return nil
}
lastHclNode := hclNodes[len(hclNodes)-1]

if parser.ContainsPos(lastHclNode.KeyRange, pos) {
defs := schema.GetDef(bodyDef.AsTypeBase(), hclNodes[0:len(hclNodes)-1], 0)
props := make([]schema.Property, 0)
for _, def := range defs {
props = append(props, schema.GetAllowedProperties(def)...)
}
if len(props) != 0 {
index := -1
for i := range props {
if props[i].Name == lastHclNode.Key {
index = i
break
}
}
if index == -1 {
return nil
}
return Hover(props[index].Name, string(props[index].Modifier), props[index].Type, props[index].Description, lastHclNode.KeyRange)
}
}
return nil
}

func GetParentType(resourceType string) string {
parts := strings.Split(resourceType, "/")
if len(parts) <= 2 {
Expand Down
Loading

0 comments on commit 861bed6

Please sign in to comment.