diff --git a/plugins/module_utils/annotation_unsupported.py b/plugins/module_utils/annotation_unsupported.py new file mode 100644 index 000000000..2d7ca128a --- /dev/null +++ b/plugins/module_utils/annotation_unsupported.py @@ -0,0 +1,832 @@ +ANNOTATION_UNSUPPORTED = [ + "aaaADomainRef", + "aaaAProvider", + "aaaARbacRule", + "aaaARetP", + "aaaAuthMethod", + "aaaBanner", + "aaaConfig", + "aaaDefinition", + "aaaEp", + "aaaProviderGroup", + "aaaRbacAnnotation", + "aaaRealm", + "aaaSystemUser", + "aaaUserAction", + "aclACL", + "aclL3ACE", + "actionACont", + "adcomARwi", + "adcomARwiAdvanced", + "adcomATsInfoUnit", + "adepgACont", + "adepgAElement", + "adepgAOrgUnit", + "adepgAResElement", + "adepgContE", + "adepgEntity", + "analyticsACfgSrv", + "analyticsACluster", + "analyticsRemoteNode", + "analyticsTarget", + "apDockerName", + "apPluginName", + "arpAIfPol", + "authASvr", + "authASvrGroup", + "authBaseUsrAccP", + "bfdAIfP", + "bfdAIfPol", + "bfdAInstPol", + "bfdAMhIfPol", + "bfdAMhInstPol", + "bfdAMhNodePol", + "bfdAMicroBfdP", + "bfdANodeP", + "bgpAAsP", + "bgpACtxAfPol", + "bgpACtxPol", + "bgpADomainIdBase", + "bgpAExtP", + "bgpALocalAsnP", + "bgpAPeerP", + "bgpAPeerPfxPol", + "bgpARRP", + "bgpARtTarget", + "bgpARtTargetInstrP", + "bgpARtTargetP", + "bgpASiteOfOriginP", + "callhomeADest", + "callhomeAGroup", + "callhomeASrc", + "cdpAIfPol", + "cloudAAEPg", + "cloudAAFilter", + "cloudAApicSubnet", + "cloudAApicSubnetPool", + "cloudAAwsFlowLogPol", + "cloudAAwsLogGroup", + "cloudAAwsProvider", + "cloudABaseEPg", + "cloudABdiId", + "cloudABgpAsP", + "cloudABgpPeerP", + "cloudABrownfield", + "cloudACertStore", + "cloudACertificate", + "cloudACidr", + "cloudACloudSvcEPg", + "cloudAComputePol", + "cloudAController", + "cloudACtxProfile", + "cloudACtxUnderlayP", + "cloudADomP", + "cloudAEPSelector", + "cloudAEPSelectorDef", + "cloudAExtNetworkP", + "cloudAFrontendIPv4Addr", + "cloudAGatewayRouterP", + "cloudAHealthProbe", + "cloudAHostBootstrapPol", + "cloudAHostIfP", + "cloudAHostRouterPol", + "cloudAIntNetworkP", + "cloudAIpSecTunnelIfP", + "cloudAIpv4AddrP", + "cloudAL3IfP", + "cloudAL3IntTunnelIfP", + "cloudAL3TunnelIfP", + "cloudALDev", + "cloudALIf", + "cloudAListener", + "cloudAListenerRule", + "cloudALoopbackIfP", + "cloudAMapping", + "cloudAMgmtPol", + "cloudANWParams", + "cloudANextHopIp", + "cloudAOspfAreaP", + "cloudAOspfIfP", + "cloudAParamPol", + "cloudAPool", + "cloudAProvResP", + "cloudAProvider", + "cloudARouterP", + "cloudARuleAction", + "cloudARuleCondition", + "cloudASelectedEP", + "cloudASubnet", + "cloudASvcEPg", + "cloudASvcPol", + "cloudATransitHubGwPol", + "cloudAVip", + "cloudAVirtualWanP", + "cloudAVpnGwPol", + "cloudAVpnNetworkP", + "cloudAVrfRouteLeakPol", + "cloudsecACapability", + "cloudsecAControl", + "cloudsecASaKeyP", + "cloudsecASaKeyPLocal", + "cloudsecASaKeyPRemote", + "cloudsecASaKeyStatus", + "cloudsecASaKeyStatusLocal", + "cloudsecASaKeyStatusRemote", + "cloudsecASpKeySt", + "cloudtemplateASubnetPool", + "commComp", + "commDefinition", + "commShell", + "commWeb", + "compAHvHealth", + "compAPltfmP", + "compAPvlanP", + "compASvcVM", + "compAVmmPltfmP", + "compAVmmSecP", + "compAccessP", + "compCont", + "compContE", + "compCtrlrP", + "compDomP", + "compElement", + "compEntity", + "compHost", + "compNameIdentEntity", + "compNic", + "compObj", + "compPHost", + "compPNic", + "compProvP", + "compUsrAccP", + "conditionCondP", + "conditionInfo", + "conditionLoggable", + "conditionRecord", + "conditionRetP", + "conditionSevAsnP", + "conditionSummary", + "configABackupP", + "coppACustomValues", + "coppAProfile", + "ctrlrDom", + "datetimeANtpAuthKey", + "datetimeANtpIFFKey", + "datetimeANtpProv", + "datetimeAPol", + "dbgACRulePCommon", + "dbgANodeInst", + "dbgacEpgCmn", + "dbgacFromEpCmn", + "dbgacFromEpgCmn", + "dbgacTenantSpaceCmn", + "dbgacToEpCmn", + "dbgacToEpgCmn", + "dbgexpExportP", + "dbgexpNodeStatus", + "dbgexpTechSupOnDBase", + "dhcpAInfraProvP", + "dhcpALbl", + "dhcpARelayP", + "dhcpAddr", + "dhcpNode", + "dhcpResp", + "dnsADomain", + "dnsALbl", + "dnsAProfile", + "dnsAProv", + "dnsepgADomain", + "dnsepgAMgmt", + "dnsepgASvr", + "dnsepgASvrGrp", + "dnsepgFault", + "dwdmAOptChnlIfPol", + "edmACapFlags", + "edmAOperCont", + "edmAStatsCont", + "edmCont", + "edmContE", + "edmElement", + "edmEntity", + "edmGroupP", + "edmMgrP", + "edmObj", + "eigrpAAuthIfP", + "eigrpACtxAfPol", + "eigrpAExtP", + "eigrpAIfP", + "eigrpAStubP", + "eptrkEpRslt", + "eqptALPort", + "eqptdiagpASynthObj", + "eqptdiagpCardTestSetOd", + "eqptdiagpExtChCardTsOd", + "eqptdiagpFcTsOd", + "eqptdiagpFpTsOd", + "eqptdiagpHealthPol", + "eqptdiagpLcTsOd", + "eqptdiagpLpTsOd", + "eqptdiagpPol", + "eqptdiagpPortTestSetBt", + "eqptdiagpPortTestSetHl", + "eqptdiagpPortTestSetOd", + "eqptdiagpSupCTsOd", + "eqptdiagpSysCTsOd", + "eqptdiagpTestSet", + "eqptdiagpTestSetBoot", + "eqptdiagpTestSetHealth", + "eqptdiagpTestSetOd", + "eventARetP", + "extdevSDWanASlaPol", + "extnwAInstPSubnet", + "extnwALIfP", + "extnwALNodeP", + "extnwDomP", + "extnwEPg", + "extnwOut", + "fabricACardPGrp", + "fabricACardS", + "fabricALink", + "fabricANode", + "fabricANodeBlk", + "fabricANodePEp", + "fabricANodePGrp", + "fabricANodeS", + "fabricAONodeS", + "fabricAOOSReln", + "fabricAPathIssues", + "fabricAPathS", + "fabricAPodBlk", + "fabricAPodS", + "fabricAPolGrp", + "fabricAPortBlk", + "fabricAPortPGrp", + "fabricAPortS", + "fabricAProfile", + "fabricAProtPol", + "fabricASubPortBlk", + "fabricCardP", + "fabricCardS", + "fabricComp", + "fabricDef", + "fabricDom", + "fabricInfrExP", + "fabricInfrP", + "fabricIntfPol", + "fabricL1IfPol", + "fabricL2BDPol", + "fabricL2DomPol", + "fabricL2IfPol", + "fabricL2InstPol", + "fabricL2PortSecurityPol", + "fabricL2ProtoComp", + "fabricL2ProtoPol", + "fabricL3CtxPol", + "fabricL3DomPol", + "fabricL3IfPol", + "fabricL3InstPol", + "fabricL3ProtoComp", + "fabricL3ProtoPol", + "fabricL4IfPol", + "fabricLeAPortPGrp", + "fabricMaintPol", + "fabricNodeGrp", + "fabricNodeP", + "fabricNodeS", + "fabricNodeToPathOverridePolicy", + "fabricNodeToPolicy", + "fabricPodGrp", + "fabricPol", + "fabricPolGrp", + "fabricPolicyGrpToMonitoring", + "fabricPortP", + "fabricPortS", + "fabricProfile", + "fabricProtoComp", + "fabricProtoConsFrom", + "fabricProtoConsTo", + "fabricProtoDomPol", + "fabricProtoIfPol", + "fabricProtoInstPol", + "fabricProtoPol", + "fabricQinqIfPol", + "fabricSelector", + "fabricSpAPortPGrp", + "fabricUtilInstPol", + "fabricVxlanInstPol", + "faultARetP", + "faultARsToRemote", + "faultAThrValue", + "faultInfo", + "fcAPinningLbl", + "fcAPinningP", + "fcprARs", + "fileARemoteHost", + "fileARemotePath", + "firmwareAFwP", + "firmwareAFwStatusCont", + "firmwareARunning", + "firmwareSource", + "frmwrkARelDelCont", + "frmwrkARelDelControl", + "fvAACrtrn", + "fvAAKeyChainPol", + "fvAAKeyPol", + "fvAAREpPUpdate", + "fvABD", + "fvABDPol", + "fvAClassifier", + "fvACont", + "fvACrRule", + "fvACrtrn", + "fvACtx", + "fvACtxRtSummPol", + "fvADeplCont", + "fvADnsAttr", + "fvADomP", + "fvADyAttr", + "fvAEPSelector", + "fvAEPSelectorDef", + "fvAEPgPathAtt", + "fvAEpAnycast", + "fvAEpDef", + "fvAEpNlb", + "fvAEpRetPol", + "fvAEpTag", + "fvAEpTaskAggr", + "fvAExtRoutableRemoteSitePodSubnet", + "fvAExtRoutes", + "fvAFBRGroup", + "fvAFBRMember", + "fvAFBRoute", + "fvAFabricExpRtctrlP", + "fvAFabricExtConnP", + "fvAIdAttr", + "fvAIntersiteConnP", + "fvAIntersiteConnPDef", + "fvAIntraVrfFabricImpRtctrlP", + "fvAIpAttr", + "fvAKeyChainPol", + "fvAKeyPol", + "fvAMacAttr", + "fvANode", + "fvAPeeringP", + "fvAPodConnP", + "fvAProtoAttr", + "fvAREpPCtrct", + "fvARogueExceptionMac", + "fvARsToRemote", + "fvARsToRemoteFC", + "fvARtSummSubnet", + "fvASCrtrn", + "fvASDWanPrefixTaskAggr", + "fvASiteConnP", + "fvAStAttr", + "fvAStCEp", + "fvAStIp", + "fvATg", + "fvAToBD", + "fvATp", + "fvAUsegAssocBD", + "fvAVip", + "fvAVmAttr", + "fvAttr", + "fvCEPg", + "fvComp", + "fvDef", + "fvDom", + "fvEPg", + "fvEPgToCollection", + "fvEPgToInterface", + "fvEp", + "fvFrom", + "fvL2Dom", + "fvL3Dom", + "fvNp", + "fvNwEp", + "fvPEp", + "fvRtScopeFrom", + "fvSyntheticIp", + "fvTo", + "fvUp", + "fvnsAAddrBlk", + "fvnsAAddrInstP", + "fvnsAEncapBlk", + "fvnsAInstP", + "fvnsAVlanInstP", + "fvnsAVsanInstP", + "fvnsAVxlanInstP", + "genericsARule", + "haHaTest", + "hcloudAExtPfx", + "hcloudAIntPfx", + "hcloudALeakedPfx", + "hcloudASvcResBase", + "hcloudATgStats", + "hcloudRouterStateOper", + "healthAInst", + "healthARetP", + "hostprotASubj", + "hsrpAGroupP", + "hsrpAGroupPol", + "hsrpAIfP", + "hsrpAIfPol", + "hsrpASecVip", + "hvsContE", + "hvsNode", + "hvsUsegContE", + "iaclAProfile", + "igmpAIfP", + "igmpASnoopAccessGroup", + "igmpASnoopPol", + "igmpASnoopStaticGroup", + "infraAAccBndlGrp", + "infraAAccGrp", + "infraACEPg", + "infraACEp", + "infraADomP", + "infraAFcAccBndlGrp", + "infraAFunc", + "infraAIpP", + "infraANode", + "infraANodeP", + "infraANodeS", + "infraAONodeS", + "infraAPEPg", + "infraAPEp", + "infraAPathS", + "infraAccBaseGrp", + "infraAccGrp", + "infraDomP", + "infraDomainToNs", + "infraEPg", + "infraExP", + "infraFcAccGrp", + "infraFexBlk", + "infraFexGrp", + "infraGeNode", + "infraGeSnNode", + "infraLbl", + "infraNodeGrp", + "infraNodeS", + "infraPodGrp", + "infraPolGrp", + "infraPortP", + "infraPortS", + "infraProfile", + "infraSpAccGrp", + "infraToAInstP", + "ipANexthopEpP", + "ipANexthopP", + "ipARouteP", + "ipmcACtxPol", + "ipmcAIfPol", + "ipmcARepPol", + "ipmcASSMXlateP", + "ipmcAStRepPol", + "ipmcAStateLPol", + "ipmcsnoopMcSrc", + "ipmcsnoopRtrIf", + "ipmcsnoopTgtIf", + "ipsecAIsakmpPhase1Pol", + "ipsecAIsakmpPhase2Pol", + "l2AInstPol", + "l2extADomP", + "l2extAIfP", + "l2extAInstPSubnet", + "l2extALNodeP", + "l3extAConsLbl", + "l3extADefaultRouteLeakP", + "l3extADomP", + "l3extAFabricExtRoutingP", + "l3extAIfP", + "l3extAInstPSubnet", + "l3extAIp", + "l3extALNodeP", + "l3extAMember", + "l3extAProvLbl", + "l3extARouteTagPol", + "l4AVxlanInstPol", + "lacpAEnhancedLagPol", + "lacpALagPol", + "leakAPrefix", + "leakARouteCont", + "leakASubnet", + "lldpAIfPol", + "macsecAAIfPol", + "macsecAAKeyChainPol", + "macsecAAKeyPol", + "macsecAAParamPol", + "macsecAIfPol", + "macsecAKeyChainPol", + "macsecAKeyPol", + "macsecAParamPol", + "maintAMaintP", + "maintUserNotif", + "mcpAIfPol", + "mdpAClassId", + "mdpADomP", + "mdpADomainPeeringPol", + "mdpAEntity", + "mdpANodeP", + "mdpAPeeringDomain", + "mdpAService", + "mdpATenant", + "mgmtAInstPSubnet", + "mgmtAIp", + "mgmtAZone", + "mldASnoopAccessGroup", + "mldASnoopPol", + "mldASnoopStaticGroup", + "moASubj", + "moModifiable", + "moOwnable", + "moResolvable", + "moTopProps", + "monATarget", + "monExportP", + "monGroup", + "monPol", + "monProtoP", + "monSecAuthP", + "monSrc", + "monSubj", + "monTarget", + "mplsAExtP", + "mplsAIfP", + "mplsALabelPol", + "mplsANodeSidP", + "mplsASrgbLabelPol", + "namingNamedIdentifiedObject", + "namingNamedObject", + "ndAIfPol", + "ndAPfxPol", + "netflowAExporterPol", + "netflowAFabExporterPol", + "netflowAMonitorPol", + "netflowARecordPol", + "netflowARsInterfaceToMonitor", + "netflowARsMonitorToExporter", + "netflowARsMonitorToRecord", + "netflowAVmmExporterPol", + "nwsAFwPol", + "nwsASrc", + "nwsASyslogSrc", + "oamExec", + "orchsElement", + "orchsEntity", + "ospfACtxPol", + "ospfAExtP", + "ospfAIfP", + "pconsRef", + "pimAIfP", + "pingAExec", + "pkiDefinition", + "pkiItem", + "plannerADomainTmpl", + "plannerAEpg", + "plannerAEpgDomain", + "plannerAEpgFilter", + "plannerAObject", + "plannerATmpl", + "plannerIPs", + "poeAIfPol", + "polAConfIssues", + "polADependentOn", + "polAObjToPolReln", + "polAPrToPol", + "polAttTgt", + "polComp", + "polCompl", + "polComplElem", + "polConsElem", + "polConsIf", + "polConsLbl", + "polCont", + "polCtrlr", + "polDef", + "polDefRoot", + "polDom", + "polIf", + "polInstr", + "polLbl", + "polNFromRef", + "polNToRef", + "polNs", + "polObj", + "polProvIf", + "polProvLbl", + "polRelnHolder", + "poolElement", + "poolPool", + "poolPoolMember", + "poolPoolable", + "ptpAACfg", + "ptpACfg", + "ptpAProfile", + "qosABuffer", + "qosACong", + "qosADot1PClass", + "qosADppPol", + "qosADscpClass", + "qosADscpTrans", + "qosAMplsEgressRule", + "qosAMplsIngressRule", + "qosAQueue", + "qosASched", + "qosAUburst", + "qosClassification", + "qosMplsMarking", + "relnFrom", + "relnInst", + "relnTaskRef", + "relnTo", + "resolutionARsToRemote", + "rtctrlAAttrP", + "rtctrlAMatchAsPathRegexTerm", + "rtctrlAMatchCommFactor", + "rtctrlAMatchCommRegexTerm", + "rtctrlAMatchCommTerm", + "rtctrlAMatchIpRule", + "rtctrlAMatchRtType", + "rtctrlAMatchRule", + "rtctrlASetASPath", + "rtctrlASetASPathASN", + "rtctrlASetComm", + "rtctrlASetDamp", + "rtctrlASetNh", + "rtctrlASetNhUnchanged", + "rtctrlASetOspfFwdAddr", + "rtctrlASetOspfNssa", + "rtctrlASetPolicyTag", + "rtctrlASetPref", + "rtctrlASetRedistMultipath", + "rtctrlASetRtMetric", + "rtctrlASetRtMetricType", + "rtctrlASetRule", + "rtctrlASetTag", + "rtctrlASetWeight", + "rtctrlASubnet", + "rtdmcAASMPatPol", + "rtdmcAAutoRPPol", + "rtdmcABDFilter", + "rtdmcABDPol", + "rtdmcABSRFilter", + "rtdmcABSRPPol", + "rtdmcABidirPatPol", + "rtdmcACtxPol", + "rtdmcAExtP", + "rtdmcAFilterPol", + "rtdmcAIfPol", + "rtdmcAIfPolCont", + "rtdmcAInterVRFEntry", + "rtdmcAInterVRFPol", + "rtdmcAJPFilterPol", + "rtdmcAMAFilter", + "rtdmcANbrFilterPol", + "rtdmcAPatPol", + "rtdmcARPGrpRangePol", + "rtdmcARPPol", + "rtdmcARegTrPol", + "rtdmcAResPol", + "rtdmcARtMapEntry", + "rtdmcARtMapPol", + "rtdmcASGRangeExpPol", + "rtdmcASSMPatPol", + "rtdmcASSMRangePol", + "rtdmcASharedRangePol", + "rtdmcAStaticRPEntry", + "rtdmcAStaticRPPol", + "ruleDefinition", + "ruleItem", + "ruleRequirement", + "ruleSizeRequirement", + "snmpAClientGrpP", + "snmpAClientP", + "snmpACommunityP", + "snmpACtrlrInst", + "snmpACtxP", + "snmpAPol", + "snmpATrapFwdServerP", + "snmpAUserP", + "snmpUser", + "spanACEpDef", + "spanADest", + "spanASpanLbl", + "spanASrc", + "spanASrcGrp", + "spanAToCEp", + "spanAVDest", + "spanAVDestGrp", + "spanAVSrc", + "spanAVSrcGrp", + "statsAALbStats", + "statsAColl", + "statsANWStatsObj", + "statsANlbStats", + "statsAThrP", + "statsATunnel", + "statsDebugItem", + "statsItem", + "stpAIfPol", + "svccoreACore", + "svccorePol", + "synceAAIfPol", + "synceAIfPol", + "syntheticAContext", + "syntheticATestObj", + "syntheticCTestObj", + "syntheticObject", + "syntheticTLTestObj", + "sysdebugFile", + "sysdebugLogBehavior", + "sysdebugRepository", + "sysfileEp", + "sysfileInstance", + "sysfileRepository", + "tagASelector", + "tagAnnotation", + "tagTag", + "taskExec", + "telemetryAFlowServers", + "telemetryAFteEvents", + "telemetryAFteEventsExt", + "telemetryAFteEventsTcpFlags", + "telemetryARemoteServer", + "telemetryAServer", + "telemetryAServerP", + "telemetryAServerPol", + "telemetryAStreamEnable", + "throttlerASub", + "topRoot", + "tracerouteAExec", + "traceroutepTrP", + "trigATriggeredWindow", + "trigExecutable", + "trigInst", + "trigSchedWindow", + "trigSchedWindowP", + "trigSingleTriggerable", + "trigTriggerable", + "trigWindow", + "usegAUsegEPg", + "vmmACapInfo", + "vmmACapObj", + "vmmAUplinkP", + "vmmAUplinkPCont", + "vmmCFaultInfo", + "vmmEpgAggr", + "vmmInjectedObject", + "vnsACCfg", + "vnsACCfgRel", + "vnsAConn", + "vnsAConnection", + "vnsAEPpInfo", + "vnsAFolder", + "vnsAFuncConn", + "vnsAFuncNode", + "vnsAGraph", + "vnsAL4L7ServiceFault", + "vnsALDev", + "vnsALDevCtx", + "vnsALDevIf", + "vnsALDevLIf", + "vnsALIf", + "vnsALIfCtx", + "vnsAMgmt", + "vnsANode", + "vnsAParam", + "vnsATerm", + "vnsATermConn", + "vnsATermNode", + "vnsAVRoutingNetworks", + "vnsAbsTermNode", + "vnsDevItem", + "vnsLBReq", + "vnsNATReq", + "vnsOrchReq", + "vnsOrchResp", + "vsanARsVsanPathAtt", + "vsanARtVsanPathAtt", + "vsvcAConsLbl", + "vsvcAProvLbl", + "vzABrCP", + "vzACollection", + "vzACompLbl", + "vzACtrct", + "vzAFiltEntry", + "vzAFilter", + "vzAFilterable", + "vzAFilterableUnit", + "vzAIf", + "vzALbl", + "vzARuleOwner", + "vzASTerm", + "vzASubj", + "vzATerm", + "vzAnyToCollection", + "vzAnyToInterface", + "vzInterfaceToCollection", + "vzSubjectToFilter", + "vzToRFltP", +] diff --git a/plugins/modules/aci_rest.py b/plugins/modules/aci_rest.py index ad73cb5bd..43986592a 100644 --- a/plugins/modules/aci_rest.py +++ b/plugins/modules/aci_rest.py @@ -3,6 +3,7 @@ # Copyright: (c) 2017, Dag Wieers (@dagwieers) # Copyright: (c) 2020, Cindy Zhao (@cizhao) +# Copyright: (c) 2023, Samita Bhattacharjee (@samitab) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -62,6 +63,7 @@ default: false extends_documentation_fragment: - cisco.aci.aci +- cisco.aci.annotation notes: - Certain payloads are known not to be idempotent, so be careful when constructing payloads, @@ -73,6 +75,7 @@ - XML payloads require the C(lxml) and C(xmljson) python libraries. For JSON payloads nothing special is needed. - If you do not have any attributes, it may be necessary to add the "attributes" key with an empty dictionnary "{}" for value as the APIC does expect the entry to precede any children. +- Annotation set directly in c(src) or C(content) will take precedent over the C(annotation) parameter. seealso: - module: cisco.aci.aci_tenant - name: Cisco APIC REST API Configuration Guide @@ -81,6 +84,7 @@ author: - Dag Wieers (@dagwieers) - Cindy Zhao (@cizhao) +- Samita Bhattacharjee (@samitab) """ EXAMPLES = r""" @@ -284,8 +288,11 @@ HAS_YAML = False from ansible.module_utils.basic import AnsibleModule -from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec from ansible.module_utils._text import to_text +from ansible_collections.cisco.aci.plugins.module_utils.annotation_unsupported import ( + ANNOTATION_UNSUPPORTED, +) def update_qsl(url, params): @@ -303,6 +310,33 @@ def update_qsl(url, params): return url + "?" + "&".join(["%s=%s" % (k, v) for k, v in params.items()]) +def add_annotation(annotation, payload): + """Add annotation to payload only if it has not already been added""" + if annotation and isinstance(payload, dict): + for key, val in payload.items(): + if key in ANNOTATION_UNSUPPORTED: + continue + att = val.get("attributes", {}) + if "annotation" not in att.keys(): + att["annotation"] = annotation + # Recursively add annotation to children + children = val.get("children", None) + if children: + for child in children: + add_annotation(annotation, child) + + +def add_annotation_xml(annotation, tree): + """Add annotation to payload xml only if it has not already been added""" + if annotation: + for element in tree.iter(): + if element.tag in ANNOTATION_UNSUPPORTED: + continue + ann = element.get("annotation") + if ann is None: + element.set("annotation", annotation) + + class ACIRESTModule(ACIModule): def changed(self, d): """Check ACI response for changes""" @@ -335,6 +369,7 @@ def response_type(self, rawoutput, rest_type="xml"): def main(): argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) argument_spec.update( path=dict(type="str", required=True, aliases=["uri"]), method=dict(type="str", default="get", choices=["delete", "get", "post"], aliases=["action"]), @@ -353,6 +388,7 @@ def main(): path = module.params.get("path") src = module.params.get("src") rsp_subtree_preserve = module.params.get("rsp_subtree_preserve") + annotation = module.params.get("annotation") # Report missing file file_exists = False @@ -388,21 +424,27 @@ def main(): if rest_type == "json": if content and isinstance(content, dict): # Validate inline YAML/JSON + add_annotation(annotation, payload) payload = json.dumps(payload) elif payload and isinstance(payload, str) and HAS_YAML: try: # Validate YAML/JSON string - payload = json.dumps(yaml.safe_load(payload)) + payload = yaml.safe_load(payload) + add_annotation(annotation, payload) + payload = json.dumps(payload) except Exception as e: module.fail_json(msg="Failed to parse provided JSON/YAML payload: {0}".format(to_text(e)), exception=to_text(e), payload=payload) elif rest_type == "xml" and HAS_LXML_ETREE: if content and isinstance(content, dict) and HAS_XMLJSON_COBRA: # Validate inline YAML/JSON + add_annotation(annotation, payload) payload = etree.tostring(cobra.etree(payload)[0], encoding="unicode") elif payload and isinstance(payload, str): try: # Validate XML string - payload = etree.tostring(etree.fromstring(payload), encoding="unicode") + payload = etree.fromstring(payload) + add_annotation_xml(annotation, payload) + payload = etree.tostring(payload, encoding="unicode") except Exception as e: module.fail_json(msg="Failed to parse provided XML payload: {0}".format(to_text(e)), payload=payload) diff --git a/tests/integration/targets/aci_rest/tasks/error_handling.yml b/tests/integration/targets/aci_rest/tasks/error_handling.yml index 48a3f92ff..2ea77b46f 100644 --- a/tests/integration/targets/aci_rest/tasks/error_handling.yml +++ b/tests/integration/targets/aci_rest/tasks/error_handling.yml @@ -114,7 +114,7 @@ that: - error_on_input_validation is failed - error_on_input_validation.method == 'POST' - - "error_on_input_validation.msg == 'APIC Error 801: property descr of tn-ansible_test failed validation for value \\'This is an [invalid] description\\''" + - "error_on_input_validation.msg is ansible.builtin.regex('APIC Error 801: property descr of.*tn-ansible_test failed validation for value \\'This is an \\[invalid\\] description\\'')" - 'error_on_input_validation.response == "HTTP Error 400: Bad Request"' - error_on_input_validation.status == 400 - "'current' not in error_on_input_validation" diff --git a/tests/integration/targets/aci_rest/tasks/json_inline.yml b/tests/integration/targets/aci_rest/tasks/json_inline.yml index 7aacbee09..74711ffed 100644 --- a/tests/integration/targets/aci_rest/tasks/json_inline.yml +++ b/tests/integration/targets/aci_rest/tasks/json_inline.yml @@ -58,6 +58,7 @@ that: - cm_add_tenant is changed - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_add_tenant.proposed.fvTenant.attributes.annotation == "orchestrator:ansible" - cm_verify_checkmode_tenant.current == [] - name: Add tenant (normal mode) @@ -72,6 +73,7 @@ ansible.builtin.assert: that: - nm_add_tenant is changed + - nm_add_tenant.imdata.0.fvTenant.attributes.annotation == "orchestrator:ansible" - nm_add_tenant_again is not changed # CHANGE TENANT @@ -179,3 +181,160 @@ ansible.builtin.assert: that: - nm_query_non_tenant is not changed + +# VERIFY ANNOTATION SUPPORT +- name: Add tenant with annotation option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test" + } + } + } + register: nm_add_tenant_annotation_option + +- name: Add tenant with annotation in content + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + content: + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test", + "annotation": "test:incontent" + } + } + } + register: nm_add_tenant_annotation_content + +- name: Add tenant with annotation in content and option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test", + "annotation": "test:optionincontent" + } + } + } + register: nm_add_tenant_annotation_option_content + +- name: Add tag to tenant with annotation unsupported + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni/tn-ansible_test/tagKey-foo.json + method: post + annotation: test:inoption + content: + { + "tagTag": { + "attributes": { + "value": "bar" + } + } + } + register: nm_add_tag_no_annotation + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_absent + +- name: Add tenant with children objects including annotation + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test" + }, + "children": [ + { + "fvCtx": { + "attributes": { + "name": "VRF1" + } + } + }, + { + "fvAp": { + "attributes": { + "name": "Application1" + }, + "children": [ + { + "fvAEPg": { + "attributes": { + "name": "WebTier", + "annotation": "test:inchild" + } + } + } + ] + } + } + ] + } + } + register: nm_add_tenant_annotation_children + +- name: Verify annotation support + assert: + that: + - nm_add_tenant_annotation_option.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_content.imdata.0.fvTenant.attributes.annotation == "test:incontent" + - nm_add_tenant_annotation_option_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tag_no_annotation.imdata.0.tagTag.attributes.annotation is undefined + - nm_add_tenant_annotation_children.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.children.0.fvAEPg.attributes.annotation == "test:inchild" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.2.fvCtx.attributes.annotation == "test:inoption" \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/json_string.yml b/tests/integration/targets/aci_rest/tasks/json_string.yml index 0b1991876..9efa19c71 100644 --- a/tests/integration/targets/aci_rest/tasks/json_string.yml +++ b/tests/integration/targets/aci_rest/tasks/json_string.yml @@ -59,6 +59,7 @@ that: - cm_add_tenant is changed - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_add_tenant.proposed.fvTenant.attributes.annotation == "orchestrator:ansible" - cm_verify_checkmode_tenant.current == [] - name: Add tenant (normal mode) @@ -73,6 +74,7 @@ ansible.builtin.assert: that: - nm_add_tenant is changed + - nm_add_tenant.imdata.0.fvTenant.attributes.annotation == "orchestrator:ansible" - nm_add_tenant_again is not changed # CHANGE TENANT @@ -180,3 +182,160 @@ ansible.builtin.assert: that: - nm_query_non_tenant is not changed + +# VERIFY ANNOTATION SUPPORT +- name: Add tenant with annotation option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: | + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test" + } + } + } + register: nm_add_tenant_annotation_option + +- name: Add tenant with annotation in content + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + content: | + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test", + "annotation": "test:incontent" + } + } + } + register: nm_add_tenant_annotation_content + +- name: Add tenant with annotation in content and option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: | + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test", + "annotation": "test:optionincontent" + } + } + } + register: nm_add_tenant_annotation_option_content + +- name: Add tag to tenant with annotation unsupported + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni/tn-ansible_test/tagKey-foo.json + method: post + annotation: test:inoption + content: | + { + "tagTag": { + "attributes": { + "value": "bar" + } + } + } + register: nm_add_tag_no_annotation + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_absent + +- name: Add tenant with children objects including annotation + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: | + { + "fvTenant": { + "attributes": { + "descr": "Ansible test tenant", + "name": "ansible_test" + }, + "children": [ + { + "fvCtx": { + "attributes": { + "name": "VRF1" + } + } + }, + { + "fvAp": { + "attributes": { + "name": "Application1" + }, + "children": [ + { + "fvAEPg": { + "attributes": { + "name": "WebTier", + "annotation": "test:inchild" + } + } + } + ] + } + } + ] + } + } + register: nm_add_tenant_annotation_children + +- name: Verify annotation support + assert: + that: + - nm_add_tenant_annotation_option.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_content.imdata.0.fvTenant.attributes.annotation == "test:incontent" + - nm_add_tenant_annotation_option_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tag_no_annotation.imdata.0.tagTag.attributes.annotation is undefined + - nm_add_tenant_annotation_children.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.children.0.fvAEPg.attributes.annotation == "test:inchild" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.2.fvCtx.attributes.annotation == "test:inoption" \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/xml_file.yml b/tests/integration/targets/aci_rest/tasks/xml_file.yml index 84b27b221..6b3ffc841 100644 --- a/tests/integration/targets/aci_rest/tasks/xml_file.yml +++ b/tests/integration/targets/aci_rest/tasks/xml_file.yml @@ -17,7 +17,7 @@ output_path: "/tmp/ansible_output_file.log" - name: Ensure tenant does not exists using ans_test_delete xml template - cisco.aci.aci_rest: + cisco.aci.aci_rest: &tenant_delete <<: *aci_info path: /api/mo/uni.xml src: "./targets/aci_rest/tasks/xml_files/tn-ans_test_delete.xml" @@ -50,6 +50,7 @@ that: - cm_add_tenant is changed - '"ans_test_create" in cm_add_tenant.proposed' + - '"orchestrator:ansible" in cm_add_tenant.proposed' - cm_verify_checkmode_tenant.current == [] - name: Add tenant using ans_test_create xml template file with normal mode @@ -67,6 +68,7 @@ - nm_add_tenant.imdata.0.fvTenant.attributes.name == "ans_test_create" - nm_add_tenant.imdata.0.fvTenant.attributes.descr == "ans_test_create tenant created successfully" - nm_add_tenant.imdata.0.fvTenant.attributes.dn == "uni/tn-ans_test_create" + - nm_add_tenant.imdata.0.fvTenant.attributes.annotation == "orchestrator:ansible" - nm_add_tenant.imdata.0.fvTenant.children != [] - name: Add tenant using ans_test_create xml template file with normal mode - idempotency works @@ -227,3 +229,96 @@ that: - query_ans_test_delete is not changed - query_ans_test_delete.imdata == [] + +# VERIFY ANNOTATION SUPPORT +- name: Add tenant with annotation option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:inoption + src: "./targets/aci_rest/tasks/xml_files/tn-ans_test_create.xml" + register: nm_add_tenant_annotation_option + +- name: Add tenant with annotation in content + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + src: "./targets/aci_rest/tasks/xml_files/tn-ans_test_annotation.xml" + register: nm_add_tenant_annotation_content + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_delete + +- name: Add tenant with annotation in content and option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:inoption + src: "./targets/aci_rest/tasks/xml_files/tn-ans_test_annotation.xml" + register: nm_add_tenant_annotation_option_content + +- name: Add tag to tenant with annotation unsupported + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni/tn-ans_test_create/tagKey-foo.xml + method: post + annotation: test:inoption + src: "./targets/aci_rest/tasks/xml_files/tag.xml" + register: nm_add_tag_no_annotation + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_delete + +- name: Add tenant with children objects including annotation + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:inoption + src: "./targets/aci_rest/tasks/xml_files/tn-ans_test_annotation_children.xml" + register: nm_add_tenant_annotation_children + +- name: Verify annotation support + assert: + that: + - nm_add_tenant_annotation_option.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tenant_annotation_option_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tag_no_annotation.imdata.0.tagTag.attributes.annotation is undefined + - nm_add_tenant_annotation_children.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.children.0.fvAEPg.attributes.annotation == "test:inchild" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.1.fvCtx.attributes.annotation == "test:inoption" \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/xml_files/tag.xml b/tests/integration/targets/aci_rest/tasks/xml_files/tag.xml new file mode 100644 index 000000000..f5918f0e3 --- /dev/null +++ b/tests/integration/targets/aci_rest/tasks/xml_files/tag.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_annotation.xml b/tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_annotation.xml new file mode 100644 index 000000000..281e6e282 --- /dev/null +++ b/tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_annotation.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_annotation_children.xml b/tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_annotation_children.xml new file mode 100644 index 000000000..448998bf6 --- /dev/null +++ b/tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_annotation_children.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/xml_string.yml b/tests/integration/targets/aci_rest/tasks/xml_string.yml index 805db3472..e3e627e3e 100644 --- a/tests/integration/targets/aci_rest/tasks/xml_string.yml +++ b/tests/integration/targets/aci_rest/tasks/xml_string.yml @@ -74,8 +74,10 @@ that: - cm_add_tenant is changed - '"ansible_test" in cm_add_tenant.proposed' + - '"orchestrator:ansible" in cm_add_tenant.proposed' - cm_add_tenant_2 is changed - - '"ansible_test" in cm_add_tenant.proposed' + - '"ansible_test" in cm_add_tenant_2.proposed' + - '"orchestrator:ansible" in cm_add_tenant_2.proposed' - cm_verify_checkmode_tenant.current == [] - name: Add tenant (normal mode) @@ -225,3 +227,120 @@ ansible.builtin.assert: that: - nm_query_non_tenant is not changed + +# VERIFY ANNOTATION +- name: Add tenant with annotation (normal mode) + cisco.aci.aci_rest: &tenant_annotation + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:label + content: + + register: nm_add_tenant_annotation + +# VERIFY ANNOTATION SUPPORT +- name: Add tenant with annotation option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:inoption + content: + + register: nm_add_tenant_annotation_option + +- name: Add tenant with annotation in content + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + content: + + register: nm_add_tenant_annotation_content + +- name: Add tenant with annotation in content and option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:inoption + content: + + register: nm_add_tenant_annotation_option_content + +- name: Add tag to tenant with annotation unsupported + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni/tn-ansible_test/tagKey-foo.xml + method: post + annotation: test:inoption + content: + + register: nm_add_tag_no_annotation + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_absent + +- name: Add tenant with children objects including annotation + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + annotation: test:inoption + content: + + + + + + + register: nm_add_tenant_annotation_children + +- name: Verify annotation support + assert: + that: + - nm_add_tenant_annotation_option.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_content.imdata.0.fvTenant.attributes.annotation == "test:incontent" + - nm_add_tenant_annotation_option_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tag_no_annotation.imdata.0.tagTag.attributes.annotation is undefined + - nm_add_tenant_annotation_children.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.children.0.fvAEPg.attributes.annotation == "test:inchild" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.1.fvCtx.attributes.annotation == "test:inoption" \ No newline at end of file diff --git a/tests/integration/targets/aci_rest/tasks/yaml_inline.yml b/tests/integration/targets/aci_rest/tasks/yaml_inline.yml index 89229bc76..3d0ca07d8 100644 --- a/tests/integration/targets/aci_rest/tasks/yaml_inline.yml +++ b/tests/integration/targets/aci_rest/tasks/yaml_inline.yml @@ -54,6 +54,7 @@ that: - cm_add_tenant is changed - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_add_tenant.proposed.fvTenant.attributes.annotation == "orchestrator:ansible" - cm_verify_checkmode_tenant.current == [] - name: Add tenant (normal mode) @@ -97,6 +98,7 @@ ansible.builtin.assert: that: - nm_add_tenant_descr is changed + - nm_add_tenant.imdata.0.fvTenant.attributes.annotation == "orchestrator:ansible" - nm_add_tenant_descr_again is not changed # ADD TENANT AGAIN @@ -171,3 +173,126 @@ ansible.builtin.assert: that: - nm_query_non_tenant is not changed + +# VERIFY ANNOTATION SUPPORT +- name: Add tenant with annotation option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + register: nm_add_tenant_annotation_option + +- name: Add tenant with annotation in content + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + content: + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + annotation: test:incontent + register: nm_add_tenant_annotation_content + +- name: Add tenant with annotation in content and option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + annotation: test:optionincontent + register: nm_add_tenant_annotation_option_content + +- name: Add tag to tenant with annotation unsupported + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni/tn-ansible_test/tagKey-foo.json + method: post + annotation: test:inoption + content: + tagTag: + attributes: + value: bar + register: nm_add_tag_no_annotation + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_absent + +- name: Add tenant with children objects including annotation + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + children: + - fvCtx: + attributes: + name: VRF1 + - fvAp: + attributes: + name: Application1 + children: + - fvAEPg: + attributes: + name: WebTier + annotation: test:inchild + register: nm_add_tenant_annotation_children + +- name: Verify annotation support + assert: + that: + - nm_add_tenant_annotation_option.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_content.imdata.0.fvTenant.attributes.annotation == "test:incontent" + - nm_add_tenant_annotation_option_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tag_no_annotation.imdata.0.tagTag.attributes.annotation is undefined + - nm_add_tenant_annotation_children.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.children.0.fvAEPg.attributes.annotation == "test:inchild" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.2.fvCtx.attributes.annotation == "test:inoption" diff --git a/tests/integration/targets/aci_rest/tasks/yaml_string.yml b/tests/integration/targets/aci_rest/tasks/yaml_string.yml index 226d8d042..982235c9a 100644 --- a/tests/integration/targets/aci_rest/tasks/yaml_string.yml +++ b/tests/integration/targets/aci_rest/tasks/yaml_string.yml @@ -54,6 +54,7 @@ that: - cm_add_tenant is changed - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_add_tenant.proposed.fvTenant.attributes.annotation == "orchestrator:ansible" - cm_verify_checkmode_tenant.current == [] - name: Add tenant (normal mode) @@ -68,6 +69,7 @@ ansible.builtin.assert: that: - nm_add_tenant is changed + - nm_add_tenant.imdata.0.fvTenant.attributes.annotation == "orchestrator:ansible" - nm_add_tenant_again is not changed # CHANGE TENANT @@ -171,3 +173,126 @@ ansible.builtin.assert: that: - nm_query_non_tenant is not changed + +# VERIFY ANNOTATION SUPPORT +- name: Add tenant with annotation option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: | + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + register: nm_add_tenant_annotation_option + +- name: Add tenant with annotation in content + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + content: | + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + annotation: test:incontent + register: nm_add_tenant_annotation_content + +- name: Add tenant with annotation in content and option + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: | + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + annotation: test:optionincontent + register: nm_add_tenant_annotation_option_content + +- name: Add tag to tenant with annotation unsupported + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni/tn-ansible_test/tagKey-foo.json + method: post + annotation: test:inoption + content: | + tagTag: + attributes: + value: bar + register: nm_add_tag_no_annotation + +- name: Remove tenant + cisco.aci.aci_rest: *tenant_absent + +- name: Add tenant with children objects including annotation + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.json + method: post + annotation: test:inoption + content: | + fvTenant: + attributes: + descr: Ansible test tenant + name: ansible_test + children: + - fvCtx: + attributes: + name: VRF1 + - fvAp: + attributes: + name: Application1 + children: + - fvAEPg: + attributes: + name: WebTier + annotation: test:inchild + register: nm_add_tenant_annotation_children + +- name: Verify annotation support + assert: + that: + - nm_add_tenant_annotation_option.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_content.imdata.0.fvTenant.attributes.annotation == "test:incontent" + - nm_add_tenant_annotation_option_content.imdata.0.fvTenant.attributes.annotation == "test:optionincontent" + - nm_add_tag_no_annotation.imdata.0.tagTag.attributes.annotation is undefined + - nm_add_tenant_annotation_children.imdata.0.fvTenant.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.attributes.annotation == "test:inoption" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.0.fvAp.children.0.fvAEPg.attributes.annotation == "test:inchild" + - nm_add_tenant_annotation_children.imdata.0.fvTenant.children.2.fvCtx.attributes.annotation == "test:inoption" \ No newline at end of file