From 363291c051c37e2f28e21981d933fb5ed08dc0a2 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 15 Feb 2022 09:30:36 +0100 Subject: [PATCH 1/6] r/snmp: add engine_id argument Fixes parts of #339 --- CHANGELOG.md | 2 ++ docs/resources/snmp.md | 3 +++ junos/resource_snmp.go | 18 ++++++++++++++++++ junos/resource_snmp_test.go | 2 ++ 4 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3749010..ccadd94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ENHANCEMENTS: +* resource/`junos_snmp`: add `engine_id` argument (Fixes parts of #339) + BUG FIXES: ## 1.24.1 (February 11, 2022) diff --git a/docs/resources/snmp.md b/docs/resources/snmp.md index 3e24a62e..1fd74d40 100644 --- a/docs/resources/snmp.md +++ b/docs/resources/snmp.md @@ -36,6 +36,9 @@ The following arguments are supported: Contact information for administrator. - **description** (Optional, String) System description. +- **engine_id** (Optional, String) + SNMPv3 engine ID. + Need to be `use-default-ip-address`, `use-mac-address` or `local ...` - **filter_duplicates** (Optional, Boolean) Filter requests with duplicate source address/port and request ID. - **filter_interfaces** (Optional, Set of String) diff --git a/junos/resource_snmp.go b/junos/resource_snmp.go index 2025c3ca..47a8c9bf 100644 --- a/junos/resource_snmp.go +++ b/junos/resource_snmp.go @@ -3,6 +3,7 @@ package junos import ( "context" "fmt" + "regexp" "strconv" "strings" @@ -20,6 +21,7 @@ type snmpOptions struct { routingInstanceAccess bool contact string description string + engineID string location string filterInterfaces []string interFace []string @@ -58,6 +60,13 @@ func resourceSnmp() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "engine_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile( + `^(use-default-ip-address|use-mac-address|local .+)$`), + "must have 'use-default-ip-address', 'use-mac-address' or 'local ...'"), + }, "filter_duplicates": { Type: schema.TypeBool, Optional: true, @@ -318,6 +327,9 @@ func setSnmp(d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) err if v := d.Get("description").(string); v != "" { configSet = append(configSet, setPrefix+"description \""+v+"\"") } + if v := d.Get("engine_id").(string); v != "" { + configSet = append(configSet, setPrefix+"engine-id "+v) + } if d.Get("filter_duplicates").(bool) { configSet = append(configSet, setPrefix+"filter-duplicates") } @@ -378,6 +390,7 @@ func delSnmp(m interface{}, jnprSess *NetconfObject) error { "arp", "contact", "description", + "engine-id", "filter-duplicates", "filter-interfaces", "health-monitor", @@ -424,6 +437,8 @@ func readSnmp(m interface{}, jnprSess *NetconfObject) (snmpOptions, error) { confRead.contact = strings.Trim(strings.TrimPrefix(itemTrim, "contact "), "\"") case strings.HasPrefix(itemTrim, "description "): confRead.description = strings.Trim(strings.TrimPrefix(itemTrim, "description "), "\"") + case strings.HasPrefix(itemTrim, "engine-id "): + confRead.engineID = strings.TrimPrefix(itemTrim, "engine-id ") case itemTrim == "filter-duplicates": confRead.filterDuplicates = true case strings.HasPrefix(itemTrim, "filter-interfaces interfaces "): @@ -523,6 +538,9 @@ func fillSnmp(d *schema.ResourceData, snmpOptions snmpOptions) { if tfErr := d.Set("description", snmpOptions.description); tfErr != nil { panic(tfErr) } + if tfErr := d.Set("engine_id", snmpOptions.engineID); tfErr != nil { + panic(tfErr) + } if tfErr := d.Set("filter_duplicates", snmpOptions.filterDuplicates); tfErr != nil { panic(tfErr) } diff --git a/junos/resource_snmp_test.go b/junos/resource_snmp_test.go index 04597cbc..0bb3b38b 100644 --- a/junos/resource_snmp_test.go +++ b/junos/resource_snmp_test.go @@ -32,6 +32,7 @@ resource "junos_snmp" "testacc_snmp" { arp = true contact = "contact@example.com" description = "snmp description" + engine_id = "use-mac-address" filter_duplicates = true filter_interfaces = ["(ge|xe|ae).*\\.0", "fxp0"] filter_internal_interfaces = true @@ -65,6 +66,7 @@ resource "junos_snmp" "testacc_snmp" { clean_on_destroy = true arp = true arp_host_name_resolution = true + engine_id = "local \"test#123\"" health_monitor {} routing_instance_access = true } From 4849510c311cdf1f73b838e93a6edbf9784f9769 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Fri, 18 Feb 2022 08:55:54 +0100 Subject: [PATCH 2/6] add junos_snmp_v3_usm_user resource Fixes parts of #339 --- CHANGELOG.md | 1 + docs/resources/snmp_v3_usm_user.md | 88 ++++ junos/constants.go | 2 + junos/func_common.go | 26 ++ junos/provider.go | 1 + junos/resource_snmp_v3_usm_user.go | 570 ++++++++++++++++++++++++ junos/resource_snmp_v3_usm_user_test.go | 92 ++++ 7 files changed, 780 insertions(+) create mode 100644 docs/resources/snmp_v3_usm_user.md create mode 100644 junos/resource_snmp_v3_usm_user.go create mode 100644 junos/resource_snmp_v3_usm_user_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ccadd94a..0acc80aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ENHANCEMENTS: * resource/`junos_snmp`: add `engine_id` argument (Fixes parts of #339) +* add `junos_snmp_v3_usm_user` resource (Fixes parts of #339) BUG FIXES: diff --git a/docs/resources/snmp_v3_usm_user.md b/docs/resources/snmp_v3_usm_user.md new file mode 100644 index 00000000..94df6db5 --- /dev/null +++ b/docs/resources/snmp_v3_usm_user.md @@ -0,0 +1,88 @@ +--- +page_title: "Junos: junos_snmp_v3_usm_user" +--- + +# junos_snmp_v3_usm_user + +Provides a snmp v3 USM user resource. + +## Example Usage + +```hcl +# Add a snmp v3 usm local-engine user +resource junos_snmp_v3_usm_user "user1" { + name = "user1" +} +# Add a snmp v3 usm remote-engine user +resource junos_snmp_v3_usm_user "user2" { + name = "user2" + engine_type = "remote" + engine_id = "800007E5804089071BC6D10A41" +} +``` + +## Argument Reference + +The following arguments are supported: + +- **name** (Required, String, Forces new resource) + The name of snmp v3 USM user. +- **engine_type** (Optional, String, Forces new resource) + Local or remote engine user. + Need to be `local` or `remote`. + Defaults to `local`. +- **engine_id** (Optional, String, Forces new resource) + Remote engine id (Hex format). +- **authentication_key** (Optional, String, Sensitive) + Encrypted key used for user authentication. + If the encrypted key is present on Junos device and `authentication_password` is used + in Terraform config, the value of this argument is left blank to avoid conflict. + Conflict with `authentication_password`. +- **authentication_password** (Optional, String, Sensitive) + User's authentication password. + Due to encryption, when Terraform refreshes the resource, the password can't be read, + so the provider only checks if it exists and can't detect a change of the password itself + outside of Terraform. + To be able to detect a change of the password outside of Terraform, + preferably use `authentication_key` argument. + Conflict with `authentication_key`. +- **authentication_type** (Optional, String) + Define authentication type. + Need to be `authentication-md5`, `authentication-sha` or `authentication-none`. + Defaults to `authentication-none`. + `authentication_key` or `authentication_password` need to set when `authentication_type` != `authentication-none`. +- **privacy_key** (Optional, String, Sensitive) + Encrypted key used for user privacy. + If the encrypted key is present on Junos device and `privacy_password` is used + in Terraform config, the value of this argument is left blank to avoid conflict. + Conflict with `privacy_password`. +- **privacy_password** (Optional, String, Sensitive) + User's privacy password. + Due to encryption, when Terraform refreshes the resource, the password can't be read, + so the provider only checks if it exists and can't detect a change of the password itself + outside of Terraform. + To be able to detect a change of the password outside of Terraform, + preferably use `privacy_key` argument. + Conflict with `privacy_key`. +- **privacy_type** (Optional, String) + Define privacy type. + Need to be `privacy-3des`, `privacy-aes128`, `privacy-des` or `privacy-none`. + Defaults to `privacy-none`. + `privacy_key` or `privacy_password` need to set when `privacy_type` != `privacy-none`. + +## Attributes Reference + +The following attributes are exported: + +- **id** (String) + An identifier for the resource with format `local_-_` or `remote_-__-_`. + +## Import + +Junos snmp v3 USM user can be imported using an id made up +of `local_-_` or `remote_-__-_`, e.g. + +```shell +$ terraform import junos_snmp_v3_usm_user.user1 local_-_user1 +$ terraform import junos_snmp_v3_usm_user.user2 remote_-_800007E5804089071BC6D10A41_-_user2 +``` diff --git a/junos/constants.go b/junos/constants.go index 227c2502..019dfb4a 100644 --- a/junos/constants.go +++ b/junos/constants.go @@ -34,4 +34,6 @@ const ( noLoopbackWord = "no-loopback" actionCos = "class-of-service" actionMarkDiffServ = "mark-diffserv" + localWord = "local" + privacyNoneWord = "privacy-none" ) diff --git a/junos/func_common.go b/junos/func_common.go index a0c4d4f7..9f86063e 100644 --- a/junos/func_common.go +++ b/junos/func_common.go @@ -343,3 +343,29 @@ func validateIsIPv6Address(i interface{}, k string) (warnings []string, errors [ return warnings, errors } + +func stringLenBetweenSensitive(min, max int) schema.SchemaValidateDiagFunc { + return func(i interface{}, path cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + v, ok := i.(string) + if !ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "expected type to be string", + AttributePath: path, + }) + + return diags + } + + if len(v) < min || len(v) > max { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("expected length to be in the range (%d - %d), got %d", min, max, len(v)), + AttributePath: path, + }) + } + + return diags + } +} diff --git a/junos/provider.go b/junos/provider.go index 89a9c427..7f9a4729 100644 --- a/junos/provider.go +++ b/junos/provider.go @@ -188,6 +188,7 @@ func Provider() *schema.Provider { "junos_snmp": resourceSnmp(), "junos_snmp_clientlist": resourceSnmpClientlist(), "junos_snmp_community": resourceSnmpCommunity(), + "junos_snmp_v3_usm_user": resourceSnmpV3UsmUser(), "junos_snmp_view": resourceSnmpView(), "junos_static_route": resourceStaticRoute(), "junos_switch_options": resourceSwitchOptions(), diff --git a/junos/resource_snmp_v3_usm_user.go b/junos/resource_snmp_v3_usm_user.go new file mode 100644 index 00000000..dbd2df0a --- /dev/null +++ b/junos/resource_snmp_v3_usm_user.go @@ -0,0 +1,570 @@ +package junos + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + jdecode "github.com/jeremmfr/junosdecode" +) + +type snmpV3UsmUserOptions struct { + name string + engineID string + engineType string + authenticationType string + authenticationKey string + authenticationPassword string + privacyType string + privacyKey string + privacyPassword string +} + +func resourceSnmpV3UsmUser() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSnmpV3UsmUserCreate, + ReadContext: resourceSnmpV3UsmUserRead, + UpdateContext: resourceSnmpV3UsmUserUpdate, + DeleteContext: resourceSnmpV3UsmUserDelete, + Importer: &schema.ResourceImporter{ + State: resourceSnmpV3UsmUserImport, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "engine_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: localWord, + ValidateFunc: validation.StringInSlice([]string{localWord, "remote"}, false), + }, + "engine_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "authentication_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"authentication_password"}, + }, + "authentication_password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"authentication_key"}, + ValidateDiagFunc: stringLenBetweenSensitive(8, 1024), + }, + "authentication_type": { + Type: schema.TypeString, + Optional: true, + Default: "authentication-none", + ValidateFunc: validation.StringInSlice([]string{ + "authentication-md5", + "authentication-sha", + "authentication-none", + }, false), + }, + "privacy_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"privacy_password"}, + }, + "privacy_password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"privacy_key"}, + ValidateDiagFunc: stringLenBetweenSensitive(8, 1024), + }, + "privacy_type": { + Type: schema.TypeString, + Optional: true, + Default: privacyNoneWord, + ValidateFunc: validation.StringInSlice([]string{ + "privacy-3des", + "privacy-aes128", + "privacy-des", + privacyNoneWord, + }, false), + }, + }, + } +} + +func resourceSnmpV3UsmUserCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeCreateSetFile != "" { + if err := setSnmpV3UsmUser(d, m, nil); err != nil { + return diag.FromErr(err) + } + if d.Get("engine_type").(string) == localWord { + d.SetId(localWord + idSeparator + d.Get("name").(string)) + } else { + d.SetId("remote" + idSeparator + d.Get("engine_id").(string) + idSeparator + d.Get("name").(string)) + } + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + snmpV3UsmUserExists, err := checkSnmpV3UsmUserExists( + d.Get("name").(string), d.Get("engine_type").(string), d.Get("engine_id").(string), m, jnprSess) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3UsmUserExists { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + if d.Get("engine_type").(string) != localWord { + return append(diagWarns, diag.FromErr(fmt.Errorf("snmp v3 usm user %v in remote-engine %s already exists", + d.Get("name").(string), d.Get("engine_id").(string)))...) + } + + return append(diagWarns, diag.FromErr(fmt.Errorf("snmp v3 usm user %v in local-engine already exists", + d.Get("name").(string)))...) + } + + if err := setSnmpV3UsmUser(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("create resource junos_snmp_v3_usm_user", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + snmpV3UsmUserExists, err = checkSnmpV3UsmUserExists( + d.Get("name").(string), d.Get("engine_type").(string), d.Get("engine_id").(string), m, jnprSess) + if err != nil { + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3UsmUserExists { + if d.Get("engine_type").(string) == localWord { + d.SetId(localWord + idSeparator + d.Get("name").(string)) + } else { + d.SetId("remote" + idSeparator + d.Get("engine_id").(string) + idSeparator + d.Get("name").(string)) + } + } else { + if d.Get("engine_type").(string) != localWord { + return append(diagWarns, diag.FromErr(fmt.Errorf("snmp v3 usm user %v in remote-engine %s not exists after commit "+ + "=> check your config", d.Get("name").(string), d.Get("engine_id").(string)))...) + } + + return append(diagWarns, diag.FromErr(fmt.Errorf("snmp v3 usm user %v in local-engine not exists after commit "+ + "=> check your config", d.Get("name").(string)))...) + } + + return append(diagWarns, resourceSnmpV3UsmUserReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3UsmUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + + return resourceSnmpV3UsmUserReadWJnprSess(d, m, jnprSess) +} + +func resourceSnmpV3UsmUserReadWJnprSess( + d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) diag.Diagnostics { + configSrc := snmpV3UsmUserOptions{ + name: d.Get("name").(string), + engineType: d.Get("engine_type").(string), + engineID: d.Get("engine_id").(string), + authenticationPassword: d.Get("authentication_password").(string), + authenticationType: d.Get("authentication_type").(string), + privacyPassword: d.Get("privacy_password").(string), + privacyType: d.Get("privacy_type").(string), + } + mutex.Lock() + snmpV3UsmUserOptions, err := readSnmpV3UsmUser(configSrc, m, jnprSess) + mutex.Unlock() + if err != nil { + return diag.FromErr(err) + } + if snmpV3UsmUserOptions.name == "" { + d.SetId("") + } else { + fillSnmpV3UsmUserData(d, snmpV3UsmUserOptions) + } + + return nil +} + +func resourceSnmpV3UsmUserUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + d.Partial(true) + sess := m.(*Session) + if sess.junosFakeUpdateAlso { + if err := delSnmpV3UsmUser( + d.Get("name").(string), d.Get("engine_type").(string), d.Get("engine_id").(string), m, nil); err != nil { + return diag.FromErr(err) + } + if err := setSnmpV3UsmUser(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.Partial(false) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3UsmUser( + d.Get("name").(string), d.Get("engine_type").(string), d.Get("engine_id").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if err := setSnmpV3UsmUser(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("update resource junos_snmp_v3_usm_user", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + d.Partial(false) + + return append(diagWarns, resourceSnmpV3UsmUserReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3UsmUserDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeDeleteAlso { + if err := delSnmpV3UsmUser( + d.Get("name").(string), d.Get("engine_type").(string), d.Get("engine_id").(string), m, nil); err != nil { + return diag.FromErr(err) + } + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3UsmUser( + d.Get("name").(string), d.Get("engine_type").(string), d.Get("engine_id").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("delete resource junos_snmp_v3_usm_user", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + + return diagWarns +} + +func resourceSnmpV3UsmUserImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return nil, err + } + defer sess.closeSession(jnprSess) + result := make([]*schema.ResourceData, 1) + idSplit := strings.Split(d.Id(), idSeparator) + var configImport snmpV3UsmUserOptions + switch { + case len(idSplit) == 2 && idSplit[0] == localWord: + snmpV3UsmUserExists, err := checkSnmpV3UsmUserExists(idSplit[1], idSplit[0], "", m, jnprSess) + if err != nil { + return nil, err + } + if !snmpV3UsmUserExists { + return nil, fmt.Errorf("don't find snmp v3 usm user with id '%v' (id must be local%s)", d.Id(), idSeparator) + } + configImport.name = idSplit[1] + configImport.engineType = idSplit[0] + case len(idSplit) == 3 && idSplit[0] == "remote": + snmpV3UsmUserExists, err := checkSnmpV3UsmUserExists(idSplit[2], idSplit[0], idSplit[1], m, jnprSess) + if err != nil { + return nil, err + } + if !snmpV3UsmUserExists { + return nil, fmt.Errorf("don't find snmp v3 usm user with id "+ + "'%v' (id must be remote%s%s)", d.Id(), idSeparator, idSeparator) + } + configImport.name = idSplit[2] + configImport.engineType = idSplit[0] + configImport.engineID = idSplit[1] + default: + return nil, fmt.Errorf("can't find snmp v3 usm user with id "+ + "'%v' (id must be local%s or remote%s%s)", d.Id(), idSeparator, idSeparator, idSeparator) + } + snmpV3UsmUserOptions, err := readSnmpV3UsmUser(configImport, m, jnprSess) + if err != nil { + return nil, err + } + fillSnmpV3UsmUserData(d, snmpV3UsmUserOptions) + + result[0] = d + + return result, nil +} + +func checkSnmpV3UsmUserExists(name, engineType, engineID string, m interface{}, jnprSess *NetconfObject) (bool, error) { + sess := m.(*Session) + if engineType == localWord { + showConfig, err := sess.command( + "show configuration snmp v3 usm local-engine user \""+name+"\" | display set", jnprSess) + if err != nil { + return false, err + } + if showConfig == emptyWord { + return false, nil + } + } else { + showConfig, err := sess.command( + "show configuration snmp v3 usm remote-engine \""+engineID+"\" user \""+name+"\" | display set", jnprSess) + if err != nil { + return false, err + } + if showConfig == emptyWord { + return false, nil + } + } + + return true, nil +} + +func setSnmpV3UsmUser(d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + + setPrefix := "set snmp v3 usm local-engine user \"" + d.Get("name").(string) + "\" " + if d.Get("engine_type").(string) != localWord { + engineID := d.Get("engine_id").(string) + if engineID == "" { + return fmt.Errorf("engine_id need to set when engine_type != local") + } + setPrefix = "set snmp v3 usm remote-engine \"" + engineID + "\" user \"" + d.Get("name").(string) + "\" " + } else if d.Get("engine_id").(string) != "" { + return fmt.Errorf("engine_id not compatible if engine_type = local") + } + configSet := make([]string, 0) + + if authType := d.Get("authentication_type").(string); authType != "authentication-none" { + if d.Get("authentication_key").(string) == "" && d.Get("authentication_password").(string) == "" { + return fmt.Errorf("authentication_key or authentication_password need to set " + + "when authentication_type != authentication-none") + } + setPrefixAuth := setPrefix + authType + " " + if authKey := d.Get("authentication_key").(string); authKey != "" { + configSet = append(configSet, setPrefixAuth+"authentication-key \""+authKey+"\"") + } + if authPass := d.Get("authentication_password").(string); authPass != "" { + configSet = append(configSet, setPrefixAuth+"authentication-password \""+authPass+"\"") + } + } else { + if d.Get("privacy_type").(string) != privacyNoneWord { + return fmt.Errorf("authentication should be configured before configuring the privacy") + } + if d.Get("authentication_key").(string) != "" { + return fmt.Errorf("authentication_key not compatible when authentication_type = authentication-none") + } + if d.Get("authentication_password").(string) != "" { + return fmt.Errorf("authentication_password not compatible when authentication_type = authentication-none") + } + configSet = append(configSet, setPrefix+"authentication-none") + } + if privType := d.Get("privacy_type").(string); privType != privacyNoneWord { + if d.Get("privacy_key").(string) == "" && d.Get("privacy_password").(string) == "" { + return fmt.Errorf("privacy_key or privacy_password need to set when privacy_type != privacy-none") + } + setPrefixPriv := setPrefix + privType + " " + if privKey := d.Get("privacy_key").(string); privKey != "" { + configSet = append(configSet, setPrefixPriv+"privacy-key \""+privKey+"\"") + } + if privPass := d.Get("privacy_password").(string); privPass != "" { + configSet = append(configSet, setPrefixPriv+"privacy-password \""+privPass+"\"") + } + } else { + if d.Get("privacy_key").(string) != "" { + return fmt.Errorf("privacy_key not compatible when privacy_type = privacy-none") + } + if d.Get("privacy_password").(string) != "" { + return fmt.Errorf("privacy_password not compatible when privacy_type = privacy-none") + } + configSet = append(configSet, setPrefix+privacyNoneWord) + } + + return sess.configSet(configSet, jnprSess) +} + +func readSnmpV3UsmUser(confSrc snmpV3UsmUserOptions, m interface{}, jnprSess *NetconfObject, +) (snmpV3UsmUserOptions, error) { + sess := m.(*Session) + var confRead snmpV3UsmUserOptions + + showCommand := "show configuration snmp v3 usm local-engine user \"" + confSrc.name + "\" | display set relative" + if confSrc.engineType != localWord { + showCommand = "show configuration snmp v3 usm remote-engine \"" + confSrc.engineID + + "\" user \"" + confSrc.name + "\" | display set relative" + } + showConfig, err := sess.command(showCommand, jnprSess) + if err != nil { + return confRead, err + } + if showConfig != emptyWord { + confRead.name = confSrc.name + confRead.engineType = confSrc.engineType + confRead.engineID = confSrc.engineID + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, "") { + continue + } + if strings.Contains(item, "") { + break + } + itemTrim := strings.TrimPrefix(item, setLineStart) + switch { + case strings.HasPrefix(itemTrim, "authentication-md5 authentication-key "): + confRead.authenticationType = "authentication-md5" + if confSrc.authenticationPassword != "" && confSrc.authenticationType == confRead.authenticationType { + confRead.authenticationPassword = confSrc.authenticationPassword + } else { + var err error + confRead.authenticationKey, err = jdecode.Decode(strings.Trim(strings.TrimPrefix( + itemTrim, "authentication-md5 authentication-key "), "\"")) + if err != nil { + return confRead, fmt.Errorf("failed to decode authentication-key : %w", err) + } + } + case itemTrim == "authentication-none": + confRead.authenticationType = itemTrim + case strings.HasPrefix(itemTrim, "authentication-sha authentication-key "): + confRead.authenticationType = "authentication-sha" + if confSrc.authenticationPassword != "" && confSrc.authenticationType == confRead.authenticationType { + confRead.authenticationPassword = confSrc.authenticationPassword + } else { + var err error + confRead.authenticationKey, err = jdecode.Decode(strings.Trim(strings.TrimPrefix( + itemTrim, "authentication-sha authentication-key "), "\"")) + if err != nil { + return confRead, fmt.Errorf("failed to decode authentication-key : %w", err) + } + } + case strings.HasPrefix(itemTrim, "privacy-3des privacy-key "): + confRead.privacyType = "privacy-3des" + if confSrc.privacyPassword != "" && confSrc.privacyType == confRead.privacyType { + confRead.privacyPassword = confSrc.privacyPassword + } else { + var err error + confRead.privacyKey, err = jdecode.Decode(strings.Trim(strings.TrimPrefix( + itemTrim, "privacy-3des privacy-key "), "\"")) + if err != nil { + return confRead, fmt.Errorf("failed to decode privacy-key : %w", err) + } + } + case strings.HasPrefix(itemTrim, "privacy-aes128 privacy-key "): + confRead.privacyType = "privacy-aes128" + if confSrc.privacyPassword != "" && confSrc.privacyType == confRead.privacyType { + confRead.privacyPassword = confSrc.privacyPassword + } else { + var err error + confRead.privacyKey, err = jdecode.Decode(strings.Trim(strings.TrimPrefix( + itemTrim, "privacy-aes128 privacy-key "), "\"")) + if err != nil { + return confRead, fmt.Errorf("failed to decode privacy-key : %w", err) + } + } + case strings.HasPrefix(itemTrim, "privacy-des privacy-key "): + confRead.privacyType = "privacy-des" + if confSrc.privacyPassword != "" && confSrc.privacyType == confRead.privacyType { + confRead.privacyPassword = confSrc.privacyPassword + } else { + var err error + confRead.privacyKey, err = jdecode.Decode(strings.Trim(strings.TrimPrefix( + itemTrim, "privacy-des privacy-key "), "\"")) + if err != nil { + return confRead, fmt.Errorf("failed to decode privacy-key : %w", err) + } + } + case itemTrim == privacyNoneWord: + confRead.privacyType = itemTrim + } + } + } + + return confRead, nil +} + +func delSnmpV3UsmUser(name, engineType, engineID string, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + var configSet []string + if engineType == localWord { + configSet = append(configSet, "delete snmp v3 usm local-engine user \""+name+"\"") + } else { + configSet = append(configSet, "delete snmp v3 usm remote-engine \""+engineID+"\" user \""+name+"\"") + } + + return sess.configSet(configSet, jnprSess) +} + +func fillSnmpV3UsmUserData(d *schema.ResourceData, snmpV3UsmUserOptions snmpV3UsmUserOptions) { + if tfErr := d.Set("name", snmpV3UsmUserOptions.name); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("engine_type", snmpV3UsmUserOptions.engineType); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("engine_id", snmpV3UsmUserOptions.engineID); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("authentication_key", snmpV3UsmUserOptions.authenticationKey); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("authentication_password", snmpV3UsmUserOptions.authenticationPassword); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("authentication_type", snmpV3UsmUserOptions.authenticationType); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("privacy_key", snmpV3UsmUserOptions.privacyKey); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("privacy_password", snmpV3UsmUserOptions.privacyPassword); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("privacy_type", snmpV3UsmUserOptions.privacyType); tfErr != nil { + panic(tfErr) + } +} diff --git a/junos/resource_snmp_v3_usm_user_test.go b/junos/resource_snmp_v3_usm_user_test.go new file mode 100644 index 00000000..76688056 --- /dev/null +++ b/junos/resource_snmp_v3_usm_user_test.go @@ -0,0 +1,92 @@ +package junos_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccJunosSnmpV3UsmUser_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccJunosSnmpV3UsmUserConfigCreate(), + }, + { + Config: testAccJunosSnmpV3UsmUserConfigUpdate(), + }, + { + ResourceName: "junos_snmp_v3_usm_user.testacc_snmpv3user_2", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "junos_snmp_v3_usm_user.testacc_snmpv3user_4", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccJunosSnmpV3UsmUserConfigCreate() string { + return ` +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user" { + name = "testacc_snmpv3user" +} +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user_2" { + name = "testacc_snmpv3user#2" + authentication_type = "authentication-md5" + authentication_key = "keymd5" + privacy_type = "privacy-3des" + privacy_key = "key3des" +} +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user_3" { + name = "testacc_snmpv3user#3" + engine_type = "remote" + engine_id = "engineID" + authentication_type = "authentication-sha" + authentication_password = "pass1234" + privacy_type = "privacy-aes128" + privacy_password = "pass5678" +} +` +} + +func testAccJunosSnmpV3UsmUserConfigUpdate() string { + return ` +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user" { + name = "testacc_snmpv3user" + authentication_type = "authentication-md5" + authentication_key = "md5key" + privacy_type = "privacy-none" +} +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user_2" { + name = "testacc_snmpv3user#2" + authentication_type = "authentication-md5" + authentication_key = "keymd555" + privacy_type = "privacy-3des" + privacy_key = "key3des" +} +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user_3" { + name = "testacc_snmpv3user#3" + engine_type = "remote" + engine_id = "engine#ID" + authentication_type = "authentication-sha" + authentication_password = "pass1234" + privacy_type = "privacy-des" + privacy_key = "aprivacykeydes" +} +resource "junos_snmp_v3_usm_user" "testacc_snmpv3user_4" { + name = "testacc_snmpv3user#4" + engine_type = "remote" + engine_id = "engine#ID" + authentication_type = "authentication-sha" + authentication_key = "keysha" + privacy_type = "privacy-des" + privacy_key = "aprivacykeydes" +} +` +} From 6e687a8d710ba321c4c1b247e829c87e4ae918da Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 21 Feb 2022 09:11:45 +0100 Subject: [PATCH 3/6] add junos_snmp_v3_vacm_securitytogroup resource Fixes parts of #339 --- CHANGELOG.md | 1 + .../resources/snmp_v3_vacm_securitytogroup.md | 46 +++ junos/provider.go | 1 + .../resource_snmp_v3_vacm_securitytogroup.go | 331 ++++++++++++++++++ ...ource_snmp_v3_vacm_securitytogroup_test.go | 67 ++++ 5 files changed, 446 insertions(+) create mode 100644 docs/resources/snmp_v3_vacm_securitytogroup.md create mode 100644 junos/resource_snmp_v3_vacm_securitytogroup.go create mode 100644 junos/resource_snmp_v3_vacm_securitytogroup_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0acc80aa..8927261a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ENHANCEMENTS: * resource/`junos_snmp`: add `engine_id` argument (Fixes parts of #339) * add `junos_snmp_v3_usm_user` resource (Fixes parts of #339) +* add `junos_snmp_v3_vacm_securitytogroup` resource (Fixes parts of #339) BUG FIXES: diff --git a/docs/resources/snmp_v3_vacm_securitytogroup.md b/docs/resources/snmp_v3_vacm_securitytogroup.md new file mode 100644 index 00000000..dbd11b21 --- /dev/null +++ b/docs/resources/snmp_v3_vacm_securitytogroup.md @@ -0,0 +1,46 @@ +--- +page_title: "Junos: junos_snmp_v3_vacm_securitytogroup" +--- + +# junos_snmp_v3_vacm_securitytogroup + +Provides a snmp v3 VACM security name assignment to group resource. + +## Example Usage + +```hcl +# Assigns security names to group +resource junos_snmp_v3_vacm_securitytogroup "read" { + model = "usm" + name = "read" + group = "group1" +} +``` + +## Argument Reference + +The following arguments are supported: + +- **model** (Required, String, Forces new resource) + Security model context for group assignment. + Need to be `usm`, `v1` or `v2c`. +- **name** (Required, String, Forces new resource) + Security name to assign to group. +- **group** (Required, String) + Group to which to assign security name. + +## Attributes Reference + +The following attributes are exported: + +- **id** (String) + An identifier for the resource with format `_-_`. + +## Import + +Junos snmp v3 VACM security name assignment to group can be imported using an id made up of +`_-_`, e.g. + +```shell +$ terraform import junos_snmp_v3_vacm_securitytogroup.read usm_-_read +``` diff --git a/junos/provider.go b/junos/provider.go index 7f9a4729..fbb9136a 100644 --- a/junos/provider.go +++ b/junos/provider.go @@ -189,6 +189,7 @@ func Provider() *schema.Provider { "junos_snmp_clientlist": resourceSnmpClientlist(), "junos_snmp_community": resourceSnmpCommunity(), "junos_snmp_v3_usm_user": resourceSnmpV3UsmUser(), + "junos_snmp_v3_vacm_securitytogroup": resourceSnmpV3VacmSecurityToGroup(), "junos_snmp_view": resourceSnmpView(), "junos_static_route": resourceStaticRoute(), "junos_switch_options": resourceSwitchOptions(), diff --git a/junos/resource_snmp_v3_vacm_securitytogroup.go b/junos/resource_snmp_v3_vacm_securitytogroup.go new file mode 100644 index 00000000..c90e6019 --- /dev/null +++ b/junos/resource_snmp_v3_vacm_securitytogroup.go @@ -0,0 +1,331 @@ +package junos + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/jeremmfr/go-utils/basiccheck" +) + +type snmpV3VacmSecurityToGroupOptions struct { + name string + model string + group string +} + +func resourceSnmpV3VacmSecurityToGroup() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSnmpV3VacmSecurityToGroupCreate, + ReadContext: resourceSnmpV3VacmSecurityToGroupRead, + UpdateContext: resourceSnmpV3VacmSecurityToGroupUpdate, + DeleteContext: resourceSnmpV3VacmSecurityToGroupDelete, + Importer: &schema.ResourceImporter{ + State: resourceSnmpV3VacmSecurityToGroupImport, + }, + Schema: map[string]*schema.Schema{ + "model": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"usm", "v1", "v2c"}, false), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "group": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceSnmpV3VacmSecurityToGroupCreate(ctx context.Context, + d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeCreateSetFile != "" { + if err := setSnmpV3VacmSecurityToGroup(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.SetId(d.Get("model").(string) + idSeparator + d.Get("name").(string)) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + snmpV3VacmSecurityToGroupExists, err := checkSnmpV3VacmSecurityToGroupExists( + d.Get("model").(string), d.Get("name").(string), m, jnprSess) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3VacmSecurityToGroupExists { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(fmt.Errorf( + "snmp v3 vacm security-to-group security-model %v security-name %v already exists", + d.Get("model").(string), d.Get("name").(string)))...) + } + + if err := setSnmpV3VacmSecurityToGroup(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("create resource junos_snmp_v3_vacm_securitytogroup", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + snmpV3VacmSecurityToGroupExists, err = checkSnmpV3VacmSecurityToGroupExists( + d.Get("model").(string), d.Get("name").(string), m, jnprSess) + if err != nil { + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3VacmSecurityToGroupExists { + d.SetId(d.Get("model").(string) + idSeparator + d.Get("name").(string)) + } else { + return append(diagWarns, diag.FromErr(fmt.Errorf( + "snmp v3 vacm security-to-group security-model %v security-name %v not exists after commit "+ + "=> check your config", d.Get("model").(string), d.Get("name").(string)))...) + } + + return append(diagWarns, resourceSnmpV3VacmSecurityToGroupReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3VacmSecurityToGroupRead(ctx context.Context, + d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + + return resourceSnmpV3VacmSecurityToGroupReadWJnprSess(d, m, jnprSess) +} + +func resourceSnmpV3VacmSecurityToGroupReadWJnprSess( + d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) diag.Diagnostics { + mutex.Lock() + snmpV3VacmSecurityToGroupOptions, err := readSnmpV3VacmSecurityToGroup( + d.Get("model").(string), d.Get("name").(string), m, jnprSess) + mutex.Unlock() + if err != nil { + return diag.FromErr(err) + } + if snmpV3VacmSecurityToGroupOptions.name == "" { + d.SetId("") + } else { + fillSnmpV3VacmSecurityToGroupData(d, snmpV3VacmSecurityToGroupOptions) + } + + return nil +} + +func resourceSnmpV3VacmSecurityToGroupUpdate(ctx context.Context, + d *schema.ResourceData, m interface{}) diag.Diagnostics { + d.Partial(true) + sess := m.(*Session) + if sess.junosFakeUpdateAlso { + if err := delSnmpV3VacmSecurityToGroup(d.Get("model").(string), d.Get("name").(string), m, nil); err != nil { + return diag.FromErr(err) + } + if err := setSnmpV3VacmSecurityToGroup(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.Partial(false) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3VacmSecurityToGroup(d.Get("model").(string), d.Get("name").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if err := setSnmpV3VacmSecurityToGroup(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("update resource junos_snmp_v3_vacm_securitytogroup", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + d.Partial(false) + + return append(diagWarns, resourceSnmpV3VacmSecurityToGroupReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3VacmSecurityToGroupDelete(ctx context.Context, + d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeDeleteAlso { + if err := delSnmpV3VacmSecurityToGroup(d.Get("model").(string), d.Get("name").(string), m, nil); err != nil { + return diag.FromErr(err) + } + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3VacmSecurityToGroup(d.Get("model").(string), d.Get("name").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("delete resource junos_snmp_v3_vacm_securitytogroup", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + + return diagWarns +} + +func resourceSnmpV3VacmSecurityToGroupImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return nil, err + } + defer sess.closeSession(jnprSess) + result := make([]*schema.ResourceData, 1) + idSplit := strings.Split(d.Id(), idSeparator) + if len(idSplit) != 2 { + return nil, fmt.Errorf("can't find snmp v3 vacm security-to-group "+ + "with id '%v' (id must be %s)", d.Id(), idSeparator) + } + if !basiccheck.StringInSlice(idSplit[0], []string{"usm", "v1", "v2c"}) { + return nil, fmt.Errorf("can't find snmp v3 vacm security-to-group "+ + "with id '%v' (id must be %s)", d.Id(), idSeparator) + } + snmpV3VacmSecurityToGroupExists, err := checkSnmpV3VacmSecurityToGroupExists(idSplit[0], idSplit[1], m, jnprSess) + if err != nil { + return nil, err + } + if !snmpV3VacmSecurityToGroupExists { + return nil, fmt.Errorf("don't find snmp v3 vacm security-to-group "+ + "with id '%v' (id must be %s)", d.Id(), idSeparator) + } + snmpV3VacmSecurityToGroupOptions, err := readSnmpV3VacmSecurityToGroup(idSplit[0], idSplit[1], m, jnprSess) + if err != nil { + return nil, err + } + fillSnmpV3VacmSecurityToGroupData(d, snmpV3VacmSecurityToGroupOptions) + + result[0] = d + + return result, nil +} + +func checkSnmpV3VacmSecurityToGroupExists(model, name string, m interface{}, jnprSess *NetconfObject) (bool, error) { + sess := m.(*Session) + showConfig, err := sess.command("show configuration snmp v3 vacm security-to-group "+ + "security-model "+model+" security-name \""+name+"\" | display set", jnprSess) + if err != nil { + return false, err + } + if showConfig == emptyWord { + return false, nil + } + + return true, nil +} + +func setSnmpV3VacmSecurityToGroup(d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + + configSet := make([]string, 1) + if group := d.Get("group").(string); group != "" { + configSet[0] = "set snmp v3 vacm security-to-group" + + " security-model " + d.Get("model").(string) + + " security-name \"" + d.Get("name").(string) + "\"" + + " group \"" + d.Get("group").(string) + "\"" + } else { + return fmt.Errorf("group need to be set") + } + + return sess.configSet(configSet, jnprSess) +} + +func readSnmpV3VacmSecurityToGroup(model, name string, m interface{}, jnprSess *NetconfObject, +) (snmpV3VacmSecurityToGroupOptions, error) { + sess := m.(*Session) + var confRead snmpV3VacmSecurityToGroupOptions + + showConfig, err := sess.command("show configuration snmp v3 vacm security-to-group "+ + "security-model "+model+" security-name \""+name+"\" | display set relative", jnprSess) + if err != nil { + return confRead, err + } + if showConfig != emptyWord { + confRead.model = model + confRead.name = name + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, "") { + continue + } + if strings.Contains(item, "") { + break + } + if strings.HasPrefix(item, setLineStart+"group ") { + confRead.group = strings.Trim(strings.TrimPrefix(item, setLineStart+"group "), "\"") + } + } + } + + return confRead, nil +} + +func delSnmpV3VacmSecurityToGroup(model, name string, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + configSet := []string{"delete snmp v3 vacm security-to-group " + + "security-model " + model + " security-name \"" + name + "\""} + + return sess.configSet(configSet, jnprSess) +} + +func fillSnmpV3VacmSecurityToGroupData( + d *schema.ResourceData, snmpV3VacmSecurityToGroupOptions snmpV3VacmSecurityToGroupOptions) { + if tfErr := d.Set("model", snmpV3VacmSecurityToGroupOptions.model); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("name", snmpV3VacmSecurityToGroupOptions.name); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("group", snmpV3VacmSecurityToGroupOptions.group); tfErr != nil { + panic(tfErr) + } +} diff --git a/junos/resource_snmp_v3_vacm_securitytogroup_test.go b/junos/resource_snmp_v3_vacm_securitytogroup_test.go new file mode 100644 index 00000000..e2e56295 --- /dev/null +++ b/junos/resource_snmp_v3_vacm_securitytogroup_test.go @@ -0,0 +1,67 @@ +package junos_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccJunosSnmpV3VacmSecurityToGroup_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccJunosSnmpV3VacmSecurityToGroupConfigCreate(), + }, + { + Config: testAccJunosSnmpV3VacmSecurityToGroupConfigUpdate(), + }, + { + ResourceName: "junos_snmp_v3_vacm_securitytogroup.testacc_snmpv3secutogrp", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccJunosSnmpV3VacmSecurityToGroupConfigCreate() string { + return ` +resource "junos_snmp_v3_vacm_securitytogroup" "testacc_snmpv3secutogrp" { + name = "testacc_snmpv3secutogrp" + model = "usm" + group = "testacc_snmpv3secutogrp" +} +resource "junos_snmp_v3_vacm_securitytogroup" "testacc_snmpv3secutogrp2" { + name = "testacc_snmpv3secutogrp" + model = "v1" + group = "testacc_snmpv3secutogrp" +} +resource "junos_snmp_v3_vacm_securitytogroup" "testacc_snmpv3secutogrp3" { + name = "testacc_snmpv3secutogrp" + model = "v2c" + group = "testacc_snmpv3secutogrp" +} +` +} + +func testAccJunosSnmpV3VacmSecurityToGroupConfigUpdate() string { + return ` +resource "junos_snmp_v3_vacm_securitytogroup" "testacc_snmpv3secutogrp" { + name = "testacc_snmpv3secutogrp" + model = "usm" + group = "testacc_snmpv3secutogrp2" +} +resource "junos_snmp_v3_vacm_securitytogroup" "testacc_snmpv3secutogrp2" { + name = "testacc_snmpv3secutogrp" + model = "v1" + group = "testacc_snmpv3secutogrp2" +} +resource "junos_snmp_v3_vacm_securitytogroup" "testacc_snmpv3secutogrp3" { + name = "testacc_snmpv3secutogrp" + model = "v2c" + group = "testacc_snmpv3secutogrp2" +} +` +} From 7f37a712eb3cd48306451a07ab20ac98b7ca4ef5 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 21 Feb 2022 09:40:02 +0100 Subject: [PATCH 4/6] add new common function copyAndRemoveItemMapList2 to copy and remove item in list of map with two identifiers --- junos/func_common.go | 22 ++++++++++++++++++++++ junos/resource_eventoptions_policy.go | 12 ++---------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/junos/func_common.go b/junos/func_common.go index 9f86063e..9fea2f89 100644 --- a/junos/func_common.go +++ b/junos/func_common.go @@ -277,6 +277,28 @@ func copyAndRemoveItemMapList(identifier string, return list } +func copyAndRemoveItemMapList2(identifier, identifier2 string, + m map[string]interface{}, list []map[string]interface{}) []map[string]interface{} { + if m[identifier] == nil { + panic(fmt.Errorf("internal error: can't find identifier %s in map", identifier)) + } + if m[identifier2] == nil { + panic(fmt.Errorf("internal error: can't find identifier %s in map", identifier2)) + } + for i, element := range list { + if element[identifier] == m[identifier] && element[identifier2] == m[identifier2] { + for key, value := range element { + m[key] = value + } + list = append(list[:i], list[i+1:]...) + + break + } + } + + return list +} + func checkCompatibilitySecurity(jnprSess *NetconfObject) bool { if strings.HasPrefix(strings.ToLower(jnprSess.SystemInformation.HardwareModel), "srx") { return true diff --git a/junos/resource_eventoptions_policy.go b/junos/resource_eventoptions_policy.go index 5acb5031..8482d6ed 100644 --- a/junos/resource_eventoptions_policy.go +++ b/junos/resource_eventoptions_policy.go @@ -1145,17 +1145,9 @@ func readEventoptionsPolicyThen(then map[string]interface{}, itemTrim string) er "transfer_delay": -1, "user_name": "", } - for i, element := range then["upload"].([]map[string]interface{}) { - if element["filename"] == upload["filename"] && element["destination"] == upload["destination"] { - for key, value := range element { - upload[key] = value - } - then["upload"] = append(then["upload"].([]map[string]interface{})[:i], - then["upload"].([]map[string]interface{})[i+1:]...) - break - } - } + then["upload"] = copyAndRemoveItemMapList2( + "filename", "destination", upload, then["upload"].([]map[string]interface{})) itemTrimUpload := strings.TrimPrefix( itemTrim, "then upload filename "+itemTrimSplit[3]+" destination "+itemTrimSplit[5]+" ") switch { From e402dc175597122f37c8498538c4a504fa4fce1b Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 21 Feb 2022 09:42:31 +0100 Subject: [PATCH 5/6] add junos_snmp_v3_vacm_accessgroup resource Fixes parts of #339 --- CHANGELOG.md | 1 + docs/resources/snmp_v3_vacm_accessgroup.md | 75 +++ junos/provider.go | 1 + junos/resource_snmp_v3_vacm_accessgroup.go | 510 ++++++++++++++++++ .../resource_snmp_v3_vacm_accessgroup_test.go | 115 ++++ 5 files changed, 702 insertions(+) create mode 100644 docs/resources/snmp_v3_vacm_accessgroup.md create mode 100644 junos/resource_snmp_v3_vacm_accessgroup.go create mode 100644 junos/resource_snmp_v3_vacm_accessgroup_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8927261a..ba50a7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ENHANCEMENTS: * resource/`junos_snmp`: add `engine_id` argument (Fixes parts of #339) * add `junos_snmp_v3_usm_user` resource (Fixes parts of #339) +* add `junos_snmp_v3_vacm_accessgroup` resource (Fixes parts of #339) * add `junos_snmp_v3_vacm_securitytogroup` resource (Fixes parts of #339) BUG FIXES: diff --git a/docs/resources/snmp_v3_vacm_accessgroup.md b/docs/resources/snmp_v3_vacm_accessgroup.md new file mode 100644 index 00000000..7d639127 --- /dev/null +++ b/docs/resources/snmp_v3_vacm_accessgroup.md @@ -0,0 +1,75 @@ +--- +page_title: "Junos: junos_snmp_v3_vacm_accessgroup" +--- + +# junos_snmp_v3_vacm_accessgroup + +Provides a snmp v3 VACM access group resource. + +## Example Usage + +```hcl +# Add a snmpv3 VACM access group +resource junos_snmp_v3_vacm_accessgroup "group1" { + name = "group1" + default_context_prefix { + model = "any" + level = "none" + read_view = "all" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +-> **Note:** At least one of `context_prefix` or `default_context_prefix` need to be set + +- **name** (Required, String, Forces new resource) + SNMPv3 VACM group name. +- **context_prefix** (Optional, Block List) + For each prefix of context-prefix access configuration + - **prefix** (Required, String) + SNMPv3 VACM context prefix. + - **access_config** (Optional, Block Set) + For each combination of `model` and `level`, define context-prefix access configuration. + See [below for nested schema](#access_config-or-default_context_prefix-arguments). +- **default_context_prefix** (Optional, Block Set) + For each combination of `model` and `level`, define default context-prefix access configuration. + See [below for nested schema](#access_config-or-default_context_prefix-arguments). + +--- + +### access_config or default_context_prefix arguments + +- **model** (Required, String) + Security model access configuration. + Need to be `any`, `usm`, `v1` or `v2c`. +- **level** (Required, String) + Security level access configuration. + Need to be `authentication`, `none` or `privacy`. +- **context_match** (Optional, String) + Type of match to perform on context-prefix. + Need to be `exact` or `prefix`. +- **notify_view** (Optional, String) + View used to notifications. +- **read_view** (Optional, String) + View used for read access. +- **write_view** (Optional, String) + View used for write access. + +## Attributes Reference + +The following attributes are exported: + +- **id** (String) + An identifier for the resource with format ``. + +## Import + +Junos snmp v3 VACM access group can be imported using an id made up of ``, e.g. + +```shell +$ terraform import junos_snmp_v3_vacm_accessgroup.group1 group1 +``` diff --git a/junos/provider.go b/junos/provider.go index fbb9136a..33b14750 100644 --- a/junos/provider.go +++ b/junos/provider.go @@ -189,6 +189,7 @@ func Provider() *schema.Provider { "junos_snmp_clientlist": resourceSnmpClientlist(), "junos_snmp_community": resourceSnmpCommunity(), "junos_snmp_v3_usm_user": resourceSnmpV3UsmUser(), + "junos_snmp_v3_vacm_accessgroup": resourceSnmpV3VacmAccessGroup(), "junos_snmp_v3_vacm_securitytogroup": resourceSnmpV3VacmSecurityToGroup(), "junos_snmp_view": resourceSnmpView(), "junos_static_route": resourceStaticRoute(), diff --git a/junos/resource_snmp_v3_vacm_accessgroup.go b/junos/resource_snmp_v3_vacm_accessgroup.go new file mode 100644 index 00000000..29f425f6 --- /dev/null +++ b/junos/resource_snmp_v3_vacm_accessgroup.go @@ -0,0 +1,510 @@ +package junos + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + bchk "github.com/jeremmfr/go-utils/basiccheck" +) + +type snmpV3VacmAccessGroupOptions struct { + name string + defaultContextPrefix []map[string]interface{} + contextPrefix []map[string]interface{} +} + +func resourceSnmpV3VacmAccessGroup() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSnmpV3VacmAccessGroupCreate, + ReadContext: resourceSnmpV3VacmAccessGroupRead, + UpdateContext: resourceSnmpV3VacmAccessGroupUpdate, + DeleteContext: resourceSnmpV3VacmAccessGroupDelete, + Importer: &schema.ResourceImporter{ + State: resourceSnmpV3VacmAccessGroupImport, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "context_prefix": { + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: []string{"context_prefix", "default_context_prefix"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Required: true, + }, + "access_config": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "model": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"any", "usm", "v1", "v2c"}, false), + }, + "level": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"authentication", "none", "privacy"}, false), + }, + "context_match": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"exact", "prefix"}, false), + }, + "notify_view": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "read_view": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "write_view": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + }, + }, + }, + }, + }, + }, + "default_context_prefix": { + Type: schema.TypeSet, + Optional: true, + AtLeastOneOf: []string{"context_prefix", "default_context_prefix"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "model": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"any", "usm", "v1", "v2c"}, false), + }, + "level": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"authentication", "none", "privacy"}, false), + }, + "context_match": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"exact", "prefix"}, false), + }, + "notify_view": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "read_view": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "write_view": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + }, + }, + }, + }, + } +} + +func resourceSnmpV3VacmAccessGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeCreateSetFile != "" { + if err := setSnmpV3VacmAccessGroup(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.SetId(d.Get("name").(string)) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + snmpV3VacmAccessGroupExists, err := checkSnmpV3VacmAccessGroupExists(d.Get("name").(string), m, jnprSess) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3VacmAccessGroupExists { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(fmt.Errorf( + "snmp v3 vacm access group %v already exists", d.Get("name").(string)))...) + } + + if err := setSnmpV3VacmAccessGroup(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("create resource junos_snmp_v3_vacm_accessgroup", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + snmpV3VacmAccessGroupExists, err = checkSnmpV3VacmAccessGroupExists(d.Get("name").(string), m, jnprSess) + if err != nil { + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3VacmAccessGroupExists { + d.SetId(d.Get("name").(string)) + } else { + return append(diagWarns, diag.FromErr(fmt.Errorf("snmp v3 vacm access group %v not exists after commit "+ + "=> check your config", d.Get("name").(string)))...) + } + + return append(diagWarns, resourceSnmpV3VacmAccessGroupReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3VacmAccessGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + + return resourceSnmpV3VacmAccessGroupReadWJnprSess(d, m, jnprSess) +} + +func resourceSnmpV3VacmAccessGroupReadWJnprSess( + d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) diag.Diagnostics { + mutex.Lock() + snmpV3VacmAccessGroupOptions, err := readSnmpV3VacmAccessGroup(d.Get("name").(string), m, jnprSess) + mutex.Unlock() + if err != nil { + return diag.FromErr(err) + } + if snmpV3VacmAccessGroupOptions.name == "" { + d.SetId("") + } else { + fillSnmpV3VacmAccessGroupData(d, snmpV3VacmAccessGroupOptions) + } + + return nil +} + +func resourceSnmpV3VacmAccessGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + d.Partial(true) + sess := m.(*Session) + if sess.junosFakeUpdateAlso { + if err := delSnmpV3VacmAccessGroup(d.Get("name").(string), m, nil); err != nil { + return diag.FromErr(err) + } + if err := setSnmpV3VacmAccessGroup(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.Partial(false) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3VacmAccessGroup(d.Get("name").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if err := setSnmpV3VacmAccessGroup(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("update resource junos_snmp_v3_vacm_accessgroup", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + d.Partial(false) + + return append(diagWarns, resourceSnmpV3VacmAccessGroupReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3VacmAccessGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeDeleteAlso { + if err := delSnmpV3VacmAccessGroup(d.Get("name").(string), m, nil); err != nil { + return diag.FromErr(err) + } + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3VacmAccessGroup(d.Get("name").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("delete resource junos_snmp_v3_vacm_accessgroup", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + + return diagWarns +} + +func resourceSnmpV3VacmAccessGroupImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return nil, err + } + defer sess.closeSession(jnprSess) + result := make([]*schema.ResourceData, 1) + + snmpV3VacmAccessGroupExists, err := checkSnmpV3VacmAccessGroupExists(d.Id(), m, jnprSess) + if err != nil { + return nil, err + } + if !snmpV3VacmAccessGroupExists { + return nil, fmt.Errorf("don't find snmp v3 vacm access group with id '%v' (id must be )", d.Id()) + } + snmpV3VacmAccessGroupOptions, err := readSnmpV3VacmAccessGroup(d.Id(), m, jnprSess) + if err != nil { + return nil, err + } + fillSnmpV3VacmAccessGroupData(d, snmpV3VacmAccessGroupOptions) + + result[0] = d + + return result, nil +} + +func checkSnmpV3VacmAccessGroupExists(name string, m interface{}, jnprSess *NetconfObject) (bool, error) { + sess := m.(*Session) + showConfig, err := sess.command("show configuration snmp v3 vacm access group \""+name+"\" | display set", jnprSess) + if err != nil { + return false, err + } + if showConfig == emptyWord { + return false, nil + } + + return true, nil +} + +func setSnmpV3VacmAccessGroup(d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + + setPrefix := "set snmp v3 vacm access group \"" + d.Get("name").(string) + "\" " + configSet := make([]string, 0) + + defaultContextPrefixList := make([]string, 0) + for _, mDefCtxPref := range d.Get("default_context_prefix").(*schema.Set).List() { + defaultContextPrefix := mDefCtxPref.(map[string]interface{}) + if bchk.StringInSlice(defaultContextPrefix["model"].(string)+idSeparator+defaultContextPrefix["level"].(string), + defaultContextPrefixList) { + return fmt.Errorf("multiple blocks default_context_prefix with the same model '%s' and level '%s'", + defaultContextPrefix["model"].(string), defaultContextPrefix["level"].(string)) + } + defaultContextPrefixList = append(defaultContextPrefixList, + defaultContextPrefix["model"].(string)+idSeparator+defaultContextPrefix["level"].(string)) + setPrefixDefCtxPref := setPrefix + " default-context-prefix security-model " + + defaultContextPrefix["model"].(string) + " security-level " + defaultContextPrefix["level"].(string) + " " + if v := defaultContextPrefix["context_match"].(string); v != "" { + configSet = append(configSet, setPrefixDefCtxPref+"context-match "+v) + } + if v := defaultContextPrefix["notify_view"].(string); v != "" { + configSet = append(configSet, setPrefixDefCtxPref+"notify-view \""+v+"\"") + } + if v := defaultContextPrefix["read_view"].(string); v != "" { + configSet = append(configSet, setPrefixDefCtxPref+"read-view \""+v+"\"") + } + if v := defaultContextPrefix["write_view"].(string); v != "" { + configSet = append(configSet, setPrefixDefCtxPref+"write-view \""+v+"\"") + } + if len(configSet) == 0 || !strings.HasPrefix(configSet[len(configSet)-1], setPrefixDefCtxPref) { + return fmt.Errorf("missing argument to default_context_prefix with model %s and level %s", + defaultContextPrefix["model"].(string), defaultContextPrefix["level"].(string)) + } + } + contextPrefixList := make([]string, 0) + for _, mCtxPref := range d.Get("context_prefix").([]interface{}) { + contextPrefix := mCtxPref.(map[string]interface{}) + if bchk.StringInSlice(contextPrefix["prefix"].(string), contextPrefixList) { + return fmt.Errorf("multiple blocks context_prefix with the same prefix '%s'", contextPrefix["prefix"].(string)) + } + contextPrefixList = append(contextPrefixList, contextPrefix["prefix"].(string)) + accessConfigList := make([]string, 0) + for _, mAccConf := range contextPrefix["access_config"].(*schema.Set).List() { + accessConfig := mAccConf.(map[string]interface{}) + if bchk.StringInSlice(accessConfig["model"].(string)+idSeparator+accessConfig["level"].(string), accessConfigList) { + return fmt.Errorf( + "multiple blocks access_config with the same model '%s' and level '%s' in context_prefix with prefix '%s'", + accessConfig["model"].(string), accessConfig["level"].(string), contextPrefix["prefix"].(string)) + } + accessConfigList = append(accessConfigList, + accessConfig["model"].(string)+idSeparator+accessConfig["level"].(string)) + setPrefixCtxPref := setPrefix + " context-prefix \"" + contextPrefix["prefix"].(string) + + "\" security-model " + accessConfig["model"].(string) + " security-level " + accessConfig["level"].(string) + " " + if v := accessConfig["context_match"].(string); v != "" { + configSet = append(configSet, setPrefixCtxPref+"context-match "+v) + } + if v := accessConfig["notify_view"].(string); v != "" { + configSet = append(configSet, setPrefixCtxPref+"notify-view \""+v+"\"") + } + if v := accessConfig["read_view"].(string); v != "" { + configSet = append(configSet, setPrefixCtxPref+"read-view \""+v+"\"") + } + if v := accessConfig["write_view"].(string); v != "" { + configSet = append(configSet, setPrefixCtxPref+"write-view \""+v+"\"") + } + if len(configSet) == 0 || !strings.HasPrefix(configSet[len(configSet)-1], setPrefixCtxPref) { + return fmt.Errorf("missing argument to access_config with model %s and level %s in context_prefix with prefix %s", + accessConfig["model"].(string), accessConfig["level"].(string), contextPrefix["prefix"].(string)) + } + } + } + + return sess.configSet(configSet, jnprSess) +} + +func readSnmpV3VacmAccessGroup(name string, m interface{}, jnprSess *NetconfObject, +) (snmpV3VacmAccessGroupOptions, error) { + sess := m.(*Session) + var confRead snmpV3VacmAccessGroupOptions + + showConfig, err := sess.command( + "show configuration snmp v3 vacm access group \""+name+"\" | display set relative", jnprSess) + if err != nil { + return confRead, err + } + if showConfig != emptyWord { + confRead.name = name + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, "") { + continue + } + if strings.Contains(item, "") { + break + } + itemTrim := strings.TrimPrefix(item, setLineStart) + switch { + case strings.HasPrefix(itemTrim, "default-context-prefix security-model ") && + strings.Contains(itemTrim, " security-level "): + itemTrimSplit := strings.Split(strings.TrimPrefix(itemTrim, "default-context-prefix security-model "), " ") + defaultContextPrefix := map[string]interface{}{ + "model": itemTrimSplit[0], + "level": itemTrimSplit[2], + "context_match": "", + "notify_view": "", + "read_view": "", + "write_view": "", + } + confRead.defaultContextPrefix = copyAndRemoveItemMapList2("model", "level", defaultContextPrefix, + confRead.defaultContextPrefix) + itemTrimCtxPref := strings.TrimPrefix(itemTrim, + "default-context-prefix security-model "+itemTrimSplit[0]+" security-level "+itemTrimSplit[2]+" ") + readSnmpV3VacmAccessGroupContextPrefixConfig(itemTrimCtxPref, defaultContextPrefix) + confRead.defaultContextPrefix = append(confRead.defaultContextPrefix, defaultContextPrefix) + case strings.HasPrefix(itemTrim, "context-prefix "): + itemTrimSplit := strings.Split(strings.TrimPrefix(itemTrim, "context-prefix "), " ") + contextPrefix := map[string]interface{}{ + "prefix": strings.Trim(itemTrimSplit[0], "\""), + "access_config": make([]map[string]interface{}, 0), + } + confRead.contextPrefix = copyAndRemoveItemMapList("prefix", contextPrefix, confRead.contextPrefix) + itemTrimCtxPref := strings.TrimPrefix(itemTrim, "context-prefix "+itemTrimSplit[0]+" ") + if strings.HasPrefix(itemTrimCtxPref, "security-model ") && + strings.Contains(itemTrimCtxPref, " security-level ") { + itemTrimCtxPrefSplit := strings.Split(strings.TrimPrefix(itemTrimCtxPref, "security-model "), " ") + contextPrefixAccessConfig := map[string]interface{}{ + "model": itemTrimCtxPrefSplit[0], + "level": itemTrimCtxPrefSplit[2], + "context_match": "", + "notify_view": "", + "read_view": "", + "write_view": "", + } + contextPrefix["access_config"] = copyAndRemoveItemMapList2("model", "level", contextPrefixAccessConfig, + contextPrefix["access_config"].([]map[string]interface{})) + itemTrimCtxPrefConfig := strings.TrimPrefix(itemTrimCtxPref, + "security-model "+itemTrimCtxPrefSplit[0]+" security-level "+itemTrimCtxPrefSplit[2]+" ") + readSnmpV3VacmAccessGroupContextPrefixConfig(itemTrimCtxPrefConfig, contextPrefixAccessConfig) + contextPrefix["access_config"] = append( + contextPrefix["access_config"].([]map[string]interface{}), contextPrefixAccessConfig) + } + confRead.contextPrefix = append(confRead.contextPrefix, contextPrefix) + } + } + } + + return confRead, nil +} + +func readSnmpV3VacmAccessGroupContextPrefixConfig(itemTrim string, config map[string]interface{}) { + switch { + case strings.HasPrefix(itemTrim, "context-match "): + config["context_match"] = strings.TrimPrefix(itemTrim, "context-match ") + case strings.HasPrefix(itemTrim, "notify-view "): + config["notify_view"] = strings.Trim(strings.TrimPrefix(itemTrim, "notify-view "), "\"") + case strings.HasPrefix(itemTrim, "read-view "): + config["read_view"] = strings.Trim(strings.TrimPrefix(itemTrim, "read-view "), "\"") + case strings.HasPrefix(itemTrim, "write-view "): + config["write_view"] = strings.Trim(strings.TrimPrefix(itemTrim, "write-view "), "\"") + } +} + +func delSnmpV3VacmAccessGroup(name string, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + + configSet := []string{"delete snmp v3 vacm access group \"" + name + "\""} + + return sess.configSet(configSet, jnprSess) +} + +func fillSnmpV3VacmAccessGroupData(d *schema.ResourceData, snmpV3VacmAccessGroupOptions snmpV3VacmAccessGroupOptions) { + if tfErr := d.Set("name", snmpV3VacmAccessGroupOptions.name); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("default_context_prefix", snmpV3VacmAccessGroupOptions.defaultContextPrefix); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("context_prefix", snmpV3VacmAccessGroupOptions.contextPrefix); tfErr != nil { + panic(tfErr) + } +} diff --git a/junos/resource_snmp_v3_vacm_accessgroup_test.go b/junos/resource_snmp_v3_vacm_accessgroup_test.go new file mode 100644 index 00000000..222eefde --- /dev/null +++ b/junos/resource_snmp_v3_vacm_accessgroup_test.go @@ -0,0 +1,115 @@ +package junos_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccJunosSnmpV3VacmAccessGroup_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccJunosSnmpV3VacmAccessGroupConfigCreate(), + }, + { + Config: testAccJunosSnmpV3VacmAccessGroupConfigUpdate(), + }, + { + ResourceName: "junos_snmp_v3_vacm_accessgroup.testacc_group", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccJunosSnmpV3VacmAccessGroupConfigCreate() string { + return ` +resource "junos_snmp_v3_vacm_accessgroup" "testacc_group" { + name = "testacc_group#1" + default_context_prefix { + model = "any" + level = "none" + notify_view = "all" + } + context_prefix { + prefix = "ctx#22" + access_config { + model = "any" + level = "authentication" + read_view = "all" + } + } +} +resource "junos_snmp_v3_vacm_accessgroup" "testacc_group2" { + name = "testacc_group#2" + context_prefix { + prefix = "ctx" + access_config { + model = "any" + level = "none" + notify_view = "all" + } + } +} +` +} + +func testAccJunosSnmpV3VacmAccessGroupConfigUpdate() string { + return ` +resource "junos_snmp_v3_vacm_accessgroup" "testacc_group" { + name = "testacc_group#1" + default_context_prefix { + model = "any" + level = "authentication" + read_view = "all" + } + default_context_prefix { + model = "any" + level = "none" + notify_view = "all" + } + default_context_prefix { + model = "usm" + level = "privacy" + context_match = "exact" + notify_view = "all" + read_view = "all" + write_view = "all" + } + context_prefix { + prefix = "ctx#22" + access_config { + model = "any" + level = "authentication" + read_view = "all" + } + access_config { + model = "any" + level = "none" + notify_view = "all" + } + access_config { + model = "usm" + level = "privacy" + context_match = "exact" + notify_view = "all" + read_view = "all" + write_view = "al1" + } + } + context_prefix { + prefix = "ctx#21" + access_config { + context_match = "prefix" + model = "any" + level = "none" + notify_view = "all" + } + } +} +` +} From 29b187f8bcbb08cb08856c621ccf6f8b785ac074 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 21 Feb 2022 18:04:04 +0100 Subject: [PATCH 6/6] add junos_snmp_v3_community resource --- CHANGELOG.md | 1 + docs/resources/snmp_v3_community.md | 47 ++++ junos/provider.go | 1 + junos/resource_snmp_v3_community.go | 340 +++++++++++++++++++++++ junos/resource_snmp_v3_community_test.go | 48 ++++ 5 files changed, 437 insertions(+) create mode 100644 docs/resources/snmp_v3_community.md create mode 100644 junos/resource_snmp_v3_community.go create mode 100644 junos/resource_snmp_v3_community_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ba50a7e2..6becbc0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ENHANCEMENTS: * resource/`junos_snmp`: add `engine_id` argument (Fixes parts of #339) +* add `junos_snmp_v3_community` resource * add `junos_snmp_v3_usm_user` resource (Fixes parts of #339) * add `junos_snmp_v3_vacm_accessgroup` resource (Fixes parts of #339) * add `junos_snmp_v3_vacm_securitytogroup` resource (Fixes parts of #339) diff --git a/docs/resources/snmp_v3_community.md b/docs/resources/snmp_v3_community.md new file mode 100644 index 00000000..4c38ff7e --- /dev/null +++ b/docs/resources/snmp_v3_community.md @@ -0,0 +1,47 @@ +--- +page_title: "Junos: junos_snmp_v3_community" +--- + +# junos_snmp_v3_community + +Provides a snmp v3 community resource. + +## Example Usage + +```hcl +# Add a snmp v3 community +resource junos_snmp_v3_community "index1" { + community_index = "index1" + security_name = "john" +} +``` + +## Argument Reference + +The following arguments are supported: + +- **community_index** (Required, String, Forces new resource) + Unique index value in this community table entry. +- **security_name** (Required, String) + Security name used when performing access control. +- **community_name** (Optional, String) + SNMPv1/v2c community name (default is same as community-index). +- **context** (Optional, String) + Context used when performing access control. +- **tag** (Optional, String) + Tag identifier for set of targets allowed to use this community string. + +## Attributes Reference + +The following attributes are exported: + +- **id** (String) + An identifier for the resource with format ``. + +## Import + +Junos snmp v3 community can be imported using an id made up of ``, e.g. + +```shell +$ terraform import junos_snmp_v3_community.index1 index1 +``` diff --git a/junos/provider.go b/junos/provider.go index 33b14750..00bbb283 100644 --- a/junos/provider.go +++ b/junos/provider.go @@ -188,6 +188,7 @@ func Provider() *schema.Provider { "junos_snmp": resourceSnmp(), "junos_snmp_clientlist": resourceSnmpClientlist(), "junos_snmp_community": resourceSnmpCommunity(), + "junos_snmp_v3_community": resourceSnmpV3Community(), "junos_snmp_v3_usm_user": resourceSnmpV3UsmUser(), "junos_snmp_v3_vacm_accessgroup": resourceSnmpV3VacmAccessGroup(), "junos_snmp_v3_vacm_securitytogroup": resourceSnmpV3VacmSecurityToGroup(), diff --git a/junos/resource_snmp_v3_community.go b/junos/resource_snmp_v3_community.go new file mode 100644 index 00000000..0a45f307 --- /dev/null +++ b/junos/resource_snmp_v3_community.go @@ -0,0 +1,340 @@ +package junos + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + jdecode "github.com/jeremmfr/junosdecode" +) + +type snmpV3CommunityOptions struct { + communityIndex string + communityName string + securityName string + context string + tag string +} + +func resourceSnmpV3Community() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSnmpV3CommunityCreate, + ReadContext: resourceSnmpV3CommunityRead, + UpdateContext: resourceSnmpV3CommunityUpdate, + DeleteContext: resourceSnmpV3CommunityDelete, + Importer: &schema.ResourceImporter{ + State: resourceSnmpV3CommunityImport, + }, + Schema: map[string]*schema.Schema{ + "community_index": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "security_name": { + Type: schema.TypeString, + Required: true, + }, + "community_name": { + Type: schema.TypeString, + Optional: true, + }, + "context": { + Type: schema.TypeString, + Optional: true, + }, + "tag": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceSnmpV3CommunityCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeCreateSetFile != "" { + if err := setSnmpV3Community(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.SetId(d.Get("community_index").(string)) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + snmpV3CommunityExists, err := checkSnmpV3CommunityExists(d.Get("community_index").(string), m, jnprSess) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3CommunityExists { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(fmt.Errorf( + "snmp v3 snmp-community %v already exists", d.Get("community_index").(string)))...) + } + + if err := setSnmpV3Community(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("create resource junos_snmp_v3_community", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + snmpV3CommunityExists, err = checkSnmpV3CommunityExists(d.Get("community_index").(string), m, jnprSess) + if err != nil { + return append(diagWarns, diag.FromErr(err)...) + } + if snmpV3CommunityExists { + d.SetId(d.Get("community_index").(string)) + } else { + return append(diagWarns, diag.FromErr(fmt.Errorf("snmp v3 snmp-community %v not exists after commit "+ + "=> check your config", d.Get("community_index").(string)))...) + } + + return append(diagWarns, resourceSnmpV3CommunityReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3CommunityRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + + return resourceSnmpV3CommunityReadWJnprSess(d, m, jnprSess) +} + +func resourceSnmpV3CommunityReadWJnprSess( + d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) diag.Diagnostics { + mutex.Lock() + snmpV3CommunityOptions, err := readSnmpV3Community(d.Get("community_index").(string), m, jnprSess) + mutex.Unlock() + if err != nil { + return diag.FromErr(err) + } + if snmpV3CommunityOptions.communityIndex == "" { + d.SetId("") + } else { + fillSnmpV3CommunityData(d, snmpV3CommunityOptions) + } + + return nil +} + +func resourceSnmpV3CommunityUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + d.Partial(true) + sess := m.(*Session) + if sess.junosFakeUpdateAlso { + if err := delSnmpV3Community(d.Get("community_index").(string), m, nil); err != nil { + return diag.FromErr(err) + } + if err := setSnmpV3Community(d, m, nil); err != nil { + return diag.FromErr(err) + } + d.Partial(false) + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3Community(d.Get("community_index").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + if err := setSnmpV3Community(d, m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("update resource junos_snmp_v3_community", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + d.Partial(false) + + return append(diagWarns, resourceSnmpV3CommunityReadWJnprSess(d, m, jnprSess)...) +} + +func resourceSnmpV3CommunityDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sess := m.(*Session) + if sess.junosFakeDeleteAlso { + if err := delSnmpV3Community(d.Get("community_index").(string), m, nil); err != nil { + return diag.FromErr(err) + } + + return nil + } + jnprSess, err := sess.startNewSession() + if err != nil { + return diag.FromErr(err) + } + defer sess.closeSession(jnprSess) + sess.configLock(jnprSess) + var diagWarns diag.Diagnostics + if err := delSnmpV3Community(d.Get("community_index").(string), m, jnprSess); err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + warns, err := sess.commitConf("delete resource junos_snmp_v3_community", jnprSess) + appendDiagWarns(&diagWarns, warns) + if err != nil { + appendDiagWarns(&diagWarns, sess.configClear(jnprSess)) + + return append(diagWarns, diag.FromErr(err)...) + } + + return diagWarns +} + +func resourceSnmpV3CommunityImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + sess := m.(*Session) + jnprSess, err := sess.startNewSession() + if err != nil { + return nil, err + } + defer sess.closeSession(jnprSess) + result := make([]*schema.ResourceData, 1) + + snmpV3CommunityExists, err := checkSnmpV3CommunityExists(d.Id(), m, jnprSess) + if err != nil { + return nil, err + } + if !snmpV3CommunityExists { + return nil, fmt.Errorf("don't find snmp v3 snmp-community with id '%v' (id must be )", d.Id()) + } + snmpV3CommunityOptions, err := readSnmpV3Community(d.Id(), m, jnprSess) + if err != nil { + return nil, err + } + fillSnmpV3CommunityData(d, snmpV3CommunityOptions) + + result[0] = d + + return result, nil +} + +func checkSnmpV3CommunityExists(communityIndex string, m interface{}, jnprSess *NetconfObject) (bool, error) { + sess := m.(*Session) + showConfig, err := sess.command( + "show configuration snmp v3 snmp-community \""+communityIndex+"\" | display set", jnprSess) + if err != nil { + return false, err + } + if showConfig == emptyWord { + return false, nil + } + + return true, nil +} + +func setSnmpV3Community(d *schema.ResourceData, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + + setPrefix := "set snmp v3 snmp-community \"" + d.Get("community_index").(string) + "\" " + configSet := make([]string, 0) + + configSet = append(configSet, setPrefix+"security-name \""+d.Get("security_name").(string)+"\"") + if v := d.Get("community_name").(string); v != "" { + configSet = append(configSet, setPrefix+"community-name \""+v+"\"") + } + if v := d.Get("context").(string); v != "" { + configSet = append(configSet, setPrefix+"context \""+v+"\"") + } + if v := d.Get("tag").(string); v != "" { + configSet = append(configSet, setPrefix+"tag \""+v+"\"") + } + + return sess.configSet(configSet, jnprSess) +} + +func readSnmpV3Community(communityIndex string, m interface{}, jnprSess *NetconfObject, +) (snmpV3CommunityOptions, error) { + sess := m.(*Session) + var confRead snmpV3CommunityOptions + + showConfig, err := sess.command( + "show configuration snmp v3 snmp-community \""+communityIndex+"\" | display set relative", jnprSess) + if err != nil { + return confRead, err + } + if showConfig != emptyWord { + confRead.communityIndex = communityIndex + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, "") { + continue + } + if strings.Contains(item, "") { + break + } + itemTrim := strings.TrimPrefix(item, setLineStart) + switch { + case strings.HasPrefix(itemTrim, "security-name "): + confRead.securityName = strings.Trim(strings.TrimPrefix(itemTrim, "security-name "), "\"") + case strings.HasPrefix(itemTrim, "community-name "): + var err error + confRead.communityName, err = jdecode.Decode(strings.Trim(strings.TrimPrefix(itemTrim, "community-name "), "\"")) + if err != nil { + return confRead, fmt.Errorf("failed to decode community-name : %w", err) + } + case strings.HasPrefix(itemTrim, "context "): + confRead.context = strings.Trim(strings.TrimPrefix(itemTrim, "context "), "\"") + case strings.HasPrefix(itemTrim, "tag "): + confRead.tag = strings.Trim(strings.TrimPrefix(itemTrim, "tag "), "\"") + } + } + } + + return confRead, nil +} + +func delSnmpV3Community(communityIndex string, m interface{}, jnprSess *NetconfObject) error { + sess := m.(*Session) + + configSet := []string{"delete snmp v3 snmp-community \"" + communityIndex + "\""} + + return sess.configSet(configSet, jnprSess) +} + +func fillSnmpV3CommunityData(d *schema.ResourceData, snmpV3CommunityOptions snmpV3CommunityOptions) { + if tfErr := d.Set("community_index", snmpV3CommunityOptions.communityIndex); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("security_name", snmpV3CommunityOptions.securityName); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("community_name", snmpV3CommunityOptions.communityName); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("context", snmpV3CommunityOptions.context); tfErr != nil { + panic(tfErr) + } + if tfErr := d.Set("tag", snmpV3CommunityOptions.tag); tfErr != nil { + panic(tfErr) + } +} diff --git a/junos/resource_snmp_v3_community_test.go b/junos/resource_snmp_v3_community_test.go new file mode 100644 index 00000000..a5cbdc29 --- /dev/null +++ b/junos/resource_snmp_v3_community_test.go @@ -0,0 +1,48 @@ +package junos_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccJunosSnmpV3Communitry_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccJunosSnmpV3CommunitryConfigCreate(), + }, + { + Config: testAccJunosSnmpV3CommunitryConfigUpdate(), + }, + { + ResourceName: "junos_snmp_v3_community.testacc_snmpv3comm", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccJunosSnmpV3CommunitryConfigCreate() string { + return ` +resource "junos_snmp_v3_community" "testacc_snmpv3comm" { + community_index = "testacc_snmpv3comm#1" + security_name = "testacc_snmpv3comm#1_security" +} +` +} + +func testAccJunosSnmpV3CommunitryConfigUpdate() string { + return ` +resource "junos_snmp_v3_community" "testacc_snmpv3comm" { + community_index = "testacc_snmpv3comm#1" + security_name = "testacc_snmpv3comm#1_security2" + community_name = "testacc_snmpcomm#1" + context = "testacc_snmpv3comm#1_ctx" + tag = "testacc_snmpv3comm#1_tag" +} +` +}