Skip to content

Commit

Permalink
Add cli resource
Browse files Browse the repository at this point in the history
  • Loading branch information
danischm committed Oct 13, 2023
1 parent d8ca2d8 commit a5ea611
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Add `iosxe_vlan_filter` resource and data source
- Add `iosxe_vlan_group` resource and data source
- Add `iosxe_save_config` resource
- Add `iosxe_cli` resource

## 0.5.0

Expand Down
1 change: 1 addition & 0 deletions docs/guides/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description: |-
- Add `iosxe_vlan_filter` resource and data source
- Add `iosxe_vlan_group` resource and data source
- Add `iosxe_save_config` resource
- Add `iosxe_cli` resource

## 0.5.0

Expand Down
33 changes: 33 additions & 0 deletions docs/resources/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "iosxe_cli Resource - terraform-provider-iosxe"
subcategory: "General"
description: |-
This resources is used to configure arbitrary CLI commands. This should be considered a last resort in case YANG models are not available, as it cannot read the state and therefore cannot reconcile changes.
---

# iosxe_cli (Resource)

This resources is used to configure arbitrary CLI commands. This should be considered a last resort in case YANG models are not available, as it cannot read the state and therefore cannot reconcile changes.

## Example Usage

```terraform
resource "iosxe_cli" "example" {
cli = <<-EOT
interface Loopback123
description configured-via-restconf-cli
EOT
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `cli` (String) This attribute contains the CLI commands.

### Optional

- `device` (String) A device name from the provider configuration.
6 changes: 6 additions & 0 deletions examples/resources/iosxe_cli/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "iosxe_cli" "example" {
cli = <<-EOT
interface Loopback123
description configured-via-restconf-cli
EOT
}
10 changes: 9 additions & 1 deletion gen/doc_category.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,17 @@ func main() {
if err != nil {
log.Fatalf("Error opening documentation: %v", err)
}

s := string(content)
s = strings.ReplaceAll(s, `subcategory: ""`, `subcategory: "General"`)
os.WriteFile(filename, []byte(s), 0644)

// update iosxe_cli resource
filename = "./docs/resources/cli.md"
content, err = os.ReadFile(filename)
if err != nil {
log.Fatalf("Error opening documentation: %v", err)
}
s = string(content)
s = strings.ReplaceAll(s, `subcategory: ""`, `subcategory: "General"`)
os.WriteFile(filename, []byte(s), 0644)
}
1 change: 1 addition & 0 deletions gen/templates/provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/provider/provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

157 changes: 157 additions & 0 deletions internal/provider/resource_iosxe_cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Mozilla Public 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
//
// https://mozilla.org/MPL/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.
//
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/netascode/go-restconf"
"github.com/tidwall/sjson"
)

// Ensure provider defined types fully satisfy framework interfaces
var _ resource.Resource = &CliResource{}

func NewCliResource() resource.Resource {
return &CliResource{}
}

type CliResource struct {
clients map[string]*restconf.Client
}

func (r *CliResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_cli"
}

func (r *CliResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "This resources is used to configure arbitrary CLI commands. This should be considered a last resort in case YANG models are not available, as it cannot read the state and therefore cannot reconcile changes.",

Attributes: map[string]schema.Attribute{
"device": schema.StringAttribute{
MarkdownDescription: "A device name from the provider configuration.",
Optional: true,
},
"cli": schema.StringAttribute{
MarkdownDescription: "This attribute contains the CLI commands.",
Required: true,
},
},
}
}

func (r *CliResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

r.clients = req.ProviderData.(map[string]*restconf.Client)
}

func (r *CliResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var device types.String
var cli types.String

diags := req.Plan.GetAttribute(ctx, path.Root("device"), &device)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = req.Plan.GetAttribute(ctx, path.Root("cli"), &cli)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

if _, ok := r.clients[device.ValueString()]; !ok {
resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", device.ValueString()))
return
}

tflog.Debug(ctx, "Beginning to send CLI commands")

body := ""
body, _ = sjson.Set(body, "Cisco-IOS-XE-cli-rpc:input.config-clis", cli.ValueString())
request := r.clients[device.ValueString()].NewReq("POST", "/operations/Cisco-IOS-XE-cli-rpc:config-ios-cli-rpc", strings.NewReader(body))
_, err := r.clients[device.ValueString()].Do(request)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to send CLI commands, got error: %s", err))
return
}

tflog.Debug(ctx, "Send CLI commands finished successfully")

diags = resp.State.SetAttribute(ctx, path.Root("device"), device)
resp.Diagnostics.Append(diags...)
diags = resp.State.SetAttribute(ctx, path.Root("cli"), cli)
resp.Diagnostics.Append(diags...)
}

func (r *CliResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
}

func (r *CliResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var device types.String
var cli types.String

diags := req.Plan.GetAttribute(ctx, path.Root("device"), &device)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = req.Plan.GetAttribute(ctx, path.Root("cli"), &cli)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

if _, ok := r.clients[device.ValueString()]; !ok {
resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", device.ValueString()))
return
}

tflog.Debug(ctx, "Beginning to send CLI commands")

body := ""
body, _ = sjson.Set(body, "Cisco-IOS-XE-cli-rpc:input.config-clis", cli.ValueString())
request := r.clients[device.ValueString()].NewReq("POST", "/operations/Cisco-IOS-XE-cli-rpc:config-ios-cli-rpc", strings.NewReader(body))
_, err := r.clients[device.ValueString()].Do(request)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to send CLI commands, got error: %s", err))
return
}

tflog.Debug(ctx, "Send CLI commands finished successfully")

diags = resp.State.SetAttribute(ctx, path.Root("device"), device)
resp.Diagnostics.Append(diags...)
diags = resp.State.SetAttribute(ctx, path.Root("cli"), cli)
resp.Diagnostics.Append(diags...)
}

func (r *CliResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
}
49 changes: 49 additions & 0 deletions internal/provider/resource_iosxe_cli_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions internal/provider/resource_iosxe_save_config_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions templates/guides/changelog.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description: |-
- Add `iosxe_vlan_filter` resource and data source
- Add `iosxe_vlan_group` resource and data source
- Add `iosxe_save_config` resource
- Add `iosxe_cli` resource

## 0.5.0

Expand Down

0 comments on commit a5ea611

Please sign in to comment.