From 953e266b2287e03bbc53a38b732d6f57a1f5a3a7 Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Mon, 3 Aug 2026 10:57:34 +0200 Subject: [PATCH] first commit --- .gitignore | 5 + CiscoNetboxSync.sln | 31 +++ CiscoNetboxSync/Cisco/ConfigParser.cs | 254 ++++++++++++++++++ CiscoNetboxSync/CiscoNetboxSync.csproj | 14 + CiscoNetboxSync/Model/Device.cs | 34 +++ CiscoNetboxSync/Model/DiffDevice.cs | 38 +++ CiscoNetboxSync/Model/Interface.cs | 96 +++++++ CiscoNetboxSync/Model/Vlan.cs | 61 +++++ CiscoNetboxSync/Netbox/NetboxParser.cs | 148 ++++++++++ CiscoNetboxSync/Program.cs | 36 +++ .../config-example/settings.conf.example | 3 + CiscoNetboxSync/lib/HttpEndpoint.cs | 62 +++++ 12 files changed, 782 insertions(+) create mode 100644 .gitignore create mode 100644 CiscoNetboxSync.sln create mode 100644 CiscoNetboxSync/Cisco/ConfigParser.cs create mode 100644 CiscoNetboxSync/CiscoNetboxSync.csproj create mode 100644 CiscoNetboxSync/Model/Device.cs create mode 100644 CiscoNetboxSync/Model/DiffDevice.cs create mode 100644 CiscoNetboxSync/Model/Interface.cs create mode 100644 CiscoNetboxSync/Model/Vlan.cs create mode 100644 CiscoNetboxSync/Netbox/NetboxParser.cs create mode 100644 CiscoNetboxSync/Program.cs create mode 100644 CiscoNetboxSync/config-example/settings.conf.example create mode 100644 CiscoNetboxSync/lib/HttpEndpoint.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5e31a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs +CiscoNetboxSync/bin +CiscoNetboxSync/obj +settings.conf +CiscoNetboxSync/Properties diff --git a/CiscoNetboxSync.sln b/CiscoNetboxSync.sln new file mode 100644 index 0000000..9acbaf4 --- /dev/null +++ b/CiscoNetboxSync.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31112.23 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CiscoNetboxSync", "CiscoNetboxSync\CiscoNetboxSync.csproj", "{35CA8211-46EF-4FEB-9515-335D1D08E704}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "litjson", "..\Librarys\litjson\litjson\litjson.csproj", "{5B043023-3191-4EA9-949F-09C6DBC1F4DA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {35CA8211-46EF-4FEB-9515-335D1D08E704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35CA8211-46EF-4FEB-9515-335D1D08E704}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35CA8211-46EF-4FEB-9515-335D1D08E704}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35CA8211-46EF-4FEB-9515-335D1D08E704}.Release|Any CPU.Build.0 = Release|Any CPU + {5B043023-3191-4EA9-949F-09C6DBC1F4DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B043023-3191-4EA9-949F-09C6DBC1F4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B043023-3191-4EA9-949F-09C6DBC1F4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B043023-3191-4EA9-949F-09C6DBC1F4DA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {90875829-0BA4-4181-93D4-CC5594DACF2B} + EndGlobalSection +EndGlobal diff --git a/CiscoNetboxSync/Cisco/ConfigParser.cs b/CiscoNetboxSync/Cisco/ConfigParser.cs new file mode 100644 index 0000000..a6bef14 --- /dev/null +++ b/CiscoNetboxSync/Cisco/ConfigParser.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; + +using BlubbFish.CiscoNetboxSync.Model; + +namespace BlubbFish.CiscoNetboxSync.Cisco { + class ConfigParser { + public Dictionary Devices { + get; + } = new Dictionary(); + + enum BlockType { + None, + Fex, + Vlan, + InterfaceVlan, + InterfacePortChannel, + InterfaceEthernet + } + + private readonly Dictionary vlans = new Dictionary(); + private readonly Dictionary interfaces = new Dictionary(); + + private readonly Boolean hasFex = false; + private readonly List fex_devices = new List(); + private readonly String hostname = ""; + private readonly Boolean defaultShutdown = false; + + public ConfigParser(String[] configfile) { + List block = new List(); + BlockType blockType = BlockType.None; + + foreach(String line in configfile) { + if(block.Count > 0 && line.StartsWith(" ")) { + block.Add(line); + } else if(!line.StartsWith(" ")) { + if(blockType == BlockType.Fex) { + if(Int32.TryParse(this.ParseFexBlockSettings(block.ToArray()), out Int32 fexid)) { + this.fex_devices.Add(fexid); + } + } else if(blockType == BlockType.Vlan) { + Vlan vlan = this.ParseVlanBlock(block.ToArray()); + this.vlans.Add(vlan.Id, vlan); + } else if(blockType == BlockType.InterfaceVlan) { + Interface interf = this.ParseInterfaceVlanBlock(block.ToArray()); + this.interfaces.Add(interf.Name, interf); + } else if(blockType == BlockType.InterfacePortChannel) { + Interface interf = this.ParseInterfacePortchannelBlock(block.ToArray()); + this.interfaces.Add(interf.Name, interf); + } else if(blockType == BlockType.InterfaceEthernet) { + Tuple interf = this.ParseInterfaceEthernetBlock(block.ToArray()); + if(!this.Devices.ContainsKey(interf.Item1.ToString())) { + Boolean isFex = this.hasFex && this.fex_devices.Contains(interf.Item1); + String host = this.hostname + (isFex ? "-FEX-" + interf.Item1 : (this.Devices.Count > 0 ? "-" + interf.Item1:"")); + this.Devices.Add(interf.Item1.ToString(), new Device(this.interfaces, host, isFex)); + } + this.Devices[interf.Item1.ToString()].Interfaces.Add(interf.Item2.Name, interf.Item2); + } + blockType = BlockType.None; + block.Clear(); + } + + if(line.StartsWith("hostname")) { + this.hostname = Regex.Match(line, "hostname (.*)$").Groups[1].Value; + } else if(line.StartsWith("feature-set fex")) { + this.hasFex = true; + } else if(line.StartsWith("system default switchport shutdown")) { + this.defaultShutdown = true; + } + + if(Regex.Match(line, "^fex [0-9]+$").Success) { + block.Add(line); + blockType = BlockType.Fex; + } else if(Regex.Match(line, "^vlan [0-9]+$").Success) { + block.Add(line); + blockType = BlockType.Vlan; + } else if(Regex.Match(line, "^interface Vlan[0-9]+$", RegexOptions.IgnoreCase).Success) { + block.Add(line); + blockType = BlockType.InterfaceVlan; + } else if(Regex.Match(line, "^interface port-channel[0-9]+$", RegexOptions.IgnoreCase).Success) { + block.Add(line); + blockType = BlockType.InterfacePortChannel; + } else if(Regex.Match(line, "^interface Ethernet[0-9]+/[0-9/]+$", RegexOptions.IgnoreCase).Success) { + block.Add(line); + blockType = BlockType.InterfaceEthernet; + } + } + } + + private Tuple ParseInterfaceEthernetBlock(String[] vs) { + Int32 DeviceId = 0; + String Name = ""; + String Description = null; + Boolean Shutdown = this.defaultShutdown; + Tuple Ip = null; + Interface.PType porttype = Interface.PType.None; + Vlan untagged = null; + List tagged = new List(); + Int32 LagID = 0; + foreach(String item in vs) { + this.ParseInterfaceDeviceIdBlockSettings(item, ref DeviceId); + this.ParseInterfaceNameBlockSettings(item, ref Name); + this.ParseDescriptionsSettins(item, ref Description); + this.ParseShutdownSettings(item, ref Shutdown); + this.ParseIpSettings(item, ref Ip); + this.ParseSwitchportModeSettings(item, ref porttype); + this.ParseUntaggedVlanSettings(item, ref untagged); + this.ParseTaggedVlanSettings(item, ref tagged); + this.ParseChannelGroupSettings(item, ref LagID); + } + return new Tuple(DeviceId, new Interface(Name, Description, Shutdown, Ip, untagged, tagged, porttype, LagID, Interface.IType.Port)); + } + private Interface ParseInterfacePortchannelBlock(String[] vs) { + Int32 LagID = 0; + String Name = ""; + String Description = null; + Boolean Shutdown = this.defaultShutdown; + Tuple Ip = null; + Interface.PType porttype = Interface.PType.None; + Vlan untagged = null; + List tagged = new List(); + foreach(String item in vs) { + this.ParseInterfaceIdBlockSettings(item, ref LagID); + this.ParseInterfaceNameBlockSettings(item, ref Name); + this.ParseDescriptionsSettins(item, ref Description); + this.ParseShutdownSettings(item, ref Shutdown); + this.ParseIpSettings(item, ref Ip); + this.ParseSwitchportModeSettings(item, ref porttype); + this.ParseUntaggedVlanSettings(item, ref untagged); + this.ParseTaggedVlanSettings(item, ref tagged); + } + return new Interface(Name, Description, Shutdown, Ip, untagged, tagged, porttype, LagID, Interface.IType.Lag); + } + private Interface ParseInterfaceVlanBlock(String[] vs) { + Int32 VlanId = 0; + String Name = ""; + String Description = null; + Boolean Shutdown = this.defaultShutdown; + Tuple Ip = null; + + foreach(String item in vs) { + this.ParseInterfaceIdBlockSettings(item, ref VlanId); + this.ParseInterfaceNameBlockSettings(item, ref Name); + this.ParseDescriptionsSettins(item, ref Description); + this.ParseShutdownSettings(item, ref Shutdown); + this.ParseIpSettings(item, ref Ip); + } + return new Interface(Name, Description, Shutdown, Ip, this.CheckAndGetVlan(VlanId)); + } + private Vlan ParseVlanBlock(String[] vs) { + Int32 VlanId = 0; + String Name = ""; + foreach(String item in vs) { + this.ParseInterfaceIdBlockSettings(item, ref VlanId); + this.ParseDescriptionsSettins(item, ref Name); + } + return new Vlan(VlanId, Name); + } + private String ParseFexBlockSettings(String[] vs) => Regex.Match(vs[0], "^fex ([0-9]+)$").Groups[1].Value; + + private List ParseVlanBlocks(String value) { + List ret = new List(); + String[] tags = value.Split(','); + foreach(String item in tags) { + if(item.Contains("-")) { + Match regblock = Regex.Match(item, "^([0-9]+)-([0-9]+)"); + if(regblock.Success && Int32.TryParse(regblock.Groups[1].Value, out Int32 vlanidstart) && Int32.TryParse(regblock.Groups[2].Value, out Int32 vlanidstop)) { + ret.AddRange(Enumerable.Range(vlanidstart, vlanidstop - vlanidstart + 1).ToList()); + } + } else { + if(Int32.TryParse(item, out Int32 vlanid)) { + ret.Add(vlanid); + } + } + } + return ret; + } + public Vlan CheckAndGetVlan(Int32 vlanid) { + if(!this.vlans.ContainsKey(vlanid)) { + this.vlans.Add(vlanid, new Vlan(vlanid, "NOT KNOWN")); + } + return this.vlans[vlanid]; + } + + public void ParseInterfaceIdBlockSettings(String item, ref Int32 VlanId) { + Match regvlanid = Regex.Match(item, "^(vlan |interface Vlan|interface port-channel)([0-9]+)$", RegexOptions.IgnoreCase); + if(regvlanid.Success && Int32.TryParse(regvlanid.Groups[2].Value, out Int32 vlanid)) { + VlanId = vlanid; + } + } + public void ParseInterfaceNameBlockSettings(String item, ref String Name) { + Match regname = Regex.Match(item, "^interface (Vlan[0-9]+|port-channel[0-9]+|Ethernet[0-9/]+)$", RegexOptions.IgnoreCase); + if(regname.Success) { + Name = regname.Groups[1].Value; + } + } + public void ParseInterfaceDeviceIdBlockSettings(String item, ref Int32 DeviceId) { + Match regdeviceid = Regex.Match(item, "^interface Ethernet([0-9]+)/[0-9/]+$", RegexOptions.IgnoreCase); + if(regdeviceid.Success && Int32.TryParse(regdeviceid.Groups[1].Value, out Int32 deviceid)) { + DeviceId = deviceid; + } + } + public void ParseDescriptionsSettins(String item, ref String Description) { + Match regdescription = Regex.Match(item, @"^\s+(name|description) (.*)$"); + if(regdescription.Success) { + Description = regdescription.Groups[2].Value; + } + } + public void ParseShutdownSettings(String item, ref Boolean Shutdown) { + if(Regex.Match(item, @"^\s+no shutdown$").Success) { + Shutdown = false; + } + if(Regex.Match(item, @"^\s+shutdown$").Success) { + Shutdown = true; + } + } + public void ParseIpSettings(String item, ref Tuple Ip) { + Match regip = Regex.Match(item, @"^\s+ip address ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/([0-9]{1,2})$"); + if(regip.Success && IPAddress.TryParse(regip.Groups[1].Value, out IPAddress ip) && Int32.TryParse(regip.Groups[2].Value, out Int32 mask)) { + Ip = new Tuple(ip, mask); + } + } + public void ParseSwitchportModeSettings(String item, ref Interface.PType porttype) { + Match regporttype = Regex.Match(item, @"^\s+switchport (mode trunk|access vlan)$"); + if(regporttype.Success) { + porttype = regporttype.Groups[1].Value == "mode trunk" ? Interface.PType.Trunk : Interface.PType.Access; + } + } + public void ParseUntaggedVlanSettings(String item, ref Vlan Untagged) { + Match reguntagged = Regex.Match(item, @"^\s+switchport (access vlan|trunk native vlan) ([0-9]+)$"); + if(reguntagged.Success && Int32.TryParse(reguntagged.Groups[2].Value, out Int32 untaggedid)) { + Untagged = this.CheckAndGetVlan(untaggedid); + } + } + public void ParseTaggedVlanSettings(String item, ref List Tagged) { + Match regtagged = Regex.Match(item, @"^\s+switchport trunk allowed vlan( add|) ([0-9,-]+)$"); + if(regtagged.Success) { + foreach(Int32 taggedid in this.ParseVlanBlocks(regtagged.Groups[2].Value)) { + Tagged.Add(this.CheckAndGetVlan(taggedid)); + } + } + } + public void ParseChannelGroupSettings(String item, ref Int32 ChannelGroup) { + Match regchannelgroup = Regex.Match(item, @"^\s+channel-group ([0-9]+) mode active$", RegexOptions.IgnoreCase); + if(regchannelgroup.Success && Int32.TryParse(regchannelgroup.Groups[1].Value, out Int32 channelgroupid)) { + ChannelGroup = channelgroupid; + } + } + } +} diff --git a/CiscoNetboxSync/CiscoNetboxSync.csproj b/CiscoNetboxSync/CiscoNetboxSync.csproj new file mode 100644 index 0000000..15b044d --- /dev/null +++ b/CiscoNetboxSync/CiscoNetboxSync.csproj @@ -0,0 +1,14 @@ + + + + Exe + netcoreapp3.1 + CiscoNetboxSync + BlubbFish.CiscoNetboxSync + + + + + + + diff --git a/CiscoNetboxSync/Model/Device.cs b/CiscoNetboxSync/Model/Device.cs new file mode 100644 index 0000000..06ed795 --- /dev/null +++ b/CiscoNetboxSync/Model/Device.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace BlubbFish.CiscoNetboxSync.Model { + public class Device { + public String Hostname { + get; + } + + public Boolean IsFex { + get; + } + + public Dictionary Interfaces { + get; + } + + public Device(Dictionary interfaces, String host, Boolean isFex) { + this.Interfaces = new Dictionary(interfaces); + this.Hostname = host; + this.IsFex = isFex; + } + + public static Boolean Compare(Device config, Device netbox) => + CompareDeviceObject(config, netbox) && + CompareHostname(config, netbox) && + CompareFex(config, netbox) && + Interface.Compare(config.Interfaces, netbox.Interfaces); + + public static Boolean CompareDeviceObject(Device config, Device netbox) => config != null && netbox != null; + public static Boolean CompareHostname(Device config, Device netbox) => config.Hostname.ToLower() == netbox.Hostname.ToLower(); + public static Boolean CompareFex(Device config, Device netbox) => config.IsFex == netbox.IsFex; + } +} diff --git a/CiscoNetboxSync/Model/DiffDevice.cs b/CiscoNetboxSync/Model/DiffDevice.cs new file mode 100644 index 0000000..0bbc90c --- /dev/null +++ b/CiscoNetboxSync/Model/DiffDevice.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BlubbFish.CiscoNetboxSync.Model { + public class DiffDevice { + public DiffDevice(AddedAttributes add) => this.Add = add; + + public AddedAttributes Add { + get; + } + + public class AddedAttributes { + public AddedAttributes(String hostname, Boolean isFex, Dictionary interfaces) { + this.Hostname = hostname; + this.IsFex = isFex; + this.Interfaces = interfaces; + } + + public String Hostname { + get; + } + public Boolean IsFex { + get; + } + public Dictionary Interfaces { + get; + } + } + + public static DiffDevice GetDiff(Device config, Device netbox) { + if(Device.CompareDeviceObject(config, netbox)) { + return new DiffDevice(new AddedAttributes(netbox.Hostname, netbox.IsFex, netbox.Interfaces)); + } + if() + } + } +} diff --git a/CiscoNetboxSync/Model/Interface.cs b/CiscoNetboxSync/Model/Interface.cs new file mode 100644 index 0000000..b489684 --- /dev/null +++ b/CiscoNetboxSync/Model/Interface.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace BlubbFish.CiscoNetboxSync.Model { + public class Interface { + public String Name { + get; + } + + public String Description { + get; + } + + public Boolean Shutdown { + get; + } + + public String Ip { + get; + } + + public PType Portmode { + get; + } + + public Vlan Untagged { + get; + } + + public List Tagged { + get; + } + + public IType InterfaceType { + get; + } + + public Int32 LagId { + get; + } + + public enum PType { + None, + Access, + Trunk + } + + public enum IType { + Vlan, + Lag, + Port + } + + public Interface(String name, String description, Boolean shutdown, Tuple ip, Vlan untagged, List tagged = null, PType porttype = PType.Access, Int32 lagID = 0, IType interfaceType = IType.Vlan) { + this.LagId = lagID; + this.Name = name; + this.Description = description; + this.Shutdown = shutdown; + this.Ip = ip != null ? ip.Item1.ToString() + "/" + ip.Item2.ToString() : ""; + this.Untagged = untagged; + this.Portmode = porttype; + this.Tagged = tagged; + this.InterfaceType = interfaceType; + } + + public static Boolean Compare(Dictionary config, Dictionary netbox) { + if(config.Count != netbox.Count) { + return false; + } + foreach(KeyValuePair item in config) { + if(!netbox.ContainsKey(item.Key)) { + return false; + } + if(!Compare(item.Value, netbox[item.Key])) { + return false; + } + } + return true; + } + + public static Boolean Compare(Interface config, Interface netbox) => + config.Name.ToLower() == netbox.Name.ToLower() && + config.Description.ToLower() == netbox.Description.ToLower() && + config.Ip == netbox.Ip && + config.Portmode == netbox.Portmode && + Vlan.Compare(config.Untagged, netbox.Untagged) && + Vlan.Compare(config.Tagged, netbox.Tagged) && + config.InterfaceType == netbox.InterfaceType && + config.LagId == netbox.LagId; + + public override String ToString() => this.Name + " " + (this.Description != null ? "(" + this.Description + ") " : "") + " " + + this.Portmode + " " + (this.Untagged != null ? "A: " + this.Untagged + " " : "") + (this.Tagged.Count > 0 ? "T: " + String.Join(",", this.Tagged) : ""); + } +} diff --git a/CiscoNetboxSync/Model/Vlan.cs b/CiscoNetboxSync/Model/Vlan.cs new file mode 100644 index 0000000..7c29f06 --- /dev/null +++ b/CiscoNetboxSync/Model/Vlan.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace BlubbFish.CiscoNetboxSync.Model { + public class Vlan { + public Int32 Id { + get; + } + + public String Name { + get; + } + + public Vlan(Int32 id, String name) { + this.Id = id; + this.Name = name; + } + + public static Boolean Compare(List configs, List netboxs) { + if(configs.Count != netboxs.Count) { + return false; + } + foreach(Vlan config in configs) { + Boolean eq = false; + foreach(Vlan netbox in netboxs) { + if(Compare(config, netbox)) { + eq = true; + break; + } + } + if(!eq) { + return false; + } + } + return true; + } + + public static Boolean Compare(Vlan config, Vlan netbox) => config.Id == netbox.Id; + + public static List GetDeleteNetbox(List configs, List netboxs) { + List ret = new List(); + foreach(Vlan netbox in netboxs) { + Boolean todel = true; + foreach(Vlan config in configs) { + if(Compare(config, netbox)) { + todel = false; + break; + } + } + if(todel) { + ret.Add(netbox); + } + } + return ret; + } + + public static List GetAddNetbox(List configs, List netboxs) => GetDeleteNetbox(netboxs, configs); + + public override String ToString() => this.Id + " = " + this.Name; + } +} diff --git a/CiscoNetboxSync/Netbox/NetboxParser.cs b/CiscoNetboxSync/Netbox/NetboxParser.cs new file mode 100644 index 0000000..214978a --- /dev/null +++ b/CiscoNetboxSync/Netbox/NetboxParser.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; + +using BlubbFish.CiscoNetboxSync.lib; +using BlubbFish.CiscoNetboxSync.Model; + +using LitJson; + +namespace BlubbFish.CiscoNetboxSync.Netbox { + class NetboxParser { + private readonly HttpEndpoint http; + + public Device NetboxDevice { + get; + } + + public NetboxParser(String hostname, String host, String authkey) { + this.http = new HttpEndpoint(host, ("Token", authkey)); + + (Int32 deviceId, String deviceHostname, Boolean deviceFex) = this.NetboxSearchDevice(hostname); + if(deviceId == 0) { + Console.WriteLine("Konnte " + hostname + " nicht finden in Netbox!"); + return; + } + + Dictionary vlans = this.GetVlans(); + + Dictionary> ips = this.GetIps(deviceId); + + Dictionary interfaces = this.GetInterfaces(deviceId, vlans, ips); + + this.NetboxDevice = new Device(interfaces, deviceHostname, deviceFex); + } + + private Dictionary> GetIps(Int32 deviceId) { + Dictionary> ret = new Dictionary>(); + JsonData json = JsonMapper.ToObject(this.http.RequestString("/api/ipam/ip-addresses/?format=json&device_id="+ deviceId).Result); + if(json.ContainsKey("results") && json["results"].IsArray && json["results"].Count > 0) { + foreach(JsonData item in json["results"]) { + if(item.ContainsKey("address") && item["address"].IsString + && item.ContainsKey("assigned_object") && item["assigned_object"].IsObject && item["assigned_object"].ContainsKey("id") && item["assigned_object"]["id"].IsInt + && !ret.ContainsKey((Int32)item["assigned_object"]["id"])) { + Match regip = Regex.Match(item["address"].ToString(), @"^\s+ip address ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/([0-9]{1,2})$"); + if(regip.Success && IPAddress.TryParse(regip.Groups[1].Value, out IPAddress ip) && Int32.TryParse(regip.Groups[2].Value, out Int32 mask)) { + ret.Add((Int32)item["assigned_object"]["id"], new Tuple(ip, mask)); + } + } + } + } + return ret; + } + + private Dictionary GetVlans() { + Dictionary ret = new Dictionary(); + JsonData json = JsonMapper.ToObject(this.http.RequestString("/api/ipam/vlans/?format=json").Result); + if(json.ContainsKey("results") && json["results"].IsArray && json["results"].Count > 0) { + foreach(JsonData item in json["results"]) { + if(item.ContainsKey("id") && item["id"].IsInt + && item.ContainsKey("vid") && item["vid"].IsInt + && item.ContainsKey("name") && item["name"].IsString) { + ret.Add((Int32)item["id"], new Vlan((Int32)item["vid"], item["name"].ToString())); + } + } + } + return ret; + } + + private Dictionary GetInterfaces(Int32 deviceId, Dictionary vlans, Dictionary> ips) { + Dictionary ret = new Dictionary(); + JsonData json = JsonMapper.ToObject(this.http.RequestString("/api/dcim/interfaces/?format=json&device_id=" + deviceId).Result); + if(json.ContainsKey("results") && json["results"].IsArray && json["results"].Count > 0) { + foreach(JsonData item in json["results"]) { + if(item.ContainsKey("name") && item["name"].IsString + && item.ContainsKey("description") && item["description"].IsString + && item.ContainsKey("enabled") && item["enabled"].IsBoolean + && item.ContainsKey("id") && item["id"].IsInt + && item.ContainsKey("mode") && (item["mode"] == null || item["mode"].IsObject && item["mode"].ContainsKey("value") && item["mode"]["value"].IsString) + && item.ContainsKey("untagged_vlan") && (item["untagged_vlan"] == null || item["untagged_vlan"].IsObject && item["untagged_vlan"].ContainsKey("id") && item["untagged_vlan"]["id"].IsInt) + && item.ContainsKey("tagged_vlans") && item["tagged_vlans"].IsArray + && item.ContainsKey("type") && item["type"].IsObject && item["type"].ContainsKey("value") && item["type"]["value"].IsString + && item.ContainsKey("lag") && (item["lag"] == null || item["lag"].IsObject && item["lag"].ContainsKey("name") && item["lag"]["name"].IsString)) { + Int32 interfaceId = (Int32)item["id"]; + String name = item["name"].ToString(); + String description = item["description"].ToString(); + Boolean shutdown = !(Boolean)item["enabled"]; + Tuple ip = ips.ContainsKey(interfaceId) ? ips[interfaceId] : null; + Interface.PType portmode = Interface.PType.None; + if(item["mode"] != null && item["mode"]["value"].ToString().ToLower() == "tagged") { + portmode = Interface.PType.Trunk; + } else if(item["mode"] != null && item["mode"]["value"].ToString().ToLower() == "access") { + portmode = Interface.PType.Access; + } + Vlan untagged = null; + if(item["untagged_vlan"] != null) { + untagged = vlans[(Int32)item["untagged_vlan"]["id"]]; + } + List tagged = new List(); + foreach(JsonData jsonTagged in item["tagged_vlans"]) { + if(jsonTagged.IsObject && jsonTagged.ContainsKey("id") && jsonTagged["id"].IsInt) { + tagged.Add(vlans[(Int32)jsonTagged["id"]]); + } + } + Int32 lagid = 0; + if(item["lag"] != null) { + Match r = Regex.Match(item["lag"]["name"].ToString(), "[0-9]+"); + if(r.Success) { + lagid = Int32.Parse(r.Groups[0].Value); + } + } + Interface.IType interfacetype = Interface.IType.Port; + if(item["type"]["value"].ToString().ToLower() == "virtual") { + interfacetype = Interface.IType.Vlan; + } else if(item["type"]["value"].ToString().ToLower() == "lag") { + interfacetype = Interface.IType.Lag; + Match r = Regex.Match(item["name"].ToString(), "[0-9]+"); + if(r.Success) { + lagid = Int32.Parse(r.Groups[0].Value); + } + } + ret.Add(name, new Interface(name, description, shutdown, ip, untagged, tagged, portmode, lagid, interfacetype)); + } + } + } + return ret; + } + + private (Int32 deviceID, String hostname, Boolean isFex) NetboxSearchDevice(String hostname) { + JsonData json = JsonMapper.ToObject(this.http.RequestString("/api/dcim/devices/?format=json&name="+hostname).Result); + if(json.ContainsKey("results") && json["results"].IsArray && json["results"].Count == 1 + && json["results"][0].ContainsKey("id") && json["results"][0]["id"].IsInt + && json["results"][0].ContainsKey("name") && json["results"][0]["name"].IsString + && json["results"][0].ContainsKey("tags") && json["results"][0]["tags"].IsArray + && (json["results"][0]["tags"].Count == 0 || json["results"][0]["tags"].Count > 0)) { + Boolean isFex = false; + foreach(JsonData item in json["results"][0]["tags"]) { + if(item.ContainsKey("name") && item["name"].ToString().ToLower() == "fex") { + isFex = true; + } + } + return ((Int32)json["results"][0]["id"], json["results"][0]["name"].ToString(), isFex); + } + return (0, "", false); + } + } +} diff --git a/CiscoNetboxSync/Program.cs b/CiscoNetboxSync/Program.cs new file mode 100644 index 0000000..efde1d4 --- /dev/null +++ b/CiscoNetboxSync/Program.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using BlubbFish.CiscoNetboxSync.Cisco; +using BlubbFish.CiscoNetboxSync.Model; +using BlubbFish.CiscoNetboxSync.Netbox; + +using LitJson; + +namespace BlubbFish.CiscoNetboxSync { + class Program { + static void Main(String[] args) { + StreamReader file = new StreamReader(args[0]); + List buf = new List(); + String fline = ""; + while(fline != null) { + fline = file.ReadLine(); + if(fline != null && fline.Length > 0 && fline.Substring(0, 1) != "!") { + buf.Add(fline); + } + } + file.Close(); + Dictionary config = new ConfigParser(buf.ToArray()).Devices; + String config_json = JsonMapper.ToJson(config); + foreach(KeyValuePair item in config) { + Device netbox = new NetboxParser(item.Value.Hostname, "url", "api-key").NetboxDevice; + if(!Device.Compare(item.Value, netbox)) { + DiffDevice deviceChanges = DiffDevice.GetDiff(item.Value, netbox); + Console.WriteLine("nicht gleich"); + } + } + Console.WriteLine(config_json); + } + } +} diff --git a/CiscoNetboxSync/config-example/settings.conf.example b/CiscoNetboxSync/config-example/settings.conf.example new file mode 100644 index 0000000..3683409 --- /dev/null +++ b/CiscoNetboxSync/config-example/settings.conf.example @@ -0,0 +1,3 @@ +[netbox] +url= +api_key= diff --git a/CiscoNetboxSync/lib/HttpEndpoint.cs b/CiscoNetboxSync/lib/HttpEndpoint.cs new file mode 100644 index 0000000..baef3e7 --- /dev/null +++ b/CiscoNetboxSync/lib/HttpEndpoint.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace BlubbFish.CiscoNetboxSync.lib { + public class HttpEndpoint { + private static readonly HttpClient client = new HttpClient(); + private readonly String server = ""; + + public HttpEndpoint(String server, String auth = null) { + this.server = server; + if(auth != null) { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(auth); + } + + } + + public HttpEndpoint(String server, (String scheme, String parameter) auth) { + this.server = server; + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(auth.scheme, auth.parameter); + } + + public async Task RequestString(String address, String json = "", Boolean withoutput = true, RequestMethod method = RequestMethod.GET) { + String ret = null; + try { + HttpResponseMessage response = null; + if(method == RequestMethod.POST || method == RequestMethod.PUT) { + HttpContent content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + //content.Headers.Add("Content-Type", "application/json"); + if(method == RequestMethod.POST) { + response = await client.PostAsync(this.server + address, content); + } else if(method == RequestMethod.PUT) { + response = await client.PutAsync(this.server + address, content); + } + content.Dispose(); + } else if(method == RequestMethod.GET) { + response = await client.GetAsync(this.server + address); + } + if(!response.IsSuccessStatusCode) { + throw new Exception(response.StatusCode + ": " + response.ReasonPhrase); + } + if(withoutput && response != null) { + ret = await response.Content.ReadAsStringAsync(); + } + } catch(Exception e) { + throw new WebException("Error while uploading to Scal. Resource: \"" + this.server + address + "\" Method: " + method + " Data: " + json + " Fehler: " + e.Message); + } + return ret; + } + + public enum RequestMethod { + GET, + POST, + PUT + } + } +}