diff --git a/pkg/commands/codegen/codegen.go b/pkg/commands/codegen/codegen.go index 0d30090a..aa9133b2 100644 --- a/pkg/commands/codegen/codegen.go +++ b/pkg/commands/codegen/codegen.go @@ -119,6 +119,8 @@ func (c *Command) Execute() error { resourceTyp = properties.ResourceEntry case properties.TerraformResourceUuid: resourceTyp = properties.ResourceUuid + case properties.TerraformResourceCustom: + resourceTyp = properties.ResourceCustom case properties.TerraformResourceConfig: panic("missing implementation for config type resources") } @@ -140,6 +142,8 @@ func (c *Command) Execute() error { resourceTyp = properties.ResourceEntryPlural case properties.TerraformResourceUuid: resourceTyp = properties.ResourceUuidPlural + case properties.TerraformResourceCustom: + resourceTyp = properties.ResourceCustom case properties.TerraformResourceConfig: panic("missing implementation for config type resources") } @@ -153,7 +157,7 @@ func (c *Command) Execute() error { resourceList = append(resourceList, resources...) dataSourceList = append(dataSourceList, dataSources...) } - } else if c.commandType == properties.CommandTypeSDK { + } else if c.commandType == properties.CommandTypeSDK && !spec.GoSdkSkip { generator := generate.NewCreator(config.Output.GoSdk, c.templatePath, spec) if err = generator.RenderTemplate(); err != nil { return fmt.Errorf("error rendering %s - %s", specPath, err) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index ea235ee5..f89b68a7 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -64,7 +64,7 @@ func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, ty name = fmt.Sprintf("%s_%s", spec.TerraformProviderConfig.Suffix, spec.TerraformProviderConfig.PluralName) case properties.ResourceEntryPlural: name = spec.TerraformProviderConfig.PluralSuffix - case properties.ResourceEntry, properties.ResourceUuid: + case properties.ResourceEntry, properties.ResourceUuid, properties.ResourceCustom: name = spec.Name } @@ -95,7 +95,7 @@ func (c *Creator) RenderTerraformProviderFile(spec *properties.Normalization, ty case properties.ResourceEntryPlural: name = spec.TerraformProviderConfig.PluralSuffix filePath = c.createTerraformProviderFilePath(name) - case properties.ResourceEntry, properties.ResourceUuid: + case properties.ResourceEntry, properties.ResourceUuid, properties.ResourceCustom: filePath = c.createTerraformProviderFilePath(spec.TerraformProviderConfig.Suffix) } @@ -243,6 +243,7 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) templatePath := filepath.Join(c.TemplatesDir, templateName) funcMap := template.FuncMap{ "renderImports": translate.RenderImports, + "RenderEntryImportStructs": func() (string, error) { return translate.RenderEntryImportStructs(c.Spec) }, "packageName": translate.PackageName, "locationType": translate.LocationType, "specParamType": translate.SpecParamType, diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 7871ee98..fec04a7c 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -18,22 +18,43 @@ import ( type Normalization struct { Name string `json:"name" yaml:"name"` TerraformProviderConfig TerraformProviderConfig `json:"terraform_provider_config" yaml:"terraform_provider_config"` + GoSdkSkip bool `json:"go_sdk_skip" yaml:"go_sdk_skip"` GoSdkPath []string `json:"go_sdk_path" yaml:"go_sdk_path"` XpathSuffix []string `json:"xpath_suffix" yaml:"xpath_suffix"` Locations map[string]*Location `json:"locations" yaml:"locations"` Entry *Entry `json:"entry" yaml:"entry"` - Imports map[string]*Import `json:"imports" yaml:"imports"` + Imports []Import `json:"imports" yaml:"imports"` Version string `json:"version" yaml:"version"` Spec *Spec `json:"spec" yaml:"spec"` Const map[string]*Const `json:"const" yaml:"const"` } +type Import struct { + Variant *NameVariant + Type *NameVariant + Locations map[string]ImportLocation +} + +type ImportLocation struct { + Name *NameVariant + Required bool + XpathElements []string + XpathVariables map[string]ImportXpathVariable +} + +type ImportXpathVariable struct { + Name *NameVariant + Description string + Default string +} + type TerraformResourceType string const ( TerraformResourceEntry TerraformResourceType = "entry" TerraformResourceUuid TerraformResourceType = "uuid" TerraformResourceConfig TerraformResourceType = "config" + TerraformResourceCustom TerraformResourceType = "custom" ) type TerraformResourceVariant string @@ -48,6 +69,7 @@ type TerraformProviderConfig struct { SkipDatasource bool `json:"skip_datasource" yaml:"skip_datasource"` SkipDatasourceListing bool `json:"skip_datasource_listing" yaml:"skip_datasource_listing"` ResourceType TerraformResourceType `json:"resource_type" yaml:"resource_type"` + CustomFuncs map[string]string `json:"custom_functions" yaml:"custom_functions"` ResourceVariants []TerraformResourceVariant `json:"resource_variants" yaml:"resource_variants"` Suffix string `json:"suffix" yaml:"suffix"` PluralSuffix string `json:"plural_suffix" yaml:"plural_suffix"` @@ -60,6 +82,14 @@ type NameVariant struct { LowerCamelCase string } +func NewNameVariant(name string) *NameVariant { + return &NameVariant{ + Underscore: naming.Underscore("", name, ""), + CamelCase: naming.CamelCase("", name, "", true), + LowerCamelCase: naming.CamelCase("", name, "", false), + } +} + type Location struct { Name *NameVariant Description string `json:"description" yaml:"description"` @@ -90,19 +120,6 @@ type Entry struct { Name *EntryName `json:"name" yaml:"name"` } -type Import struct { - Name *NameVariant - Xpath []string `json:"xpath" yaml:"xpath"` - Vars map[string]*ImportVar `json:"vars" yaml:"vars"` - OnlyForParams []string `json:"only_for_params" yaml:"only_for_params"` -} - -type ImportVar struct { - Name *NameVariant - Description string `json:"description" yaml:"description"` - Default string `json:"default" yaml:"default"` -} - type EntryName struct { Description string `json:"description" yaml:"description"` Length *EntryNameLength `json:"length" yaml:"length"` @@ -371,8 +388,11 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam Type: schemaSpec.Hashing.Type, } } + + var sensitive bool var terraformProviderConfig *SpecParamTerraformProviderConfig if schemaSpec.CodegenOverrides != nil { + sensitive = schemaSpec.CodegenOverrides.Terraform.Sensitive terraformProviderConfig = &SpecParamTerraformProviderConfig{ Computed: schemaSpec.CodegenOverrides.Terraform.Computed, } @@ -382,6 +402,7 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam Type: specType, Default: defaultVal, Required: schemaSpec.Required, + Sensitive: sensitive, TerraformProviderConfig: terraformProviderConfig, Hashing: specHashing, Profiles: profiles, @@ -447,19 +468,6 @@ func generateXpathVariables(variables []xpathschema.Variable) map[string]*Locati return xpathVars } -func generateImportVariables(variables []xpathschema.Variable) map[string]*ImportVar { - importVars := make(map[string]*ImportVar) - for _, variable := range variables { - entry := &ImportVar{ - Description: variable.Description, - Default: variable.Default, - } - importVars[variable.Name] = entry - } - - return importVars -} - func schemaToSpec(object object.Object) (*Normalization, error) { var resourceVariants []TerraformResourceVariant for _, elt := range object.TerraformConfig.ResourceVariants { @@ -472,13 +480,14 @@ func schemaToSpec(object object.Object) (*Normalization, error) { SkipDatasource: object.TerraformConfig.SkipDatasource, SkipDatasourceListing: object.TerraformConfig.SkipdatasourceListing, ResourceType: TerraformResourceType(object.TerraformConfig.ResourceType), + CustomFuncs: object.TerraformConfig.CustomFunctions, ResourceVariants: resourceVariants, Suffix: object.TerraformConfig.Suffix, PluralSuffix: object.TerraformConfig.PluralSuffix, PluralName: object.TerraformConfig.PluralName, }, Locations: make(map[string]*Location), - Imports: make(map[string]*Import), + GoSdkSkip: object.GoSdkConfig.Skip, GoSdkPath: object.GoSdkConfig.Package, XpathSuffix: object.XpathSuffix, Version: object.Version, @@ -560,39 +569,53 @@ func schemaToSpec(object object.Object) (*Normalization, error) { } - imports := make(map[string]*Import, len(object.Imports)) + var imports []Import for _, elt := range object.Imports { - var xpath []string - - schemaXpathVars := make(map[string]xpathschema.Variable) - for _, xpathVariable := range elt.Xpath.Variables { - schemaXpathVars[xpathVariable.Name] = xpathVariable - } - - for _, element := range elt.Xpath.Elements { - var eltEntry string - if xpathVar, ok := schemaXpathVars[element[1:]]; ok { - if xpathVar.Type == "entry" { - eltEntry = fmt.Sprintf("{{ Entry %s }}", elt.Name) - } else if xpathVar.Type == "object" { - eltEntry = fmt.Sprintf("{{ Object %s }}", elt.Name) + locations := make(map[string]ImportLocation, len(elt.Locations)) + for _, location := range elt.Locations { + schemaXpathVars := make(map[string]xpathschema.Variable, len(location.Xpath.Variables)) + xpathVars := make(map[string]ImportXpathVariable, len(location.Xpath.Variables)) + for _, xpathVariable := range location.Xpath.Variables { + schemaXpathVars[xpathVariable.Name] = xpathVariable + xpathVars[xpathVariable.Name] = ImportXpathVariable{ + Name: &NameVariant{ + Underscore: naming.Underscore("", xpathVariable.Name, ""), + CamelCase: naming.CamelCase("", xpathVariable.Name, "", true), + LowerCamelCase: naming.CamelCase("", xpathVariable.Name, "", false), + }, + Description: xpathVariable.Description, + Default: xpathVariable.Default, } - } else { - eltEntry = element } - xpath = append(xpath, eltEntry) - } - importVariables := generateImportVariables(elt.Xpath.Variables) - if len(importVariables) == 0 { - importVariables = nil - } + var xpath []string + xpath = append(xpath, location.Xpath.Elements...) - imports[elt.Name] = &Import{ - Xpath: xpath, - Vars: importVariables, - OnlyForParams: elt.OnlyForParams, + locations[location.Name] = ImportLocation{ + Name: &NameVariant{ + Underscore: naming.Underscore("", location.Name, ""), + CamelCase: naming.CamelCase("", location.Name, "", true), + LowerCamelCase: naming.CamelCase("", location.Name, "", false), + }, + Required: location.Required, + XpathVariables: xpathVars, + XpathElements: xpath, + } } + + imports = append(imports, Import{ + Type: &NameVariant{ + Underscore: naming.Underscore("", elt.Type, ""), + CamelCase: naming.CamelCase("", elt.Type, "", true), + LowerCamelCase: naming.CamelCase("", elt.Type, "", false), + }, + Variant: &NameVariant{ + Underscore: naming.Underscore("", elt.Variant, ""), + CamelCase: naming.CamelCase("", elt.Variant, "", true), + LowerCamelCase: naming.CamelCase("", elt.Variant, "", false), + }, + Locations: locations, + }) } if len(imports) > 0 { @@ -660,11 +683,6 @@ func ParseSpec(input []byte) (*Normalization, error) { return nil, err } - err = spec.AddNameVariantsForImports() - if err != nil { - return nil, err - } - err = spec.AddNameVariantsForParams() if err != nil { return nil, err @@ -704,27 +722,6 @@ func (spec *Normalization) AddNameVariantsForLocation() error { return nil } -// AddNameVariantsForImports add name variants for imports (under_score and CamelCase). -func (spec *Normalization) AddNameVariantsForImports() error { - for key, imp := range spec.Imports { - imp.Name = &NameVariant{ - Underscore: naming.Underscore("", key, ""), - CamelCase: naming.CamelCase("", key, "", true), - LowerCamelCase: naming.CamelCase("", key, "", false), - } - - for subkey, variable := range imp.Vars { - variable.Name = &NameVariant{ - Underscore: naming.Underscore("", subkey, ""), - CamelCase: naming.CamelCase("", subkey, "", true), - LowerCamelCase: naming.CamelCase("", subkey, "", false), - } - } - } - - return nil -} - // AddNameVariantsForParams recursively add name variants for params for nested specs. func AddNameVariantsForParams(name string, param *SpecParam) error { param.Name = &NameVariant{ diff --git a/pkg/properties/resourcetype.go b/pkg/properties/resourcetype.go index 4cb311f1..ab70f5cf 100644 --- a/pkg/properties/resourcetype.go +++ b/pkg/properties/resourcetype.go @@ -4,6 +4,7 @@ type ResourceType int const ( ResourceEntry ResourceType = iota + ResourceCustom ResourceType = iota ResourceEntryPlural ResourceType = iota ResourceUuid ResourceType = iota ResourceUuidPlural ResourceType = iota diff --git a/pkg/schema/imports/import.go b/pkg/schema/imports/import.go index d188536e..006a544a 100644 --- a/pkg/schema/imports/import.go +++ b/pkg/schema/imports/import.go @@ -1,9 +1,11 @@ package imports -import "github.com/paloaltonetworks/pan-os-codegen/pkg/schema/xpath" +import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/schema/location" +) type Import struct { - Name string `yaml:"name"` - Xpath xpathschema.Xpath `yaml:"xpath"` - OnlyForParams []string `yaml:"only_for_params"` + Variant string `yaml:"variant"` + Type string `yaml:"type"` + Locations []location.Location `yaml:"locations"` } diff --git a/pkg/schema/location/location.go b/pkg/schema/location/location.go index e060b397..810b25fa 100644 --- a/pkg/schema/location/location.go +++ b/pkg/schema/location/location.go @@ -14,6 +14,8 @@ const ( type Location struct { Name string `yaml:"name"` + Required bool `yaml:"required"` + ReadOnly bool `yaml:"read_only"` Description string `yaml:"description"` Devices []Device `yaml:"devices"` Xpath xpathschema.Xpath `yaml:"xpath"` diff --git a/pkg/schema/object/object.go b/pkg/schema/object/object.go index 68f8abc1..0c143700 100644 --- a/pkg/schema/object/object.go +++ b/pkg/schema/object/object.go @@ -15,6 +15,7 @@ const ( TerraformResourceEntry TerraformResourceType = "entry" TerraformResourceUuid TerraformResourceType = "uuid" TerraformResourceConfig TerraformResourceType = "config" + TerraformResourceCustom TerraformResourceType = "custom" ) type TerraformResourceVariant string @@ -29,6 +30,7 @@ type TerraformConfig struct { SkipDatasource bool `yaml:"skip_datasource"` SkipdatasourceListing bool `yaml:"skip_datasource_listing"` ResourceType TerraformResourceType `yaml:"resource_type"` + CustomFunctions map[string]string `yaml:"custom_functions"` ResourceVariants []TerraformResourceVariant `yaml:"resource_variants"` Suffix string `yaml:"suffix"` PluralSuffix string `yaml:"plural_suffix"` @@ -36,7 +38,8 @@ type TerraformConfig struct { } type GoSdkConfig struct { - Package []string + Skip bool `yaml:"skip"` + Package []string `yaml:"package"` } type Entry struct { diff --git a/pkg/schema/parameter/parameter.go b/pkg/schema/parameter/parameter.go index 68dcf1b0..a801cef2 100644 --- a/pkg/schema/parameter/parameter.go +++ b/pkg/schema/parameter/parameter.go @@ -43,7 +43,8 @@ type EnumSpecValue struct { } type CodegenOverridesTerraform struct { - Computed bool `yaml:"computed"` + Sensitive bool `yaml:"sensitive"` + Computed bool `yaml:"computed"` } type CodegenOverrides struct { diff --git a/pkg/translate/imports.go b/pkg/translate/imports.go index 0a23cc44..bb7aa9e5 100644 --- a/pkg/translate/imports.go +++ b/pkg/translate/imports.go @@ -47,6 +47,8 @@ func RenderImports(templateTypes ...string) (string, error) { manager.AddSdkImport("github.com/PaloAltoNetworks/pango/version", "") case "template": manager.AddSdkImport("github.com/PaloAltoNetworks/pango/panorama/template", "") + case "imports": + manager.AddStandardImport("encoding/xml", "") } } diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index b36730e7..380175a4 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -1,8 +1,11 @@ package translate import ( + "bytes" "fmt" + "log" "strings" + "text/template" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" @@ -66,6 +69,276 @@ func addNameAsParamForNestedSpec(parent []string, nestedSpecs map[string]*proper } } +const importLocationStructTmpl = ` +type ILocation interface { + xpath(version.Number) ([]string, error) +} + +type ImportLocation interface { + XpathForLocation(version.Number, ILocation) ([]string, error) + MarshalPangoXML([]string) (string, error) + UnmarshalPangoXML([]byte) ([]string, error) +} + +{{- range .Specs }} + {{- $spec := . }} + {{- $const := printf "%s%sType" $spec.Variant.CamelCase $spec.Type.CamelCase }} +type {{ $const }} int + +const ( + {{- range .Locations }} + {{ $spec.Variant.LowerCamelCase }}{{ $spec.Type.CamelCase }}{{ .Name.CamelCase }} {{ $const }} = iota + {{- end }} +) + + + {{ $topType := printf "%s%sImportLocation" $spec.Variant.CamelCase $spec.Type.CamelCase }} +type {{ $spec.Variant.CamelCase }}{{ $spec.Type.CamelCase }}ImportLocation struct { + typ {{ $const }} + {{- range .Locations }} + + {{- $typeName := printf "%s%s%sImportLocation" $spec.Variant.CamelCase $spec.Type.CamelCase .Name.CamelCase }} + {{ .Name.LowerCamelCase }} *{{ $typeName }} + {{- end }} +} + + {{- range .Locations }} + {{ $location := . }} + {{- $typeName := printf "%s%s%sImportLocation" $spec.Variant.CamelCase $spec.Type.CamelCase .Name.CamelCase }} +type {{ $typeName }} struct { + xpath []string + {{- range .XpathVariables }} + {{ .Name.LowerCamelCase }} string + {{- end }} +} + +type {{ $typeName }}Spec struct { + {{- range .XpathVariables }} + {{ .Name.CamelCase }} string + {{- end }} +} + +func New{{ $typeName }}(spec {{ $typeName }}Spec) *{{ $topType }} { + location := &{{ $typeName }}{ + {{- range .XpathVariables }} + {{ .Name.LowerCamelCase }}: spec.{{ .Name.CamelCase }}, + {{- end }} + } + + return &{{ $topType }}{ + typ: {{ $spec.Variant.LowerCamelCase }}{{ $spec.Type.CamelCase }}{{ .Name.CamelCase }}, + {{ $location.Name.LowerCamelCase }}: location, + } +} + +func (o *{{ $typeName }}) XpathForLocation(vn version.Number, loc ILocation) ([]string, error) { + ans, err := loc.xpath(vn) + if err != nil { + return nil, err + } + + importAns := []string{ + {{- range .XpathElements }} + {{ . }}, + {{- end }} + } + + return append(ans, importAns...), nil +} + +func (o *{{ $typeName }}) MarshalPangoXML(interfaces []string) (string, error) { + type member struct { + Name string ` + "`" + `xml:",chardata"` + "`" + ` + } + + type request struct { + XMLName xml.Name ` + "`" + `xml:"{{ .XpathFinalElement }}"` + "`" + ` + Members []member ` + "`" + `xml:"member"` + "`" + ` + } + + var members []member + for _, elt := range interfaces { + members = append(members, member{Name: elt}) + } + + expected := request { + Members: members, + } + bytes, err := xml.Marshal(expected) + if err != nil { + return "", err + } + + return string(bytes), nil +} + +func (o *{{ $typeName }}) UnmarshalPangoXML(bytes []byte) ([]string, error) { + type member struct { + Name string ` + "`" + `xml:",chardata"` + "`" + ` + } + + type response struct { + Members []member ` + "`" + `xml:"{{ .ResponseXpath }}"` + "`" + ` + } + + var existing response + err := xml.Unmarshal(bytes, &existing) + if err != nil { + return nil, err + } + + var interfaces []string + for _, elt := range existing.Members { + interfaces = append(interfaces, elt.Name) + } + + return interfaces, nil +} + {{- end }} + +func (o *{{ $topType }}) MarshalPangoXML(interfaces []string) (string, error) { + switch o.typ { + {{- range .Locations }} + case {{ $spec.Variant.LowerCamelCase }}{{ $spec.Type.CamelCase }}{{ .Name.CamelCase }}: + return o.{{ .Name.LowerCamelCase }}.MarshalPangoXML(interfaces) + {{- end }} + default: + return "", fmt.Errorf("invalid import location") + } +} + +func (o *{{ $topType }}) UnmarshalPangoXML(bytes []byte) ([]string, error) { + switch o.typ { + {{- range .Locations }} + case {{ $spec.Variant.LowerCamelCase }}{{ $spec.Type.CamelCase }}{{ .Name.CamelCase }}: + return o.{{ .Name.LowerCamelCase }}.UnmarshalPangoXML(bytes) + {{- end }} + default: + return nil, fmt.Errorf("invalid import location") + } +} + +func (o *{{ $topType }}) XpathForLocation(vn version.Number, loc ILocation) ([]string, error) { + switch o.typ { + {{- range .Locations }} + case {{ $spec.Variant.LowerCamelCase }}{{ $spec.Type.CamelCase }}{{ .Name.CamelCase }}: + return o.{{ .Name.LowerCamelCase }}.XpathForLocation(vn, loc) + {{- end }} + default: + return nil, fmt.Errorf("invalid import location") + } +} +{{- end }} +` + +type importXpathVariableSpec struct { + Name *properties.NameVariant + Description string + Default *string +} + +type importLocationSpec struct { + Name *properties.NameVariant + XpathElements []string + XpathVariables []importXpathVariableSpec + XpathFinalElement string + ResponseXpath string +} + +type importSpec struct { + Variant *properties.NameVariant + Type *properties.NameVariant + Locations []importLocationSpec +} + +func createImportLocationSpecsForLocation(location properties.ImportLocation) importLocationSpec { + var variables []importXpathVariableSpec + variablesByName := make(map[string]importXpathVariableSpec, len(location.XpathVariables)) + + for _, elt := range location.XpathVariables { + var defaultValue *string + if elt.Default != "" { + defaultValue = &elt.Default + } + variableSpec := importXpathVariableSpec{ + Name: elt.Name, + Description: elt.Description, + Default: defaultValue, + } + + variables = append(variables, variableSpec) + variablesByName[elt.Name.Underscore] = variableSpec + } + + var elements []string + for _, elt := range location.XpathElements { + if strings.HasPrefix(elt, "$") { + variableName := elt[1:] + asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s})", variablesByName[variableName].Name.LowerCamelCase) + elements = append(elements, asEntryXpath) + } else { + elements = append(elements, fmt.Sprintf("\"%s\"", elt)) + } + } + + xpathFinalElement := location.XpathElements[len(location.XpathElements)-1] + responseXpath := fmt.Sprintf("result>%s>member", xpathFinalElement) + + return importLocationSpec{ + Name: location.Name, + XpathElements: elements, + XpathVariables: variables, + XpathFinalElement: xpathFinalElement, + ResponseXpath: responseXpath, + } +} + +func createImportSpecsForNormalization(spec *properties.Normalization) []importSpec { + var specs []importSpec + + if spec.Name == "Ethernet interface" { + log.Printf("FOUND") + } + + for _, imp := range spec.Imports { + var locations []importLocationSpec + for _, location := range imp.Locations { + locations = append(locations, createImportLocationSpecsForLocation(location)) + } + + specs = append(specs, importSpec{ + Variant: imp.Variant, + Type: imp.Type, + Locations: locations, + }) + } + + return specs +} + +func RenderEntryImportStructs(spec *properties.Normalization) (string, error) { + type renderContext struct { + Specs []importSpec + } + + tmpl, err := template.New("render-entry-import-structs").Parse(importLocationStructTmpl) + if err != nil { + return "", err + } + + data := renderContext{ + Specs: createImportSpecsForNormalization(spec), + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + if err != nil { + return "", err + } + + return buf.String(), nil +} + // SpecParamType returns param type (it can be a nested spec) for structs based on spec from YAML files. func SpecParamType(parent string, param *properties.SpecParam) string { prefix := determinePrefix(param, false) diff --git a/pkg/translate/terraform_provider/device_group_parent_crud.go b/pkg/translate/terraform_provider/device_group_parent_crud.go new file mode 100644 index 00000000..a2352393 --- /dev/null +++ b/pkg/translate/terraform_provider/device_group_parent_crud.go @@ -0,0 +1,192 @@ +package terraform_provider + +const deviceGroupParentImports = ` +import ( + "encoding/xml" + + "github.com/PaloAltoNetworks/pango/xmlapi" + "github.com/PaloAltoNetworks/pango/util" +) +` + +const deviceGroupParentCommon = ` +var _ = tflog.Warn +type _ = diag.Diagnostics +type dgpReq struct { + XMLName xml.Name ` + "`" + `xml:"show"` + "`" + ` + Cmd string ` + "`" + `xml:"dg-hierarchy"` + "`" + ` +} + +type dgpResp struct { + Result *dgHierarchy ` + "`" + `xml:"result>dg-hierarchy"` + "`" + ` +} + +func (o *dgpResp) results() map[string]string { + ans := make(map[string]string) + + if o.Result != nil { + for _, v := range o.Result.Info { + ans[v.Name] = "" + v.results(ans) + } + } + + return ans +} + +type dgHierarchy struct { + Info []dghInfo ` + "`" + `xml:"dg"` + "`" + ` +} + +type dghInfo struct { + Name string ` + "`" + `xml:"name,attr"` + "`" + ` + Children []dghInfo ` + "`" + `xml:"dg"` + "`" + ` +} + +func (o *dghInfo) results(ans map[string]string) { + for _, v := range o.Children { + ans[v.Name] = o.Name + v.results(ans) + } +} + +type apReq struct { + XMLName xml.Name ` + "`" + `xml:"request"` + "`" + ` + Info apInfo ` + "`" + `xml:"move-dg>entry"` + "`" + ` +} + +type apInfo struct { + Child string ` + "`" + `xml:"name,attr"` + "`" + ` + Parent string ` + "`" + `xml:"new-parent-dg,omitempty"` + "`" + ` +} + +func getParents(ctx context.Context, client util.PangoClient, deviceGroup string) (map[string]string, error) { + cmd := &xmlapi.Op{ + Command: dgpReq{}, + } + + var ans dgpResp + if _, _, err := client.Communicate(ctx, cmd, false, &ans); err != nil { + return nil, err + } + + return ans.results(), nil +} + +func assignParent(ctx context.Context, client util.PangoClient, deviceGroup string, parent string) error { + cmd := &xmlapi.Op{ + Command: apReq{ + Info: apInfo{ + Child: deviceGroup, + Parent: parent, + }, + }, + } + + ans := util.JobResponse{} + if _, _, err := client.Communicate(ctx, cmd, false, &ans); err != nil { + return err + } + if err := client.WaitForJob(ctx, ans.Id, 0, nil); err != nil { + return err + } + + return nil +} +` + +const deviceGroupParentDataSourceRead = ` +var state DeviceGroupParentResourceModel +resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) +if resp.Diagnostics.HasError() { + return +} + + +name := state.DeviceGroup.ValueString() +hierarchy, err := getParents(ctx, o.client, name) +if err != nil { + resp.Diagnostics.AddError("Failed to query for the device group parent", err.Error()) + return +} + +parent, ok := hierarchy[name] +if !ok { + resp.Diagnostics.AddError("Failed to query for the device group parent", fmt.Sprintf("Device Group '%s' doesn't exist", name)) + return +} +state.Parent = types.StringValue(parent) + +resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +` + +const deviceGroupParentResourceRead = ` +var state DeviceGroupParentResourceModel +resp.Diagnostics.Append(req.State.Get(ctx, &state)...) +if resp.Diagnostics.HasError() { + return +} + +name := state.DeviceGroup.ValueString() +hierarchy, err := getParents(ctx, o.client, name) +if err != nil { + resp.Diagnostics.AddError("Failed to query for the device group parent", err.Error()) + return +} + +parent, ok := hierarchy[name] +if !ok { + resp.Diagnostics.AddError("Failed to query for the device group parent", fmt.Sprintf("Device Group '%s' doesn't exist", name)) + return +} +state.Parent = types.StringValue(parent) + +resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +` + +const deviceGroupParentResourceCreate = ` +var state DeviceGroupParentResourceModel +resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) +if resp.Diagnostics.HasError() { + return +} + +deviceGroup := state.DeviceGroup.ValueString() +parent := state.Parent.ValueString() +if err := assignParent(ctx, r.client, deviceGroup, parent); err != nil { + resp.Diagnostics.AddError("Failed to assign parent to the device group", err.Error()) + return +} + +state.Tfid = types.StringValue("") +resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +` +const deviceGroupParentResourceUpdate = deviceGroupParentResourceCreate +const deviceGroupParentResourceDelete = ` +var state DeviceGroupParentResourceModel +resp.Diagnostics.Append(req.State.Get(ctx, &state)...) +if resp.Diagnostics.HasError() { + return +} + +name := state.DeviceGroup.ValueString() +hierarchy, err := getParents(ctx, r.client, name) +if err != nil { + resp.Diagnostics.AddError("Failed to query for the device group parent", err.Error()) + return +} + +parent, ok := hierarchy[name] +if !ok { + resp.Diagnostics.AddError("Failed to query for the device group parent", fmt.Sprintf("Device Group '%s' doesn't exist", name)) + return +} + +if parent != "" { + deviceGroup := state.DeviceGroup.ValueString() + if err := assignParent(ctx, r.client, deviceGroup, ""); err != nil { + resp.Diagnostics.AddError("Failed to assign parent to the device group", err.Error()) + return + } +} +` diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index 3ec9ef66..3b5dbac0 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -166,6 +166,8 @@ func generateFromTerraformToPangoParameter(resourceTyp properties.ResourceType, Params: paramSpecs, OneOf: oneofSpecs, }) + case properties.ResourceCustom: + panic("custom resources don't generate anything") } for _, elt := range prop.Spec.Params { @@ -504,6 +506,10 @@ func pascalCase(value string) string { } func RenderCopyToPangoFunctions(resourceTyp properties.ResourceType, pkgName string, terraformTypePrefix string, property *properties.Normalization) (string, error) { + if resourceTyp == properties.ResourceCustom { + return "", nil + } + specs := generateFromTerraformToPangoParameter(resourceTyp, pkgName, terraformTypePrefix, "", property, "") type context struct { @@ -520,6 +526,10 @@ func RenderCopyToPangoFunctions(resourceTyp properties.ResourceType, pkgName str } func RenderCopyFromPangoFunctions(resourceTyp properties.ResourceType, pkgName string, terraformTypePrefix string, property *properties.Normalization) (string, error) { + if resourceTyp == properties.ResourceCustom { + return "", nil + } + specs := generateFromTerraformToPangoParameter(resourceTyp, pkgName, terraformTypePrefix, "", property, "") type context struct { @@ -590,10 +600,32 @@ func RenderLocationStructs(resourceTyp properties.ResourceType, names *NameProvi } var fields []fieldCtx + + for _, i := range spec.Imports { + if i.Type.CamelCase != data.Name.CamelCase { + continue + } + + for _, elt := range i.Locations { + if elt.Required { + fields = append(fields, fieldCtx{ + Name: elt.Name.CamelCase, + Type: "types.String", + Tags: []string{fmt.Sprintf("`tfsdk:\"%s\"`", elt.Name.Underscore)}, + }) + } + } + } + for _, param := range data.Vars { paramTag := fmt.Sprintf("`tfsdk:\"%s\"`", param.Name.Underscore) + name := param.Name.CamelCase + if name == data.Name.CamelCase { + name = "Name" + paramTag = "`tfsdk:\"name\"`" + } fields = append(fields, fieldCtx{ - Name: param.Name.CamelCase, + Name: name, Type: "types.String", Tags: []string{paramTag}, }) @@ -619,7 +651,7 @@ const locationSchemaGetterTmpl = ` "{{ .Name.Underscore }}": {{ .SchemaType }}{ Description: "{{ .Description }}", {{- if .Required }} - Required: true + Required: true, {{- else }} Optional: true, {{- end }} @@ -709,9 +741,31 @@ func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalizat } var variableAttrs []attributeCtx + + for _, i := range spec.Imports { + if i.Type.CamelCase != data.Name.CamelCase { + continue + } + + for _, elt := range i.Locations { + if elt.Required { + variableAttrs = append(variableAttrs, attributeCtx{ + Name: elt.Name, + SchemaType: "rsschema.StringAttribute", + Required: true, + ModifierType: "String", + }) + } + } + } + for _, variable := range data.Vars { + name := variable.Name + if name.CamelCase == data.Name.CamelCase { + name = properties.NewNameVariant("name") + } attribute := attributeCtx{ - Name: variable.Name, + Name: name, Description: variable.Description, SchemaType: "rsschema.StringAttribute", Required: false, @@ -771,6 +825,17 @@ func RenderLocationSchemaGetter(names *NameProvider, spec *properties.Normalizat return processTemplate(locationSchemaGetterTmpl, "render-location-schema-getter", data, commonFuncMap) } +func RenderCustomImports(spec *properties.Normalization) string { + template, _ := getCustomTemplateForFunction(spec, "Imports") + return template +} + +func RenderCustomCommonCode(names *NameProvider, spec *properties.Normalization) string { + template, _ := getCustomTemplateForFunction(spec, "Common") + return template + +} + func createSchemaSpecForParameter(schemaTyp schemaType, structPrefix string, packageName string, param *properties.SpecParam) []schemaCtx { var schemas []schemaCtx @@ -821,8 +886,13 @@ func createSchemaSpecForParameter(schemaTyp schemaType, structPrefix string, pac } var computed bool - if param.TerraformProviderConfig != nil { - computed = param.TerraformProviderConfig.Computed + switch schemaTyp { + case schemaDataSource: + computed = true + case schemaResource: + if param.TerraformProviderConfig != nil { + computed = param.TerraformProviderConfig.Computed + } } schemas = append(schemas, schemaCtx{ @@ -890,10 +960,15 @@ func createSchemaAttributeForParameter(schemaTyp schemaType, packageName string, } var computed bool - if param.TerraformProviderConfig != nil { - computed = param.TerraformProviderConfig.Computed - } else if param.Default != "" { + switch schemaTyp { + case schemaDataSource: computed = true + case schemaResource: + if param.TerraformProviderConfig != nil { + computed = param.TerraformProviderConfig.Computed + } else if param.Default != "" { + computed = true + } } // TODO(kklimonda): This is pretty one-off implementation to @@ -1152,10 +1227,8 @@ func createSchemaSpecForModel(resourceTyp properties.ResourceType, schemaTyp sch structName = names.ResourceStructName } - log.Printf("createSchemaSpecForModel: structName: %s", structName) - switch resourceTyp { - case properties.ResourceEntry: + case properties.ResourceEntry, properties.ResourceCustom: return createSchemaSpecForEntrySingularModel(resourceTyp, schemaTyp, spec, packageName, structName) case properties.ResourceEntryPlural: return createSchemaSpecForEntryListModel(resourceTyp, schemaTyp, spec, packageName, structName) @@ -1371,9 +1444,124 @@ func RenderDataSourceSchema(resourceTyp properties.ResourceType, names *NameProv return processTemplate(renderSchemaTemplate, "render-resource-schema", data, commonFuncMap) } +const importLocationAssignmentTmpl = ` +var location {{ $.PackageName }}.ImportLocation +{{- range .Specs }} +{{ $type := . }} +if {{ $.LocationVar }}.{{ .Name.CamelCase }} != nil { + {{- range .Locations }} + {{- $pangoStruct := GetPangoStructForLocation $.Variants $type.Name .Name }} + // {{ .Name.CamelCase }} + location = {{ $.PackageName }}.New{{ $pangoStruct }}({{ $.PackageName }}.{{ $pangoStruct }}Spec{ + {{- range .Fields }} + {{ . }}: {{ $.LocationVar }}.{{ $type.Name.CamelCase }}.{{ . }}.ValueString(), + {{- end }} + }) + {{- end }} +} +{{- end }} +` + +func RenderImportLocationAssignment(names *NameProvider, spec *properties.Normalization, locationVar string) (string, error) { + if len(spec.Imports) == 0 { + return "", nil + } + + type importVariantSpec struct { + PangoStructNames *map[string]string + } + + type importLocationSpec struct { + Name *properties.NameVariant + Fields []string + } + + type importSpec struct { + Name *properties.NameVariant + Locations []importLocationSpec + } + + var importSpecs []importSpec + variantsByName := make(map[string]importVariantSpec) + for _, elt := range spec.Imports { + existing, found := variantsByName[elt.Type.CamelCase] + if !found { + pangoStructNames := make(map[string]string) + existing = importVariantSpec{ + PangoStructNames: &pangoStructNames, + } + } + + var locations []importLocationSpec + for _, loc := range elt.Locations { + if !loc.Required { + continue + } + + var fields []string + for _, elt := range loc.XpathVariables { + fields = append(fields, elt.Name.CamelCase) + } + + pangoStructName := fmt.Sprintf("%s%s%sImportLocation", elt.Variant.CamelCase, elt.Type.CamelCase, loc.Name.CamelCase) + (*existing.PangoStructNames)[loc.Name.CamelCase] = pangoStructName + locations = append(locations, importLocationSpec{ + Name: loc.Name, + Fields: fields, + }) + } + variantsByName[elt.Type.CamelCase] = existing + + importSpecs = append(importSpecs, importSpec{ + Name: elt.Type, + Locations: locations, + }) + } + + type context struct { + PackageName string + LocationVar string + Variants map[string]importVariantSpec + Specs []importSpec + } + + data := context{ + PackageName: names.PackageName, + LocationVar: locationVar, + Variants: variantsByName, + Specs: importSpecs, + } + + funcMap := template.FuncMap{ + "GetPangoStructForLocation": func(variants map[string]importVariantSpec, typ *properties.NameVariant, location *properties.NameVariant) (string, error) { + log.Printf("len(variants): %d", len(variants)) + for name, elt := range variants { + log.Printf("Type: %s", name) + for name, structName := range *elt.PangoStructNames { + log.Printf(" Name: %s, StructName: %s", name, structName) + } + } + variantSpec, found := variants[typ.CamelCase] + if !found { + return "", fmt.Errorf("failed to find variant for type '%s'", typ.CamelCase) + } + + structName, found := (*variantSpec.PangoStructNames)[location.CamelCase] + if !found { + return "", fmt.Errorf("failed to find variant for type '%s', location '%s'", typ.CamelCase, location.CamelCase) + } + + return structName, nil + }, + } + + return processTemplate(importLocationAssignmentTmpl, "render-locations-pango-to-state", data, funcMap) +} + type locationFieldCtx struct { - Name string - Type string + PangoName string + TerraformName string + Type string } type locationCtx struct { @@ -1390,9 +1578,15 @@ func renderLocationsGetContext(names *NameProvider, spec *properties.Normalizati for _, location := range spec.Locations { var fields []locationFieldCtx for _, variable := range location.Vars { + name := variable.Name.CamelCase + if variable.Name.CamelCase == location.Name.CamelCase { + name = "Name" + } + fields = append(fields, locationFieldCtx{ - Name: variable.Name.CamelCase, - Type: "String", + PangoName: variable.Name.CamelCase, + TerraformName: name, + Type: "String", }) } locations = append(locations, locationCtx{ @@ -1418,7 +1612,7 @@ if {{ $.Source }}.{{ .Name }} != nil { {{ $.Dest }}.{{ .Name }} = &{{ .TerraformStructName }}{ {{ $locationName := .Name }} {{- range .Fields }} - {{ .Name }}: types.{{ .Type }}Value({{ $.Source }}.{{ $locationName }}.{{ .Name }}), + {{ .TerraformName }}: types.{{ .Type }}Value({{ $.Source }}.{{ $locationName }}.{{ .PangoName }}), {{- end }} } } @@ -1447,7 +1641,7 @@ if {{ $.Source }}.{{ .Name }} != nil { {{ $.Dest }}.{{ .Name }} = &{{ .SdkStructName }}{ {{ $locationName := .Name }} {{- range .Fields }} - {{ .Name }}: {{ $.Source }}.{{ $locationName }}.{{ .Name }}.ValueString(), + {{ .PangoName }}: {{ $.Source }}.{{ $locationName }}.{{ .TerraformName }}.ValueString(), {{- end }} } } @@ -1735,7 +1929,7 @@ func createStructSpecForModel(resourceTyp properties.ResourceType, schemaTyp sch } switch resourceTyp { - case properties.ResourceEntry: + case properties.ResourceEntry, properties.ResourceCustom: return createStructSpecForEntryModel(resourceTyp, schemaTyp, spec, names) case properties.ResourceEntryPlural: return createStructSpecForEntryListModel(resourceTyp, schemaTyp, spec, names) @@ -1809,9 +2003,24 @@ func RenderDataSourceStructs(resourceTyp properties.ResourceType, names *NamePro return processTemplate(dataSourceStructs, "render-structs", data, commonFuncMap) } +func getCustomTemplateForFunction(spec *properties.Normalization, function string) (string, error) { + if resource, found := customResourceFuncsMap[spec.TerraformProviderConfig.Suffix]; !found { + return "", fmt.Errorf("cannot find a list of custom functions for %s", spec.TerraformProviderConfig.Suffix) + } else { + if template, found := resource[function]; !found { + return "", fmt.Errorf("No template for function '%s'", function) + } else { + return template, nil + } + } +} + func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProvider, serviceName string, paramSpec *properties.Normalization, terraformProvider *properties.TerraformProviderFile, resourceSDKName string) (string, error) { funcMap := template.FuncMap{ "ConfigToEntry": ConfigEntry, + "RenderImportLocationAssignment": func(locationVar string) (string, error) { + return RenderImportLocationAssignment(names, paramSpec, locationVar) + }, "RenderCreateUpdateMovementRequired": func(state string, entries string) (string, error) { return RendeCreateUpdateMovementRequired(state, entries) }, @@ -1846,6 +2055,12 @@ func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProv exhaustive = false tmpl = resourceCreateManyFunction listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName) + case properties.ResourceCustom: + var err error + tmpl, err = getCustomTemplateForFunction(paramSpec, "Create") + if err != nil { + return "", err + } } listAttributeVariant := &properties.NameVariant{ @@ -1860,6 +2075,7 @@ func ResourceCreateFunction(resourceTyp properties.ResourceType, names *NameProv } data := map[string]interface{}{ "HasEncryptedResources": paramSpec.HasEncryptedResources(), + "HasImports": len(paramSpec.Imports) > 0, "Exhaustive": exhaustive, "ResourceIsMap": resourceIsMap, "ListAttribute": listAttributeVariant, @@ -1891,6 +2107,12 @@ func DataSourceReadFunction(resourceTyp properties.ResourceType, names *NameProv case properties.ResourceUuid, properties.ResourceUuidPlural: tmpl = resourceReadManyFunction listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName) + case properties.ResourceCustom: + var err error + tmpl, err = getCustomTemplateForFunction(paramSpec, "DataSourceRead") + if err != nil { + return "", err + } } listAttributeVariant := &properties.NameVariant{ @@ -1951,6 +2173,12 @@ func ResourceReadFunction(resourceTyp properties.ResourceType, names *NameProvid case properties.ResourceUuidPlural: tmpl = resourceReadManyFunction listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName) + case properties.ResourceCustom: + var err error + tmpl, err = getCustomTemplateForFunction(paramSpec, "ResourceRead") + if err != nil { + return "", err + } } listAttributeVariant := &properties.NameVariant{ @@ -2012,6 +2240,12 @@ func ResourceUpdateFunction(resourceTyp properties.ResourceType, names *NameProv case properties.ResourceUuidPlural: tmpl = resourceUpdateManyFunction listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName) + case properties.ResourceCustom: + var err error + tmpl, err = getCustomTemplateForFunction(paramSpec, "Update") + if err != nil { + return "", err + } } listAttributeVariant := &properties.NameVariant{ @@ -2073,6 +2307,12 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv case properties.ResourceUuidPlural: tmpl = resourceDeleteManyFunction listAttribute = pascalCase(paramSpec.TerraformProviderConfig.PluralName) + case properties.ResourceCustom: + var err error + tmpl, err = getCustomTemplateForFunction(paramSpec, "Delete") + if err != nil { + return "", err + } } listAttributeVariant := &properties.NameVariant{ @@ -2089,6 +2329,7 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv data := map[string]interface{}{ "HasEncryptedResources": paramSpec.HasEncryptedResources(), "ResourceIsMap": resourceIsMap, + "HasImports": len(paramSpec.Imports) > 0, "EntryOrConfig": paramSpec.EntryOrConfig(), "ListAttribute": listAttributeVariant, "Exhaustive": exhaustive, @@ -2099,6 +2340,9 @@ func ResourceDeleteFunction(resourceTyp properties.ResourceType, names *NameProv } funcMap := template.FuncMap{ + "RenderImportLocationAssignment": func(locationVar string) (string, error) { + return RenderImportLocationAssignment(names, paramSpec, locationVar) + }, "RenderLocationsStateToPango": func(source string, dest string) (string, error) { return RenderLocationsStateToPango(names, paramSpec, source, dest) }, @@ -2128,3 +2372,15 @@ func ConfigEntry(entryName string, param *properties.SpecParam) (string, error) return processTemplate(resourceConfigEntry, "config-entry", entryData, nil) } + +var customResourceFuncsMap = map[string]map[string]string{ + "device_group_parent": { + "Imports": deviceGroupParentImports, + "DataSourceRead": deviceGroupParentDataSourceRead, + "ResourceRead": deviceGroupParentResourceRead, + "Create": deviceGroupParentResourceCreate, + "Update": deviceGroupParentResourceUpdate, + "Delete": deviceGroupParentResourceDelete, + "Common": deviceGroupParentCommon, + }, +} diff --git a/pkg/translate/terraform_provider/template.go b/pkg/translate/terraform_provider/template.go index a5e2b254..dc8c2e15 100644 --- a/pkg/translate/terraform_provider/template.go +++ b/pkg/translate/terraform_provider/template.go @@ -249,21 +249,20 @@ type {{ resourceStructName }} struct { client *pango.Client } - +{{- if not GoSDKSkipped }} type {{ resourceStructName }}Tfid struct { {{ CreateTfIdStruct }} } - func (o *{{ resourceStructName }}Tfid) IsValid() error { -{{- if .HasEntryName }} + {{- if .HasEntryName }} if o.Name == "" { return fmt.Errorf("name is unspecified") } -{{- end }} + {{- end }} return o.Location.IsValid() } - +{{- end }} func {{ resourceStructName }}LocationSchema() rsschema.Attribute { return {{ structName }}LocationSchema() @@ -754,7 +753,12 @@ const resourceCreateFunction = ` */ // Perform the operation. +{{- if .HasImports }} + {{ RenderImportLocationAssignment "state.Location" }} + create, err := svc.Create(ctx, loc.Location, []{{ .resourceSDKName }}.ImportLocation{location}, *obj) +{{- else }} create, err := svc.Create(ctx, loc.Location, *obj) +{{- end }} if err != nil { resp.Diagnostics.AddError("Error in create", err.Error()) return @@ -1095,14 +1099,13 @@ const resourceReadFunction = ` {{- end }} resp.Diagnostics.Append(copy_diags...) - {{ RenderLocationsPangoToState "loc.Location" "state.Location" }} - /* // Keep the timeouts. // TODO: This won't work for state import. state.Timeouts = savestate.Timeouts */ + state.Location = savestate.Location // Save tfid to state. state.Tfid = savestate.Tfid @@ -2019,7 +2022,13 @@ const resourceDeleteFunction = ` // Perform the operation. {{- if .HasEntryName }} - if err := svc.Delete(ctx, loc.Location, loc.Name); err != nil && !IsObjectNotFound(err) { + {{- if .HasImports }} + {{ RenderImportLocationAssignment "state.Location" }} + err := svc.Delete(ctx, loc.Location, []{{ .resourceSDKName }}.ImportLocation{location}, loc.Name) + {{- else }} + err := svc.Delete(ctx, loc.Location, loc.Name) + {{- end }} + if err != nil && !IsObjectNotFound(err) { resp.Diagnostics.AddError("Error in delete", err.Error()) } {{- else }} @@ -2034,7 +2043,9 @@ const resourceDeleteFunction = ` if diags.HasError() { return } - if err := svc.Delete(ctx, loc.Location, *obj); err != nil && !IsObjectNotFound(err) { + // HasImports: {{ .HasImports }} + err := svc.Delete(ctx, loc.Location, *obj) + if err != nil && !IsObjectNotFound(err) { resp.Diagnostics.AddError("Error in delete", err.Error()) } {{- end }} @@ -2044,6 +2055,8 @@ const commonTemplate = ` {{- RenderLocationStructs }} {{- RenderLocationSchemaGetter }} + +{{- RenderCustomCommonCode }} ` const dataSourceSingletonObj = ` @@ -2067,18 +2080,21 @@ type {{ dataSourceStructName }}Filter struct { //TODO: Generate Data Source filter via function } +{{- if not GoSDKSkipped }} type {{ dataSourceStructName }}Tfid struct { {{ CreateTfIdStruct }} } func (o *{{ dataSourceStructName }}Tfid) IsValid() error { -{{- if .HasEntryName }} + {{- if .HasEntryName }} if o.Name == "" { return fmt.Errorf("name is unspecified") } -{{- end }} + {{- end }} return o.Location.IsValid() } +{{- end }} + {{ RenderDataSourceStructs }} @@ -2123,6 +2139,7 @@ package provider // Note: This file is automatically generated. Manually made changes // will be overwritten when the provider is generated. {{ renderImports }} +{{ renderCustomImports }} {{ renderCode }} {{- /* Done */ -}} diff --git a/pkg/translate/terraform_provider/terraform_provider_file.go b/pkg/translate/terraform_provider/terraform_provider_file.go index 49cf98d7..9614c66c 100644 --- a/pkg/translate/terraform_provider/terraform_provider_file.go +++ b/pkg/translate/terraform_provider/terraform_provider_file.go @@ -25,8 +25,8 @@ type NameProvider struct { func NewNameProvider(spec *properties.Normalization, resourceTyp properties.ResourceType) *NameProvider { var tfName string switch resourceTyp { - case properties.ResourceEntry: - tfName = spec.Name + case properties.ResourceEntry, properties.ResourceCustom: + tfName = spec.TerraformProviderConfig.Suffix case properties.ResourceEntryPlural: tfName = spec.TerraformProviderConfig.PluralSuffix case properties.ResourceUuid: @@ -121,11 +121,12 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper switch resourceTyp { case properties.ResourceUuidPlural: hasPosition = true - case properties.ResourceEntry, properties.ResourceEntryPlural, properties.ResourceUuid: + case properties.ResourceEntry, properties.ResourceEntryPlural, properties.ResourceUuid, properties.ResourceCustom: hasPosition = false } funcMap := template.FuncMap{ + "GoSDKSkipped": func() bool { return spec.GoSdkSkip }, "HasPosition": func() bool { return hasPosition }, "metaName": func() string { return names.MetaName }, "structName": func() string { return names.StructName }, @@ -183,13 +184,15 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper case properties.ResourceEntryPlural: terraformProvider.ImportManager.AddSdkImport("github.com/PaloAltoNetworks/pango/xmlapi", "") terraformProvider.ImportManager.AddSdkImport("github.com/PaloAltoNetworks/pango/util", "") - case properties.ResourceEntry: + case properties.ResourceEntry, properties.ResourceCustom: } // Generate Resource with entry style terraformProvider.ImportManager.AddStandardImport("fmt", "") - terraformProvider.ImportManager.AddSdkImport(sdkPkgPath(spec), "") + if !spec.GoSdkSkip { + terraformProvider.ImportManager.AddSdkImport(sdkPkgPath(spec), "") + } conditionallyAddValidators(terraformProvider.ImportManager, spec) conditionallyAddModifiers(terraformProvider.ImportManager, spec) @@ -213,7 +216,9 @@ func (g *GenerateTerraformProvider) GenerateTerraformResource(resourceTyp proper terraformProvider.ImportManager.AddStandardImport("fmt", "") - terraformProvider.ImportManager.AddSdkImport(sdkPkgPath(spec), "") + if !spec.GoSdkSkip { + terraformProvider.ImportManager.AddSdkImport(sdkPkgPath(spec), "") + } conditionallyAddValidators(terraformProvider.ImportManager, spec) conditionallyAddModifiers(terraformProvider.ImportManager, spec) @@ -262,6 +267,7 @@ func (g *GenerateTerraformProvider) GenerateTerraformDataSource(resourceTyp prop resourceType := "DataSource" names := NewNameProvider(spec, resourceTyp) funcMap := template.FuncMap{ + "GoSDKSkipped": func() bool { return spec.GoSdkSkip }, "metaName": func() string { return names.MetaName }, "structName": func() string { return names.StructName }, "serviceName": func() string { return names.TfName }, @@ -306,6 +312,7 @@ func (g *GenerateTerraformProvider) GenerateCommonCode(resourceTyp properties.Re funcMap := template.FuncMap{ "RenderLocationStructs": func() (string, error) { return RenderLocationStructs(resourceTyp, names, spec) }, "RenderLocationSchemaGetter": func() (string, error) { return RenderLocationSchemaGetter(names, spec) }, + "RenderCustomCommonCode": func() string { return RenderCustomCommonCode(names, spec) }, } return g.generateTerraformEntityTemplate("Common", names, spec, terraformProvider, commonTemplate, funcMap) } @@ -316,8 +323,9 @@ func (g *GenerateTerraformProvider) GenerateTerraformProviderFile(spec *properti // terraformProvider.ImportManager.AddSdkImport(sdkPkgPath(spec), "") funcMap := template.FuncMap{ - "renderImports": func() (string, error) { return terraformProvider.ImportManager.RenderImports() }, - "renderCode": func() string { return terraformProvider.Code.String() }, + "renderImports": func() (string, error) { return terraformProvider.ImportManager.RenderImports() }, + "renderCustomImports": func() string { return RenderCustomImports(spec) }, + "renderCode": func() string { return terraformProvider.Code.String() }, } return g.generateTerraformEntityTemplate("ProviderFile", &NameProvider{}, spec, terraformProvider, providerFile, funcMap) } diff --git a/specs/network/interface/ethernet.yaml b/specs/network/interface/ethernet.yaml index b2a1b0c5..3c635279 100644 --- a/specs/network/interface/ethernet.yaml +++ b/specs/network/interface/ethernet.yaml @@ -82,26 +82,69 @@ entries: min: 10 max: 20 imports: - - name: template - xpath: - path: - - "config" - - "devices" - - "$ngfw_device" - - "vsys" - - "$vsys" - - "import" - - "network" - - "interfaces" - vars: - - name: "ngfw_device" - description: "The NGFW device." - default: "localhost.localdomain" - - name: "vsys" - description: "The vsys." - default: "vsys1" - only_for_params: - - layer3 + - variant: layer3 + type: template + locations: + - name: virtual-router + xpath: + path: + - network + - virtual-router + - $router + - interface + vars: + - name: "vsys" + description: "The vsys." + default: "vsys1" + - name: router + description: Virtual Router + - name: logical-router + xpath: + path: + - network + - logical-router + - $router + - vrf + - $vrf + - interface + vars: + - name: "vsys" + description: "The vsys." + default: "vsys1" + - name: router + description: Logical Router Name + - name: vrf + description: Logical Router VRF + - name: vsys + required: true + xpath: + path: + - vsys + - $vsys + - import + - network + - interface + vars: + - name: "vsys" + description: "The vsys." + default: "vsys1" + - name: zone + xpath: + path: + - vsys + - $vsys + - zone + - $zone + - network + - layer3 + vars: + - name: "vsys" + description: "The vsys." + default: "vsys1" + - name: "vsys" + description: "Security Zone" + - name: zone + description: Security Zone Identifier version: "10.1.0" spec: params: diff --git a/specs/objects/tag.yaml b/specs/objects/administrative-tag.yaml similarity index 50% rename from specs/objects/tag.yaml rename to specs/objects/administrative-tag.yaml index e0097f42..65f970b3 100644 --- a/specs/objects/tag.yaml +++ b/specs/objects/administrative-tag.yaml @@ -112,15 +112,135 @@ spec: validators: - type: values spec: - values: ["color1", "color2", "color24"] + values: + [ + color1, + color2, + color3, + color4, + color5, + color6, + color7, + color8, + color9, + color10, + color11, + color12, + color13, + color14, + color15, + color16, + color17, + color18, + color19, + color20, + color21, + color22, + color23, + color24, + color25, + color26, + color27, + color28, + color29, + color30, + color31, + color32, + color33, + color34, + color35, + color36, + color37, + color38, + color39, + color40, + color41, + color42, + ] spec: values: - value: color1 const: red - value: color2 const: green + - value: color3 + const: yellow + - value: color4 + const: copper + - value: color5 + const: orange + - value: color6 + const: purple + - value: color7 + const: gray + - value: color8 + const: light green + - value: color9 + const: cyan + - value: color10 + const: light gray + - value: color11 + const: blue gray + - value: color12 + const: blue gray + - value: color13 + const: lime + - value: color14 + const: black + - value: color15 + const: gold + - value: color16 + const: brown + - value: color17 + const: olive + - value: color19 + const: maroon + - value: color20 + const: red-orange + - value: color21 + const: yellow-orange + - value: color22 + const: forest green + - value: color23 + const: turquoise blue - value: color24 const: azure blue + - value: color25 + const: cerulean blue + - value: color26 + const: midnight blue + - value: color27 + const: medium blue + - value: color28 + const: cobalt blue + - value: color29 + const: violet blue + - value: color30 + const: blue violet + - value: color31 + const: medium violet + - value: color32 + const: medium rose + - value: color33 + const: lavender + - value: color34 + const: orchid + - value: color35 + const: thistle + - value: color36 + const: peach + - value: color37 + const: salmon + - value: color38 + const: magenta + - value: color39 + const: red violet + - value: color40 + const: mahogany + - value: color41 + const: burnt sienna + - value: color42 + const: chestnut profiles: - xpath: ["color"] - name: comments @@ -133,88 +253,3 @@ spec: max: 1023 profiles: - xpath: ["comments"] -# const: -# color: -# values: -# red: -# color1 -# green: -# value: color2 -# blue: -# value: color3 -# yellow: -# value: color4 -# copper: -# value: color5 -# orange: -# value: color6 -# purple: -# value: color7 -# gray: -# value: color8 -# light green: -# value: color9 -# cyan: -# value: color10 -# light gray: -# value: color11 -# blue gray: -# value: color12 -# lime: -# value: color13 -# black: -# value: color14 -# gold: -# value: color15 -# brown: -# value: color16 -# olive: -# value: color17 -# maroon: -# value: color19 -# red-orange: -# value: color20 -# yellow-orange: -# value: color21 -# forest green: -# value: color22 -# turquoise blue: -# value: color23 -# azure blue: -# value: color24 -# cerulean blue: -# value: color25 -# midnight blue: -# value: color26 -# medium blue: -# value: color27 -# cobalt blue: -# value: color28 -# violet blue: -# value: color29 -# blue violet: -# value: color30 -# medium violet: -# value: color31 -# medium rose: -# value: color32 -# lavender: -# value: color33 -# orchid: -# value: color34 -# thistle: -# value: color35 -# peach: -# value: color36 -# salmon: -# value: color37 -# magenta: -# value: color38 -# red violet: -# value: color39 -# mahogany: -# value: color40 -# burnt sienna: -# value: color41 -# chestnut: -# value: color42 diff --git a/specs/panorama/device-group-parent.yaml b/specs/panorama/device-group-parent.yaml new file mode 100644 index 00000000..de5727b2 --- /dev/null +++ b/specs/panorama/device-group-parent.yaml @@ -0,0 +1,43 @@ +name: "Device group Parent" +terraform_provider_config: + resource_type: custom + custom_functions: + Imports: deviceGroupParentImports + ResourceRead: deviceGroupParentResourceRead + DataSourceRead: deviceGroupParentDataSouceRead + Create: deviceGroupParentResourceCreate + Update: deviceGroupParentResourceUpdate + Delete: deviceGroupParentResourceDelete + suffix: "device_group_parent" +go_sdk_config: + skip: true + package: ["device_group_parent"] +xpath_suffix: + - device-group +locations: + - name: "panorama" + description: "Located in a specific Panorama." + devices: + - panorama + xpath: + path: + - "config" + - "devices" + - "$panorama_device" + vars: + - name: "panorama_device" + description: "The Panorama device." + default: "localhost.localdomain" +version: "10.1.0" +spec: + params: + - name: device-group + description: "The device group whose parent is being set" + type: string + profiles: + - xpath: ["device-group"] + - name: parent + description: "The parent device group. Leaving it empty moves 'device-group' under 'shared'." + type: string + profiles: + - xpath: ["parent"] diff --git a/specs/panorama/template-variable.yaml b/specs/panorama/template-variable.yaml new file mode 100644 index 00000000..bce867cf --- /dev/null +++ b/specs/panorama/template-variable.yaml @@ -0,0 +1,100 @@ +name: "Template Variable" +terraform_provider_config: + suffix: "panorama_template_variable" +go_sdk_config: + package: + - "panorama" + - "template_variable" +xpath_suffix: + - "variable" +locations: + - name: "template" + description: "Located in a specific template." + devices: + - panorama + xpath: + path: + - "config" + - "devices" + - "$panorama_device" + - "template" + - "$template" + vars: + - name: "panorama_device" + description: "The panorama device." + default: "localhost.localdomain" + - name: "template" + description: "The template." + required: true +entries: + - name: name + description: "The name of the service." + validators: + - type: length + spec: + min: 1 + max: 63 + - type: regexp + spec: + expr: "^$.+" +version: "10.1.0" +spec: + params: + - name: description + type: string + profiles: + - xpath: ["description"] + validators: + - type: length + spec: + max: 255 + - name: type + type: object + profiles: + - xpath: ["type"] + spec: + variants: + - name: ip-netmask + type: string + profiles: + - xpath: ["ip-netmask"] + - name: ip-range + type: string + profiles: + - xpath: [ip-range] + - name: fqdn + type: string + profiles: + - xpath: [fqdn] + - name: group-id + type: string + profiles: + - xpath: [group-id] + - name: device-priority + type: string + profiles: + - xpath: [device-priority] + - name: device-id + type: string + profiles: + - xpath: [device-id] + - name: interface + type: string + profiles: + - xpath: [interface] + - name: as-number + type: string + profiles: + - xpath: [as-number] + - name: qos-profile + type: string + profiles: + - xpath: [qos-profile] + - name: egress-max + type: string + profiles: + - xpath: [egress-max] + - name: link-tag + type: string + profiles: + - xpath: [link-tag] diff --git a/specs/schema/import.schema.json b/specs/schema/import.schema.json index fedbb4be..19626edb 100644 --- a/specs/schema/import.schema.json +++ b/specs/schema/import.schema.json @@ -3,13 +3,21 @@ "$id": "import.schema.json", "type": "object", "additionalProperties": false, - "required": ["name", "xpath"], + "required": ["variant", "type", "locations"], "properties": { - "name": { "type": "string" }, - "xpath": { "$ref": "xpath.schema.json" }, - "only_for_params": { + "variant": { "type": "string" }, + "type": { "type": "string" }, + "locations": { "type": "array", - "items": { "type": "string" } + "items": { + "required": ["name", "xpath"], + "additionalProperties": false, + "properties": { + "name": { "type": "string" }, + "required": { "type": "boolean", "default": false }, + "xpath": { "$ref": "xpath.schema.json" } + } + } } } } diff --git a/specs/schema/object.schema.json b/specs/schema/object.schema.json index 1a657103..484ff98a 100644 --- a/specs/schema/object.schema.json +++ b/specs/schema/object.schema.json @@ -22,7 +22,18 @@ "properties": { "resource_type": { "type": "string", - "enum": ["entry", "config", "uuid"] + "enum": ["entry", "config", "uuid", "custom"] + }, + "custom_functions": { + "type": "object", + "required": ["Read", "Create", "Update", "Delete"], + "additionalProperties": false, + "properties": { + "Read": { "type": "string" }, + "Create": { "type": "string" }, + "Update": { "type": "string" }, + "Delete": { "type": "string" } + } }, "resource_variants": { "type": "array", @@ -42,6 +53,7 @@ "type": "object", "required": ["package"], "properties": { + "generate": { "type": "boolean" }, "package": { "type": "array", "items": { diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index a3bcbf04..b18569bf 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -24,25 +24,9 @@ {{$param.Name.CamelCase}} {{specParamType "" $param}} {{- end}} - {{- if $.Imports}} - {{- range $_, $import := $.Imports}} - Import *Import{{$import.Name.CamelCase}} - {{- end}} - {{- end}} - Misc map[string][]generic.Xml } - {{ if $.Imports}} - {{- range $_, $import := $.Imports}} - type Import{{$import.Name.CamelCase}} struct { - {{- range $_, $var := $import.Vars}} - {{$var.Name.CamelCase}} string - {{- end}} - } - {{- end}} - {{- end}} - {{ range $name, $spec := nestedSpecs $.Spec }} type {{$name}}{{createGoSuffixFromVersion ""}} struct { {{- range $_, $param := $spec.Params}} @@ -125,18 +109,6 @@ ) {{- end}} - {{ if $.Imports}} - {{- range $_, $import := $.Imports}} - func DefaultImport{{$import.Name.CamelCase}}() *Import{{$import.Name.CamelCase}} { - return &Import{{$import.Name.CamelCase}}{ - {{- range $_, $var := $import.Vars}} - {{$var.Name.CamelCase}}: "{{$var.Default}}", - {{- end}} - } - } - {{- end}} - {{- end}} - func (e *Entry) Field(v string) (any, error) { if v == "name" || v == "Name" { return e.Name, nil diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index 45f00971..86530425 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -1,6 +1,12 @@ package {{packageName .GoSdkPath}} -{{renderImports "location"}} +{{- if $.Imports}} + {{ renderImports "location" "imports"}} +{{- else }} + {{ renderImports "location"}} +{{- end }} + +{{ RenderEntryImportStructs }} type Location struct { {{range $key, $location := .Locations}} diff --git a/templates/sdk/service.tmpl b/templates/sdk/service.tmpl index 60d881be..e2175712 100644 --- a/templates/sdk/service.tmpl +++ b/templates/sdk/service.tmpl @@ -2,9 +2,9 @@ package {{packageName .GoSdkPath}} {{- if .Entry}} {{- if $.Imports}} {{- if $.Spec.Params.uuid}} - {{renderImports "service" "filtering" "template" "audit" "rule" "version"}} + {{renderImports "service" "filtering" "audit" "rule" "version"}} {{- else}} - {{renderImports "service" "filtering" "template"}} + {{renderImports "service" "filtering"}} {{- end}} {{- else}} {{- if $.Spec.Params.uuid}} @@ -33,9 +33,11 @@ client: client, } // Create adds new item, then returns the result. -{{- if .Entry}} +{{- if and (.Entry) (.Imports)}} + func (s *Service) Create(ctx context.Context, loc Location, importLocations []ImportLocation, entry Entry) (*Entry, error) { +{{- else if (.Entry) }} func (s *Service) Create(ctx context.Context, loc Location, entry Entry) (*Entry, error) { -{{- else}} +{{- else }} func (s *Service) Create(ctx context.Context, loc Location, config Config) (*Config, error) { {{- end}} @@ -81,11 +83,12 @@ if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil { return nil, err } -{{- if $.Imports}} -if err := s.AddToImport(ctx, loc, entry); err != nil { -return nil, err +{{- if .Imports }} +err = s.importToLocations(ctx, loc, importLocations, entry.Name) +if err != nil { + return nil, err } -{{- end}} +{{- end }} {{- if .Entry}} return s.Read(ctx, loc, entry.Name, "get") @@ -94,187 +97,107 @@ return nil, err {{- end}} } -{{- if $.Imports}} -// AddToImport adds the given config object to import -func (s *Service) AddToImport(ctx context.Context, loc Location, entry Entry) error { - {{- range $_, $import := $.Imports}} - if loc.{{$import.Name.CamelCase}} != nil {{- range $_, $param := $import.OnlyForParams}} && entry.{{ camelCase "" $param "" true }} != nil {{- end}} { - templateLocation, templateApi, templateEntry, importedNames, err := s.getImportedNamesFor{{$import.Name.CamelCase}}(ctx, loc, entry) - if err != nil { - return err - } +{{- if .Imports }} - alreadyImported := false - for _, item := range importedNames { - if item == entry.Name { - alreadyImported = true - } +func (s *Service) importToLocations(ctx context.Context, loc Location, importLocations []ImportLocation, entryName string) error { + vn := s.client.Versioning() + for _, elt := range importLocations { + xpath, err := elt.XpathForLocation(vn, loc) + + cmd := &xmlapi.Config{ + Action: "get", + Xpath: util.AsXpath(xpath), } - if !alreadyImported { - importedNames = append(importedNames, entry.Name) + + bytes, _, err := s.client.Communicate(ctx, cmd, false, nil) + if err != nil { + return err } - err = s.setImportedNamesFor{{$import.Name.CamelCase}}(ctx, loc, entry, templateLocation, templateApi, templateEntry, importedNames) - if err != nil { - return err - } - } - {{- end}} - return nil -} + existing, err := elt.UnmarshalPangoXML(bytes) + if err != nil { + return err + } -{{- range $_, $import := $.Imports}} -func (s *Service) getImportedNamesFor{{$import.Name.CamelCase}}(ctx context.Context, loc Location, entry Entry) (template.Location, *template.Service, *template.Entry, []string, error) { - if entry.Import == nil { - entry.Import = DefaultImport{{$import.Name.CamelCase}}() - } + for _, elt := range existing { + if elt == entryName { + return nil + } + } - {{$import.Name.LowerCamelCase}}Location := {{$import.Name.LowerCamelCase}}.Location{ - Panorama: &{{$import.Name.LowerCamelCase}}.PanoramaLocation{ - PanoramaDevice: "localhost.localdomain", - }, - } + existing = append(existing, entryName) - {{$import.Name.LowerCamelCase}}Api := {{$import.Name.LowerCamelCase}}.NewService(s.client) + element, err := elt.MarshalPangoXML(existing) + if err != nil { + return err + } - {{$import.Name.LowerCamelCase}}Entry, err := {{$import.Name.LowerCamelCase}}Api.Read(ctx, {{$import.Name.LowerCamelCase}}Location, loc.{{$import.Name.CamelCase}}.{{$import.Name.CamelCase}}, "get") - if err != nil { - return {{$import.Name.LowerCamelCase}}Location, nil, nil, nil, err - } + cmd = &xmlapi.Config{ + Action: "set", + Xpath: util.AsXpath(xpath[:len(xpath)-1]), + Element: element, + } - importedNames := []string{} - {{- $prefixXpath := "" }} - {{- $lastXpath := "" }} - {{- $lastEntry := print $import.Name.LowerCamelCase "Entry" }} - {{- $checkName := false}} - {{- $indexVar := 0}} - {{- range $_, $xpath := $import.Xpath}} - {{- if not (contains $xpath "Entry")}} - {{- $prefixXpath = print $prefixXpath "." (camelCase "" $xpath "" true)}} - {{- $lastXpath = $xpath }} - {{- if $checkName}} - {{- $index := 0}} - {{- range $_, $var := $import.Vars}} - {{- if eq $indexVar $index}} - if {{$lastEntry}}.Name == entry.Import.{{$var.Name.CamelCase}} && {{$lastEntry}}{{$prefixXpath}} != nil { - {{- end}} - {{- $index = add $index 1}} - {{- end}} - {{- $indexVar = add $indexVar 1}} - {{- $checkName = false}} - {{- else}} - if {{$lastEntry}}{{$prefixXpath}} != nil { - {{- end}} - {{- else}} - for _, {{$lastXpath}} := range {{$lastEntry}}{{$prefixXpath}} { - {{- $prefixXpath = "" }} - {{- $lastEntry = $lastXpath }} - {{- $lastXpath = "" }} - {{- $checkName = true}} - {{- end}} - {{- end}} - importedNames = {{$lastEntry}}{{$prefixXpath}} - {{- range $_, $xpath := $import.Xpath}} + _, _, err = s.client.Communicate(ctx, cmd, false, nil) + if err != nil { + return err + } } - {{- end}} - return {{$import.Name.LowerCamelCase}}Location, {{$import.Name.LowerCamelCase}}Api, {{$import.Name.LowerCamelCase}}Entry, importedNames, nil + return nil } -func (s *Service) setImportedNamesFor{{$import.Name.CamelCase}}(ctx context.Context, loc Location, entry Entry, templateLocation template.Location, templateApi *template.Service, templateEntry *template.Entry, importedNames []string) error { - if entry.Import == nil { - entry.Import = DefaultImport{{$import.Name.CamelCase}}() - } +func (s *Service) unimportFromLocations(ctx context.Context, updates *xmlapi.MultiConfig, loc Location, importLocations []ImportLocation, values []string) error { + vn := s.client.Versioning() + valuesByName := make(map[string]bool) + for _, elt := range values { + valuesByName[elt] = true + } + for _, elt := range importLocations { + xpath, err := elt.XpathForLocation(vn, loc) - {{- $prefixXpath := "" }} - {{- $lastXpath := "" }} - {{- $entryPath := "Entry" }} - {{- $typeName := ""}} - {{- $lastEntry := print $import.Name.LowerCamelCase "Entry" }} - {{- $checkName := false}} - {{- $indexVar := 0}} - {{- $importInXpath := false}} - {{- range $_, $xpath := $import.Xpath}} - {{- if not $importInXpath}} - {{- $typeName = print $typeName (camelCase "" $xpath "" true)}} - {{- if not (contains $xpath "Entry")}} - {{- $prefixXpath = print $prefixXpath "." (camelCase "" $xpath "" true)}} - {{- $lastXpath = $xpath }} - {{- if $checkName}} - {{- $index := 0}} - {{- range $_, $var := $import.Vars}} - {{- if eq $indexVar $index}} - if {{$lastEntry}}.Name == entry.Import.{{$var.Name.CamelCase}} { - {{- end}} - {{- $index = add $index 1}} - {{- end}} - {{- $indexVar = add $indexVar 1}} - {{- $checkName = false}} - {{- else}} - if {{$lastEntry}}{{$prefixXpath}} != nil { - {{- end}} - {{- $entryPath = print $entryPath "." (camelCase "" $xpath "" true)}} - {{- else}} - for {{$lastXpath}}Index, {{$lastXpath}} := range {{$lastEntry}}{{$prefixXpath}} { - {{- $entryPath = print $entryPath (camelCase "" $xpath "" true) "[" $lastXpath "Index]"}} - {{- $prefixXpath = "" }} - {{- $lastEntry = $lastXpath }} - {{- $lastXpath = "" }} - {{- $checkName = true}} - {{- end}} - {{- end}} - {{- if eq $xpath "import"}} - {{- $importInXpath = true}} - {{- end}} - {{- end}} - {{$import.Name.LowerCamelCase}}{{$entryPath}} = &{{$import.Name.LowerCamelCase}}.{{$typeName}}{ - {{- $importInXpath = false}} - {{- range $index, $xpath := $import.Xpath}} - {{- if $importInXpath}} - {{ camelCase "" $xpath "" true }}: - {{- if eq $index (subtract (len $import.Xpath) 1)}} - importedNames, - {{- else}} - &{{$import.Name.LowerCamelCase}}.{{$typeName}}{{ camelCase "" $xpath "" true }}{ - {{- end}} - {{- end}} - {{- if eq $xpath "import"}} - {{- $importInXpath = true}} - {{- end}} - {{- end}} - {{- $importInXpath = false}} - {{- range $index, $xpath := $import.Xpath}} - {{- if $importInXpath}} - {{- if eq $index (subtract (len $import.Xpath) 1)}} - {{- else}} - }, - {{- end}} - {{- end}} - {{- if eq $xpath "import"}} - {{- $importInXpath = true}} - {{- end}} - {{- end}} - } - {{- $importInXpath = false}} - {{- range $_, $xpath := $import.Xpath}} - {{- if not $importInXpath}} - } - {{- end}} - {{- if eq $xpath "import"}} - {{- $importInXpath = true}} - {{- end}} - {{- end}} + cmd := &xmlapi.Config{ + Action: "get", + Xpath: util.AsXpath(xpath), + } - _, err := {{$import.Name.LowerCamelCase}}Api.Update(ctx, {{$import.Name.LowerCamelCase}}Location, *{{$import.Name.LowerCamelCase}}Entry, loc.{{$import.Name.CamelCase}}.{{$import.Name.CamelCase}}) - if err != nil { - return err - } + bytes, _, err := s.client.Communicate(ctx, cmd, false, nil) + if err != nil { + return err + } - return nil -} + existing, err := elt.UnmarshalPangoXML(bytes) + if err != nil { + return err + } -{{- end}} -{{- end}} + var filtered []string + for _, elt := range existing { + if _, found := valuesByName[elt]; !found { + filtered = append(filtered, elt) + } + } + + element, err := elt.MarshalPangoXML(filtered) + if err != nil { + return err + } + + cmd = &xmlapi.Config{ + Action: "edit", + Xpath: util.AsXpath(xpath), + Element: element, + } + + _, _, err = s.client.Communicate(ctx, cmd, false, nil) + if err != nil { + return err + } + } + + return nil +} +{{- end }} // Read returns the given config object, using the specified action ("get" or "show"). {{- if .Entry}} @@ -532,7 +455,21 @@ func (s *Service) Update(ctx context.Context, loc Location, entry {{ $object }}) } // Delete deletes the given item. -{{- if .Entry}} +{{- if and .Entry .Imports}} + func (s *Service) Delete(ctx context.Context, loc Location, importLocations []ImportLocation, name ...string) error { + {{- if $.Spec.Params.uuid}} + return s.delete(ctx, loc, importLocations, name, true) + {{- else}} + return s.delete(ctx, loc, importLocations, name) + {{- end}} + } + {{- if $.Spec.Params.uuid}} + // DeleteById deletes the given item with specified ID. + func (s *Service) DeleteById(ctx context.Context, loc Location, importLocations []ImportLocation, uuid ...string) error { + return s.delete(ctx, loc, importLocations, uuid, false) + } + {{- end}} +{{- else if .Entry }} func (s *Service) Delete(ctx context.Context, loc Location, name ...string) error { {{- if $.Spec.Params.uuid}} return s.delete(ctx, loc, name, true) @@ -546,13 +483,19 @@ func (s *Service) Update(ctx context.Context, loc Location, entry {{ $object }}) return s.delete(ctx, loc, uuid, false) } {{- end}} -{{- else}} +{{- else }} func (s *Service) Delete(ctx context.Context, loc Location, config Config) error { return s.delete(ctx, loc, config) } {{- end}} -{{if .Entry}} +{{- if and .Entry .Imports }} + {{- if $.Spec.Params.uuid}} + func (s *Service) delete(ctx context.Context, loc Location, importLocations []ImportLocation, values []string, byName bool) error { + {{- else}} + func (s *Service) delete(ctx context.Context, loc Location, importLocations []ImportLocation, values []string) error { + {{- end}} +{{- else if .Entry}} {{- if $.Spec.Params.uuid}} func (s *Service) delete(ctx context.Context, loc Location, values []string, byName bool) error { {{- else}} @@ -585,6 +528,12 @@ vn := s.client.Versioning() {{- if .Entry}} var err error deletes := xmlapi.NewMultiConfig(len(values)) + {{- if .Imports }} + err = s.unimportFromLocations(ctx, deletes, loc, importLocations, values) + if err != nil { + return err + } + {{- end }} for _, value := range values { var path []string {{- if $.Spec.Params.uuid}} @@ -609,16 +558,6 @@ return err Xpath: util.AsXpath(path), Target: s.client.GetTarget(), }) - - {{- if $.Imports}} - entry, err := s.Read(ctx, loc, value, "get") - if err != nil { - return err - } - if err = s.RemoveFromImport(ctx, loc, *entry); err != nil { - return err - } - {{- end}} } _, _, _, err = s.client.MultiConfig(ctx, deletes, false, nil) @@ -641,7 +580,7 @@ return err {{- if ne $version ""}} {{- if eq $index 1}} if vn.Gte(version{{createGoSuffixFromVersion $version}}) { - {{- else}} + {{- else}} } else if vn.Gte(version{{createGoSuffixFromVersion $version}}) { {{- end}} {{- range $_, $param := $.Spec.Params}} @@ -703,7 +642,7 @@ return err {{- end}} } -{{- if $.Imports}} +{{- if false }} // RemoveFromImport removes the given config object from import func (s *Service) RemoveFromImport(ctx context.Context, loc Location, entry Entry) error { {{- range $_, $import := $.Imports}}