From 10745b7d557268d9586b0ec30cc7d180d5b7948a Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Thu, 23 Jan 2025 18:56:40 -0500 Subject: [PATCH 1/6] WIP: configlet update --- apstra/api_anomalies_integration_test.go | 2 +- apstra/api_blueprints.go | 72 +---- apstra/api_blueprints_integration_test.go | 36 +-- apstra/api_design_configlet_structures.go | 269 ------------------ apstra/api_design_configlets.go | 177 +++--------- apstra/api_design_configlets_helper.go | 52 ++++ apstra/api_design_configlets_test.go | 85 +----- apstra/client.go | 44 +-- apstra/client_test.go | 5 +- apstra/enum/enums.go | 31 ++ apstra/enum/generated_enums.go | 121 ++++++++ apstra/helpers_test.go | 18 +- apstra/two_stage_l3_clos_client.go | 42 +-- apstra/two_stage_l3_clos_configlet.go | 83 ++---- ...tage_l3_clos_configlet_integration_test.go | 11 +- ...ge_l3_clos_lock_status_integration_test.go | 6 +- 16 files changed, 364 insertions(+), 690 deletions(-) delete mode 100644 apstra/api_design_configlet_structures.go create mode 100644 apstra/api_design_configlets_helper.go diff --git a/apstra/api_anomalies_integration_test.go b/apstra/api_anomalies_integration_test.go index 1086510d..ee0ae05e 100644 --- a/apstra/api_anomalies_integration_test.go +++ b/apstra/api_anomalies_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2023-2024. +// Copyright (c) Juniper Networks, Inc., 2023-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 diff --git a/apstra/api_blueprints.go b/apstra/api_blueprints.go index 5d690296..449c16f8 100644 --- a/apstra/api_blueprints.go +++ b/apstra/api_blueprints.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -13,6 +13,8 @@ import ( "net/http" "net/url" "time" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) const ( @@ -33,16 +35,6 @@ const ( cablingMapMaxWaitSec = 30 ) -const ( - RefDesignTwoStageL3Clos = RefDesign(iota) - RefDesignFreeform - RefDesignUnknown = "unknown reference design '%s'" - - refDesignTwoStageL3Clos = refDesign("two_stage_l3clos") - refDesignFreeform = refDesign("freeform") - refDesignUnknown = refDesign("unknown reference design %d") -) - type BlueprintRequestFabricAddressingPolicy struct { SpineSuperspineLinks AddressingScheme SpineLeafLinks AddressingScheme @@ -69,42 +61,6 @@ type rawBlueprintRequestFabricAddressingPolicy struct { FabricL3Mtu *uint16 `json:"fabric_l3_mtu,omitempty"` } -type ( - RefDesign int - refDesign string -) - -func (o RefDesign) String() string { - switch o { - case RefDesignTwoStageL3Clos: - return string(refDesignTwoStageL3Clos) - case RefDesignFreeform: - return string(refDesignFreeform) - default: - return fmt.Sprintf(string(refDesignUnknown), o) - } -} - -func (o *RefDesign) FromString(s string) error { - i, err := refDesign(s).parse() - if err != nil { - return err - } - *o = i - return nil -} - -func (o refDesign) parse() (RefDesign, error) { - switch o { - case refDesignTwoStageL3Clos: - return RefDesignTwoStageL3Clos, nil - case refDesignFreeform: - return RefDesignFreeform, nil - default: - return 0, fmt.Errorf(RefDesignUnknown, o) - } -} - type getBluePrintsResponse struct { Items []rawBlueprintStatus `json:"items"` } @@ -123,7 +79,7 @@ type Blueprint struct { client *Client Id ObjectId Version int - Design RefDesign + Design enum.RefDesign LastModifiedAt time.Time Label string Relationships map[string]json.RawMessage @@ -136,7 +92,7 @@ type Blueprint struct { type rawBlueprint struct { Id ObjectId `json:"id"` Version int `json:"version"` - Design refDesign `json:"design"` + Design enum.RefDesign `json:"design"` LastModifiedAt time.Time `json:"last_modified_at"` Label string `json:"label"` Relationships map[string]json.RawMessage `json:"relationships"` @@ -147,15 +103,11 @@ type rawBlueprint struct { } func (o *rawBlueprint) polish() (*Blueprint, error) { - design, err := o.Design.parse() - if err != nil { - return nil, err - } return &Blueprint{ client: nil, Id: o.Id, Version: o.Version, - Design: design, + Design: o.Design, LastModifiedAt: o.LastModifiedAt, Label: o.Label, Relationships: o.Relationships, @@ -209,7 +161,7 @@ type BlueprintStatus struct { Id ObjectId `json:"id"` Label string `json:"label"` Status string `json:"status"` - Design RefDesign `json:"design"` + Design enum.RefDesign `json:"design"` HasUncommittedChanges bool `json:"has_uncommitted_changes"` Version int `json:"version"` LastModifiedAt time.Time `json:"last_modified_at"` @@ -234,7 +186,7 @@ type rawBlueprintStatus struct { Id ObjectId `json:"id"` Label string `json:"label"` Status string `json:"status"` - Design refDesign `json:"design"` + Design enum.RefDesign `json:"design"` HasUncommittedChanges bool `json:"has_uncommitted_changes"` Version int `json:"version"` LastModifiedAt time.Time `json:"last_modified_at"` @@ -263,15 +215,11 @@ type rawBlueprintStatus struct { } func (o *rawBlueprintStatus) polish() (*BlueprintStatus, error) { - design, err := o.Design.parse() - if err != nil { - return nil, err - } return &BlueprintStatus{ Id: o.Id, Label: o.Label, Status: o.Status, - Design: design, + Design: o.Design, HasUncommittedChanges: o.HasUncommittedChanges, Version: o.Version, LastModifiedAt: o.LastModifiedAt, @@ -294,7 +242,7 @@ func (o *rawBlueprintStatus) polish() (*BlueprintStatus, error) { } type CreateBlueprintFromTemplateRequest struct { - RefDesign RefDesign + RefDesign enum.RefDesign Label string TemplateId ObjectId FabricSettings *FabricSettings diff --git a/apstra/api_blueprints_integration_test.go b/apstra/api_blueprints_integration_test.go index 4ffd97d1..e77edaca 100644 --- a/apstra/api_blueprints_integration_test.go +++ b/apstra/api_blueprints_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -77,7 +77,7 @@ func TestCreateDeleteBlueprint(t *testing.T) { t.Parallel() req := CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(10, "hex"), TemplateId: "L2_Virtual_EVPN", FabricSettings: &FabricSettings{ @@ -327,14 +327,14 @@ func TestCreateDeleteEvpnBlueprint(t *testing.T) { testCases := map[string]testCase{ "simple": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", }, }, "4.1.1_and_later": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", FabricSettings: &FabricSettings{ @@ -345,7 +345,7 @@ func TestCreateDeleteEvpnBlueprint(t *testing.T) { }, "4.2.0_specific_test": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", FabricSettings: &FabricSettings{ @@ -357,7 +357,7 @@ func TestCreateDeleteEvpnBlueprint(t *testing.T) { }, "lots_of_values": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", FabricSettings: &FabricSettings{ @@ -392,7 +392,7 @@ func TestCreateDeleteEvpnBlueprint(t *testing.T) { }, "different_values": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", FabricSettings: &FabricSettings{ @@ -541,14 +541,14 @@ func TestCreateDeleteIpFabricBlueprint(t *testing.T) { testCases := map[string]testCase{ "simple": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual", }, }, "4.1.1_and_later": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual", FabricSettings: &FabricSettings{ @@ -559,7 +559,7 @@ func TestCreateDeleteIpFabricBlueprint(t *testing.T) { }, "4.2.0_specific_test": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual", FabricSettings: &FabricSettings{ @@ -571,7 +571,7 @@ func TestCreateDeleteIpFabricBlueprint(t *testing.T) { }, "lots_of_values": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual", FabricSettings: &FabricSettings{ @@ -606,7 +606,7 @@ func TestCreateDeleteIpFabricBlueprint(t *testing.T) { }, "different_values": { req: CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual", FabricSettings: &FabricSettings{ @@ -747,10 +747,12 @@ func TestCreateDeleteBlueprintWithRoutingLimits(t *testing.T) { clients, err := getTestClients(ctx, t) require.NoError(t, err) - blueprintRequest := CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, - Label: randString(5, "hex"), - TemplateId: "L2_Virtual", + blueprintRequest := func() CreateBlueprintFromTemplateRequest { + return CreateBlueprintFromTemplateRequest{ + RefDesign: enum.RefDesignDatacenter, + Label: randString(5, "hex"), + TemplateId: "L2_Virtual", + } } type testCase struct { @@ -795,7 +797,7 @@ func TestCreateDeleteBlueprintWithRoutingLimits(t *testing.T) { tCase := tCase t.Run(tCase.name, func(t *testing.T) { - bpr := blueprintRequest + bpr := blueprintRequest() bpr.FabricSettings = &tCase.fabricSettings t.Logf("testing CreateBlueprintFromTemplate() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion()) id, err := client.client.CreateBlueprintFromTemplate(ctx, &bpr) diff --git a/apstra/api_design_configlet_structures.go b/apstra/api_design_configlet_structures.go deleted file mode 100644 index 142da6f3..00000000 --- a/apstra/api_design_configlet_structures.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) Juniper Networks, Inc., 2023-2024. -// All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package apstra - -import ( - "fmt" -) - -//CONFIGLET_OS_SECTION_SUPPORT = { -//'cumulus': ('system', 'interface', 'file', 'frr', 'ospf'), -//'nxos': ('system', 'system_top', 'interface', 'ospf'), -//'eos': ('system', 'system_top', 'interface', 'ospf'), -//'junos': ('system', 'set_based_system', 'interface', 'set_based_interface', -//'delete_based_interface'), -//'sonic': ('system', 'file', 'frr', 'ospf'), -//} - -type ( - PlatformOS int - platformOS string -) - -const ( - PlatformOSCumulus = PlatformOS(iota) - PlatformOSNxos - PlatformOSEos - PlatformOSJunos - PlatformOSSonic - PlatformOSUnknown = "unknown os '%s'" - - platformOSCumulus = platformOS("cumulus") - platformOSNxos = platformOS("nxos") - platformOSEos = platformOS("eos") - platformOSJunos = platformOS("junos") - platformOSSonic = platformOS("sonic") - platformOSUnknown = "unknown type %d" -) - -func (o PlatformOS) Int() int { - return int(o) -} - -func (o PlatformOS) String() string { - switch o { - case PlatformOSCumulus: - return string(platformOSCumulus) - case PlatformOSNxos: - return string(platformOSNxos) - case PlatformOSEos: - return string(platformOSEos) - case PlatformOSJunos: - return string(platformOSJunos) - case PlatformOSSonic: - return string(platformOSSonic) - default: - return fmt.Sprintf(platformOSUnknown, o) - } -} - -func (o *PlatformOS) FromString(s string) error { - i, err := platformOS(s).parse() - if err != nil { - return err - } - *o = PlatformOS(i) - return nil -} - -func (o PlatformOS) raw() platformOS { - return platformOS(o.String()) -} - -func (o PlatformOS) ValidSections() []ConfigletSection { - switch o { - case PlatformOSCumulus: - return []ConfigletSection{ - ConfigletSectionFile, - ConfigletSectionFRR, - ConfigletSectionInterface, - ConfigletSectionOSPF, - ConfigletSectionSystem, - } - case PlatformOSEos: - return []ConfigletSection{ - ConfigletSectionInterface, - ConfigletSectionOSPF, - ConfigletSectionSystem, - ConfigletSectionSystemTop, - } - case PlatformOSJunos: - return []ConfigletSection{ - ConfigletSectionInterface, - ConfigletSectionDeleteBasedInterface, - ConfigletSectionSetBasedInterface, - ConfigletSectionSystem, - ConfigletSectionSetBasedSystem, - } - case PlatformOSNxos: - return []ConfigletSection{ - ConfigletSectionSystem, - ConfigletSectionInterface, - ConfigletSectionSystemTop, - ConfigletSectionOSPF, - } - case PlatformOSSonic: - return []ConfigletSection{ - ConfigletSectionFile, - ConfigletSectionFRR, - ConfigletSectionOSPF, - ConfigletSectionSystem, - } - } - return nil -} - -func (o platformOS) string() string { - return string(o) -} - -func (o platformOS) parse() (int, error) { - switch o { - case platformOSCumulus: - return int(PlatformOSCumulus), nil - case platformOSNxos: - return int(PlatformOSNxos), nil - case platformOSEos: - return int(PlatformOSEos), nil - case platformOSJunos: - return int(PlatformOSJunos), nil - case platformOSSonic: - return int(PlatformOSSonic), nil - default: - return 0, fmt.Errorf(PlatformOSUnknown, o) - } -} - -// AllPlatformOSes returns the []PlatformOS representing -// each supported PlatformOS -func AllPlatformOSes() []PlatformOS { - i := 0 - var result []PlatformOS - for { - var os PlatformOS - err := os.FromString(PlatformOS(i).String()) - if err != nil { - return result[:i] - } - result = append(result, os) - i++ - } -} - -type ( - ConfigletSection int - configletSection string -) - -const ( - ConfigletSectionSystem = ConfigletSection(iota) - ConfigletSectionInterface - ConfigletSectionFile - ConfigletSectionFRR - ConfigletSectionOSPF - ConfigletSectionSystemTop - ConfigletSectionSetBasedSystem - ConfigletSectionSetBasedInterface - ConfigletSectionDeleteBasedInterface - ConfigletSectionUnknown = "unknown section '%s'" - - configletSectionSystem = configletSection("system") - configletSectionInterface = configletSection("interface") - configletSectionFile = configletSection("file") - configletSectionFRR = configletSection("frr") - configletSectionOSPF = configletSection("ospf") - configletSectionSystemTop = configletSection("system_top") - configletSectionSetBasedSystem = configletSection("set_based_system") - configletSectionSetBasedInterface = configletSection("set_based_interface") - configletSectionDeleteBasedInterface = configletSection("delete_based_interface") - configletSectionUnknown = "unknown section %d" -) - -func (o ConfigletSection) Int() int { - return int(o) -} - -func (o ConfigletSection) String() string { - switch o { - case ConfigletSectionSystem: - return string(configletSectionSystem) - case ConfigletSectionInterface: - return string(configletSectionInterface) - case ConfigletSectionFile: - return string(configletSectionFile) - case ConfigletSectionFRR: - return string(configletSectionFRR) - case ConfigletSectionOSPF: - return string(configletSectionOSPF) - case ConfigletSectionSystemTop: - return string(configletSectionSystemTop) - case ConfigletSectionSetBasedSystem: - return string(configletSectionSetBasedSystem) - case ConfigletSectionSetBasedInterface: - return string(configletSectionSetBasedInterface) - case ConfigletSectionDeleteBasedInterface: - return string(configletSectionDeleteBasedInterface) - default: - return fmt.Sprintf(configletSectionUnknown, o) - } -} - -func (o ConfigletSection) raw() configletSection { - return configletSection(o.String()) -} - -func (o configletSection) string() string { - return string(o) -} - -func (o configletSection) parse() (int, error) { - switch o { - case configletSectionSystem: - return int(ConfigletSectionSystem), nil - case configletSectionInterface: - return int(ConfigletSectionInterface), nil - case configletSectionFile: - return int(ConfigletSectionFile), nil - case configletSectionFRR: - return int(ConfigletSectionFRR), nil - case configletSectionOSPF: - return int(ConfigletSectionOSPF), nil - case configletSectionSystemTop: - return int(ConfigletSectionSystemTop), nil - case configletSectionSetBasedSystem: - return int(ConfigletSectionSetBasedSystem), nil - case configletSectionSetBasedInterface: - return int(ConfigletSectionSetBasedInterface), nil - case configletSectionDeleteBasedInterface: - return int(ConfigletSectionDeleteBasedInterface), nil - default: - return 0, fmt.Errorf(ConfigletSectionUnknown, o) - } -} - -func (o *ConfigletSection) FromString(s string) error { - i, err := configletSection(s).parse() - if err != nil { - return err - } - *o = ConfigletSection(i) - return nil -} - -// AllConfigletSections returns the []ConfigletSection representing -// each supported ConfigletSection -func AllConfigletSections() []ConfigletSection { - i := 0 - var result []ConfigletSection - for { - var sec ConfigletSection - err := sec.FromString(ConfigletSection(i).String()) - if err != nil { - return result[:i] - } - result = append(result, sec) - i++ - } -} diff --git a/apstra/api_design_configlets.go b/apstra/api_design_configlets.go index ffe64405..218d84d3 100644 --- a/apstra/api_design_configlets.go +++ b/apstra/api_design_configlets.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -6,9 +6,12 @@ package apstra import ( "context" + "encoding/json" "fmt" "net/http" "time" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) const ( @@ -18,21 +21,23 @@ const ( ) type ConfigletGenerator struct { - ConfigStyle PlatformOS - Section ConfigletSection - TemplateText string - NegationTemplateText string - Filename string + ConfigStyle enum.ConfigletStyle `json:"config_style"` + Section enum.ConfigletSection `json:"section"` + TemplateText string `json:"template_text"` + NegationTemplateText string `json:"negation_template_text"` + Filename string `json:"filename"` } type rawConfigletGenerator struct { - ConfigStyle platformOS `json:"config_style"` - Section configletSection `json:"section"` - TemplateText string `json:"template_text"` - NegationTemplateText string `json:"negation_template_text"` - Filename string `json:"filename"` + ConfigStyle enum.ConfigletStyle `json:"config_style"` + Section enum.ConfigletSection `json:"section"` + TemplateText string `json:"template_text"` + NegationTemplateText string `json:"negation_template_text"` + Filename string `json:"filename"` } +var _ json.Unmarshaler = (*Configlet)(nil) + type Configlet struct { Id ObjectId CreatedAt time.Time @@ -40,125 +45,37 @@ type Configlet struct { Data *ConfigletData } -type ConfigletData struct { - RefArchs []RefDesign - Generators []ConfigletGenerator - DisplayName string -} - -type rawConfigletData struct { - RefArchs []refDesign `json:"ref_archs"` - Generators []rawConfigletGenerator `json:"generators"` - DisplayName string `json:"display_name"` -} - -type rawConfiglet struct { - RefArchs []refDesign `json:"ref_archs"` - Generators []rawConfigletGenerator `json:"generators"` - CreatedAt time.Time `json:"created_at"` - Id ObjectId `json:"id,omitempty"` - LastModifiedAt time.Time `json:"last_modified_at"` - DisplayName string `json:"display_name"` -} - -func (o *ConfigletData) raw() *rawConfigletData { - refArchs := make([]refDesign, len(o.RefArchs)) - for i, j := range o.RefArchs { - refArchs[i] = refDesign(j.String()) - } - - generators := make([]rawConfigletGenerator, len(o.Generators)) - for i, j := range o.Generators { - generators[i] = *j.raw() - } - - return &rawConfigletData{ - DisplayName: o.DisplayName, - RefArchs: refArchs, - Generators: generators, +func (o *Configlet) UnmarshalJSON(bytes []byte) error { + var raw struct { + RefArchs []enum.RefDesign `json:"ref_archs"` + Generators []ConfigletGenerator `json:"generators"` + CreatedAt time.Time `json:"created_at"` + Id ObjectId `json:"id,omitempty"` + LastModifiedAt time.Time `json:"last_modified_at"` + DisplayName string `json:"display_name"` } -} -func (o *rawConfigletData) polish() (*ConfigletData, error) { - var err error - - refArchs := make([]RefDesign, len(o.RefArchs)) - for i, refArch := range o.RefArchs { - refArchs[i], err = refDesign(refArch).parse() - if err != nil { - return nil, err - } - } - generators := make([]ConfigletGenerator, len(o.Generators)) - for i, generator := range o.Generators { - polished, err := generator.polish() - if err != nil { - return nil, err - } - generators[i] = *polished - } - return &ConfigletData{ - RefArchs: refArchs, - Generators: generators, - DisplayName: o.DisplayName, - }, nil -} - -func (o *rawConfigletGenerator) polish() (*ConfigletGenerator, error) { - platform, err := o.ConfigStyle.parse() - if err != nil { - return nil, err - } - section, err := o.Section.parse() + err := json.Unmarshal(bytes, &raw) if err != nil { - return nil, err + return err } - return &ConfigletGenerator{ - ConfigStyle: PlatformOS(platform), - Section: ConfigletSection(section), - TemplateText: o.TemplateText, - NegationTemplateText: o.NegationTemplateText, - Filename: o.Filename, - }, nil -} -func (o *ConfigletGenerator) raw() *rawConfigletGenerator { - return &rawConfigletGenerator{ - TemplateText: o.TemplateText, - Filename: o.Filename, - NegationTemplateText: o.NegationTemplateText, - ConfigStyle: o.ConfigStyle.raw(), - Section: o.Section.raw(), + o.Id = raw.Id + o.CreatedAt = raw.CreatedAt + o.LastModifiedAt = raw.LastModifiedAt + o.Data = &ConfigletData{ + RefArchs: raw.RefArchs, + Generators: raw.Generators, + DisplayName: raw.DisplayName, } + + return nil } -func (o *rawConfiglet) polish() (*Configlet, error) { - var err error - refArchs := make([]RefDesign, len(o.RefArchs)) - for i, refArch := range o.RefArchs { - refArchs[i], err = refDesign(refArch).parse() - if err != nil { - return nil, err - } - } - generators := make([]ConfigletGenerator, len(o.Generators)) - for i, generator := range o.Generators { - polished, err := generator.polish() - if err != nil { - return nil, err - } - generators[i] = *polished - } - return &Configlet{ - Id: o.Id, - CreatedAt: o.CreatedAt, - LastModifiedAt: o.LastModifiedAt, - Data: &ConfigletData{ - RefArchs: refArchs, - Generators: generators, - DisplayName: o.DisplayName, - }, - }, nil +type ConfigletData struct { + RefArchs []enum.RefDesign `json:"ref_archs"` + Generators []ConfigletGenerator `json:"generators"` + DisplayName string `json:"display_name"` } func (o *Client) listAllConfiglets(ctx context.Context) ([]ObjectId, error) { @@ -177,8 +94,8 @@ func (o *Client) listAllConfiglets(ctx context.Context) ([]ObjectId, error) { return response.Items, nil } -func (o *Client) getConfiglet(ctx context.Context, id ObjectId) (*rawConfiglet, error) { - response := &rawConfiglet{} +func (o *Client) getConfiglet(ctx context.Context, id ObjectId) (*Configlet, error) { + response := &Configlet{} err := o.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodGet, urlStr: fmt.Sprintf(apiUrlDesignConfigletsById, id), @@ -190,7 +107,7 @@ func (o *Client) getConfiglet(ctx context.Context, id ObjectId) (*rawConfiglet, return response, nil } -func (o *Client) getConfigletByName(ctx context.Context, name string) (*rawConfiglet, error) { +func (o *Client) getConfigletByName(ctx context.Context, name string) (*Configlet, error) { configlets, err := o.getAllConfiglets(ctx) if err != nil { return nil, convertTtaeToAceWherePossible(err) @@ -198,7 +115,7 @@ func (o *Client) getConfigletByName(ctx context.Context, name string) (*rawConfi foundIdx := -1 for i, configlet := range configlets { - if configlet.DisplayName == name { + if configlet.Data.DisplayName == name { if foundIdx >= 0 { return nil, ClientErr{ errType: ErrMultipleMatch, @@ -219,9 +136,9 @@ func (o *Client) getConfigletByName(ctx context.Context, name string) (*rawConfi } } -func (o *Client) getAllConfiglets(ctx context.Context) ([]rawConfiglet, error) { +func (o *Client) getAllConfiglets(ctx context.Context) ([]Configlet, error) { response := &struct { - Items []rawConfiglet `json:"items"` + Items []Configlet `json:"items"` }{} err := o.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodGet, @@ -234,7 +151,7 @@ func (o *Client) getAllConfiglets(ctx context.Context) ([]rawConfiglet, error) { return response.Items, nil } -func (o *Client) createConfiglet(ctx context.Context, in *rawConfigletData) (ObjectId, error) { +func (o *Client) createConfiglet(ctx context.Context, in *ConfigletData) (ObjectId, error) { response := &objectIdResponse{} err := o.talkToApstra(ctx, &talkToApstraIn{ @@ -249,7 +166,7 @@ func (o *Client) createConfiglet(ctx context.Context, in *rawConfigletData) (Obj return response.Id, nil } -func (o *Client) updateConfiglet(ctx context.Context, id ObjectId, in *rawConfigletData) error { +func (o *Client) updateConfiglet(ctx context.Context, id ObjectId, in *ConfigletData) error { err := o.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodPut, urlStr: fmt.Sprintf(apiUrlDesignConfigletsById, id), diff --git a/apstra/api_design_configlets_helper.go b/apstra/api_design_configlets_helper.go new file mode 100644 index 00000000..1e4e749e --- /dev/null +++ b/apstra/api_design_configlets_helper.go @@ -0,0 +1,52 @@ +// Copyright (c) Juniper Networks, Inc., 2023-2025. +// All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package apstra + +import ( + "github.com/Juniper/apstra-go-sdk/apstra/enum" +) + +func ValidConfigletSections(platform enum.ConfigletStyle) []enum.ConfigletSection { + switch platform { + case enum.ConfigletStyleCumulus: + return []enum.ConfigletSection{ + enum.ConfigletSectionFile, + enum.ConfigletSectionFrr, + enum.ConfigletSectionInterface, + enum.ConfigletSectionOspf, + enum.ConfigletSectionSystem, + } + case enum.ConfigletStyleEos: + return []enum.ConfigletSection{ + enum.ConfigletSectionInterface, + enum.ConfigletSectionOspf, + enum.ConfigletSectionSystem, + enum.ConfigletSectionSystemTop, + } + case enum.ConfigletStyleJunos: + return []enum.ConfigletSection{ + enum.ConfigletSectionInterface, + enum.ConfigletSectionDeleteBasedInterface, + enum.ConfigletSectionSetBasedInterface, + enum.ConfigletSectionSystem, + enum.ConfigletSectionSetBasedSystem, + } + case enum.ConfigletStyleNxos: + return []enum.ConfigletSection{ + enum.ConfigletSectionSystem, + enum.ConfigletSectionInterface, + enum.ConfigletSectionSystemTop, + enum.ConfigletSectionOspf, + } + case enum.ConfigletStyleSonic: + return []enum.ConfigletSection{ + enum.ConfigletSectionFile, + enum.ConfigletSectionFrr, + enum.ConfigletSectionOspf, + enum.ConfigletSectionSystem, + } + } + return nil +} diff --git a/apstra/api_design_configlets_test.go b/apstra/api_design_configlets_test.go index 9c230bf1..5c5c569f 100644 --- a/apstra/api_design_configlets_test.go +++ b/apstra/api_design_configlets_test.go @@ -1,9 +1,8 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 //go:build integration -// +build integration package apstra @@ -11,6 +10,8 @@ import ( "context" "log" "testing" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) func TestCreateUpdateGetDeleteConfiglet(t *testing.T) { @@ -24,17 +25,14 @@ func TestCreateUpdateGetDeleteConfiglet(t *testing.T) { var cg []ConfigletGenerator cg = append(cg, ConfigletGenerator{ - ConfigStyle: PlatformOSJunos, - Section: ConfigletSectionSystem, + ConfigStyle: enum.ConfigletStyleJunos, + Section: enum.ConfigletSectionSystem, TemplateText: "interfaces {\n {% if 'leaf1' in hostname %}\n xe-0/0/3 {\n disable;\n }\n {% endif %}\n {% if 'leaf2' in hostname %}\n xe-0/0/2 {\n disable;\n }\n {% endif %}\n}", }) - var refarchs []RefDesign - - refarchs = append(refarchs, RefDesignTwoStageL3Clos) id1, err := client.client.CreateConfiglet(context.Background(), &ConfigletData{ DisplayName: Name, - RefArchs: refarchs, + RefArchs: []enum.RefDesign{enum.RefDesignDatacenter}, Generators: cg, }) if err != nil { @@ -53,8 +51,8 @@ func TestCreateUpdateGetDeleteConfiglet(t *testing.T) { log.Println(c) g1 := len(c.Data.Generators) c.Data.Generators = append(c.Data.Generators, ConfigletGenerator{ - ConfigStyle: PlatformOSJunos, - Section: ConfigletSectionSystem, + ConfigStyle: enum.ConfigletStyleJunos, + Section: enum.ConfigletSectionSystem, TemplateText: "interfaces {\n {% if 'leaf1' in hostname %}\n xe-0/0/3 {\n disable;\n }\n {% endif %}\n {% if 'leaf2' in hostname %}\n xe-0/0/2 {\n disable;\n }\n {% endif %}\n}", }) log.Println("Update Config") @@ -93,70 +91,3 @@ func TestCreateUpdateGetDeleteConfiglet(t *testing.T) { } } } - -func TestConfigletStrings(t *testing.T) { - type apiStringIota interface { - String() string - Int() int - } - - type apiIotaString interface { - parse() (int, error) - string() string - } - - type stringTestData struct { - stringVal string - intType apiStringIota - stringType apiIotaString - } - testData := []stringTestData{ - {stringVal: "system", intType: ConfigletSectionSystem, stringType: configletSectionSystem}, - {stringVal: "interface", intType: ConfigletSectionInterface, stringType: configletSectionInterface}, - {stringVal: "file", intType: ConfigletSectionFile, stringType: configletSectionFile}, - {stringVal: "frr", intType: ConfigletSectionFRR, stringType: configletSectionFRR}, - {stringVal: "ospf", intType: ConfigletSectionOSPF, stringType: configletSectionOSPF}, - {stringVal: "system_top", intType: ConfigletSectionSystemTop, stringType: configletSectionSystemTop}, - {stringVal: "set_based_system", intType: ConfigletSectionSetBasedSystem, stringType: configletSectionSetBasedSystem}, - {stringVal: "set_based_interface", intType: ConfigletSectionSetBasedInterface, stringType: configletSectionSetBasedInterface}, - {stringVal: "delete_based_interface", intType: ConfigletSectionDeleteBasedInterface, stringType: configletSectionDeleteBasedInterface}, - - {stringVal: "cumulus", intType: PlatformOSCumulus, stringType: platformOSCumulus}, - {stringVal: "nxos", intType: PlatformOSNxos, stringType: platformOSNxos}, - {stringVal: "eos", intType: PlatformOSEos, stringType: platformOSEos}, - {stringVal: "junos", intType: PlatformOSJunos, stringType: platformOSJunos}, - {stringVal: "sonic", intType: PlatformOSSonic, stringType: platformOSSonic}, - } - - for i, td := range testData { - ii := td.intType.Int() - is := td.intType.String() - sp, err := td.stringType.parse() - if err != nil { - t.Fatal(err) - } - ss := td.stringType.string() - if td.intType.String() != td.stringType.string() || - td.intType.Int() != sp || - td.stringType.string() != td.stringVal { - t.Fatalf("test index %d mismatch: %d %d '%s' '%s' '%s'", - i, ii, sp, is, ss, td.stringVal) - } - } -} - -func TestAllPlatformOSes(t *testing.T) { - all := AllPlatformOSes() - expected := 5 - if len(all) != expected { - t.Fatalf("expected %d platform OSes, got %d", expected, len(all)) - } -} - -func TestAllConfigletSections(t *testing.T) { - all := AllConfigletSections() - expected := 9 - if len(all) != expected { - t.Fatalf("expected %d configlet sections, got %d", expected, len(all)) - } -} diff --git a/apstra/client.go b/apstra/client.go index f305aa35..735f75bf 100644 --- a/apstra/client.go +++ b/apstra/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -211,9 +211,9 @@ func (o *Client) NewTwoStageL3ClosClient(ctx context.Context, blueprintId Object if err != nil { return nil, err } - if bp.Design != refDesignTwoStageL3Clos { + if bp.Design != enum.RefDesignDatacenter { return nil, fmt.Errorf("cannot create '%s' client for blueprint '%s' (type '%s')", - RefDesignTwoStageL3Clos.String(), blueprintId, bp.Design) + enum.RefDesignDatacenter, blueprintId, bp.Design) } result := &TwoStageL3ClosClient{ @@ -233,7 +233,7 @@ func (o *Client) CreateFreeformBlueprint(ctx context.Context, label string) (Obj Label string `json:"label"` } - request.Design = RefDesignFreeform.String() + request.Design = enum.RefDesignFreeform.String() request.Label = label var response postBlueprintsResponse @@ -256,9 +256,9 @@ func (o *Client) NewFreeformClient(ctx context.Context, blueprintId ObjectId) (* if err != nil { return nil, err } - if bp.Design != refDesignFreeform { + if bp.Design != enum.RefDesignFreeform { return nil, fmt.Errorf("cannot create '%s' client for blueprint '%s' (type '%s')", - RefDesignFreeform, blueprintId, bp.Design) + enum.RefDesignFreeform, blueprintId, bp.Design) } return &FreeformClient{ @@ -1164,7 +1164,7 @@ func (o *Client) DeleteTag(ctx context.Context, id ObjectId) error { // CreateConfiglet creates a Configlet and returns its ObjectId. func (o *Client) CreateConfiglet(ctx context.Context, in *ConfigletData) (ObjectId, error) { - return o.createConfiglet(ctx, in.raw()) + return o.createConfiglet(ctx, in) } // DeleteConfiglet deletes a configlet. @@ -1174,35 +1174,17 @@ func (o *Client) DeleteConfiglet(ctx context.Context, in ObjectId) error { // GetConfiglet Accepts an ID and returns the Configlet object func (o *Client) GetConfiglet(ctx context.Context, in ObjectId) (*Configlet, error) { - r, err := o.getConfiglet(ctx, in) - if err != nil { - return nil, err - } - return r.polish() + return o.getConfiglet(ctx, in) } // UpdateConfiglet updates a configlet func (o *Client) UpdateConfiglet(ctx context.Context, id ObjectId, in *ConfigletData) error { - return o.updateConfiglet(ctx, id, in.raw()) + return o.updateConfiglet(ctx, id, in) } // GetAllConfiglets returns []Configlet representing all configlets func (o *Client) GetAllConfiglets(ctx context.Context) ([]Configlet, error) { - rawConfiglets, err := o.getAllConfiglets(ctx) - if err != nil { - return nil, err - } - - result := make([]Configlet, len(rawConfiglets)) - for i := range rawConfiglets { - polished, err := rawConfiglets[i].polish() - if err != nil { - return nil, err - } - result[i] = *polished - } - - return result, nil + return o.getAllConfiglets(ctx) } // ListAllConfiglets gets the List of All configlet IDs @@ -1212,11 +1194,7 @@ func (o *Client) ListAllConfiglets(ctx context.Context) ([]ObjectId, error) { // GetConfigletByName gets a configlet by Name func (o *Client) GetConfigletByName(ctx context.Context, Name string) (*Configlet, error) { - c, err := o.getConfigletByName(ctx, Name) - if err != nil { - return nil, err - } - return c.polish() + return o.getConfigletByName(ctx, Name) } // ListAllTemplateIds returns []ObjectId representing all blueprint templates diff --git a/apstra/client_test.go b/apstra/client_test.go index 84e98c0e..10294823 100644 --- a/apstra/client_test.go +++ b/apstra/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -16,6 +16,7 @@ import ( "strings" "testing" + "github.com/Juniper/apstra-go-sdk/apstra/enum" "github.com/stretchr/testify/require" ) @@ -352,7 +353,7 @@ func TestBlueprintOverlayControlProtocol(t *testing.T) { t.Helper() id, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: templateId, }) diff --git a/apstra/enum/enums.go b/apstra/enum/enums.go index 71d1a97b..0111387d 100644 --- a/apstra/enum/enums.go +++ b/apstra/enum/enums.go @@ -252,3 +252,34 @@ var ( NodeRoleSpine = NodeRole{Value: "spine"} NodeRoleSuperspine = NodeRole{Value: "superspine"} ) + +type RefDesign oenum.Member[string] + +var ( + RefDesignDatacenter = RefDesign{Value: "two_stage_l3clos"} + RefDesignFreeform = RefDesign{Value: "freeform"} +) + +type ConfigletStyle oenum.Member[string] + +var ( + ConfigletStyleCumulus = ConfigletStyle{Value: "cumulus"} + ConfigletStyleEos = ConfigletStyle{Value: "eos"} + ConfigletStyleJunos = ConfigletStyle{Value: "junos"} + ConfigletStyleNxos = ConfigletStyle{Value: "nxos"} + ConfigletStyleSonic = ConfigletStyle{Value: "sonic"} +) + +type ConfigletSection oenum.Member[string] + +var ( + ConfigletSectionDeleteBasedInterface = ConfigletSection{Value: "delete_based_interface"} + ConfigletSectionFile = ConfigletSection{Value: "file"} + ConfigletSectionFrr = ConfigletSection{Value: "frr"} + ConfigletSectionInterface = ConfigletSection{Value: "interface"} + ConfigletSectionOspf = ConfigletSection{Value: "ospf"} + ConfigletSectionSetBasedInterface = ConfigletSection{Value: "set_based_interface"} + ConfigletSectionSetBasedSystem = ConfigletSection{Value: "set_based_system"} + ConfigletSectionSystem = ConfigletSection{Value: "system"} + ConfigletSectionSystemTop = ConfigletSection{Value: "system_top"} +) diff --git a/apstra/enum/generated_enums.go b/apstra/enum/generated_enums.go index 22a2dbaf..57abd0bf 100644 --- a/apstra/enum/generated_enums.go +++ b/apstra/enum/generated_enums.go @@ -43,6 +43,68 @@ func (o *ApiFeature) UnmarshalJSON(bytes []byte) error { return o.FromString(s) } +var ( + _ enum = (*ConfigletSection)(nil) + _ json.Marshaler = (*ConfigletSection)(nil) + _ json.Unmarshaler = (*ConfigletSection)(nil) +) + +func (o ConfigletSection) String() string { + return o.Value +} + +func (o *ConfigletSection) FromString(s string) error { + if ConfigletSections.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + +func (o *ConfigletSection) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +func (o *ConfigletSection) UnmarshalJSON(bytes []byte) error { + var s string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + return o.FromString(s) +} + +var ( + _ enum = (*ConfigletStyle)(nil) + _ json.Marshaler = (*ConfigletStyle)(nil) + _ json.Unmarshaler = (*ConfigletStyle)(nil) +) + +func (o ConfigletStyle) String() string { + return o.Value +} + +func (o *ConfigletStyle) FromString(s string) error { + if ConfigletStyles.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + +func (o *ConfigletStyle) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +func (o *ConfigletStyle) UnmarshalJSON(bytes []byte) error { + var s string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + return o.FromString(s) +} + var ( _ enum = (*DeployMode)(nil) _ json.Marshaler = (*DeployMode)(nil) @@ -508,6 +570,37 @@ func (o *RedundancyGroupType) UnmarshalJSON(bytes []byte) error { return o.FromString(s) } +var ( + _ enum = (*RefDesign)(nil) + _ json.Marshaler = (*RefDesign)(nil) + _ json.Unmarshaler = (*RefDesign)(nil) +) + +func (o RefDesign) String() string { + return o.Value +} + +func (o *RefDesign) FromString(s string) error { + if RefDesigns.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + +func (o *RefDesign) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +func (o *RefDesign) UnmarshalJSON(bytes []byte) error { + var s string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + return o.FromString(s) +} + var ( _ enum = (*RemoteGatewayRouteType)(nil) _ json.Marshaler = (*RemoteGatewayRouteType)(nil) @@ -829,6 +922,28 @@ var ( ApiFeatureTaskApi, ) + _ enum = new(ConfigletSection) + ConfigletSections = oenum.New( + ConfigletSectionDeleteBasedInterface, + ConfigletSectionFile, + ConfigletSectionFrr, + ConfigletSectionInterface, + ConfigletSectionOspf, + ConfigletSectionSetBasedInterface, + ConfigletSectionSetBasedSystem, + ConfigletSectionSystem, + ConfigletSectionSystemTop, + ) + + _ enum = new(ConfigletStyle) + ConfigletStyles = oenum.New( + ConfigletStyleCumulus, + ConfigletStyleEos, + ConfigletStyleJunos, + ConfigletStyleNxos, + ConfigletStyleSonic, + ) + _ enum = new(DeployMode) DeployModes = oenum.New( DeployModeDeploy, @@ -946,6 +1061,12 @@ var ( RedundancyGroupTypeMlag, ) + _ enum = new(RefDesign) + RefDesigns = oenum.New( + RefDesignDatacenter, + RefDesignFreeform, + ) + _ enum = new(RemoteGatewayRouteType) RemoteGatewayRouteTypes = oenum.New( RemoteGatewayRouteTypeAll, diff --git a/apstra/helpers_test.go b/apstra/helpers_test.go index e598d04d..369360c7 100644 --- a/apstra/helpers_test.go +++ b/apstra/helpers_test.go @@ -266,7 +266,7 @@ func testBlueprintA(ctx context.Context, t *testing.T, client *Client) *TwoStage t.Helper() bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L3_Collapsed_ESI", }) @@ -284,7 +284,7 @@ func testBlueprintB(ctx context.Context, t *testing.T, client *Client) *TwoStage t.Helper() bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual", }) @@ -302,7 +302,7 @@ func testBlueprintC(ctx context.Context, t testing.TB, client *Client) *TwoStage t.Helper() bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", }) @@ -319,7 +319,7 @@ func testBlueprintD(ctx context.Context, t *testing.T, client *Client) *TwoStage t.Helper() bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_ESI_2x_Links", }) @@ -362,7 +362,7 @@ func testBlueprintD(ctx context.Context, t *testing.T, client *Client) *TwoStage func testBlueprintE(ctx context.Context, t *testing.T, client *Client) *TwoStageL3ClosClient { bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_ESI_Access", }) @@ -439,7 +439,7 @@ func testBlueprintH(ctx context.Context, t *testing.T, client *Client) *TwoStage t.Helper() bpRequest := CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L2_Virtual_EVPN", FabricSettings: &FabricSettings{ @@ -483,7 +483,7 @@ func testBlueprintI(ctx context.Context, t *testing.T, client *Client) *TwoStage t.Helper() bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: "L3_Collapsed_ESI", }) @@ -690,7 +690,7 @@ func testBlueprintF(ctx context.Context, t *testing.T, client *Client) (*TwoStag } bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: templateId, }) @@ -715,7 +715,7 @@ func testBlueprintG(ctx context.Context, t *testing.T, client *Client) *TwoStage templateId := testTemplateB(ctx, t, client) bpId, err := client.CreateBlueprintFromTemplate(ctx, &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: randString(5, "hex"), TemplateId: templateId, FabricSettings: &FabricSettings{ diff --git a/apstra/two_stage_l3_clos_client.go b/apstra/two_stage_l3_clos_client.go index 2a3293a0..076ad538 100644 --- a/apstra/two_stage_l3_clos_client.go +++ b/apstra/two_stage_l3_clos_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -523,19 +523,7 @@ func (o *TwoStageL3ClosClient) DeletePropertySet(ctx context.Context, id ObjectI // GetAllConfiglets returns []TwoStageL3ClosConfiglet representing all // configlets imported into a blueprint func (o *TwoStageL3ClosClient) GetAllConfiglets(ctx context.Context) ([]TwoStageL3ClosConfiglet, error) { - rawConfiglets, err := o.getAllConfiglets(ctx) - if err != nil { - return nil, err - } - result := make([]TwoStageL3ClosConfiglet, len(rawConfiglets)) - for i := range rawConfiglets { - polished, err := rawConfiglets[i].polish() - if err != nil { - return nil, err - } - result[i] = *polished - } - return result, nil + return o.getAllConfiglets(ctx) } // GetAllConfigletIds returns Ids of all the configlets imported into a @@ -547,21 +535,13 @@ func (o *TwoStageL3ClosClient) GetAllConfigletIds(ctx context.Context) ([]Object // GetConfiglet returns *TwoStageL3ClosConfiglet representing the imported // configlet with the given ID in the specified blueprint func (o *TwoStageL3ClosClient) GetConfiglet(ctx context.Context, id ObjectId) (*TwoStageL3ClosConfiglet, error) { - c, err := o.getConfiglet(ctx, id) - if err != nil { - return nil, err - } - return c.polish() + return o.getConfiglet(ctx, id) } // GetConfigletByName returns *TwoStageL3ClosConfiglet representing the only // configlet with the given label, or an error if no configlet by that name exists func (o *TwoStageL3ClosClient) GetConfigletByName(ctx context.Context, in string) (*TwoStageL3ClosConfiglet, error) { - c, err := o.getConfigletByName(ctx, in) - if err != nil { - return nil, err - } - return c.polish() + return o.getConfigletByName(ctx, in) } // ImportConfigletById imports a configlet from the catalog into a blueprint. @@ -575,13 +555,13 @@ func (o *TwoStageL3ClosClient) ImportConfigletById(ctx context.Context, cid Obje return "", err } - return o.createConfiglet(ctx, &rawTwoStageL3ClosConfigletData{ + return o.createConfiglet(ctx, &TwoStageL3ClosConfigletData{ Condition: condition, Label: label, - Data: rawConfigletData{ - RefArchs: cfg.RefArchs, - Generators: cfg.Generators, - DisplayName: cfg.DisplayName, + Data: &ConfigletData{ + RefArchs: cfg.Data.RefArchs, + Generators: cfg.Data.Generators, + DisplayName: cfg.Data.DisplayName, }, }) } @@ -589,12 +569,12 @@ func (o *TwoStageL3ClosClient) ImportConfigletById(ctx context.Context, cid Obje // CreateConfiglet creates a configlet described by a TwoStageL3ClosConfigletData structure // in a blueprint. func (o *TwoStageL3ClosClient) CreateConfiglet(ctx context.Context, c *TwoStageL3ClosConfigletData) (ObjectId, error) { - return o.createConfiglet(ctx, c.raw()) + return o.createConfiglet(ctx, c) } // UpdateConfiglet updates a configlet imported into a blueprint. func (o *TwoStageL3ClosClient) UpdateConfiglet(ctx context.Context, id ObjectId, c *TwoStageL3ClosConfigletData) error { - return o.updateConfiglet(ctx, id, c.raw()) + return o.updateConfiglet(ctx, id, c) } // DeleteConfiglet deletes a configlet from the blueprint given the id diff --git a/apstra/two_stage_l3_clos_configlet.go b/apstra/two_stage_l3_clos_configlet.go index e595aa0c..426d63eb 100644 --- a/apstra/two_stage_l3_clos_configlet.go +++ b/apstra/two_stage_l3_clos_configlet.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2023-2024. +// Copyright (c) Juniper Networks, Inc., 2023-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -6,6 +6,7 @@ package apstra import ( "context" + "encoding/json" "fmt" "net/http" ) @@ -16,67 +17,45 @@ const ( apiUrlBlueprintConfigletsById = apiUrlBlueprintConfigletsPrefix + "%s" ) -type rawTwoStageL3ClosConfigletData struct { - Label string `json:"label"` - Condition string `json:"condition"` - Data rawConfigletData `json:"configlet"` -} - type TwoStageL3ClosConfigletData struct { - Label string - Condition string - Data *ConfigletData + Label string `json:"label"` + Condition string `json:"condition"` + Data *ConfigletData `json:"configlet"` } -type rawTwoStageL3ClosConfiglet struct { - Id ObjectId `json:"id"` - Condition string `json:"condition"` - Label string `json:"label"` - Data rawConfigletData `json:"configlet"` -} +var _ json.Unmarshaler = (*TwoStageL3ClosConfiglet)(nil) type TwoStageL3ClosConfiglet struct { Id ObjectId Data *TwoStageL3ClosConfigletData } -func (o *TwoStageL3ClosConfigletData) raw() *rawTwoStageL3ClosConfigletData { - return &rawTwoStageL3ClosConfigletData{ - Label: o.Label, - Condition: o.Condition, - Data: *o.Data.raw(), +func (o *TwoStageL3ClosConfiglet) UnmarshalJSON(bytes []byte) error { + var raw struct { + Id ObjectId `json:"id"` + Condition string `json:"condition"` + Label string `json:"label"` + Data *ConfigletData `json:"configlet"` } -} -func (o *TwoStageL3ClosConfiglet) raw() *rawTwoStageL3ClosConfiglet { - d := o.Data.raw() - return &rawTwoStageL3ClosConfiglet{ - Id: o.Id, - Label: d.Label, - Condition: d.Condition, - Data: d.Data, + err := json.Unmarshal(bytes, &raw) + if err != nil { + return err } -} -func (o *rawTwoStageL3ClosConfiglet) polish() (*TwoStageL3ClosConfiglet, error) { - d, err := o.Data.polish() - if err != nil { - return nil, err + o.Id = raw.Id + o.Data = &TwoStageL3ClosConfigletData{ + Label: raw.Label, + Condition: raw.Condition, + Data: raw.Data, } - return &TwoStageL3ClosConfiglet{ - Id: o.Id, - Data: &TwoStageL3ClosConfigletData{ - Data: d, - Condition: o.Condition, - Label: o.Label, - }, - }, nil + return nil } -func (o *TwoStageL3ClosClient) getAllConfiglets(ctx context.Context) ([]rawTwoStageL3ClosConfiglet, error) { +func (o *TwoStageL3ClosClient) getAllConfiglets(ctx context.Context) ([]TwoStageL3ClosConfiglet, error) { response := &struct { - Items []rawTwoStageL3ClosConfiglet `json:"items"` + Items []TwoStageL3ClosConfiglet `json:"items"` }{} err := o.client.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodGet, @@ -101,27 +80,27 @@ func (o *TwoStageL3ClosClient) getAllConfigletIds(ctx context.Context) ([]Object return ids, nil } -func (o *TwoStageL3ClosClient) getConfiglet(ctx context.Context, id ObjectId) (*rawTwoStageL3ClosConfiglet, error) { - response := &rawTwoStageL3ClosConfiglet{} +func (o *TwoStageL3ClosClient) getConfiglet(ctx context.Context, id ObjectId) (*TwoStageL3ClosConfiglet, error) { + var response TwoStageL3ClosConfiglet err := o.client.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodGet, urlStr: fmt.Sprintf(apiUrlBlueprintConfigletsById, o.blueprintId.String(), id.String()), - apiResponse: response, + apiResponse: &response, }) if err != nil { return nil, convertTtaeToAceWherePossible(err) } - return response, nil + return &response, nil } -func (o *TwoStageL3ClosClient) getConfigletByName(ctx context.Context, name string) (*rawTwoStageL3ClosConfiglet, error) { +func (o *TwoStageL3ClosClient) getConfigletByName(ctx context.Context, name string) (*TwoStageL3ClosConfiglet, error) { cgs, err := o.getAllConfiglets(ctx) if err != nil { return nil, convertTtaeToAceWherePossible(err) } idx := -1 for i, t := range cgs { - if t.Label == name { + if t.Data.Label == name { if idx == -1 { idx = i } else { // This is clearly the second occurrence @@ -141,7 +120,7 @@ func (o *TwoStageL3ClosClient) getConfigletByName(ctx context.Context, name stri } } -func (o *TwoStageL3ClosClient) createConfiglet(ctx context.Context, in *rawTwoStageL3ClosConfigletData) (ObjectId, error) { +func (o *TwoStageL3ClosClient) createConfiglet(ctx context.Context, in *TwoStageL3ClosConfigletData) (ObjectId, error) { response := &objectIdResponse{} if len(in.Label) == 0 { in.Label = in.Data.DisplayName @@ -159,7 +138,7 @@ func (o *TwoStageL3ClosClient) createConfiglet(ctx context.Context, in *rawTwoSt return response.Id, nil } -func (o *TwoStageL3ClosClient) updateConfiglet(ctx context.Context, id ObjectId, in *rawTwoStageL3ClosConfigletData) error { +func (o *TwoStageL3ClosClient) updateConfiglet(ctx context.Context, id ObjectId, in *TwoStageL3ClosConfigletData) error { err := o.client.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodPut, urlStr: fmt.Sprintf(apiUrlBlueprintConfigletsById, o.blueprintId.String(), id), diff --git a/apstra/two_stage_l3_clos_configlet_integration_test.go b/apstra/two_stage_l3_clos_configlet_integration_test.go index c2013716..832263a0 100644 --- a/apstra/two_stage_l3_clos_configlet_integration_test.go +++ b/apstra/two_stage_l3_clos_configlet_integration_test.go @@ -1,9 +1,8 @@ -// Copyright (c) Juniper Networks, Inc., 2023-2024. +// Copyright (c) Juniper Networks, Inc., 2023-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 //go:build integration -// +build integration package apstra @@ -12,6 +11,8 @@ import ( "errors" "log" "testing" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) func TestImportGetUpdateGetDeleteConfiglet(t *testing.T) { @@ -23,10 +24,10 @@ func TestImportGetUpdateGetDeleteConfiglet(t *testing.T) { configletData := ConfigletData{ DisplayName: randString(5, "hex"), - RefArchs: []RefDesign{RefDesignTwoStageL3Clos}, + RefArchs: []enum.RefDesign{enum.RefDesignDatacenter}, Generators: []ConfigletGenerator{{ - ConfigStyle: PlatformOSJunos, - Section: ConfigletSectionSystem, + ConfigStyle: enum.ConfigletStyleJunos, + Section: enum.ConfigletSectionSystem, TemplateText: "interfaces {\n {% if 'leaf1' in hostname %}\n xe-0/0/3 {\n disable;\n }\n {% endif %}\n {% if 'leaf2' in hostname %}\n xe-0/0/2 {\n disable;\n }\n {% endif %}\n}", }}, } diff --git a/apstra/two_stage_l3_clos_lock_status_integration_test.go b/apstra/two_stage_l3_clos_lock_status_integration_test.go index b0a25b14..9a0187ef 100644 --- a/apstra/two_stage_l3_clos_lock_status_integration_test.go +++ b/apstra/two_stage_l3_clos_lock_status_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2022-2024. +// Copyright (c) Juniper Networks, Inc., 2022-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -10,6 +10,8 @@ import ( "context" "log" "testing" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) func TestGetLockInfo(t *testing.T) { @@ -25,7 +27,7 @@ func TestGetLockInfo(t *testing.T) { log.Printf("testing createBlueprintFromTemplate() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion()) name := randString(10, "hex") id, err := client.client.CreateBlueprintFromTemplate(context.TODO(), &CreateBlueprintFromTemplateRequest{ - RefDesign: RefDesignTwoStageL3Clos, + RefDesign: enum.RefDesignDatacenter, Label: name, TemplateId: "L2_Virtual_EVPN", }) From 762dce856a262795244e85084584471441438eee Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Thu, 23 Jan 2025 18:59:58 -0500 Subject: [PATCH 2/6] revert unnecessary edit --- apstra/api_anomalies_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apstra/api_anomalies_integration_test.go b/apstra/api_anomalies_integration_test.go index ee0ae05e..1086510d 100644 --- a/apstra/api_anomalies_integration_test.go +++ b/apstra/api_anomalies_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2023-2025. +// Copyright (c) Juniper Networks, Inc., 2023-2024. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 From c8bb2873bc83d29ee0ad478e19ebcbc1ffa87c86 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Thu, 23 Jan 2025 19:02:28 -0500 Subject: [PATCH 3/6] fix license header --- apstra/api_design_configlets_helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apstra/api_design_configlets_helper.go b/apstra/api_design_configlets_helper.go index 1e4e749e..d674b4bf 100644 --- a/apstra/api_design_configlets_helper.go +++ b/apstra/api_design_configlets_helper.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2023-2025. +// Copyright (c) Juniper Networks, Inc., 2025-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 From a46b63f199776729de244bf0cd120dbde847b7f3 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Thu, 23 Jan 2025 21:32:53 -0500 Subject: [PATCH 4/6] add delay for API in test --- apstra/client_rendered_diff_integration_test.go | 9 +++++++-- .../two_stage_l3_clos_subinterface_integration_test.go | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apstra/client_rendered_diff_integration_test.go b/apstra/client_rendered_diff_integration_test.go index 1b32bfd9..e79be067 100644 --- a/apstra/client_rendered_diff_integration_test.go +++ b/apstra/client_rendered_diff_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2024-2024. +// Copyright (c) Juniper Networks, Inc., 2024-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -14,6 +14,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/Juniper/apstra-go-sdk/apstra/enum" "github.com/stretchr/testify/require" @@ -103,6 +104,8 @@ func TestGetNodeRenderedDiff(t *testing.T) { require.NoError(t, err) t.Logf(vnId.String()) + time.Sleep(time.Second) // ensure time for config diffs to render + leafWg.Add(len(leafIds)) for _, leafId := range leafIds { t.Run("leaf_"+leafId.String(), func(t *testing.T) { @@ -128,7 +131,7 @@ func TestGetNodeRenderedDiff(t *testing.T) { require.Greater(t, adds, 40) require.Equal(t, dels, 0) - leafWg.Done() + leafWg.Done() // there is a deadlock here if require above fails }) } @@ -151,6 +154,8 @@ func TestGetNodeRenderedDiff(t *testing.T) { err = bp.DeleteVirtualNetwork(ctx, vnId) require.NoError(t, err) + time.Sleep(time.Second) // ensure time for config diffs to render + for _, leafId := range leafIds { // staging config should have diffs at this point diff, err := bp.Client().GetNodeRenderedConfigDiff(ctx, bp.Id(), leafId) diff --git a/apstra/two_stage_l3_clos_subinterface_integration_test.go b/apstra/two_stage_l3_clos_subinterface_integration_test.go index 84d535cb..7ecb576d 100644 --- a/apstra/two_stage_l3_clos_subinterface_integration_test.go +++ b/apstra/two_stage_l3_clos_subinterface_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Juniper Networks, Inc., 2024-2024. +// Copyright (c) Juniper Networks, Inc., 2024-2025. // All rights reserved. // SPDX-License-Identifier: Apache-2.0 @@ -12,6 +12,7 @@ import ( "math/rand" "net" "testing" + "time" "github.com/Juniper/apstra-go-sdk/apstra/enum" "github.com/stretchr/testify/require" @@ -210,6 +211,8 @@ func TestUpdateTwoStageL3ClosSubinterface(t *testing.T) { // update the link endpoints require.NoError(t, bp.UpdateSubinterfaces(ctx, subinterfaceConfigs)) + time.Sleep(time.Second) + // fetch the result links, err = bp.GetAllSubinterfaceLinks(ctx) require.NoError(t, err) From ae5a6fe8d1cf67fa0cf6212d39e3c63ed8683b7f Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Thu, 23 Jan 2025 21:40:24 -0500 Subject: [PATCH 5/6] eliminate unused struct --- apstra/api_design_configlets.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apstra/api_design_configlets.go b/apstra/api_design_configlets.go index 218d84d3..867076cc 100644 --- a/apstra/api_design_configlets.go +++ b/apstra/api_design_configlets.go @@ -28,14 +28,6 @@ type ConfigletGenerator struct { Filename string `json:"filename"` } -type rawConfigletGenerator struct { - ConfigStyle enum.ConfigletStyle `json:"config_style"` - Section enum.ConfigletSection `json:"section"` - TemplateText string `json:"template_text"` - NegationTemplateText string `json:"negation_template_text"` - Filename string `json:"filename"` -} - var _ json.Unmarshaler = (*Configlet)(nil) type Configlet struct { From 27a77ad9192d9363e62b86b4e16af51a244375f3 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Thu, 23 Jan 2025 21:42:46 -0500 Subject: [PATCH 6/6] remove superflous json tag --- apstra/api_design_configlets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apstra/api_design_configlets.go b/apstra/api_design_configlets.go index 867076cc..d7f079ef 100644 --- a/apstra/api_design_configlets.go +++ b/apstra/api_design_configlets.go @@ -42,7 +42,7 @@ func (o *Configlet) UnmarshalJSON(bytes []byte) error { RefArchs []enum.RefDesign `json:"ref_archs"` Generators []ConfigletGenerator `json:"generators"` CreatedAt time.Time `json:"created_at"` - Id ObjectId `json:"id,omitempty"` + Id ObjectId `json:"id"` LastModifiedAt time.Time `json:"last_modified_at"` DisplayName string `json:"display_name"` }