From 7c0a9b20203e13a4da06fd79081215f712cceb38 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 14 Oct 2010 15:28:58 -0500 Subject: web reports: new skin --- reports/site_media/AnchorPosition.js | 147 ++ reports/site_media/CalendarPopup.js | 739 +++++++- reports/site_media/PopupWindow.js | 336 ++++ reports/site_media/base.css | 5 - reports/site_media/bcfg2.js | 26 + reports/site_media/bcfg2_base.css | 244 +++ reports/site_media/boxypastel.css | 230 --- reports/site_media/date.js | 335 ++++ reports/site_media/global.css | 10 - reports/site_media/layout.css | 44 - reports/site_media/main.js | 27 - reports/site_media/sorttable.js | 203 -- reports/site_media/syntax-coloring.css | 59 - reports/site_media/yui/dom/README | 75 - reports/site_media/yui/dom/dom-debug.js | 927 --------- reports/site_media/yui/dom/dom-min.js | 59 - reports/site_media/yui/dom/dom.js | 892 --------- reports/site_media/yui/event/README | 135 -- reports/site_media/yui/event/event-debug.js | 1797 ------------------ reports/site_media/yui/event/event-min.js | 69 - reports/site_media/yui/event/event.js | 1771 ------------------ reports/site_media/yui/round_tabs.css | 76 - reports/site_media/yui/tabview/README | 16 - .../site_media/yui/tabview/assets/border_tabs.css | 48 - reports/site_media/yui/tabview/assets/tabview.css | 69 - reports/site_media/yui/tabview/tabview-debug.js | 1964 -------------------- reports/site_media/yui/tabview/tabview-min.js | 61 - reports/site_media/yui/tabview/tabview.js | 1945 ------------------- reports/site_media/yui/yahoo/README | 45 - reports/site_media/yui/yahoo/yahoo-debug.js | 144 -- reports/site_media/yui/yahoo/yahoo-min.js | 12 - reports/site_media/yui/yahoo/yahoo.js | 143 -- setup.py | 14 +- src/lib/Server/Plugin.py | 4 +- src/lib/Server/Reports/reports/models.py | 120 +- src/lib/Server/Reports/reports/models.py.orig | 330 ++++ src/lib/Server/Reports/reports/models.py.rej | 15 + src/lib/Server/Reports/reports/templates/404.html | 8 + .../Reports/reports/templates/base-timeview.html | 25 + src/lib/Server/Reports/reports/templates/base.html | 122 +- .../reports/templates/clients/client-nodebox.html | 63 - .../Reports/reports/templates/clients/detail.html | 130 +- .../reports/templates/clients/detailed-list.html | 79 +- .../Reports/reports/templates/clients/history.html | 20 + .../Reports/reports/templates/clients/index.html | 61 +- .../Reports/reports/templates/clients/manage.html | 62 +- .../reports/templates/config_items/index.html | 100 - .../reports/templates/config_items/item.html | 109 ++ .../reports/templates/config_items/listing.html | 62 +- .../Reports/reports/templates/displays/index.html | 18 - .../displays/summary-block-direct-links.html | 7 - .../reports/templates/displays/summary-block.html | 90 - .../reports/templates/displays/summary.html | 63 +- .../reports/templates/displays/sys_view.html | 20 - .../Reports/reports/templates/displays/timing.html | 72 +- .../Server/Reports/reports/templates/index.html | 15 - .../reports/templates/widgets/filter_bar.html | 13 + .../reports/templates/widgets/interaction_list.inc | 38 + .../reports/templates/widgets/page_bar.html | 23 + .../Reports/reports/templatetags/bcfg2_tags.py | 239 +++ .../reports/templatetags/django_templating_sigh.py | 41 - .../reports/templatetags/syntax_coloring.py | 26 +- src/lib/Server/Reports/reports/urls.py | 55 + src/lib/Server/Reports/reports/views.py | 647 +++---- src/lib/Server/Reports/settings.py | 25 +- src/lib/Server/Reports/urls.py | 56 +- src/lib/Server/Reports/utils.py | 94 +- 67 files changed, 3538 insertions(+), 11981 deletions(-) create mode 100644 reports/site_media/AnchorPosition.js create mode 100644 reports/site_media/PopupWindow.js delete mode 100644 reports/site_media/base.css create mode 100644 reports/site_media/bcfg2.js create mode 100644 reports/site_media/bcfg2_base.css delete mode 100644 reports/site_media/boxypastel.css create mode 100644 reports/site_media/date.js delete mode 100644 reports/site_media/global.css delete mode 100644 reports/site_media/layout.css delete mode 100644 reports/site_media/main.js delete mode 100644 reports/site_media/sorttable.js delete mode 100644 reports/site_media/syntax-coloring.css delete mode 100644 reports/site_media/yui/dom/README delete mode 100644 reports/site_media/yui/dom/dom-debug.js delete mode 100644 reports/site_media/yui/dom/dom-min.js delete mode 100644 reports/site_media/yui/dom/dom.js delete mode 100644 reports/site_media/yui/event/README delete mode 100644 reports/site_media/yui/event/event-debug.js delete mode 100644 reports/site_media/yui/event/event-min.js delete mode 100644 reports/site_media/yui/event/event.js delete mode 100644 reports/site_media/yui/round_tabs.css delete mode 100644 reports/site_media/yui/tabview/README delete mode 100644 reports/site_media/yui/tabview/assets/border_tabs.css delete mode 100644 reports/site_media/yui/tabview/assets/tabview.css delete mode 100644 reports/site_media/yui/tabview/tabview-debug.js delete mode 100644 reports/site_media/yui/tabview/tabview-min.js delete mode 100644 reports/site_media/yui/tabview/tabview.js delete mode 100644 reports/site_media/yui/yahoo/README delete mode 100644 reports/site_media/yui/yahoo/yahoo-debug.js delete mode 100644 reports/site_media/yui/yahoo/yahoo-min.js delete mode 100644 reports/site_media/yui/yahoo/yahoo.js create mode 100644 src/lib/Server/Reports/reports/models.py.orig create mode 100644 src/lib/Server/Reports/reports/models.py.rej create mode 100644 src/lib/Server/Reports/reports/templates/404.html create mode 100644 src/lib/Server/Reports/reports/templates/base-timeview.html delete mode 100644 src/lib/Server/Reports/reports/templates/clients/client-nodebox.html create mode 100644 src/lib/Server/Reports/reports/templates/clients/history.html delete mode 100644 src/lib/Server/Reports/reports/templates/config_items/index.html create mode 100644 src/lib/Server/Reports/reports/templates/config_items/item.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/index.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/summary-block.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/sys_view.html delete mode 100644 src/lib/Server/Reports/reports/templates/index.html create mode 100644 src/lib/Server/Reports/reports/templates/widgets/filter_bar.html create mode 100644 src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc create mode 100644 src/lib/Server/Reports/reports/templates/widgets/page_bar.html create mode 100644 src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py delete mode 100644 src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py create mode 100644 src/lib/Server/Reports/reports/urls.py diff --git a/reports/site_media/AnchorPosition.js b/reports/site_media/AnchorPosition.js new file mode 100644 index 000000000..7db0cc89e --- /dev/null +++ b/reports/site_media/AnchorPosition.js @@ -0,0 +1,147 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// =================================================================== + +/* +AnchorPosition.js +Author: Matt Kruse +Last modified: 10/11/02 + +DESCRIPTION: These functions find the position of an tag in a document, +so other elements can be positioned relative to it. + +COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. + +FUNCTIONS: +getAnchorPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor. Position is relative to the PAGE. + +getAnchorWindowPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor, relative to the WHOLE SCREEN. + +NOTES: + +1) For popping up separate browser windows, use getAnchorWindowPosition. + Otherwise, use getAnchorPosition + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. +*/ + +// getAnchorPosition(anchorname) +// This function returns an object having .x and .y properties which are the coordinates +// of the named anchor, relative to the page. +function getAnchorPosition(anchorname) { + // This function will return an Object with x and y properties + var useWindow=false; + var coordinates=new Object(); + var x=0,y=0; + // Browser capability sniffing + var use_gebi=false, use_css=false, use_layers=false; + if (document.getElementById) { use_gebi=true; } + else if (document.all) { use_css=true; } + else if (document.layers) { use_layers=true; } + // Logic to find position + if (use_gebi && document.all) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_gebi) { + var o=document.getElementById(anchorname); + x=AnchorPosition_getPageOffsetLeft(o); + y=AnchorPosition_getPageOffsetTop(o); + } + else if (use_css) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_layers) { + var found=0; + for (var i=0; i9?"":"0")+x} -function isDate(val,format){var date=getDateFromFormat(val,format);if(date==0){return false;}return true;} -function compareDates(date1,dateformat1,date2,dateformat2){var d1=getDateFromFormat(date1,dateformat1);var d2=getDateFromFormat(date2,dateformat2);if(d1==0 || d2==0){return -1;}else if(d1 > d2){return 1;}return 0;} -function formatDate(date,format){format=format+"";var result="";var i_format=0;var c="";var token="";var y=date.getYear()+"";var M=date.getMonth()+1;var d=date.getDate();var E=date.getDay();var H=date.getHours();var m=date.getMinutes();var s=date.getSeconds();var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;var value=new Object();if(y.length < 4){y=""+(y-0+1900);}value["y"]=""+y;value["yyyy"]=y;value["yy"]=y.substring(2,4);value["M"]=M;value["MM"]=LZ(M);value["MMM"]=MONTH_NAMES[M-1];value["NNN"]=MONTH_NAMES[M+11];value["d"]=d;value["dd"]=LZ(d);value["E"]=DAY_NAMES[E+7];value["EE"]=DAY_NAMES[E];value["H"]=H;value["HH"]=LZ(H);if(H==0){value["h"]=12;}else if(H>12){value["h"]=H-12;}else{value["h"]=H;}value["hh"]=LZ(value["h"]);if(H>11){value["K"]=H-12;}else{value["K"]=H;}value["k"]=H+1;value["KK"]=LZ(value["K"]);value["kk"]=LZ(value["k"]);if(H > 11){value["a"]="PM";}else{value["a"]="AM";}value["m"]=m;value["mm"]=LZ(m);value["s"]=s;value["ss"]=LZ(s);while(i_format < format.length){c=format.charAt(i_format);token="";while((format.charAt(i_format)==c) &&(i_format < format.length)){token += format.charAt(i_format++);}if(value[token] != null){result=result + value[token];}else{result=result + token;}}return result;} -function _isInteger(val){var digits="1234567890";for(var i=0;i < val.length;i++){if(digits.indexOf(val.charAt(i))==-1){return false;}}return true;} -function _getInt(str,i,minlength,maxlength){for(var x=maxlength;x>=minlength;x--){var token=str.substring(i,i+x);if(token.length < minlength){return null;}if(_isInteger(token)){return token;}}return null;} -function getDateFromFormat(val,format){val=val+"";format=format+"";var i_val=0;var i_format=0;var c="";var token="";var token2="";var x,y;var now=new Date();var year=now.getYear();var month=now.getMonth()+1;var date=1;var hh=now.getHours();var mm=now.getMinutes();var ss=now.getSeconds();var ampm="";while(i_format < format.length){c=format.charAt(i_format);token="";while((format.charAt(i_format)==c) &&(i_format < format.length)){token += format.charAt(i_format++);}if(token=="yyyy" || token=="yy" || token=="y"){if(token=="yyyy"){x=4;y=4;}if(token=="yy"){x=2;y=2;}if(token=="y"){x=2;y=4;}year=_getInt(val,i_val,x,y);if(year==null){return 0;}i_val += year.length;if(year.length==2){if(year > 70){year=1900+(year-0);}else{year=2000+(year-0);}}}else if(token=="MMM"||token=="NNN"){month=0;for(var i=0;i11)){month=i+1;if(month>12){month -= 12;}i_val += month_name.length;break;}}}if((month < 1)||(month>12)){return 0;}}else if(token=="EE"||token=="E"){for(var i=0;i12)){return 0;}i_val+=month.length;}else if(token=="dd"||token=="d"){date=_getInt(val,i_val,token.length,2);if(date==null||(date<1)||(date>31)){return 0;}i_val+=date.length;}else if(token=="hh"||token=="h"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<1)||(hh>12)){return 0;}i_val+=hh.length;}else if(token=="HH"||token=="H"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<0)||(hh>23)){return 0;}i_val+=hh.length;}else if(token=="KK"||token=="K"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<0)||(hh>11)){return 0;}i_val+=hh.length;}else if(token=="kk"||token=="k"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<1)||(hh>24)){return 0;}i_val+=hh.length;hh--;}else if(token=="mm"||token=="m"){mm=_getInt(val,i_val,token.length,2);if(mm==null||(mm<0)||(mm>59)){return 0;}i_val+=mm.length;}else if(token=="ss"||token=="s"){ss=_getInt(val,i_val,token.length,2);if(ss==null||(ss<0)||(ss>59)){return 0;}i_val+=ss.length;}else if(token=="a"){if(val.substring(i_val,i_val+2).toLowerCase()=="am"){ampm="AM";}else if(val.substring(i_val,i_val+2).toLowerCase()=="pm"){ampm="PM";}else{return 0;}i_val+=2;}else{if(val.substring(i_val,i_val+token.length)!=token){return 0;}else{i_val+=token.length;}}}if(i_val != val.length){return 0;}if(month==2){if( ((year%4==0)&&(year%100 != 0) ) ||(year%400==0) ){if(date > 29){return 0;}}else{if(date > 28){return 0;}}}if((month==4)||(month==6)||(month==9)||(month==11)){if(date > 30){return 0;}}if(hh<12 && ampm=="PM"){hh=hh-0+12;}else if(hh>11 && ampm=="AM"){hh-=12;}var newdate=new Date(year,month-1,date,hh,mm,ss);return newdate.getTime();} -function parseDate(val){var preferEuro=(arguments.length==2)?arguments[1]:false;generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');var d=null;for(var i=0;i screen.availHeight){this.y = screen.availHeight - this.height;}}if(screen && screen.availWidth){if((this.x + this.width) > screen.availWidth){this.x = screen.availWidth - this.width;}}var avoidAboutBlank = window.opera ||( document.layers && !navigator.mimeTypes['*']) || navigator.vendor == 'KDE' ||( document.childNodes && !document.all && !navigator.taintEnabled);this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+"");}this.refresh();}} -function PopupWindow_hidePopup(){if(this.divName != null){if(this.use_gebi){document.getElementById(this.divName).style.visibility = "hidden";}else if(this.use_css){document.all[this.divName].style.visibility = "hidden";}else if(this.use_layers){document.layers[this.divName].visibility = "hidden";}}else{if(this.popupWindow && !this.popupWindow.closed){this.popupWindow.close();this.popupWindow = null;}}} -function PopupWindow_isClicked(e){if(this.divName != null){if(this.use_layers){var clickX = e.pageX;var clickY = e.pageY;var t = document.layers[this.divName];if((clickX > t.left) &&(clickX < t.left+t.clip.width) &&(clickY > t.top) &&(clickY < t.top+t.clip.height)){return true;}else{return false;}}else if(document.all){var t = window.event.srcElement;while(t.parentElement != null){if(t.id==this.divName){return true;}t = t.parentElement;}return false;}else if(this.use_gebi && e){var t = e.originalTarget;while(t.parentNode != null){if(t.id==this.divName){return true;}t = t.parentNode;}return false;}return false;}return false;} -function PopupWindow_hideIfNotClicked(e){if(this.autoHideEnabled && !this.isClicked(e)){this.hidePopup();}} -function PopupWindow_autoHide(){this.autoHideEnabled = true;} -function PopupWindow_hidePopupWindows(e){for(var i=0;i0){this.type="DIV";this.divName = arguments[0];}else{this.type="WINDOW";}this.use_gebi = false;this.use_css = false;this.use_layers = false;if(document.getElementById){this.use_gebi = true;}else if(document.all){this.use_css = true;}else if(document.layers){this.use_layers = true;}else{this.type = "WINDOW";}this.offsetX = 0;this.offsetY = 0;this.getXYPosition = PopupWindow_getXYPosition;this.populate = PopupWindow_populate;this.setUrl = PopupWindow_setUrl;this.setWindowProperties = PopupWindow_setWindowProperties;this.refresh = PopupWindow_refresh;this.showPopup = PopupWindow_showPopup;this.hidePopup = PopupWindow_hidePopup;this.setSize = PopupWindow_setSize;this.isClicked = PopupWindow_isClicked;this.autoHide = PopupWindow_autoHide;this.hideIfNotClicked = PopupWindow_hideIfNotClicked;} - - -/* SOURCE FILE: CalendarPopup.js */ - -function CalendarPopup(){var c;if(arguments.length>0){c = new PopupWindow(arguments[0]);}else{c = new PopupWindow();c.setSize(150,175);}c.offsetX = -152;c.offsetY = 25;c.autoHide();c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December");c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");c.dayHeaders = new Array("S","M","T","W","T","F","S");c.returnFunction = "CP_tmpReturnFunction";c.returnMonthFunction = "CP_tmpReturnMonthFunction";c.returnQuarterFunction = "CP_tmpReturnQuarterFunction";c.returnYearFunction = "CP_tmpReturnYearFunction";c.weekStartDay = 0;c.isShowYearNavigation = false;c.displayType = "date";c.disabledWeekDays = new Object();c.disabledDatesExpression = "";c.yearSelectStartOffset = 2;c.currentDate = null;c.todayText="Today";c.cssPrefix="";c.isShowNavigationDropdowns=false;c.isShowYearNavigationInput=false;window.CP_calendarObject = null;window.CP_targetInput = null;window.CP_dateFormat = "MM/dd/yyyy";c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow;c.setReturnFunction = CP_setReturnFunction;c.setReturnMonthFunction = CP_setReturnMonthFunction;c.setReturnQuarterFunction = CP_setReturnQuarterFunction;c.setReturnYearFunction = CP_setReturnYearFunction;c.setMonthNames = CP_setMonthNames;c.setMonthAbbreviations = CP_setMonthAbbreviations;c.setDayHeaders = CP_setDayHeaders;c.setWeekStartDay = CP_setWeekStartDay;c.setDisplayType = CP_setDisplayType;c.setDisabledWeekDays = CP_setDisabledWeekDays;c.addDisabledDates = CP_addDisabledDates;c.setYearSelectStartOffset = CP_setYearSelectStartOffset;c.setTodayText = CP_setTodayText;c.showYearNavigation = CP_showYearNavigation;c.showCalendar = CP_showCalendar;c.hideCalendar = CP_hideCalendar;c.getStyles = getCalendarStyles;c.refreshCalendar = CP_refreshCalendar;c.getCalendar = CP_getCalendar;c.select = CP_select;c.setCssPrefix = CP_setCssPrefix;c.showNavigationDropdowns = CP_showNavigationDropdowns;c.showYearNavigationInput = CP_showYearNavigationInput;c.copyMonthNamesToWindow();return c;} -function CP_copyMonthNamesToWindow(){if(typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null){window.MONTH_NAMES = new Array();for(var i=0;i\n";result += '
\n';}else{result += '
\n';result += '
\n';result += '
\n';}if(this.displayType=="date" || this.displayType=="week-end"){if(this.currentDate==null){this.currentDate = now;}if(arguments.length > 0){var month = arguments[0];}else{var month = this.currentDate.getMonth()+1;}if(arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]){var year = arguments[1];}else{var year = this.currentDate.getFullYear();}var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);if( ((year%4 == 0)&&(year%100 != 0) ) ||(year%400 == 0) ){daysinmonth[2] = 29;}var current_month = new Date(year,month-1,1);var display_year = year;var display_month = month;var display_date = 1;var weekday= current_month.getDay();var offset = 0;offset =(weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ;if(offset > 0){display_month--;if(display_month < 1){display_month = 12;display_year--;}display_date = daysinmonth[display_month]-offset+1;}var next_month = month+1;var next_month_year = year;if(next_month > 12){next_month=1;next_month_year++;}var last_month = month-1;var last_month_year = year;if(last_month < 1){last_month=12;last_month_year--;}var date_class;if(this.type!="WINDOW"){result += "";}result += '\n';var refresh = windowref+'CP_refreshCalendar';var refreshLink = 'javascript:' + refresh;if(this.isShowNavigationDropdowns){result += '';result += '';result += '';}else{if(this.isShowYearNavigation){result += '';result += '';result += '';result += '';result += '';if(this.isShowYearNavigationInput){result += '';}else{result += '';}result += '';}else{result += '\n';result += '\n';result += '\n';}}result += '
 <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
\n';result += '\n';result += '\n';for(var j=0;j<7;j++){result += '\n';}result += '\n';for(var row=1;row<=6;row++){result += '\n';for(var col=1;col<=7;col++){var disabled=false;if(this.disabledDatesExpression!=""){var ds=""+display_year+LZ(display_month)+LZ(display_date);eval("disabled=("+this.disabledDatesExpression+")");}var dateClass = "";if((display_month == this.currentDate.getMonth()+1) &&(display_date==this.currentDate.getDate()) &&(display_year==this.currentDate.getFullYear())){dateClass = "cpCurrentDate";}else if(display_month == month){dateClass = "cpCurrentMonthDate";}else{dateClass = "cpOtherMonthDate";}if(disabled || this.disabledWeekDays[col-1]){result += ' \n';}else{var selected_date = display_date;var selected_month = display_month;var selected_year = display_year;if(this.displayType=="week-end"){var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0);d.setDate(d.getDate() +(7-col));selected_year = d.getYear();if(selected_year < 1000){selected_year += 1900;}selected_month = d.getMonth()+1;selected_date = d.getDate();}result += ' \n';}display_date++;if(display_date > daysinmonth[display_month]){display_date=1;display_month++;}if(display_month > 12){display_month=1;display_year++;}}result += '';}var current_weekday = now.getDay() - this.weekStartDay;if(current_weekday < 0){current_weekday += 7;}result += '\n';result += '
'+this.dayHeaders[(this.weekStartDay+j)%7]+'
'+display_date+''+display_date+'
\n';if(this.disabledDatesExpression!=""){var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate());eval("disabled=("+this.disabledDatesExpression+")");}if(disabled || this.disabledWeekDays[current_weekday+1]){result += ' '+this.todayText+'\n';}else{result += ' '+this.todayText+'\n';}result += '
\n';result += '
\n';}if(this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year"){if(arguments.length > 0){var year = arguments[0];}else{if(this.displayType=="year"){var year = now.getFullYear()-this.yearSelectStartOffset;}else{var year = now.getFullYear();}}if(this.displayType!="year" && this.isShowYearNavigation){result += "";result += '\n';result += ' \n';result += ' \n';result += ' \n';result += '
<<'+year+'>>
\n';}}if(this.displayType=="month"){result += '\n';for(var i=0;i<4;i++){result += '';for(var j=0;j<3;j++){var monthindex =((i*3)+j);result += '';}result += '';}result += '
'+this.monthAbbreviations[monthindex]+'
\n';}if(this.displayType=="quarter"){result += '
\n';for(var i=0;i<2;i++){result += '';for(var j=0;j<2;j++){var quarter =((i*2)+j+1);result += '';}result += '';}result += '

Q'+quarter+'

\n';}if(this.displayType=="year"){var yearColumnSize = 4;result += "";result += '\n';result += ' \n';result += ' \n';result += '
<<>>
\n';result += '\n';for(var i=0;i'+currentyear+'';}result += '';}result += '
\n';}if(this.type == "WINDOW"){result += "\n";}return result;} +// HISTORY +// ------------------------------------------------------------------ +// Feb 7, 2005: Fixed a CSS styles to use px unit +// March 29, 2004: Added check in select() method for the form field +// being disabled. If it is, just return and don't do anything. +// March 24, 2004: Fixed bug - when month name and abbreviations were +// changed, date format still used original values. +// January 26, 2004: Added support for drop-down month and year +// navigation (Thanks to Chris Reid for the idea) +// September 22, 2003: Fixed a minor problem in YEAR calendar with +// CSS prefix. +// August 19, 2003: Renamed the function to get styles, and made it +// work correctly without an object reference +// August 18, 2003: Changed showYearNavigation and +// showYearNavigationInput to optionally take an argument of +// true or false +// July 31, 2003: Added text input option for year navigation. +// Added a per-calendar CSS prefix option to optionally use +// different styles for different calendars. +// July 29, 2003: Fixed bug causing the Today link to be clickable +// even though today falls in a disabled date range. +// Changed formatting to use pure CSS, allowing greater control +// over look-and-feel options. +// June 11, 2003: Fixed bug causing the Today link to be unselectable +// under certain cases when some days of week are disabled +// March 14, 2003: Added ability to disable individual dates or date +// ranges, display as light gray and strike-through +// March 14, 2003: Removed dependency on graypixel.gif and instead +/// use table border coloring +// March 12, 2003: Modified showCalendar() function to allow optional +// start-date parameter +// March 11, 2003: Modified select() function to allow optional +// start-date parameter +/* +DESCRIPTION: This object implements a popup calendar to allow the user to +select a date, month, quarter, or year. +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. +The calendar can be modified to work for any location in the world by +changing which weekday is displayed as the first column, changing the month +names, and changing the column headers for each day. + +USAGE: +// Create a new CalendarPopup object of type WINDOW +var cal = new CalendarPopup(); + +// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv' +var cal = new CalendarPopup('mydiv'); + +// Easy method to link the popup calendar with an input box. +cal.select(inputObject, anchorname, dateFormat); +// Same method, but passing a default date other than the field's current value +cal.select(inputObject, anchorname, dateFormat, '01/02/2000'); +// This is an example call to the popup calendar from a link to populate an +// input box. Note that to use this, date.js must also be included!! +Select + +// Set the type of date select to be used. By default it is 'date'. +cal.setDisplayType(type); + +// When a date, month, quarter, or year is clicked, a function is called and +// passed the details. You must write this function, and tell the calendar +// popup what the function name is. +// Function to be called for 'date' select receives y, m, d +cal.setReturnFunction(functionname); +// Function to be called for 'month' select receives y, m +cal.setReturnMonthFunction(functionname); +// Function to be called for 'quarter' select receives y, q +cal.setReturnQuarterFunction(functionname); +// Function to be called for 'year' select receives y +cal.setReturnYearFunction(functionname); + +// Show the calendar relative to a given anchor +cal.showCalendar(anchorname); + +// Hide the calendar. The calendar is set to autoHide automatically +cal.hideCalendar(); + +// Set the month names to be used. Default are English month names +cal.setMonthNames("January","February","March",...); + +// Set the month abbreviations to be used. Default are English month abbreviations +cal.setMonthAbbreviations("Jan","Feb","Mar",...); + +// Show navigation for changing by the year, not just one month at a time +cal.showYearNavigation(); + +// Show month and year dropdowns, for quicker selection of month of dates +cal.showNavigationDropdowns(); + +// Set the text to be used above each day column. The days start with +// sunday regardless of the value of WeekStartDay +cal.setDayHeaders("S","M","T",...); + +// Set the day for the first column in the calendar grid. By default this +// is Sunday (0) but it may be changed to fit the conventions of other +// countries. +cal.setWeekStartDay(1); // week is Monday - Sunday + +// Set the weekdays which should be disabled in the 'date' select popup. You can +// then allow someone to only select week end dates, or Tuedays, for example +cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week + +// Selectively disable individual days or date ranges. Disabled days will not +// be clickable, and show as strike-through text on current browsers. +// Date format is any format recognized by parseDate() in date.js +// Pass a single date to disable: +cal.addDisabledDates("2003-01-01"); +// Pass null as the first parameter to mean "anything up to and including" the +// passed date: +cal.addDisabledDates(null, "01/02/03"); +// Pass null as the second parameter to mean "including the passed date and +// anything after it: +cal.addDisabledDates("Jan 01, 2003", null); +// Pass two dates to disable all dates inbetween and including the two +cal.addDisabledDates("January 01, 2003", "Dec 31, 2003"); + +// When the 'year' select is displayed, set the number of years back from the +// current year to start listing years. Default is 2. +// This is also used for year drop-down, to decide how many years +/- to display +cal.setYearSelectStartOffset(2); + +// Text for the word "Today" appearing on the calendar +cal.setTodayText("Today"); + +// The calendar uses CSS classes for formatting. If you want your calendar to +// have unique styles, you can set the prefix that will be added to all the +// classes in the output. +// For example, normal output may have this: +// Today +// But if you set the prefix like this: +cal.setCssPrefix("Test"); +// The output will then look like: +// Today +// And you can define that style somewhere in your page. + +// When using Year navigation, you can make the year be an input box, so +// the user can manually change it and jump to any year +cal.showYearNavigationInput(); + +// Set the calendar offset to be different than the default. By default it +// will appear just below and to the right of the anchorname. So if you have +// a text box where the date will go and and anchor immediately after the +// text box, the calendar will display immediately under the text box. +cal.offsetX = 20; +cal.offsetY = 20; + +NOTES: +1) Requires the functions in AnchorPosition.js and PopupWindow.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a CalendarPopup object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a CalendarPopup object + or the autoHide() will not work correctly. + +5) The calendar popup display uses style sheets to make it look nice. + +*/ + +// CONSTRUCTOR for the CalendarPopup Object +function CalendarPopup() { + var c; + if (arguments.length>0) { + c = new PopupWindow(arguments[0]); + } + else { + c = new PopupWindow(); + c.setSize(150,175); + } + c.offsetX = -152; + c.offsetY = 25; + c.autoHide(); + // Calendar-specific properties + c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December"); + c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); + c.dayHeaders = new Array("S","M","T","W","T","F","S"); + c.returnFunction = "CP_tmpReturnFunction"; + c.returnMonthFunction = "CP_tmpReturnMonthFunction"; + c.returnQuarterFunction = "CP_tmpReturnQuarterFunction"; + c.returnYearFunction = "CP_tmpReturnYearFunction"; + c.weekStartDay = 0; + c.isShowYearNavigation = false; + c.displayType = "date"; + c.disabledWeekDays = new Object(); + c.disabledDatesExpression = ""; + c.yearSelectStartOffset = 2; + c.currentDate = null; + c.todayText="Today"; + c.cssPrefix=""; + c.isShowNavigationDropdowns=false; + c.isShowYearNavigationInput=false; + window.CP_calendarObject = null; + window.CP_targetInput = null; + window.CP_dateFormat = "MM/dd/yyyy"; + // Method mappings + c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow; + c.setReturnFunction = CP_setReturnFunction; + c.setReturnMonthFunction = CP_setReturnMonthFunction; + c.setReturnQuarterFunction = CP_setReturnQuarterFunction; + c.setReturnYearFunction = CP_setReturnYearFunction; + c.setMonthNames = CP_setMonthNames; + c.setMonthAbbreviations = CP_setMonthAbbreviations; + c.setDayHeaders = CP_setDayHeaders; + c.setWeekStartDay = CP_setWeekStartDay; + c.setDisplayType = CP_setDisplayType; + c.setDisabledWeekDays = CP_setDisabledWeekDays; + c.addDisabledDates = CP_addDisabledDates; + c.setYearSelectStartOffset = CP_setYearSelectStartOffset; + c.setTodayText = CP_setTodayText; + c.showYearNavigation = CP_showYearNavigation; + c.showCalendar = CP_showCalendar; + c.hideCalendar = CP_hideCalendar; + c.getStyles = getCalendarStyles; + c.refreshCalendar = CP_refreshCalendar; + c.getCalendar = CP_getCalendar; + c.select = CP_select; + c.setCssPrefix = CP_setCssPrefix; + c.showNavigationDropdowns = CP_showNavigationDropdowns; + c.showYearNavigationInput = CP_showYearNavigationInput; + c.copyMonthNamesToWindow(); + // Return the object + return c; + } +function CP_copyMonthNamesToWindow() { + // Copy these values over to the date.js + if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) { + window.MONTH_NAMES = new Array(); + for (var i=0; i\n"; + result += '
\n'; + } + else { + result += '
\n'; + result += '
\n'; + result += '
\n'; + } + // Code for DATE display (default) + // ------------------------------- + if (this.displayType=="date" || this.displayType=="week-end") { + if (this.currentDate==null) { this.currentDate = now; } + if (arguments.length > 0) { var month = arguments[0]; } + else { var month = this.currentDate.getMonth()+1; } + if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; } + else { var year = this.currentDate.getFullYear(); } + var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31); + if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) { + daysinmonth[2] = 29; + } + var current_month = new Date(year,month-1,1); + var display_year = year; + var display_month = month; + var display_date = 1; + var weekday= current_month.getDay(); + var offset = 0; + + offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ; + if (offset > 0) { + display_month--; + if (display_month < 1) { display_month = 12; display_year--; } + display_date = daysinmonth[display_month]-offset+1; + } + var next_month = month+1; + var next_month_year = year; + if (next_month > 12) { next_month=1; next_month_year++; } + var last_month = month-1; + var last_month_year = year; + if (last_month < 1) { last_month=12; last_month_year--; } + var date_class; + if (this.type!="WINDOW") { + result += ""; + } + result += '\n'; + var refresh = windowref+'CP_refreshCalendar'; + var refreshLink = 'javascript:' + refresh; + if (this.isShowNavigationDropdowns) { + result += ''; + result += ''; + + result += ''; + } + else { + if (this.isShowYearNavigation) { + result += ''; + result += ''; + result += ''; + result += ''; + + result += ''; + if (this.isShowYearNavigationInput) { + result += ''; + } + else { + result += ''; + } + result += ''; + } + else { + result += '\n'; + result += '\n'; + result += '\n'; + } + } + result += '
 <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
\n'; + result += '\n'; + result += '\n'; + for (var j=0; j<7; j++) { + + result += '\n'; + } + result += '\n'; + for (var row=1; row<=6; row++) { + result += '\n'; + for (var col=1; col<=7; col++) { + var disabled=false; + if (this.disabledDatesExpression!="") { + var ds=""+display_year+LZ(display_month)+LZ(display_date); + eval("disabled=("+this.disabledDatesExpression+")"); + } + var dateClass = ""; + if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) { + dateClass = "cpCurrentDate"; + } + else if (display_month == month) { + dateClass = "cpCurrentMonthDate"; + } + else { + dateClass = "cpOtherMonthDate"; + } + if (disabled || this.disabledWeekDays[col-1]) { + result += ' \n'; + } + else { + var selected_date = display_date; + var selected_month = display_month; + var selected_year = display_year; + if (this.displayType=="week-end") { + var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0); + d.setDate(d.getDate() + (7-col)); + selected_year = d.getYear(); + if (selected_year < 1000) { selected_year += 1900; } + selected_month = d.getMonth()+1; + selected_date = d.getDate(); + } + result += ' \n'; + } + display_date++; + if (display_date > daysinmonth[display_month]) { + display_date=1; + display_month++; + } + if (display_month > 12) { + display_month=1; + display_year++; + } + } + result += ''; + } + var current_weekday = now.getDay() - this.weekStartDay; + if (current_weekday < 0) { + current_weekday += 7; + } + result += '\n'; + result += '
'+this.dayHeaders[(this.weekStartDay+j)%7]+'
'+display_date+''+display_date+'
\n'; + if (this.disabledDatesExpression!="") { + var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate()); + eval("disabled=("+this.disabledDatesExpression+")"); + } + if (disabled || this.disabledWeekDays[current_weekday+1]) { + result += ' '+this.todayText+'\n'; + } + else { + result += ' '+this.todayText+'\n'; + } + result += '
\n'; + result += '
\n'; + } + + // Code common for MONTH, QUARTER, YEAR + // ------------------------------------ + if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") { + if (arguments.length > 0) { var year = arguments[0]; } + else { + if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; } + else { var year = now.getFullYear(); } + } + if (this.displayType!="year" && this.isShowYearNavigation) { + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += ' \n'; + result += '
<<'+year+'>>
\n'; + } + } + + // Code for MONTH display + // ---------------------- + if (this.displayType=="month") { + // If POPUP, write entire HTML document + result += '\n'; + for (var i=0; i<4; i++) { + result += ''; + for (var j=0; j<3; j++) { + var monthindex = ((i*3)+j); + result += ''; + } + result += ''; + } + result += '
'+this.monthAbbreviations[monthindex]+'
\n'; + } + + // Code for QUARTER display + // ------------------------ + if (this.displayType=="quarter") { + result += '
\n'; + for (var i=0; i<2; i++) { + result += ''; + for (var j=0; j<2; j++) { + var quarter = ((i*2)+j+1); + result += ''; + } + result += ''; + } + result += '

Q'+quarter+'

\n'; + } + + // Code for YEAR display + // --------------------- + if (this.displayType=="year") { + var yearColumnSize = 4; + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += '
<<>>
\n'; + result += '\n'; + for (var i=0; i'+currentyear+''; + } + result += ''; + } + result += '
\n'; + } + // Common + if (this.type == "WINDOW") { + result += "\n"; + } + return result; + } diff --git a/reports/site_media/PopupWindow.js b/reports/site_media/PopupWindow.js new file mode 100644 index 000000000..5a5823590 --- /dev/null +++ b/reports/site_media/PopupWindow.js @@ -0,0 +1,336 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// =================================================================== + +/* +PopupWindow.js +Author: Matt Kruse +Last modified: 02/16/04 + +DESCRIPTION: This object allows you to easily and quickly popup a window +in a certain place. The window can either be a DIV or a separate browser +window. + +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. Due to bugs in Netscape 4.x, populating the popup +window with +{% endblock %} + +{% block body_onload %}javascript:clientdetailload(){% endblock %} + +{% block pagebanner %}Client Details{% endblock %} {% block content %} -

Client Status Detail page for {{client.name}}


-Select time: - + + {% for i in client.interactions.all|slice:":25" %} + + {% endfor %} +
+ + + {% if interaction.isstale %} +
+ This node did not run within the last 24 hours — it may be out of date. +
+ {% endif %} + + + {% if interaction.server %} + + {% endif %} + {% if interaction.repo_rev_code %} + + {% endif %} + + + {% if not interaction.isclean %} + + {% endif %} +
Timestamp{{interaction.timestamp}}
Served by{{interaction.server}}
Revision{{interaction.repo_rev_code}}
State{{interaction.state|capfirst}}
Managed entries{{interaction.totalcount}}
Deviation{{interaction.percentbad|floatformat:"3"}}%
+ + {% if interaction.bad_entry_count %} +
+
+
[+]
+

Bad Entries — {{ interaction.bad_entry_count }}

+
+ + {% for e in interaction.bad|sortwell %} + + + + + {% endfor %} +
{{e.entry.kind}}: + {{e.entry.name}}
+
+ {% endif %} + + {% if interaction.modified_entry_count %} +
+
+
[+]
+

Modified Entries — {{ interaction.modified_entry_count }}

+
+ + {% for e in interaction.modified|sortwell %} + + + + + {% endfor %} +
{{e.entry.kind}}: + {{e.entry.name}}
+
+ {% endif %} + + {% if interaction.extra_entry_count %} +
+
+
[+]
+

Extra Entries — {{ interaction.extra_entry_count }}

+
+ + {% for e in interaction.extra|sortwell %} + + + + {% endfor %} - -         -Manage {{client.name}} options.
+
{{e.entry.kind}}:{{e.entry.name}}
+
+ {% endif %} -{% include "clients/client-nodebox.html" %} + {% if entry_list %} +
+
+

Recent Interactions

+
+
+ {% include "widgets/interaction_list.inc" %} + +
+
+ {% endif %} {% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html b/src/lib/Server/Reports/reports/templates/clients/detailed-list.html index 5a1352cff..0c1fae8d5 100644 --- a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html +++ b/src/lib/Server/Reports/reports/templates/clients/detailed-list.html @@ -1,57 +1,15 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} -{% block title %}Detailed Client Listing{% endblock %} - -{% block extra_header_info %} - - - -{% endblock%} - -{% block pagebanner %} -
-

Detailed Client List

-
-
-{% endblock %} +{% block title %}Bcfg2 - Detailed Client Listing{% endblock %} +{% block pagebanner %}Clients - Detailed View{% endblock %} {% block content %} -
-
- -Enter date or use calendar popup: -@ - -Calendar - - | -

-
-
- -
+
{% if entry_list %} + {% filter_navigator %} - + @@ -61,30 +19,19 @@ - {% for client,entry,stale in entry_list %} + {% for entry in entry_list %} - - + + - +
Node State GoodLast Run Server
{{ client }}{{ entry.state }}{{ entry.client.name }}{{ entry.state }} {{ entry.goodcount }} {{ entry.bad_entry_count }} {{ entry.modified_entry_count }} {{ entry.extra_entry_count }}{{ entry.timestamp|date:"Y-m-d H:i" }}{{ entry.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe }} {% if entry.server %} - {{ entry.server }} + {{ entry.server }} {% else %}   {% endif %} diff --git a/src/lib/Server/Reports/reports/templates/clients/history.html b/src/lib/Server/Reports/reports/templates/clients/history.html new file mode 100644 index 000000000..01d4ec2f4 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/clients/history.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% load bcfg2_tags %} + +{% block title %}Bcfg2 - Interaction History{% endblock %} +{% block pagebanner %}Interaction history{% if client %} for {{ client.name }}{% endif %}{% endblock %} + +{% block extra_header_info %} +{% endblock %} + +{% block content %} +
+{% if entry_list %} + {% filter_navigator %} + {% include "widgets/interaction_list.inc" %} +{% else %} +

No client records are available.

+{% endif %} +
+{% page_navigator %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/clients/index.html b/src/lib/Server/Reports/reports/templates/clients/index.html index cfb8a6c83..e0c0d2d7a 100644 --- a/src/lib/Server/Reports/reports/templates/clients/index.html +++ b/src/lib/Server/Reports/reports/templates/clients/index.html @@ -1,56 +1,33 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} {% block extra_header_info %} - - - {% endblock%} -{% block title %}Client Index Listing{% endblock %} +{% block title %}Bcfg2 - Client Grid View{% endblock %} -{% block pagebanner %} -
-

Clients List

-
-
-{% endblock %} +{% block pagebanner %}Clients - Grid View{% endblock %} {% block content %} -
- -Enter date or use calendar popup: - -
- -@ - -Calendar - - | -
-


{% if inter_list %} - + {% if forloop.last %} + {% else %} - {% url Bcfg2.Server.Reports.reports.views.client_detail client,inter.id %} - {% endifequal %} - {% endspaceless %}">{{ client }} - - {% ifequal half_list forloop.counter0 %} - -{% endif %} + {% endif %} {% endfor %} - -
-
-
    - {% endifequal %} + {% if forloop.counter|divisibleby:"4" %}
+
{% else %}

No client records are available.

{% endif %} diff --git a/src/lib/Server/Reports/reports/templates/clients/manage.html b/src/lib/Server/Reports/reports/templates/clients/manage.html index 61f0fe017..5725ae577 100644 --- a/src/lib/Server/Reports/reports/templates/clients/manage.html +++ b/src/lib/Server/Reports/reports/templates/clients/manage.html @@ -1,29 +1,45 @@ {% extends "base.html" %} + {% block extra_header_info %} - - {% endblock%} -{% block title %}{{client.name}}{% endblock %} -{% block content %} -

Client Options Management page for {{client.name}}


-

Client status detail page: {{client.name}}.

-

Hosts may be prevented from showing up in the reporting system if they have been retired, are no longer managed by bcfg2 :(, etc.

-Select deactivation date: -
- -Enter date or use calendar popup: - -
- -@ - -Calendar - -
-


-

-

{{message}}

+{% block title %}Bcfg2 - Manage Clients{% endblock %} +{% block pagebanner %}Clients - Manage{% endblock %} + +{% block content %} +
+ {% if message %} +
{{ message }}
+ {% endif %} +{% if clients %} + + + + + + + {% for client in clients %} + + + + + + {% endfor %} +
NodeExpirationManage
+ + + {{ client.name }}{% firstof client.expiration 'Active' %} +
+
{# here for no reason other then to validate #} + + + +
+
+
+
+{% else %} +

No client records are available.

+{% endif %} {% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/index.html b/src/lib/Server/Reports/reports/templates/config_items/index.html deleted file mode 100644 index 04083344c..000000000 --- a/src/lib/Server/Reports/reports/templates/config_items/index.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends "base.html" %} - -{% load syntax_coloring %} - -{% block extra_header_info %} - - - -{% endblock%} -{% block title %}Configuration Element Details{% endblock %} - -{% block pagebanner %} -
-

Configuration Element Details

-
-
-{% endblock %} - -{% block content %} - -{% ifequal mod_or_bad "bad" %} -
-

Bad {{item.entry.kind}}: {{item.entry.name}}

-
-{% else %} -
-

Modified {{item.entry.kind}}: {{item.entry.name}}

-
-{% endifequal %} -
- - -{% if item.reason.current_owner %} - -{% endif %}{% if item.reason.current_group %} - -{% endif %}{% if item.reason.current_perms %} - -{% endif %}{% if item.reason.current_status %} - -{% endif %}{% if item.reason.current_to %} - -{% endif %}{% if item.reason.current_version %} - -{% endif %}{% if not item.reason.current_exists %} - -{% endif %}{% if item.reason.current_diff %} - -{% endif %} -
ReasonCurrent StatusSpecified in bcfg2
Owner: {{item.reason.current_owner}}{{item.reason.owner}}
Group: {{item.reason.current_group}}{{item.reason.group}}
Permissions: {{item.reason.current_perms}}{{item.reason.perms}}
Status: {{item.reason.current_status}}{{item.reason.status}}
Link Destination: {{item.reason.current_to}}{{item.reason.to}}
Version: {{item.reason.current_version}}{{item.reason.version}}
Existence: This item does not currently exist on the host but is specified to exist in the configuration.
NDiff:
{{item.reason.current_diff|syntaxhilight:"diff"}}
-
-
- -Enter date or use calendar popup: - -
- -@ - -Calendar -{% ifequal mod_or_bad "modified" %} - - | -{% else %} - - | -{% endifequal %} -
-


-{% if associated_client_list %} -

The following clients had this problem as of {{timestamp_date}}@{{timestamp_time}}:

- {% for client in associated_client_list %} - {{client.name}}
- {% endfor %} -
-
-{% else %} -

No Clients had this problem at {{timestamp}}

-{% endif %} - - - - - - - -{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/item.html b/src/lib/Server/Reports/reports/templates/config_items/item.html new file mode 100644 index 000000000..41474922b --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/config_items/item.html @@ -0,0 +1,109 @@ +{% extends "base.html" %} +{% load syntax_coloring %} + + +{% block title %}Bcfg2 - Element Details{% endblock %} + + +{% block extra_header_info %} + +{% endblock%} + +{% block pagebanner %}Element Details{% endblock %} + +{% block content %} +
+

{{mod_or_bad|capfirst}} {{item.entry.kind}}: {{item.entry.name}}

+
+ +
+ + {% if isextra %} +

This item exists on the host but is not defined in the configuration.

+ {% endif %} + + {% if not item.reason.current_exists %} +
This item does not currently exist on the host but is specified to exist in the configuration.
+ {% endif %} + + {% if item.reason.current_owner or item.reason.current_group or item.reason.current_perms or item.reason.current_status or item.reason.current_status or item.reason.current_to or item.reason.current_version %} + + + + {% if item.reason.current_owner %} + + + {% endif %} + {% if item.reason.current_group %} + + + {% endif %} + {% if item.reason.current_perms %} + + + {% endif %} + {% if item.reason.current_status %} + + + {% endif %} + {% if item.reason.current_to %} + + + {% endif %} + {% if item.reason.current_version %} + + + {% endif %} +
Problem TypeExpectedFound
Owner{{item.reason.owner}}{{item.reason.current_owner}}
Group{{item.reason.group}}{{item.reason.current_group}}
Permissions{{item.reason.perms}}{{item.reason.current_perms}}
Status{{item.reason.status}}{{item.reason.current_status}}
Symlink Target{{item.reason.to}}{{item.reason.current_to}}
Package Version{{item.reason.version|cut:"("|cut:")"}}{{item.reason.current_version|cut:"("|cut:")"}}
+ {% endif %} + + {% if item.reason.current_diff %} +
+
+

Incorrect file contents

+
+
+ {{ item.reason.current_diff|syntaxhilight }} +
+
+ {% endif %} + + +
+
+

Occurances on {{ timestamp|date:"Y-m-d" }}

+
+ {% if associated_list %} + + {% for inter in associated_list %} + + + + {% endfor %} +
{{inter.client.name}}{{inter.timestamp}}
+ {% else %} +

Missing client list

+ {% endif %} +
+ +
+{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/listing.html b/src/lib/Server/Reports/reports/templates/config_items/listing.html index 64a60e506..572249470 100644 --- a/src/lib/Server/Reports/reports/templates/config_items/listing.html +++ b/src/lib/Server/Reports/reports/templates/config_items/listing.html @@ -1,50 +1,32 @@ -{% extends "base.html" %} -{% load django_templating_sigh %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} -{% block extra_header_info %} - - - - - - - - - -{% endblock %} +{% block pagebanner %}{{mod_or_bad|capfirst}} Element Listing{% endblock %} -{% block title %}{{mod_or_bad|capfirst}} Item Listing{% endblock %} +{% block content %} +{% if item_list_dict %} + {% for kind, entries in item_list_dict.items %} -{% block pagebanner %} -
-

{{mod_or_bad|capfirst}} Configuration Elements

+
+
+
[+]
+

{{ kind }} — {{ entries|length }}

-
-{% endblock %} -{% block content %} -{% if item_list_pseudodict %} -
+ + {% for e in entries %} + + + + {% endfor %} +
{{e.entry.name}}
+
+ {% endfor %} {% else %}

There are currently no inconsistent configuration entries.

{% endif %} diff --git a/src/lib/Server/Reports/reports/templates/displays/index.html b/src/lib/Server/Reports/reports/templates/displays/index.html deleted file mode 100644 index c078539b6..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/index.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Display Index Listing{% endblock %} -{% block pagebanner %} -
-

BCFG Display Index

- {% comment %} Report Run @ {% now "F j, Y P"%}{% endcomment %} -
-
-{% endblock %} - -{% block content %} - -{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html b/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html deleted file mode 100644 index 60f97eadc..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "displays/summary-block.html" %} -{% block linkprefix1 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix2 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix3 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix4 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix5 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix6 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} \ No newline at end of file diff --git a/src/lib/Server/Reports/reports/templates/displays/summary-block.html b/src/lib/Server/Reports/reports/templates/displays/summary-block.html deleted file mode 100644 index 060ff0fa1..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/summary-block.html +++ /dev/null @@ -1,90 +0,0 @@ -{% load django_templating_sigh %} - -
-

Summary:

-

{{client_list|length }} Nodes were included in your report.

- {% if clean_client_list %} -
- {{clean_client_list|length}} nodes are clean.
-
    - {% for client in clean_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if bad_client_list %} -
- {{bad_client_list|length}} nodes are bad.
-
    - {% for client in bad_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if modified_client_list %} -
- {{modified_client_list|length}} nodes were modified in the previous run.
-
    - {% for client in modified_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if extra_client_list %} -
- {{extra_client_list|length}} nodes have extra configuration. (includes both good and bad nodes)
-
    - {% for client in extra_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if stale_up_client_list %} -
- {{stale_up_client_list|length}} nodes did not run within the last 24 hours but were pingable.
-
    - {% for client in stale_up_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if stale_all_client_list %} -
- {{stale_all_client_list|length}} nodes did not run within the last 24 hours. (includes nodes up and down)
-
    - {% for client in stale_all_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if down_client_list %} -
- {{down_client_list|length}} nodes were down.
-
    - {% for client in down_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} -
diff --git a/src/lib/Server/Reports/reports/templates/displays/summary.html b/src/lib/Server/Reports/reports/templates/displays/summary.html index 29cbb22d7..0124f635d 100644 --- a/src/lib/Server/Reports/reports/templates/displays/summary.html +++ b/src/lib/Server/Reports/reports/templates/displays/summary.html @@ -1,31 +1,42 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} + +{% block title %}Bcfg2 - Client Summary{% endblock %} +{% block pagebanner %}Clients - Summary{% endblock %} + +{% block body_onload %}javascript:hide_table_array(hide_tables){% endblock %} + {% block extra_header_info %} - - + {% endblock%} -{% block title %}Display Index Listing{% endblock %} -{% block pagebanner %} -
-

BCFG Clients Summary

- Report Run @ {% now "F j, Y P"%} -
-
-{% endblock %} {% block content %} -
- -Enter date or use calendar popup: - -
- -@ - -Calendar - - | -
-


- {% include "displays/summary-block-direct-links.html" %} +
+

{{ node_count }} nodes reporting in

+
+{% if summary_data %} + {% for summary in summary_data %} +
+
+
[+]
+

{{ summary.nodes|length }} {{ summary.label }}

+
+ + + {% for node in summary.nodes|sort_interactions_by_name %} + + + + {% endfor %} +
{{ node.client.name }}
+
+ {% endfor %} +{% else %} +

No data to report on

+{% endif %} {% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/sys_view.html b/src/lib/Server/Reports/reports/templates/displays/sys_view.html deleted file mode 100644 index 1298059bf..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/sys_view.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% load django_templating_sigh %} - -{% block title %}System-View Display{% endblock %} -{% block pagebanner %} -
-

Grand System View

- Report Run @ {% now "F j, Y P"%} -
-
-{% endblock %} -{% block content %} -

This view is deprecated and will be removed soon.


Please use the "Summary" view and drill down instead.
- - {% include "displays/summary-block.html" %} - {% for client in client_list %} - {% set_interaction "foo" %} - {% include "clients/client-nodebox.html" %} - {% endfor %} -{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/timing.html b/src/lib/Server/Reports/reports/templates/displays/timing.html index 32ddab464..47accb2cb 100644 --- a/src/lib/Server/Reports/reports/templates/displays/timing.html +++ b/src/lib/Server/Reports/reports/templates/displays/timing.html @@ -1,54 +1,38 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} + +{% block title %}Bcfg2 - Performance Metrics{% endblock %} +{% block pagebanner %}Performance Metrics{% endblock %} + {% block extra_header_info %} - - - {% endblock%} -{% block title %}Display Index Listing{% endblock %} {% block content %} -
-

BCFG Performance Timings

- Report Run @ {% now "F j, Y P"%} -
-
-
- -Enter date or use calendar popup: - -
- -@ - -Calendar - - | -
-


-
- - - - - - - - - +
+ {% if metrics %} +
HostnameParseProbeInventoryInstallConfigTotal
+ + + + + + + + - {% for dict_unit in stats_list %} - - - - - - - - + {% for metric in metrics|dictsort:"name" %} + + + {% for mitem in metric|build_metric_list %} + + {% endfor %} {% endfor %}
NameParseProbeInventoryInstallConfigTotal
{{dict_unit.name}}{{dict_unit.parse}}{{dict_unit.probe}}{{dict_unit.inventory}}{{dict_unit.install}}{{dict_unit.config}}{{dict_unit.total}}
{{ metric.name }}{{ mitem }}
-
+ {% else %} +

No metric data available

+ {% endif %} +
{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/index.html b/src/lib/Server/Reports/reports/templates/index.html deleted file mode 100644 index 002a3f770..000000000 --- a/src/lib/Server/Reports/reports/templates/index.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base.html" %} - -{% block pagebanner %} -
-

BCFG Reports

- {% comment %} Report Run @ {% now "F j, Y P"%}{% endcomment %} -
-
-{% endblock %} -{% block content %} -

Welcome to the Bcfg2 Reporting System

-

-Please use the links at the left to navigate. -

-{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html b/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html new file mode 100644 index 000000000..6b57baf6a --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html @@ -0,0 +1,13 @@ +{% spaceless %} +{% if filters %} +{% for filter, filter_url in filters %} + {% if forloop.first %} +
Active filters (click to remove): + {% endif %} + {{ filter|capfirst }}{% if not forloop.last %}, {% endif %} + {% if forloop.last %} +
+ {% endif %} +{% endfor %} +{% endif %} +{% endspaceless %} diff --git a/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc b/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc new file mode 100644 index 000000000..8f2dec1dc --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc @@ -0,0 +1,38 @@ +{% load bcfg2_tags %} +
+ + + + {% if not client %} + + {% endif %} + + + + + + + + {% for entry in entry_list %} + + + {% if not client %} + + {% endif %} + + + + + + + + {% endfor %} +
TimestampClientStateGoodBadModifiedExtraServer
{{ entry.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe }}{{ entry.client.name }}{{ entry.state }}{{ entry.goodcount }}{{ entry.bad_entry_count }}{{ entry.modified_entry_count }}{{ entry.extra_entry_count }} + {% if entry.server %} + {{ entry.server }} + {% else %} +   + {% endif %} +
+
diff --git a/src/lib/Server/Reports/reports/templates/widgets/page_bar.html b/src/lib/Server/Reports/reports/templates/widgets/page_bar.html new file mode 100644 index 000000000..aa0def83e --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/widgets/page_bar.html @@ -0,0 +1,23 @@ +{% spaceless %} +{% for page, page_url in pager %} + {% if forloop.first %} +
+ {% if prev_page %}< Prev {% endif %} + {% if first_page %}1 ... {% endif %} + {% endif %} + {% ifequal page current_page %} + {{ page }} + {% else %} + {{ page }} + {% endifequal %} + {% if forloop.last %} + {% if last_page %} ... {{ total_pages }} {% endif %} + {% if next_page %}Next > {% endif %} + |{% for limit, limit_url in page_limits %} {{ limit }}{% endfor %} +
+ {% else %} +   + {% endif %} +{% endfor %} +{% endspaceless %} + diff --git a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py new file mode 100644 index 000000000..2c27aab04 --- /dev/null +++ b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py @@ -0,0 +1,239 @@ +from django import template +from django.core.urlresolvers import resolve, reverse, Resolver404, NoReverseMatch +from django.utils.encoding import smart_unicode, smart_str +from datetime import datetime, timedelta +from Bcfg2.Server.Reports.utils import filter_list + +register = template.Library() + +__PAGE_NAV_LIMITS__ = (10, 25, 50, 100) + +@register.inclusion_tag('widgets/page_bar.html', takes_context=True) +def page_navigator(context): + """ + Creates paginated links. + + Expects the context to be a RequestContext and views.prepare_paginated_list() + to have populated page information. + """ + fragment = dict() + try: + path = context['request'].path + total_pages = int(context['total_pages']) + records_per_page = int(context['records_per_page']) + except KeyError, e: + return fragment + except ValueError, e: + return fragment + + if total_pages < 2: + return {} + + try: + view, args, kwargs = resolve(path) + current_page = int(kwargs.get('page_number',1)) + fragment['current_page'] = current_page + fragment['page_number'] = current_page + fragment['total_pages'] = total_pages + fragment['records_per_page'] = records_per_page + if current_page > 1: + kwargs['page_number'] = current_page - 1 + fragment['prev_page'] = reverse(view, args=args, kwargs=kwargs) + if current_page < total_pages: + kwargs['page_number'] = current_page + 1 + fragment['next_page'] = reverse(view, args=args, kwargs=kwargs) + + view_range = 5 + if total_pages > view_range: + pager_start = current_page - 2 + pager_end = current_page + 2 + if pager_start < 1: + pager_end += (1 - pager_start) + pager_start = 1 + if pager_end > total_pages: + pager_start -= (pager_end - total_pages) + pager_end = total_pages + else: + pager_start = 1 + pager_end = total_pages + + if pager_start > 1: + kwargs['page_number'] = 1 + fragment['first_page'] = reverse(view, args=args, kwargs=kwargs) + if pager_end < total_pages: + kwargs['page_number'] = total_pages + fragment['last_page'] = reverse(view, args=args, kwargs=kwargs) + + pager = [] + for page in range(pager_start, int(pager_end) + 1): + kwargs['page_number'] = page + pager.append( (page, reverse(view, args=args, kwargs=kwargs)) ) + + kwargs['page_number'] = 1 + page_limits = [] + for limit in __PAGE_NAV_LIMITS__: + kwargs['page_limit'] = limit + page_limits.append( (limit, reverse(view, args=args, kwargs=kwargs)) ) + # resolver doesn't like this + del kwargs['page_number'] + del kwargs['page_limit'] + page_limits.append( ('all', reverse(view, args=args, kwargs=kwargs) + "|all") ) + + fragment['pager'] = pager + fragment['page_limits'] = page_limits + + except Resolver404: + path = "404" + except NoReverseMatch, nr: + path = "NoReverseMatch: %s" % nr + except ValueError: + path = "ValueError" + #FIXME - Handle these + + fragment['path'] = path + return fragment + +@register.inclusion_tag('widgets/filter_bar.html', takes_context=True) +def filter_navigator(context): + try: + path = context['request'].path + view, args, kwargs = resolve(path) + + # Strip any page limits and numbers + if 'page_number' in kwargs: + del kwargs['page_number'] + if 'page_limit' in kwargs: + del kwargs['page_limit'] + + filters = [] + for filter in filter_list: + if filter in kwargs: + myargs = kwargs.copy() + del myargs[filter] + filters.append( (filter, reverse(view, args=args, kwargs=myargs) ) ) + filters.sort(lambda x,y: cmp(x[0], y[0])) + return { 'filters': filters } + except (Resolver404, NoReverseMatch, ValueError, KeyError): + pass + return dict() + +def _subtract_or_na(mdict, x, y): + """ + Shortcut for build_metric_list + """ + try: + return round(mdict[x] - mdict[y], 4) + except: + return "n/a" + +@register.filter +def build_metric_list(mdict): + """ + Create a list of metric table entries + + Moving this here it simplify the view. Should really handle the case where these + are missing... + """ + td_list = [] + # parse + td_list.append( _subtract_or_na(mdict, 'config_parse', 'config_download')) + #probe + td_list.append( _subtract_or_na(mdict, 'probe_upload', 'start')) + #inventory + td_list.append( _subtract_or_na(mdict, 'inventory', 'initialization')) + #install + td_list.append( _subtract_or_na(mdict, 'install', 'inventory')) + #cfg download & parse + td_list.append( _subtract_or_na(mdict, 'config_parse', 'probe_upload')) + #total + td_list.append( _subtract_or_na(mdict, 'finished', 'start')) + return td_list + +@register.filter +def isstale(timestamp, entry_max=None): + """ + Check for a stale timestamp + + Compares two timestamps and returns True if the + difference is greater then 24 hours. + """ + if not entry_max: + entry_max = datetime.now() + return entry_max - timestamp > timedelta(hours=24) + +@register.filter +def sort_interactions_by_name(value): + """ + Sort an interaction list by client name + """ + inters = list(value) + inters.sort(lambda a,b: cmp(a.client.name, b.client.name)) + return inters + +class AddUrlFilter(template.Node): + def __init__(self, filter_name, filter_value): + self.filter_name = filter_name + self.filter_value = filter_value + self.fallback_view = 'Bcfg2.Server.Reports.reports.views.render_history_view' + + def render(self, context): + link = '#' + try: + path = context['request'].path + view, args, kwargs = resolve(path) + filter_value = self.filter_value.resolve(context, True) + if filter_value: + filter_name = smart_str(self.filter_name) + filter_value = smart_unicode(filter_value) + kwargs[filter_name] = filter_value + # These two don't make sense + if filter_name == 'server' and 'hostname' in kwargs: + del kwargs['hostname'] + elif filter_name == 'hostname' and 'server' in kwargs: + del kwargs['server'] + try: + link = reverse(view, args=args, kwargs=kwargs) + except NoReverseMatch, rm: + link = reverse(self.fallback_view, args=None, + kwargs={ filter_name: filter_value }) + except NoReverseMatch, rm: + raise rm + except (Resolver404, ValueError), e: + pass + return link + +@register.tag +def add_url_filter(parser, token): + """ + Return a url with the filter added to the current view. + + Takes a new filter and resolves the current view with the new filter + applied. Resolves to Bcfg2.Server.Reports.reports.views.client_history + by default. + + {% add_url_filter server=interaction.server %} + """ + try: + tag_name, filter_pair = token.split_contents() + filter_name, filter_value = filter_pair.split('=', 1) + filter_name = filter_name.strip() + filter_value = parser.compile_filter(filter_value) + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0] + if not filter_name or not filter_value: + raise template.TemplateSyntaxError, "argument should be a filter=value pair" + + return AddUrlFilter(filter_name, filter_value) + +@register.filter +def sortwell(value): + """ + Sorts a list(or evaluates queryset to list) of bad, extra, or modified items in the best + way for presentation + """ + + configItems = list(value) + configItems.sort(lambda x,y: cmp(x.entry.name, y.entry.name)) + configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) + return configItems + diff --git a/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py b/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py deleted file mode 100644 index c0d05d2c1..000000000 --- a/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py +++ /dev/null @@ -1,41 +0,0 @@ -from django import template -#from Bcfg2.Server.Reports.reports.models import Client, Interaction, Bad, Modified, Extra - -register = template.Library() - -def set_interaction(parser, token): - try: - # Splitting by None == splitting by spaces. - tag_name, format_string = token.contents.split(None, 1) - except ValueError: - raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0] - if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): - raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name - return SetInteraction(format_string[1:-1]) - -def sortwell(value): - "sorts a list(or evaluates queryset to list) of bad, extra, or modified items in the best" - "way for presentation" - configItems = list(value) - configItems.sort(lambda x,y: cmp(x.entry.name, y.entry.name)) - configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) - return configItems -def sortname(value): - "sorts a list( or evaluates queryset) by name" - configItems = list(value) - configItems.sort(lambda x,y: cmp(x.name, y.name)) - return configItems - -class SetInteraction(template.Node): - def __init__(self, times): - self.times = times#do soemthing to select different interaction with host? - def render(self, context): - try: - context['interaction'] = context['client_interaction_dict'][context['client'].id] - except:#I don't fully know what the implications of this are. - pass - return '' - -register.tag('set_interaction', set_interaction) -register.filter('sortwell', sortwell) -register.filter('sortname', sortname) diff --git a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py b/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py index 083b83a73..43dafb262 100644 --- a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py +++ b/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py @@ -1,4 +1,7 @@ from django import template +from django.utils.encoding import smart_unicode, smart_str +from django.utils.html import conditional_escape +from django.utils.safestring import mark_safe register = template.Library() @@ -11,15 +14,28 @@ try: except: colorize = False -def syntaxhilight(value, arg="diff"): - '''Returns a syntax-hilighted version of Code; requires code/language arguments''' +@register.filter +def syntaxhilight(value, arg="diff", autoescape=None): + """ + Returns a syntax-hilighted version of Code; requires code/language arguments + """ + + if autoescape: + value = conditional_escape(value) + arg = conditional_escape(arg) + if colorize: try: + output = u'' + lexer = get_lexer_by_name(arg) - return highlight(value, lexer, HtmlFormatter()) + output += highlight(value, lexer, HtmlFormatter()) + return mark_safe(output) except: return value else: - return value + return mark_safe(u'
Tip: Install pygments for highlighting
%s
' % value) +syntaxhilight.needs_autoescape = True -register.filter('syntaxhilight', syntaxhilight) diff --git a/src/lib/Server/Reports/reports/urls.py b/src/lib/Server/Reports/reports/urls.py new file mode 100644 index 000000000..9970d26a1 --- /dev/null +++ b/src/lib/Server/Reports/reports/urls.py @@ -0,0 +1,55 @@ +from django.conf.urls.defaults import * +from django.core.urlresolvers import reverse, NoReverseMatch +from django.http import HttpResponsePermanentRedirect +from Bcfg2.Server.Reports.utils import filteredUrls, paginatedUrls, timeviewUrls + +def newRoot(request): + try: + grid_view = reverse('reports_grid_view') + except NoReverseMatch: + grid_view = '/grid' + return HttpResponsePermanentRedirect(grid_view) + +urlpatterns = patterns('Bcfg2.Server.Reports.reports', + (r'^$', newRoot), + + url(r'^manage/?$', 'views.client_manage', name='reports_client_manage'), + url(r'^client/(?P\S+)/(?P\d+)/?$', 'views.client_detail', name='reports_client_detail_pk'), + url(r'^client/(?P\S+)/?$', 'views.client_detail', name='reports_client_detail'), + url(r'^elements/(?P\w+)/(?P\d+)/?$', 'views.config_item', name='reports_item'), +) + +urlpatterns += patterns('Bcfg2.Server.Reports.reports', + *timeviewUrls( + (r'^grid/?$', 'views.client_index', None, 'reports_grid_view'), + (r'^summary/?$', 'views.display_summary', None, 'reports_summary'), + (r'^timing/?$', 'views.display_timing', None, 'reports_timing'), + (r'^elements/(?P\w+)/?$', 'views.config_item_list', None, 'reports_item_list'), +)) + +urlpatterns += patterns('Bcfg2.Server.Reports.reports', + *filteredUrls(*timeviewUrls( + (r'^detailed/?$', + 'views.client_detailed_list', None, 'reports_detailed_list') +))) + +urlpatterns += patterns('Bcfg2.Server.Reports.reports', + *paginatedUrls( *filteredUrls( + (r'^history/?$', + 'views.render_history_view', None, 'reports_history'), + (r'^history/(?P[\w\-\.]+)/?$', + 'views.render_history_view', None, 'reports_client_history'), +))) + + # Uncomment this for admin: + #(r'^admin/', include('django.contrib.admin.urls')), + + +## Uncomment this section if using authentication +#urlpatterns += patterns('', +# (r'^login/$', 'django.contrib.auth.views.login', +# {'template_name': 'auth/login.html'}), +# (r'^logout/$', 'django.contrib.auth.views.logout', +# {'template_name': 'auth/logout.html'}) +# ) + diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Server/Reports/reports/views.py index d159dcd43..64617ce70 100644 --- a/src/lib/Server/Reports/reports/views.py +++ b/src/lib/Server/Reports/reports/views.py @@ -1,354 +1,379 @@ -# Create your views here. -#from django.shortcuts import get_object_or_404, render_to_response -from django.template import Context, loader -from django.http import HttpResponseRedirect, HttpResponse +""" +Report views + +Functions to handle all of the reporting views. +""" +from django.template import Context, RequestContext, loader +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError, Http404 from django.shortcuts import render_to_response, get_object_or_404 -from Bcfg2.Server.Reports.reports.models import Client, Interaction, Entries, Entries_interactions, Performance, Reason -from Bcfg2.Server.Reports.reports.models import TYPE_BAD, TYPE_MODIFIED, TYPE_EXTRA -from datetime import datetime, timedelta -from time import strptime +from django.core.urlresolvers import resolve, reverse, Resolver404, NoReverseMatch from django.db import connection from django.db.backends import util -from django.contrib.auth.decorators import login_required -def index(request): - return render_to_response('index.html') +from Bcfg2.Server.Reports.reports.models import * +from datetime import datetime, timedelta +from time import strptime +import sys -def config_item_modified(request, eyedee =None, timestamp = 'now', type=TYPE_MODIFIED): - #if eyedee = None, dump with a 404 - timestamp = timestamp.replace("@"," ") - if type == TYPE_MODIFIED: - mod_or_bad = "modified" - else: - mod_or_bad = "bad" - - item = get_object_or_404(Entries_interactions, id=eyedee) - #if everything is blank except current_exists, do something special - cursor = connection.cursor() - if timestamp == 'now': - cursor.execute("select client_id from reports_interaction, reports_entries_interactions, reports_client "+ - "WHERE reports_client.current_interaction_id = reports_entries_interactions.interaction_id "+ - "AND reports_entries_interactions.interaction_id = reports_interaction.id "+ - "AND reports_entries_interactions.entry_id = %s " + - "AND reports_entries_interactions.reason_id = %s", [item.entry.id, item.reason.id]) - associated_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - else: - interact_queryset = Interaction.objects.interaction_per_client(timestamp) - interactionlist = [] - [interactionlist.append(x.id) for x in interact_queryset] - if not interactionlist == []: - cursor.execute("select client_id from reports_interaction, reports_entries_interactions, reports_client "+ - "WHERE reports_entries_interactions.interaction_id IN %s "+ - "AND reports_entries_interactions.interaction_id = reports_interaction.id "+ - "AND reports_entries_interactions.entry_id = %s " + - "AND reports_entries_interactions.reason_id = %s ", [interactionlist, item.entry_id, item.reason.id]) - associated_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - else: - associated_client_list = [] +class PaginationError(Exception): + """This error is raised when pagination cannot be completed.""" + pass - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') +def server_error(request): + """ + 500 error handler. - return render_to_response('config_items/index.html', {'item':item, - 'mod_or_bad':mod_or_bad, - 'associated_client_list':associated_client_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) + For now always return the debug response. Mailing isn't appropriate here. + """ + from django.views import debug + return debug.technical_500_response(request, *sys.exc_info()) -def config_item_bad(request, eyedee = None, timestamp = 'now'): - return config_item_modified(request, eyedee, timestamp, TYPE_BAD) +def timeview(fn): + """ + Setup a timeview view -def bad_item_index(request, timestamp = 'now', type=TYPE_BAD): - timestamp = timestamp.replace("@"," ") - if type == TYPE_BAD: - mod_or_bad = "bad" - else: - mod_or_bad = "modified" + Handles backend posts from the calendar and converts date pieces + into a 'timestamp' parameter + + """ + def _handle_timeview(request, **kwargs): + """Send any posts back.""" + if request.method == 'POST': + cal_date = request.POST['cal_date'] + try: + fmt = "%Y/%m/%d" + if cal_date.find(' ') > -1: + fmt += " %H:%M" + timestamp = datetime(*strptime(cal_date, fmt)[0:6]) + view, args, kw = resolve(request.path) + kw['year'] = "%0.4d" % timestamp.year + kw['month'] = "%02.d" % timestamp.month + kw['day'] = "%02.d" % timestamp.day + if cal_date.find(' ') > -1: + kw['hour'] = timestamp.hour + kw['minute'] = timestamp.minute + return HttpResponseRedirect(reverse(view, args=args, kwargs=kw)) + except KeyError: + pass + except: + pass + # FIXME - Handle this + + """Extract timestamp from args.""" + timestamp = None + try: + timestamp = datetime(int(kwargs.pop('year')), int(kwargs.pop('month')), + int(kwargs.pop('day')), int(kwargs.pop('hour', 0)), + int(kwargs.pop('minute', 0)), 0) + kwargs['timestamp'] = timestamp + except KeyError: + pass + except: + raise + return fn(request, **kwargs) + + return _handle_timeview + +def config_item(request, pk, type="bad"): + """ + Display a single entry. + + Dispalys information about a single entry. + + """ + item = get_object_or_404(Entries_interactions, id=pk) + timestamp=item.interaction.timestamp + time_start=item.interaction.timestamp.replace(\ + hour=0, minute=0, second=0, microsecond=0) + time_end=time_start + timedelta(days=1) + + todays_data = Interaction.objects.filter(\ + timestamp__gte=time_start,\ + timestamp__lt=time_end) + shared_entries = Entries_interactions.objects.filter(entry=item.entry,\ + reason=item.reason, type=item.type, + interaction__in=[x['id']\ + for x in todays_data.values('id')]) + + associated_list = Interaction.objects.filter(id__in=[x['interaction']\ + for x in shared_entries.values('interaction')])\ + .order_by('client__name','timestamp').select_related().all() + + return render_to_response('config_items/item.html', + {'item':item, + 'isextra': item.type == TYPE_EXTRA, + 'mod_or_bad': type, + 'associated_list':associated_list, + 'timestamp' : timestamp}, + context_instance=RequestContext(request)) + +@timeview +def config_item_list(request, type, timestamp=None): + """Render a listing of affected elements""" + mod_or_bad = type.lower() + type = convert_entry_type_to_id(type) + if type < 0: + raise Http404 - current_clients = [c.current_interaction - for c in Client.objects.active(timestamp)] + current_clients = Interaction.objects.get_interaction_per_client_ids(timestamp) item_list_dict = {} - for x in Entries_interactions.objects.select_related().filter(interaction__in=current_clients, type=type).distinct(): + seen = dict() + for x in Entries_interactions.objects.filter(interaction__in=current_clients, type=type).select_related(): + if (x.entry, x.reason) in seen: + continue + seen[(x.entry, x.reason)] = 1 if item_list_dict.get(x.entry.kind, None): item_list_dict[x.entry.kind].append(x) else: item_list_dict[x.entry.kind] = [x] - item_list_pseudodict = item_list_dict.items() - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') + for kind in item_list_dict: + item_list_dict[kind].sort(lambda a,b: cmp(a.entry.name, b.entry.name)) - return render_to_response('config_items/listing.html', {'item_list_pseudodict':item_list_pseudodict, + return render_to_response('config_items/listing.html', {'item_list_dict':item_list_dict, 'mod_or_bad':mod_or_bad, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) -def modified_item_index(request, timestamp = 'now'): - return bad_item_index(request, timestamp, TYPE_MODIFIED) - -def client_index(request, timestamp = 'now'): - timestamp = timestamp.replace("@"," ") - - c_dict = dict() - [c_dict.__setitem__(cl.id,cl.name) for cl in Client.objects.active(timestamp).order_by('name')] - - list = [] - for inter in Interaction.objects.interaction_per_client(timestamp): - if inter.client_id in c_dict: - list.append([c_dict[inter.client_id], inter]) - list.sort(lambda a,b: cmp(a[0], b[0])) - half_list = len(list) / 2 - - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') + 'timestamp' : timestamp}, + context_instance=RequestContext(request)) + +@timeview +def client_index(request, timestamp=None): + """ + Render a grid view of active clients. + + Keyword parameters: + timestamp -- datetime objectto render from + + """ + list = Interaction.objects.interaction_per_client(timestamp).select_related()\ + .order_by("client__name").all() + return render_to_response('clients/index.html', - {'inter_list': list, - 'half_list': half_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) + { 'inter_list': list, 'timestamp' : timestamp}, + context_instance=RequestContext(request)) -def client_detailed_list(request, **kwargs): +@timeview +def client_detailed_list(request, timestamp=None, **kwargs): """ Provides a more detailed list view of the clients. Allows for extra - filters to be passed in. Somewhat clunky now that dates are allowed. + filters to be passed in. """ - context = dict(path=request.path) - timestamp = 'now' - entry_max = datetime.now() - if request.GET: - context['qsa']='?%s' % request.GET.urlencode() - if request.GET.has_key('date1') and request.GET.has_key('time'): - timestamp = "%s %s" % (request.GET['date1'],request.GET['time']) - entry_max = datetime(*strptime(timestamp, "%Y-%m-%d %H:%M:%S")[0:6]) - client_list = Client.objects.active(timestamp).order_by('name') - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') - context['timestamp_date'] = timestamp[:10] - context['timestamp_time'] = timestamp[11:19] - - interactions = Interaction.objects.interaction_per_client(timestamp) - if 'state' in kwargs and kwargs['state']: - context['state'] = kwargs['state'] - interactions=interactions.filter(state__exact=kwargs['state']) - if 'server' in kwargs and kwargs['server']: - interactions=interactions.filter(server__exact=kwargs['server']) - context['server'] = kwargs['server'] - # build the entry list from available clients - c_dict = dict() - [c_dict.__setitem__(cl.id,cl.name) for cl in client_list] - - entry_list = [] - for inter in interactions: - if inter.client_id in c_dict: - entry_list.append([c_dict[inter.client_id], inter, \ - entry_max - inter.timestamp > timedelta(hours=24)]) - entry_list.sort(lambda a,b: cmp(a[0], b[0])) - ''' - if(datetime.now()-self.timestamp > timedelta(hours=25) ): - return True - else: - return False - ''' - - context['entry_list'] = entry_list - return render_to_response('clients/detailed-list.html', context) + kwargs['interaction_base'] = Interaction.objects.interaction_per_client(timestamp).select_related() + kwargs['orderby'] = "client__name" + kwargs['page_limit'] = 0 + return render_history_view(request, 'clients/detailed-list.html', **kwargs) def client_detail(request, hostname = None, pk = None): - #SETUP error pages for when you specify a client or interaction that doesn't exist + context = dict() client = get_object_or_404(Client, name=hostname) if(pk == None): - interaction = client.current_interaction + context['interaction'] = client.current_interaction + return render_history_view(request, 'clients/detail.html', page_limit=5, + client=client, context=context) else: - interaction = client.interactions.get(pk=pk)#can this be a get object or 404? - return render_to_response('clients/detail.html', {'client': client, 'interaction': interaction}) + context['interaction'] = client.interactions.get(pk=pk) + return render_history_view(request, 'clients/detail.html', page_limit=5, + client=client, maxdate=context['interaction'].timestamp, context=context) -def client_manage(request, hostname = None): - #SETUP error pages for when you specify a client or interaction that doesn't exist - client = get_object_or_404(Client, name=hostname) - currenttime = datetime.now().isoformat('@') - if client.expiration != None: - message = ("This client currently has an expiration date of %s. " - "Reports after %s will not include data for this host " - "You may change this if you wish by selecting a new " - "time, earlier or later." - % (client.expiration, client.expiration)) - else: - message = ("This client is currently active and displayed. You " - "may choose a date after which this client will no " - "longer appear in reports.") +def client_manage(request): + """Manage client expiration""" + message = '' if request.method == 'POST': - date = request.POST['date1'] - time = request.POST['time'] try: - timestamp = datetime(*(strptime(date+"@"+time, "%Y-%m-%d@%H:%M:%S")[0:6])) - except ValueError: - timestamp = None - if timestamp == None: - message = "Invalid removal date, please try again using the format: yyyy-mm-dd hh:mm:ss." - else: - client.expiration = timestamp + client_name = request.POST.get('client_name', None) + client_action = request.POST.get('client_action', None) + client = Client.objects.get(name=client_name) + if client_action == 'expire': + client.expiration = datetime.now(); + client.save() + message = "Expiration for %s set to %s." % \ + (client_name, client.expiration.strftime("%Y-%m-%d %H:%M:%S")) + elif client_action == 'unexpire': + client.expiration = None; client.save() - message = "Expiration for client set to %s." % client.expiration - return render_to_response('clients/manage.html', {'client': client, 'message': message, - 'timestamp_date' : currenttime[:10], - 'timestamp_time' : currenttime[11:19]}) - -def display_sys_view(request, timestamp = 'now'): - client_lists = prepare_client_lists(request, timestamp) - return render_to_response('displays/sys_view.html', client_lists) - -def display_summary(request, timestamp = 'now'): - - client_lists = prepare_client_lists(request, timestamp) - #this returns timestamp and the timestamp parts too - return render_to_response('displays/summary.html', client_lists) - -def display_timing(request, timestamp = 'now'): - #We're going to send a list of dictionaries. Each dictionary will be a row in the table - #+------+-------+----------------+-----------+---------+----------------+-------+ - #| name | parse | probe download | inventory | install | cfg dl & parse | total | - #+------+-------+----------------+-----------+---------+----------------+-------+ - client_list = Client.objects.active(timestamp.replace("@"," ")).order_by('name') - stats_list = [] - - if not timestamp == 'now': - results = Performance.objects.performance_per_client(timestamp.replace("@"," ")) + message = "%s is now active." % client_name else: - results = Performance.objects.performance_per_client() - timestamp = datetime.now().isoformat('@') - - for client in client_list:#Go explicitly to an interaction ID! (new item in dictionary) + message = "Missing action" + except Client.DoesNotExist: + if not client_name: + client_name = "" + message = "Couldn't find client \"%s\"" % client_name + + return render_to_response('clients/manage.html', + {'clients': Client.objects.order_by('name').all(), 'message': message}, + context_instance=RequestContext(request)) + +@timeview +def display_summary(request, timestamp=None): + """ + Display a summary of the bcfg2 world + """ + query = Interaction.objects.interaction_per_client(timestamp).select_related() + node_count = query.count() + recent_data = query.all() + if not timestamp: + timestamp = datetime.now() + + collected_data = dict(clean=[],bad=[],modified=[],extra=[],stale=[],pings=[]) + for node in recent_data: + if timestamp - node.timestamp > timedelta(hours=24): + collected_data['stale'].append(node) + # If stale check for uptime try: - d = results[client.name] - except KeyError: - d = {} + if node.client.pings.latest().status == 'N': + collected_data['pings'].append(node) + except Ping.DoesNotExist: + collected_data['pings'].append(node) + continue + if node.bad_entry_count() > 0: + collected_data['bad'].append(node) + else: + collected_data['clean'].append(node) + if node.modified_entry_count() > 0: + collected_data['modified'].append(node) + if node.extra_entry_count() > 0: + collected_data['extra'].append(node) + + # label, header_text, node_list + summary_data = [] + get_dict = lambda name, label: { 'name': name, + 'nodes': collected_data[name], + 'label': label } + if len(collected_data['clean']) > 0: + summary_data.append( get_dict('clean', 'nodes are clean.') ) + if len(collected_data['bad']) > 0: + summary_data.append( get_dict('bad', 'nodes are bad.') ) + if len(collected_data['modified']) > 0: + summary_data.append( get_dict('modified', 'nodes were modified.') ) + if len(collected_data['extra']) > 0: + summary_data.append( get_dict('extra', + 'nodes have extra configurations.') ) + if len(collected_data['stale']) > 0: + summary_data.append( get_dict('stale', + 'nodes did not run within the last 24 hours.') ) + if len(collected_data['pings']) > 0: + summary_data.append( get_dict('pings', + 'are down.') ) + + return render_to_response('displays/summary.html', + {'summary_data': summary_data, 'node_count': node_count, + 'timestamp': timestamp}, + context_instance=RequestContext(request)) + +@timeview +def display_timing(request, timestamp=None): + mdict = dict() + inters = Interaction.objects.interaction_per_client(timestamp).select_related().all() + [mdict.__setitem__(inter, {'name': inter.client.name}) \ + for inter in inters] + for metric in Performance.objects.filter(interaction__in=mdict.keys()).all(): + for i in metric.interaction.all(): + mdict[i][metric.metric] = metric.value + return render_to_response('displays/timing.html', + {'metrics': mdict.values(), 'timestamp': timestamp}, + context_instance=RequestContext(request)) + + +def render_history_view(request, template='clients/history.html', **kwargs): + """ + Provides a detailed history of a clients interactions. + + Renders a detailed history of a clients interactions. Allows for various + filters and settings. Automatically sets pagination data into the context. + + Keyword arguments: + interaction_base -- Interaction QuerySet to build on + (default Interaction.objects) + context -- Additional context data to render with + page_number -- Page to display (default 1) + page_limit -- Number of results per page, if 0 show all (default 25) + client -- Client object to render + hostname -- Client hostname to lookup and render. Returns a 404 if + not found + server -- Filter interactions by server + state -- Filter interactions by state + entry_max -- Most recent interaction to display + orderby -- Sort results using this field - dict_unit = {} - try: - dict_unit["name"] = client.name #node name - except: - dict_unit["name"] = "n/a" - try: - dict_unit["parse"] = round(d["config_parse"] - d["config_download"], 4) #parse - except: - dict_unit["parse"] = "n/a" - try: - dict_unit["probe"] = round(d["probe_upload"] - d["start"], 4) #probe - except: - dict_unit["probe"] = "n/a" - try: - dict_unit["inventory"] = round(d["inventory"] - d["initialization"], 4) #inventory - except: - dict_unit["inventory"] = "n/a" - try: - dict_unit["install"] = round(d["install"] - d["inventory"], 4) #install - except: - dict_unit["install"] = "n/a" - try: - dict_unit["config"] = round(d["config_parse"] - d["probe_upload"], 4)#config download & parse - except: - dict_unit["config"] = "n/a" + """ + + context = kwargs.get('context', dict()) + max_results = int(kwargs.get('page_limit', 25)) + page = int(kwargs.get('page_number', 1)) + + client=kwargs.get('client', None) + if not client and 'hostname' in kwargs: + client = get_object_or_404(Client, name=kwargs['hostname']) + if client: + context['client'] = client + + entry_max = kwargs.get('maxdate', None) + context['entry_max'] = entry_max + + # Either filter by client or limit by clients + iquery = kwargs.get('interaction_base', Interaction.objects) + if client: + iquery = iquery.filter(client__exact=client).select_related() + + if 'orderby' in kwargs and kwargs['orderby']: + iquery = iquery.order_by(kwargs['orderby']) + + if 'state' in kwargs and kwargs['state']: + iquery = iquery.filter(state__exact=kwargs['state']) + if 'server' in kwargs and kwargs['server']: + iquery = iquery.filter(server__exact=kwargs['server']) + + if entry_max: + iquery = iquery.filter(timestamp__lte=entry_max) + + if max_results < 0: + max_results = 1 + entry_list = [] + if max_results > 0: try: - dict_unit["total"] = round(d["finished"] - d["start"], 4) #total - except: - dict_unit["total"] = "n/a" - - stats_list.append(dict_unit) - - return render_to_response('displays/timing.html', {'client_list': client_list, - 'stats_list': stats_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) - -def display_index(request): - return render_to_response('displays/index.html') - -def prepare_client_lists(request, timestamp = 'now'): - #I suggest we implement "expiration" here. - - timestamp = timestamp.replace("@"," ") - #client_list = Client.objects.all().order_by('name')#change this to order by interaction's state - client_interaction_dict = {} - clean_client_list = [] - bad_client_list = [] - extra_client_list = [] - modified_client_list = [] - stale_up_client_list = [] - #stale_all_client_list = [] - down_client_list = [] - - cursor = connection.cursor() - - interact_queryset = Interaction.objects.interaction_per_client(timestamp) - # or you can specify a time like this: '2007-01-01 00:00:00' - [client_interaction_dict.__setitem__(x.client_id, x) for x in interact_queryset] - client_list = Client.objects.active(timestamp).filter(id__in=client_interaction_dict.keys()).order_by('name') - - [clean_client_list.append(x) for x in Client.objects.active(timestamp).filter(id__in=[y.client_id for y in interact_queryset.filter(state='clean')])] - [bad_client_list.append(x) for x in Client.objects.active(timestamp).filter(id__in=[y.client_id for y in interact_queryset.filter(state='dirty')])] - - client_ping_dict = {} - [client_ping_dict.__setitem__(x,'Y') for x in client_interaction_dict.keys()]#unless we know otherwise... + rec_start, rec_end = prepare_paginated_list(request, context, iquery, page, max_results) + except PaginationError, page_error: + if isinstance(page_error[0], HttpResponse): + return page_error[0] + return HttpResponseServerError(page_error) + context['entry_list'] = iquery.all()[rec_start:rec_end] + else: + context['entry_list'] = iquery.all() + + return render_to_response(template, context, + context_instance=RequestContext(request)) + +def prepare_paginated_list(request, context, paged_list, page=1, max_results=25): + """ + Prepare context and slice an object for pagination. + """ + if max_results < 1: + raise PaginationError, "Max results less then 1" + if paged_list == None: + raise PaginationError, "Invalid object" + + try: + nitems = paged_list.count() + except TypeError: + nitems = len(paged_list) + rec_start = (page - 1) * int(max_results) try: - cursor.execute("select reports_ping.status, x.client_id from (select client_id, MAX(endtime) "+ - "as timer from reports_ping GROUP BY client_id) x, reports_ping where "+ - "reports_ping.client_id = x.client_id AND reports_ping.endtime = x.timer") - [client_ping_dict.__setitem__(x[1], x[0]) for x in cursor.fetchall()] + total_pages = (nitems / int(max_results)) + 1 except: - pass #This is to fix problems when you have only zero records returned - - client_down_ids = [y for y in client_ping_dict.keys() if client_ping_dict[y]=='N'] - if not client_down_ids == []: - [down_client_list.append(x) for x in Client.objects.active(timestamp).filter(id__in=client_down_ids)] - - if (timestamp == 'now' or timestamp == None): - cursor.execute("select client_id, MAX(timestamp) as timestamp from reports_interaction GROUP BY client_id") - results = cursor.fetchall() - for x in results: - if type(x[1]) == type("") or type(x[1]) == type(u""): - ts = util.typecast_timestamp(x[1]) - else: - ts = x[1] - stale_all_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in results if datetime.now() - ts > timedelta(days=1)]) - else: - cursor.execute("select client_id, timestamp, MAX(timestamp) as timestamp from reports_interaction "+ - "WHERE timestamp < %s GROUP BY client_id", [timestamp]) - t = strptime(timestamp,"%Y-%m-%d %H:%M:%S") - datetimestamp = datetime(t[0], t[1], t[2], t[3], t[4], t[5]) - results = cursor.fetchall() - for x in results: - if type(x[1]) == type(""): - x[1] = util.typecast_timestamp(x[1]) - stale_all_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in results if datetimestamp - x[1] > timedelta(days=1)]) - - [stale_up_client_list.append(x) for x in stale_all_client_list if not client_ping_dict[x.id]=='N'] + total_pages = 1 + if page > total_pages: + # If we passed beyond the end send back + try: + view, args, kwargs = resolve(request.path) + kwargs['page_number'] = total_pages + raise PaginationError, HttpResponseRedirect( reverse(view, kwargs=kwargs) ) + except (Resolver404, NoReverseMatch, ValueError): + raise "Accessing beyond last page. Unable to resolve redirect." + + context['total_pages'] = total_pages + context['records_per_page'] = max_results + return (rec_start, rec_start + int(max_results)) - - cursor.execute("SELECT reports_client.id FROM reports_client, reports_interaction, reports_entries_interactions WHERE reports_client.id = reports_interaction.client_id AND reports_client.current_interaction_id = reports_entries_interactions.interaction_id and reports_entries_interactions.type=%s GROUP BY reports_client.id", [TYPE_MODIFIED]) - modified_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - - cursor.execute("SELECT reports_client.id FROM reports_client, reports_interaction, reports_entries_interactions WHERE reports_client.id = reports_interaction.client_id AND reports_client.current_interaction_id = reports_entries_interactions.interaction_id and reports_entries_interactions.type=%s GROUP BY reports_client.id", [TYPE_EXTRA]) - extra_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') - - return {'client_list': client_list, - 'client_interaction_dict':client_interaction_dict, - 'clean_client_list': clean_client_list, - 'bad_client_list': bad_client_list, - 'extra_client_list': extra_client_list, - 'modified_client_list': modified_client_list, - 'stale_up_client_list': stale_up_client_list, - 'stale_all_client_list': stale_all_client_list, - 'down_client_list': down_client_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]} diff --git a/src/lib/Server/Reports/settings.py b/src/lib/Server/Reports/settings.py index 59d29114d..81220c0e3 100644 --- a/src/lib/Server/Reports/settings.py +++ b/src/lib/Server/Reports/settings.py @@ -1,3 +1,5 @@ +import django + # Django settings for bcfg2 reports project. from ConfigParser import ConfigParser, NoSectionError, NoOptionError c = ConfigParser() @@ -62,7 +64,9 @@ MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. # Example: "http://media.lawrence.com" -MEDIA_URL = '' +MEDIA_URL = '/site_media' +if c.has_option('statistics', 'web_prefix'): + MEDIA_URL = c.get('statistics', 'web_prefix').rstrip('/') + MEDIA_URL # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. @@ -109,9 +113,26 @@ TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates". # Always use forward slashes, even on Windows. '/usr/share/python-support/python-django/django/contrib/admin/templates/', - '/usr/share/bcfg2/Reports/templates' + 'Bcfg2.Server.Reports.reports' ) +if django.VERSION[0] == 1 and django.VERSION[1] < 2: + TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.request' + ) +else: + TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.request' + ) + INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/src/lib/Server/Reports/urls.py b/src/lib/Server/Reports/urls.py index e1326b5ea..5d298c974 100644 --- a/src/lib/Server/Reports/urls.py +++ b/src/lib/Server/Reports/urls.py @@ -1,4 +1,7 @@ from django.conf.urls.defaults import * +from django.http import HttpResponsePermanentRedirect + +handler500 = 'Bcfg2.Server.Reports.reports.views.server_error' from ConfigParser import ConfigParser, NoSectionError, NoOptionError c = ConfigParser() @@ -9,56 +12,15 @@ c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']) # web_prefix_root is a workaround for the index if c.has_option('statistics', 'web_prefix'): web_prefix = c.get('statistics', 'web_prefix').lstrip('/') - web_prefix_root = web_prefix else: web_prefix = '' - web_prefix_root = '/' urlpatterns = patterns('', - # Example: - # (r'^%sBcfg2.Server.Reports/' % web_prefix, include('Bcfg2.Server.Reports.apps.foo.urls.foo')), - (r'^%s*$' % web_prefix_root,'Bcfg2.Server.Reports.reports.views.index'), - - (r'^%sclients-detailed/state/(?P\w+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients-detailed/server/(?P[\w\-\.]+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients-detailed/server/(?P[\w\-\.]+)/(?P[A-Za-z]+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients-detailed/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_index'), - (r'^%sclients/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_index'), - (r'^%sclients/(?P\S+)/(?P\d+)/$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_detail'), - (r'^%sclients/(?P\S+)/manage/$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_manage'), - (r'^%sclients/(?P\S+)/$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_detail'), - (r'^%sclients/(?P\S+)$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_detail'), - #hack because hostnames have periods and we still want to append slash - (r'^%sclients/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_index'), - (r'^%sdisplays/sys-view/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_sys_view'), - (r'^%sdisplays/sys-view/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_sys_view'), - (r'^%sdisplays/summary/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_summary'), - (r'^%sdisplays/summary/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_summary'), - (r'^%sdisplays/timing/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_timing'), - (r'^%sdisplays/timing/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_timing'), - (r'^%sdisplays/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_index'), - - (r'^%selements/modified/(?P\d+)/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_modified'), - (r'^%selements/modified/(?P\d+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_modified'), - (r'^%selements/modified/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01]\ - [0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.modified_item_index'), - (r'^%selements/modified/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.modified_item_index'), - (r'^%selements/bad/(?P\d+)/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_bad'), - (r'^%selements/bad/(?P\d+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_bad'), - (r'^%selements/bad/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01]\ - [0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.bad_item_index'), - (r'^%selements/bad/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.bad_item_index'), + (r'^%s' % web_prefix, include('Bcfg2.Server.Reports.reports.urls')) ) - # Uncomment this for admin: - #(r'^%sadmin/' % web_prefix, include('django.contrib.admin.urls')), - - -## Uncomment this section if using authentication -#urlpatterns += patterns('', -# (r'^%slogin/$' % web_prefix, 'django.contrib.auth.views.login', -# {'template_name': 'auth/login.html'}), -# (r'^%slogout/$' % web_prefix, 'django.contrib.auth.views.logout', -# {'template_name': 'auth/logout.html'}) -# ) +urlpatterns += patterns("django.views", + url(r"media/(?P.*)$", "static.serve", { + "document_root": '/Users/tlaszlo/svn/bcfg2/reports/site_media/', + }) +) diff --git a/src/lib/Server/Reports/utils.py b/src/lib/Server/Reports/utils.py index 2ef21e446..b74f09e74 100755 --- a/src/lib/Server/Reports/utils.py +++ b/src/lib/Server/Reports/utils.py @@ -1,7 +1,13 @@ -'''Helper functions for reports''' +"""Helper functions for reports""" +from Bcfg2.Server.Reports.reports.models import TYPE_CHOICES +from django.conf.urls.defaults import * +import re + +"""List of filters provided by filteredUrls""" +filter_list = ('server', 'state') class BatchFetch(object): - '''Fetch Django objects in smaller batches to save memory''' + """Fetch Django objects in smaller batches to save memory""" def __init__(self, obj, step=10000): self.count = 0 @@ -15,8 +21,8 @@ class BatchFetch(object): return self def next(self): - '''Return the next object from our array and fetch from the - database when needed''' + """Return the next object from our array and fetch from the + database when needed""" if self.block_count + self.count - self.step == self.max: raise StopIteration if self.block_count == 0 or self.count == self.step: @@ -28,3 +34,83 @@ class BatchFetch(object): self.count += 1 return self.data[self.count - 1] +def generateUrls(fn): + """ + Parse url tuples and send to functions. + + Decorator for url generators. Handles url tuple parsing + before the actual function is called. + """ + def url_gen(*urls): + results = [] + for url_tuple in urls: + if isinstance(url_tuple, (list, tuple)): + results += fn(*url_tuple) + else: + raise ValueError("Unable to handle compiled urls") + return results + return url_gen + +@generateUrls +def paginatedUrls(pattern, view, kwargs=None, name=None): + """ + Takes a group of url tuples and adds paginated urls. + + Extends a url tuple to include paginated urls. Currently doesn't handle url() compiled + patterns. + + """ + results = [(pattern, view, kwargs, name)] + tail = '' + mtail = re.search('(/+\+?\\*?\??\$?)$', pattern) + if mtail: + tail = mtail.group(1) + pattern = pattern[:len(pattern) - len(tail)] + results += [(pattern + "/(?P\d+)" + tail, view, kwargs)] + results += [(pattern + "/(?P\d+)\|(?P\d+)" + tail, view, kwargs)] + if not kwargs: + kwargs = dict() + kwargs['page_limit'] = 0 + results += [(pattern + "/?\|(?Pall)" + tail, view, kwargs)] + return results + +@generateUrls +def filteredUrls(pattern, view, kwargs=None, name=None): + """ + Takes a url and adds filtered urls. + + Extends a url tuple to include filtered view urls. Currently doesn't + handle url() compiled patterns. + """ + results = [(pattern, view, kwargs, name)] + tail = '' + mtail = re.search('(/+\+?\\*?\??\$?)$', pattern) + if mtail: + tail = mtail.group(1) + pattern = pattern[:len(pattern) - len(tail)] + for filter in ('/state/(?P\w+)', + '/server/(?P[\w\-\.]+)', + '/server/(?P[\w\-\.]+)/(?P[A-Za-z]+)'): + results += [(pattern + filter + tail, view, kwargs)] + return results + +@generateUrls +def timeviewUrls(pattern, view, kwargs=None, name=None): + """ + Takes a url and adds timeview urls + + Extends a url tuple to include filtered view urls. Currently doesn't + handle url() compiled patterns. + """ + results = [(pattern, view, kwargs, name)] + tail = '' + mtail = re.search('(/+\+?\\*?\??\$?)$', pattern) + if mtail: + tail = mtail.group(1) + pattern = pattern[:len(pattern) - len(tail)] + for filter in ('/(?P\d{4})-(?P\d{2})-(?P\d{2})/' + \ + '(?P\d\d)-(?P\d\d)', + '/(?P\d{4})-(?P\d{2})-(?P\d{2})'): + results += [(pattern + filter + tail, view, kwargs)] + return results + -- cgit v1.2.3-1-g7c22 From 3afda0494c157804e17b468ddbc1ab872348eeab Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 14 Oct 2010 15:30:51 -0500 Subject: web reports: removing old images --- reports/site_media/img/loading.gif | Bin 729 -> 0 bytes reports/site_media/img/newcats_bkgd.gif | Bin 93 -> 0 bytes reports/site_media/img/ptr.gif | Bin 261 -> 0 bytes reports/site_media/img/round_4px_trans_gray.gif | Bin 463 -> 0 bytes reports/site_media/img/tab_left.gif | Bin 129 -> 0 bytes reports/site_media/img/tab_right.gif | Bin 491 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 reports/site_media/img/loading.gif delete mode 100644 reports/site_media/img/newcats_bkgd.gif delete mode 100644 reports/site_media/img/ptr.gif delete mode 100644 reports/site_media/img/round_4px_trans_gray.gif delete mode 100644 reports/site_media/img/tab_left.gif delete mode 100644 reports/site_media/img/tab_right.gif diff --git a/reports/site_media/img/loading.gif b/reports/site_media/img/loading.gif deleted file mode 100644 index 6a56815b2..000000000 Binary files a/reports/site_media/img/loading.gif and /dev/null differ diff --git a/reports/site_media/img/newcats_bkgd.gif b/reports/site_media/img/newcats_bkgd.gif deleted file mode 100644 index f5a9585c6..000000000 Binary files a/reports/site_media/img/newcats_bkgd.gif and /dev/null differ diff --git a/reports/site_media/img/ptr.gif b/reports/site_media/img/ptr.gif deleted file mode 100644 index f3a14a102..000000000 Binary files a/reports/site_media/img/ptr.gif and /dev/null differ diff --git a/reports/site_media/img/round_4px_trans_gray.gif b/reports/site_media/img/round_4px_trans_gray.gif deleted file mode 100644 index f313dd96b..000000000 Binary files a/reports/site_media/img/round_4px_trans_gray.gif and /dev/null differ diff --git a/reports/site_media/img/tab_left.gif b/reports/site_media/img/tab_left.gif deleted file mode 100644 index 9bb2fb041..000000000 Binary files a/reports/site_media/img/tab_left.gif and /dev/null differ diff --git a/reports/site_media/img/tab_right.gif b/reports/site_media/img/tab_right.gif deleted file mode 100644 index 67b8076c5..000000000 Binary files a/reports/site_media/img/tab_right.gif and /dev/null differ -- cgit v1.2.3-1-g7c22 From 9705a971f8f16d8a313e5acf379d53edc917ff87 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 14 Oct 2010 15:56:20 -0500 Subject: web reports: tweaks and updates --- reports/site_media/bcfg2_logo.png | Bin 0 -> 27109 bytes src/lib/Server/Reports/reports/views.py | 42 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 reports/site_media/bcfg2_logo.png diff --git a/reports/site_media/bcfg2_logo.png b/reports/site_media/bcfg2_logo.png new file mode 100644 index 000000000..aeb3b7f5f Binary files /dev/null and b/reports/site_media/bcfg2_logo.png differ diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Server/Reports/reports/views.py index 64617ce70..a061a3964 100644 --- a/src/lib/Server/Reports/reports/views.py +++ b/src/lib/Server/Reports/reports/views.py @@ -182,24 +182,24 @@ def client_manage(request): message = '' if request.method == 'POST': try: - client_name = request.POST.get('client_name', None) - client_action = request.POST.get('client_action', None) - client = Client.objects.get(name=client_name) - if client_action == 'expire': - client.expiration = datetime.now(); - client.save() - message = "Expiration for %s set to %s." % \ - (client_name, client.expiration.strftime("%Y-%m-%d %H:%M:%S")) - elif client_action == 'unexpire': - client.expiration = None; - client.save() - message = "%s is now active." % client_name - else: - message = "Missing action" - except Client.DoesNotExist: - if not client_name: - client_name = "" - message = "Couldn't find client \"%s\"" % client_name + client_name = request.POST.get('client_name', None) + client_action = request.POST.get('client_action', None) + client = Client.objects.get(name=client_name) + if client_action == 'expire': + client.expiration = datetime.now(); + client.save() + message = "Expiration for %s set to %s." % \ + (client_name, client.expiration.strftime("%Y-%m-%d %H:%M:%S")) + elif client_action == 'unexpire': + client.expiration = None; + client.save() + message = "%s is now active." % client_name + else: + message = "Missing action" + except Client.DoesNotExist: + if not client_name: + client_name = "" + message = "Couldn't find client \"%s\"" % client_name return render_to_response('clients/manage.html', {'clients': Client.objects.order_by('name').all(), 'message': message}, @@ -222,10 +222,10 @@ def display_summary(request, timestamp=None): collected_data['stale'].append(node) # If stale check for uptime try: - if node.client.pings.latest().status == 'N': - collected_data['pings'].append(node) - except Ping.DoesNotExist: + if node.client.pings.latest().status == 'N': collected_data['pings'].append(node) + except Ping.DoesNotExist: + collected_data['pings'].append(node) continue if node.bad_entry_count() > 0: collected_data['bad'].append(node) -- cgit v1.2.3-1-g7c22 From db690fdb6c9b4872a9ea46179fbf514c5d2f6fe0 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 14 Oct 2010 16:43:42 -0500 Subject: web reports: fix quotes in base-timeview.html --- src/lib/Server/Reports/reports/templates/base-timeview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Server/Reports/reports/templates/base-timeview.html b/src/lib/Server/Reports/reports/templates/base-timeview.html index d0617cde7..842de36f0 100644 --- a/src/lib/Server/Reports/reports/templates/base-timeview.html +++ b/src/lib/Server/Reports/reports/templates/base-timeview.html @@ -6,7 +6,7 @@ function showCalendar() { var cal = new CalendarPopup("calendar_div"); cal.showYearNavigation(); cal.select(document.forms['cal_form'].cal_date,'cal_link', - 'yyyy/MM/dd' {% if timestamp %}, '{{ timestamp|date:'Y/m/d' }}'{% endif %} ); + 'yyyy/MM/dd' {% if timestamp %}, '{{ timestamp|date:"Y/m/d" }}'{% endif %} ); return false; } function bcfg2_check_date() { -- cgit v1.2.3-1-g7c22 From 4c57be471244980318bfe26f59eb0e59f3f39e1d Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 14 Oct 2010 17:40:24 -0500 Subject: Keep interpreter specifications consistent Signed-off-by: Sol Jerome --- src/lib/Client/Tools/rpmtools.py | 2 +- src/sbin/bcfg2-info | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Client/Tools/rpmtools.py b/src/lib/Client/Tools/rpmtools.py index e224a3fb3..3cd2b7014 100755 --- a/src/lib/Client/Tools/rpmtools.py +++ b/src/lib/Client/Tools/rpmtools.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python """ Module that uses rpm-python to implement the following rpm functionality for the bcfg2 RPM and YUM client drivers: diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 6732d8830..d3a9bf8be 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python """This tool loads the Bcfg2 core into an interactive debugger.""" __revision__ = '$Revision$' -- cgit v1.2.3-1-g7c22 From 3a6936c85a405cbc507394b44a6eb620aa9d970c Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 14 Oct 2010 17:53:48 -0500 Subject: reports: Point to the bcfg2.org URLs Signed-off-by: Sol Jerome --- src/lib/Server/Reports/reports/templates/base.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/Server/Reports/reports/templates/base.html b/src/lib/Server/Reports/reports/templates/base.html index 64c105e34..7a36c9893 100644 --- a/src/lib/Server/Reports/reports/templates/base.html +++ b/src/lib/Server/Reports/reports/templates/base.html @@ -22,7 +22,7 @@ @@ -77,8 +77,8 @@ {% endcomment %} {% endblock %}
-- cgit v1.2.3-1-g7c22 From 9a2bb764cf73911885a8e801882219b052f11475 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 14 Oct 2010 19:09:11 -0500 Subject: reports: Remove svn merge conflicts Signed-off-by: Sol Jerome --- src/lib/Server/Reports/reports/models.py.orig | 330 -------------------------- src/lib/Server/Reports/reports/models.py.rej | 15 -- 2 files changed, 345 deletions(-) delete mode 100644 src/lib/Server/Reports/reports/models.py.orig delete mode 100644 src/lib/Server/Reports/reports/models.py.rej diff --git a/src/lib/Server/Reports/reports/models.py.orig b/src/lib/Server/Reports/reports/models.py.orig deleted file mode 100644 index 5468420f6..000000000 --- a/src/lib/Server/Reports/reports/models.py.orig +++ /dev/null @@ -1,330 +0,0 @@ -"""Django models for Bcfg2 reports.""" -from django.db import models -from django.db import connection, transaction -from django.db.models import Q -from datetime import datetime, timedelta -from time import strptime - -KIND_CHOICES = ( - #These are the kinds of config elements - ('Package', 'Package'), - ('Path', 'directory'), - ('Path', 'file'), - ('Path', 'permissions'), - ('Path', 'symlink'), - ('Service', 'Service'), -) -PING_CHOICES = ( - #These are possible ping states - ('Up (Y)', 'Y'), - ('Down (N)', 'N') -) -TYPE_BAD = 1 -TYPE_MODIFIED = 2 -TYPE_EXTRA = 3 - -TYPE_CHOICES = ( - (TYPE_BAD, 'Bad'), - (TYPE_MODIFIED, 'Modified'), - (TYPE_EXTRA, 'Extra'), -) -class ClientManager(models.Manager): - """Extended client manager functions.""" - def active(self, timestamp='now'): - '''returns a set of clients that have been created and have not yet been - expired as of optional timestmamp argument. Timestamp should be a - string formatted in the fashion: 2006-01-01 00:00:00''' - - if timestamp == 'now': - timestamp = datetime.now() - else: - print timestamp - try: - timestamp = datetime(*strptime(timestamp, "%Y-%m-%d %H:%M:%S")[0:6]) - except ValueError: - return self.filter(expiration__lt=timestamp, creation__gt=timestamp); - ''' - - this is a really hacky way to return an empty QuerySet - - this should return Client.objects.none() in Django - development version. - ''' - - return self.filter(Q(expiration__gt=timestamp) | Q(expiration__isnull=True), - creation__lt=timestamp) - - -class Client(models.Model): - """Object representing every client we have seen stats for.""" - creation = models.DateTimeField(auto_now_add=True) - name = models.CharField(max_length=128,) - current_interaction = models.ForeignKey('Interaction', - null=True, blank=True, - related_name="parent_client") - expiration = models.DateTimeField(blank=True, null=True) - - def __str__(self): - return self.name - - objects = ClientManager() - - class Admin: - pass - -class Ping(models.Model): - """Represents a ping of a client (sparsely).""" - client = models.ForeignKey(Client, related_name="pings") - starttime = models.DateTimeField() - endtime = models.DateTimeField() - status = models.CharField(max_length=4, choices=PING_CHOICES)#up/down - - class Meta: - get_latest_by = 'endtime' - -class InteractiveManager(models.Manager): - """Manages interactions objects. - - Returns most recent interaction as of specified timestamp in format: - '2006-01-01 00:00:00' or 'now' or None->'now' - - """ - def interaction_per_client(self, maxdate = None): - """Returns the most recent interactions for clients as of a date. - FIXME - check the dates passed in. - - """ - from django.db import connection - cursor = connection.cursor() - - sql = 'select reports_interaction.id, x.client_id from (select client_id, MAX(timestamp) ' + \ - 'as timer from reports_interaction' - if maxdate != 'now': - sql = sql + " where timestamp < '%s' " % maxdate - sql = sql + ' GROUP BY client_id) x, reports_interaction where ' + \ - 'reports_interaction.client_id = x.client_id AND reports_interaction.timestamp = x.timer' - try: - cursor.execute(sql) - except: - '''FIXME - really need some error hadling''' - return self.none() - return self.filter(id__in = [item[0] for item in cursor.fetchall()]) - - -class Interaction(models.Model): - """Models each reconfiguration operation interaction between client and server.""" - client = models.ForeignKey(Client, related_name="interactions",) - timestamp = models.DateTimeField()#Timestamp for this record - state = models.CharField(max_length=32)#good/bad/modified/etc - repo_rev_code = models.CharField(max_length=64)#repo revision at time of interaction - client_version = models.CharField(max_length=32)#Client Version - goodcount = models.IntegerField()#of good config-items - totalcount = models.IntegerField()#of total config-items - server = models.CharField(max_length=256) # Name of the server used for the interaction - bad_entries = models.IntegerField(default=-1) - modified_entries = models.IntegerField(default=-1) - extra_entries = models.IntegerField(default=-1) - - def __str__(self): - return "With " + self.client.name + " @ " + self.timestamp.isoformat() - - def percentgood(self): - if not self.totalcount == 0: - return (self.goodcount/float(self.totalcount))*100 - else: - return 0 - - def percentbad(self): - if not self.totalcount == 0: - return ((self.totalcount-self.goodcount)/(float(self.totalcount)))*100 - else: - return 0 - - def isclean(self): - if (self.bad_entry_count() == 0 and self.goodcount == self.totalcount): - return True - else: - return False - - def isstale(self): - if (self == self.client.current_interaction):#Is Mostrecent - if(datetime.now()-self.timestamp > timedelta(hours=25) ): - return True - else: - return False - else: - #Search for subsequent Interaction for this client - #Check if it happened more than 25 hrs ago. - if (self.client.interactions.filter(timestamp__gt=self.timestamp) - .order_by('timestamp')[0].timestamp - - self.timestamp > timedelta(hours=25)): - return True - else: - return False - def save(self): - super(Interaction, self).save() #call the real save... - self.client.current_interaction = self.client.interactions.latest() - self.client.save()#save again post update - - def delete(self): - '''Override the default delete. Allows us to remove Performance items''' - pitems = list(self.performance_items.all()) - super(Interaction, self).delete() - for perf in pitems: - if perf.interaction.count() == 0: - perf.delete() - - def badcount(self): - return self.totalcount - self.goodcount - - def bad(self): - return Entries_interactions.objects.select_related().filter(interaction=self, type=TYPE_BAD) - - def bad_entry_count(self): - """Number of bad entries. Store the count in the interation field to save db queries.""" - if self.bad_entries < 0: - self.bad_entries = Entries_interactions.objects.filter(interaction=self, type=TYPE_BAD).count() - self.save() - return self.bad_entries - - def modified(self): - return Entries_interactions.objects.select_related().filter(interaction=self, type=TYPE_MODIFIED) - - def modified_entry_count(self): - """Number of modified entries. Store the count in the interation field to save db queries.""" - if self.modified_entries < 0: - self.modified_entries = Entries_interactions.objects.filter(interaction=self, type=TYPE_MODIFIED).count() - self.save() - return self.modified_entries - - def extra(self): - return Entries_interactions.objects.select_related().filter(interaction=self, type=TYPE_EXTRA) - - def extra_entry_count(self): - """Number of extra entries. Store the count in the interation field to save db queries.""" - if self.extra_entries < 0: - self.extra_entries = Entries_interactions.objects.filter(interaction=self, type=TYPE_EXTRA).count() - self.save() - return self.extra_entries - - objects = InteractiveManager() - - class Admin: - list_display = ('client', 'timestamp', 'state') - list_filter = ['client', 'timestamp'] - pass - class Meta: - get_latest_by = 'timestamp' - unique_together = ("client", "timestamp") - -class Reason(models.Model): - """reason why modified or bad entry did not verify, or changed.""" - owner = models.TextField(max_length=128, blank=True) - current_owner = models.TextField(max_length=128, blank=True) - group = models.TextField(max_length=128, blank=True) - current_group = models.TextField(max_length=128, blank=True) - perms = models.TextField(max_length=4, blank=True)#txt fixes typing issue - current_perms = models.TextField(max_length=4, blank=True) - status = models.TextField(max_length=3, blank=True)#on/off/(None) - current_status = models.TextField(max_length=1, blank=True)#on/off/(None) - to = models.TextField(max_length=256, blank=True) - current_to = models.TextField(max_length=256, blank=True) - version = models.TextField(max_length=128, blank=True) - current_version = models.TextField(max_length=128, blank=True) - current_exists = models.BooleanField()#False means its missing. Default True - current_diff = models.TextField(max_length=1280, blank=True) - is_binary = models.BooleanField(default=False) - def _str_(self): - return "Reason" - - @staticmethod - @transaction.commit_on_success - def prune_orphans(): - '''Prune oprhaned rows... no good way to use the ORM''' - cursor = connection.cursor() - cursor.execute('delete from reports_reason where not exists (select rei.id from reports_entries_interactions rei where rei.reason_id = reports_reason.id)') - transaction.set_dirty() - - -class Entries(models.Model): - """Contains all the entries feed by the client.""" - name = models.CharField(max_length=128, db_index=True) - kind = models.CharField(max_length=16, choices=KIND_CHOICES, db_index=True) - - def __str__(self): - return self.name - - @staticmethod - @transaction.commit_on_success - def prune_orphans(): - '''Prune oprhaned rows... no good way to use the ORM''' - cursor = connection.cursor() - cursor.execute('delete from reports_entries where not exists (select rei.id from reports_entries_interactions rei where rei.entry_id = reports_entries.id)') - transaction.set_dirty() - -class Entries_interactions(models.Model): - """Define the relation between the reason, the interaction and the entry.""" - entry = models.ForeignKey(Entries) - reason = models.ForeignKey(Reason) - interaction = models.ForeignKey(Interaction) - type = models.IntegerField(choices=TYPE_CHOICES) - -class PerformanceManager(models.Manager): - """ - Provides ability to effectively query for performance information - It is possible this should move to the view - - """ - #Date format for maxdate: '2006-01-01 00:00:00' - def performance_per_client(self, maxdate = None): - from django.db import connection - cursor = connection.cursor() - if (maxdate == 'now' or maxdate == None): - cursor.execute("SELECT reports_client.name, reports_performance.metric, reports_performance.value "+ - "FROM reports_performance, reports_performance_interaction, reports_client WHERE ( "+ - "reports_client.current_interaction_id = reports_performance_interaction.interaction_id AND "+ - "reports_performance.id = reports_performance_interaction.performance_id)") - else: - cursor.execute("select reports_client.name, reports_performance.metric, "+ - "reports_performance.value from (Select reports_interaction.client_id as client_id, "+ - "MAX(reports_interaction.timestamp) as timestamp from reports_interaction where "+ - "timestamp < %s GROUP BY reports_interaction.client_id) x, reports_client, "+ - "reports_interaction, reports_performance, reports_performance_interaction where "+ - "reports_client.id = x.client_id AND x.timestamp = reports_interaction.timestamp AND "+ - "x.client_id = reports_interaction.client_id AND reports_performance.id = "+ - "reports_performance_interaction.performance_id AND "+ - "reports_performance_interaction.interaction_id = reports_interaction.id", [maxdate]) - - results = {} - for row in cursor.fetchall(): - try: - results[row[0]].__setitem__(row[1], row[2]) - except KeyError: - results[row[0]] = {row[1]:row[2]} - - return results - -#performance metrics, models a performance-metric-item -class Performance(models.Model): - """Object representing performance data for any interaction.""" - interaction = models.ManyToManyField(Interaction, related_name="performance_items") - metric = models.CharField(max_length=128) - value = models.DecimalField(max_digits=32, decimal_places=16) - def __str__(self): - return self.metric - - @staticmethod - @transaction.commit_on_success - def prune_orphans(): - '''Prune oprhaned rows... no good way to use the ORM''' - cursor = connection.cursor() - cursor.execute('delete from reports_performance where not exists (select ri.id from reports_performance_interaction ri where ri.performance_id = reports_performance.id)') - transaction.set_dirty() - - objects = PerformanceManager() - -class InternalDatabaseVersion(models.Model): - """Object that tell us to witch version is the database.""" - version = models.IntegerField() - updated = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return "version %d updated the %s" % (self.version, self.updated.isoformat()) diff --git a/src/lib/Server/Reports/reports/models.py.rej b/src/lib/Server/Reports/reports/models.py.rej deleted file mode 100644 index c8e1b2229..000000000 --- a/src/lib/Server/Reports/reports/models.py.rej +++ /dev/null @@ -1,15 +0,0 @@ -*************** -*** 1,6 **** - """Django models for Bcfg2 reports.""" - from django.db import models -- from django.db.models import Q - from datetime import datetime, timedelta - from time import strptime - ---- 1,6 ---- - """Django models for Bcfg2 reports.""" - from django.db import models -+ from django.db.models import Q, Max - from datetime import datetime, timedelta - from time import strptime - -- cgit v1.2.3-1-g7c22 From 598eef149378a0676a921559f7eef379ed2db46f Mon Sep 17 00:00:00 2001 From: asaf Date: Fri, 15 Oct 2010 14:27:25 +0200 Subject: Packages plugin / Client tools : add support for pacman based distros(arch, parabola), needs some more work --- src/lib/Client/Tools/Pacman.py | 81 +++++++++++++++++++++++++++ src/lib/Server/Plugins/Packages.py | 112 +++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/lib/Client/Tools/Pacman.py diff --git a/src/lib/Client/Tools/Pacman.py b/src/lib/Client/Tools/Pacman.py new file mode 100644 index 000000000..f155adfd4 --- /dev/null +++ b/src/lib/Client/Tools/Pacman.py @@ -0,0 +1,81 @@ +"""This is the bcfg2 support for pacman""" + +import Bcfg2.Client.Tools +import Bcfg2.Options +import Bcfg2.Client.Tools + +class Pacman(Bcfg2.Client.Tools.PkgTool): + '''Archlinux package support''' + name = 'Pacman' + __execs__ = ["/usr/bin/pacman"] + __handles__ = [('Package', 'txz')] + __req__ = {'Package': ['name', 'version']} + pkgtype = 'pacman' + pkgtool = ("/usr/bin/pacman --needed --noconfirm --noprogressbar -S %s") + + def __init__(self, logger, setup, config): + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + self.installed = {} + self.RefreshPackages() + + def RefreshPackages(self): + '''Refresh memory hashes of packages''' + pkgcache = self.cmd.run("/usr/bin/pacman -Q")[1] + self.installed = {} + for pkg in pkgcache: + pkgname = pkg.split(' ')[0].strip() + version = pkg.split(' ')[1].strip() + #self.logger.info(" pkgname: %s, version: %s" % (pkgname, version)) + self.installed[pkgname] = version + + def VerifyPackage(self, entry, modlist): + '''Verify Package status for entry''' + if not 'version' in entry.attrib: + self.logger.info("Cannot verify unversioned package %s" % + (entry.attrib['name'])) + return False + + if entry.attrib['name'] in self.installed: + if self.installed[entry.attrib['name']] == entry.attrib['version']: + #if not self.setup['quick'] and \ + # entry.get('verify', 'true') == 'true': + #FIXME: We should be able to check this once + # http://trac.macports.org/ticket/15709 is implemented + return True + else: + entry.set('current_version', self.installed[entry.get('name')]) + self.logger.info("attribname: %s" % (entry.attrib['name'])) + self.logger.info("attribname: %s" % (entry.attrib['name'])) + return False + entry.set('current_exists', 'false') + self.logger.info("attribname: %s" % (entry.attrib['name'])) + return False + + def RemovePackages(self, packages): + '''Remove extra packages''' + names = [pkg.get('name') for pkg in packages] + self.logger.info("Removing packages: %s" % " ".join(names)) + self.cmd.run("/usr/bin/pacman --noconfirm --noprogressbar -R %s" % \ + " ".join(names)) + self.RefreshPackages() + self.extra = self.FindExtraPackages() + + def Install(self, packages, states): + ''' + Pacman Install + ''' + pkgline = "" + for pkg in packages: + pkgline += " " + pkg.get('name') + + print "packages : " + pkgline + + try: + self.logger.debug('Running Pacman.Install()') + print "AAAAA" + s = self.cmd.run("%s install" % self.pkgtool) + print "BBB: " + str(s) + except Exception as ex: + print "error in cmd.run ", ex + + self.logger.debug('Running Pacman.Install()') diff --git a/src/lib/Server/Plugins/Packages.py b/src/lib/Server/Plugins/Packages.py index b83b7444f..b24cd48da 100644 --- a/src/lib/Server/Plugins/Packages.py +++ b/src/lib/Server/Plugins/Packages.py @@ -1,6 +1,7 @@ import cPickle import copy import gzip +import tarfile import glob import logging import lxml.etree @@ -467,6 +468,114 @@ class APTSource(Source): pkg not in self.blacklist and \ (len(self.whitelist) == 0 or pkg in self.whitelist) +class PACSource(Source): + basegroups = ['arch', 'parabola'] + ptype = 'txz' + + def __init__(self, basepath, url, version, arches, components, groups, + rawurl, blacklist, whitelist, recommended): + Source.__init__(self, basepath, url, version, arches, components, groups, + rawurl, blacklist, whitelist, recommended) + self.pkgnames = set() + + self.url_map = [{'rawurl': self.rawurl, 'url': self.url, 'version': self.version, \ + 'components': self.components, 'arches': self.arches, 'groups': self.groups}] + + def save_state(self): + cache = file(self.cachefile, 'wb') + cPickle.dump((self.pkgnames, self.deps, self.provides), + cache, 2) + cache.close() + + def load_state(self): + data = file(self.cachefile) + self.pkgnames, self.deps, self.provides = cPickle.load(data) + + def filter_unknown(self, unknown): + filtered = set([u for u in unknown if u.startswith('choice')]) + unknown.difference_update(filtered) + + def get_urls(self): + if not self.rawurl: + return ["%s/%s/os/%s/%s.db.tar.gz" % \ + (self.url, part, arch, part) for part in self.components \ + for arch in self.arches] + else: + raise Exception("PACSource : RAWUrl not supported (yet)") + urls = property(get_urls) + + + def read_files(self): + bdeps = dict() + bprov = dict() + + if self.recommended: + depfnames = ['Depends', 'Pre-Depends', 'Recommends'] + else: + depfnames = ['Depends', 'Pre-Depends'] + + for fname in self.files: + if not self.rawurl: + barch = [x for x in fname.split('@') if x in self.arches][0] + else: + # RawURL entries assume that they only have one + # element and that it is the architecture of the source. + barch = self.arches[0] + + if barch not in bdeps: + bdeps[barch] = dict() + bprov[barch] = dict() + try: + print "try to read : " + fname + tar = tarfile.open(fname, "r") + reader = gzip.GzipFile(fname) + except: + print("Failed to read file %s" % fname) + raise + + for tarinfo in tar: + if tarinfo.isdir(): + self.pkgnames.add(tarinfo.name.rsplit("-",2)[0]) + print "added : " + tarinfo.name.rsplit("-",2)[0] + tar.close() + + self.deps['global'] = dict() + self.provides['global'] = dict() + for barch in bdeps: + self.deps[barch] = dict() + self.provides[barch] = dict() + for pkgname in self.pkgnames: + pset = set() + for barch in bdeps: + if pkgname not in bdeps[barch]: + bdeps[barch][pkgname] = [] + pset.add(tuple(bdeps[barch][pkgname])) + if len(pset) == 1: + self.deps['global'][pkgname] = pset.pop() + else: + for barch in bdeps: + self.deps[barch][pkgname] = bdeps[barch][pkgname] + provided = set() + for bprovided in bprov.values(): + provided.update(set(bprovided)) + for prov in provided: + prset = set() + for barch in bprov: + if prov not in bprov[barch]: + continue + prset.add(tuple(bprov[barch].get(prov, ()))) + if len(prset) == 1: + self.provides['global'][prov] = prset.pop() + else: + for barch in bprov: + self.provides[barch][prov] = bprov[barch].get(prov, ()) + self.save_state() + + def is_package(self, _, pkg): + return pkg in self.pkgnames and \ + pkg not in self.blacklist and \ + (len(self.whitelist) == 0 or pkg in self.whitelist) + class Packages(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.StructureValidator, Bcfg2.Server.Plugin.Generator, @@ -742,6 +851,9 @@ class Packages(Bcfg2.Server.Plugin.Plugin, self.sources.append(APTSource(self.cachepath, **source_from_xml(s))) for s in xdata.findall('.//YUMSource'): self.sources.append(YUMSource(self.cachepath, **source_from_xml(s))) + for s in xdata.findall('.//PACSource'): + self.sources.append(PACSource(self.cachepath, **source_from_xml(s))) + cachefiles = [] for source in self.sources: cachefiles.append(source.cachefile) -- cgit v1.2.3-1-g7c22 From ff01b4e6ad3dcf7fdae92adc16c1a3481a37d8db Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Fri, 15 Oct 2010 19:50:42 -0500 Subject: doc: Fix centos quickstart instructions (reported by Joe Sauer) Signed-off-by: Sol Jerome --- doc/quickstart/centos.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/quickstart/centos.txt b/doc/quickstart/centos.txt index 2aaa8d744..4a702683e 100644 --- a/doc/quickstart/centos.txt +++ b/doc/quickstart/centos.txt @@ -424,7 +424,7 @@ packages. Currently, the way to manage them is using :ref:`BoundEntries .. code-block:: xml - + @@ -445,6 +445,8 @@ packages. Currently, the way to manage them is using :ref:`BoundEntries +.. note:: version="foo" is just a dummy attribute for the gpg-pubkey Package + To actually push the gpg keys out via Bcfg2, you will need to manage the files as well. This can be done by adding Path entries for each of the gpg keys you want to manage -- cgit v1.2.3-1-g7c22 From 2a4716d8726e383291637e8f5ae2dff49248ac9b Mon Sep 17 00:00:00 2001 From: Asaf Date: Sun, 17 Oct 2010 07:44:56 +0200 Subject: changed package type to 'pacman', if request package version is 'auto' and its installed dont re-install --- src/lib/Client/Tools/Pacman.py | 21 +++++++++++++-------- src/lib/Server/Plugins/Packages.py | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/Client/Tools/Pacman.py b/src/lib/Client/Tools/Pacman.py index f155adfd4..a9edc4d65 100644 --- a/src/lib/Client/Tools/Pacman.py +++ b/src/lib/Client/Tools/Pacman.py @@ -8,10 +8,10 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): '''Archlinux package support''' name = 'Pacman' __execs__ = ["/usr/bin/pacman"] - __handles__ = [('Package', 'txz')] + __handles__ = [('Package', 'pacman')] __req__ = {'Package': ['name', 'version']} pkgtype = 'pacman' - pkgtool = ("/usr/bin/pacman --needed --noconfirm --noprogressbar -S %s") + pkgtool = ("/usr/bin/pacman --needed --noconfirm --noprogressbar") def __init__(self, logger, setup, config): Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) @@ -30,13 +30,18 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): def VerifyPackage(self, entry, modlist): '''Verify Package status for entry''' + + print "VerifyPackage : " + entry.get('name')+ " : " + entry.get('version') + if not 'version' in entry.attrib: self.logger.info("Cannot verify unversioned package %s" % (entry.attrib['name'])) return False if entry.attrib['name'] in self.installed: - if self.installed[entry.attrib['name']] == entry.attrib['version']: + if entry.attrib['version'] == 'auto': + return True + elif self.installed[entry.attrib['name']] == entry.attrib['version']: #if not self.setup['quick'] and \ # entry.get('verify', 'true') == 'true': #FIXME: We should be able to check this once @@ -55,8 +60,8 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): '''Remove extra packages''' names = [pkg.get('name') for pkg in packages] self.logger.info("Removing packages: %s" % " ".join(names)) - self.cmd.run("/usr/bin/pacman --noconfirm --noprogressbar -R %s" % \ - " ".join(names)) + self.cmd.run("%s --noconfirm --noprogressbar -R %s" % \ + (self.pkgtool, " ".join(names))) self.RefreshPackages() self.extra = self.FindExtraPackages() @@ -72,9 +77,9 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): try: self.logger.debug('Running Pacman.Install()') - print "AAAAA" - s = self.cmd.run("%s install" % self.pkgtool) - print "BBB: " + str(s) + print "running : %s -S %s" % (self.pkgtool, pkgline) + s = self.cmd.run("%s -S %s" % (self.pkgtool, pkgline)) + print "pacman : " + str(s) except Exception as ex: print "error in cmd.run ", ex diff --git a/src/lib/Server/Plugins/Packages.py b/src/lib/Server/Plugins/Packages.py index b24cd48da..194330723 100644 --- a/src/lib/Server/Plugins/Packages.py +++ b/src/lib/Server/Plugins/Packages.py @@ -470,7 +470,7 @@ class APTSource(Source): class PACSource(Source): basegroups = ['arch', 'parabola'] - ptype = 'txz' + ptype = 'pacman' def __init__(self, basepath, url, version, arches, components, groups, rawurl, blacklist, whitelist, recommended): -- cgit v1.2.3-1-g7c22 From e560e2c7f384bdd5ca675465b85cc5a6c911f747 Mon Sep 17 00:00:00 2001 From: Asaf Ohaion Date: Sun, 17 Oct 2010 10:18:50 +0200 Subject: merged 9a2bb764cf73911885a8e801882219b052f11475 from Sol J --- doc/quickstart/centos.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/quickstart/centos.txt b/doc/quickstart/centos.txt index 2aaa8d744..4a702683e 100644 --- a/doc/quickstart/centos.txt +++ b/doc/quickstart/centos.txt @@ -424,7 +424,7 @@ packages. Currently, the way to manage them is using :ref:`BoundEntries .. code-block:: xml - + @@ -445,6 +445,8 @@ packages. Currently, the way to manage them is using :ref:`BoundEntries +.. note:: version="foo" is just a dummy attribute for the gpg-pubkey Package + To actually push the gpg keys out via Bcfg2, you will need to manage the files as well. This can be done by adding Path entries for each of the gpg keys you want to manage -- cgit v1.2.3-1-g7c22 From b056804f3d3e3a2d92dac42062527873255d14c7 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Sun, 17 Oct 2010 20:43:16 -0500 Subject: Client Tools: Pylint/PEP8 fixes Signed-off-by: Sol Jerome --- src/lib/Client/Tools/Action.py | 23 ++++++--- src/lib/Client/Tools/Blast.py | 5 +- src/lib/Client/Tools/Chkconfig.py | 8 ++- src/lib/Client/Tools/DebInit.py | 10 ++-- src/lib/Client/Tools/FreeBSDInit.py | 1 + src/lib/Client/Tools/FreeBSDPackage.py | 3 +- src/lib/Client/Tools/IPS.py | 5 +- src/lib/Client/Tools/MacPorts.py | 1 + src/lib/Client/Tools/POSIX.py | 13 +++-- src/lib/Client/Tools/Portage.py | 8 +-- src/lib/Client/Tools/RcUpdate.py | 4 +- src/lib/Client/Tools/SMF.py | 17 ++++--- src/lib/Client/Tools/SYSV.py | 6 ++- src/lib/Client/Tools/YUMng.py | 89 +++++++++++++++++++--------------- src/lib/Client/Tools/launchd.py | 18 ++++--- 15 files changed, 133 insertions(+), 78 deletions(-) diff --git a/src/lib/Client/Tools/Action.py b/src/lib/Client/Tools/Action.py index 3610d9015..452788f94 100644 --- a/src/lib/Client/Tools/Action.py +++ b/src/lib/Client/Tools/Action.py @@ -3,23 +3,34 @@ __revision__ = '$Revision$' import Bcfg2.Client.Tools -# -# -# => +""" + + + => +""" + class Action(Bcfg2.Client.Tools.Tool): """Implement Actions""" name = 'Action' __handles__ = [('PostInstall', None), ('Action', None)] __req__ = {'PostInstall': ['name'], - 'Action':['name', 'timing', 'when', 'command', 'status']} + 'Action': ['name', 'timing', 'when', 'command', 'status']} def RunAction(self, entry): """This method handles command execution and status return.""" if not self.setup['dryrun']: if self.setup['interactive']: - prompt = 'Run Action %s, %s: (y/N): ' % (entry.get('name'), entry.get('command')) + prompt = ('Run Action %s, %s: (y/N): ' % + (entry.get('name'), entry.get('command'))) if raw_input(prompt) not in ['y', 'Y']: return False if self.setup['servicemode'] == 'build': diff --git a/src/lib/Client/Tools/Blast.py b/src/lib/Client/Tools/Blast.py index 737c6924e..4f2891fd6 100644 --- a/src/lib/Client/Tools/Blast.py +++ b/src/lib/Client/Tools/Blast.py @@ -2,7 +2,9 @@ """This provides bcfg2 support for blastwave""" __revision__ = '$Revision$' -import Bcfg2.Client.Tools.SYSV, tempfile +import tempfile +import Bcfg2.Client.Tools.SYSV + class Blast(Bcfg2.Client.Tools.SYSV.SYSV): """Support for Blastwave packages""" @@ -27,7 +29,6 @@ class Blast(Bcfg2.Client.Tools.SYSV.SYSV): # Install comes from Bcfg2.Client.Tools.PkgTool # Extra comes from Bcfg2.Client.Tools.Tool # Remove comes from Bcfg2.Client.Tools.SYSV - def FindExtraPackages(self): """Pass through to null FindExtra call.""" return [] diff --git a/src/lib/Client/Tools/Chkconfig.py b/src/lib/Client/Tools/Chkconfig.py index 5dbb7b345..b7227ec3d 100644 --- a/src/lib/Client/Tools/Chkconfig.py +++ b/src/lib/Client/Tools/Chkconfig.py @@ -81,7 +81,9 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): self.logger.info("Installing Service %s" % (entry.get('name'))) pass1 = True if entry.get('status') == 'off': - rc = self.cmd.run(rcmd % (entry.get('name'), entry.get('status')) + " --level 0123456")[0] + rc = self.cmd.run(rcmd % (entry.get('name'), + entry.get('status')) + \ + " --level 0123456")[0] pass1 = rc == 0 rc = self.cmd.run(rcmd % (entry.get('name'), entry.get('status')))[0] return pass1 and rc == 0 @@ -93,5 +95,7 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): self.logger.debug('Found active services:') self.logger.debug(allsrv) specified = [srv.get('name') for srv in self.getSupportedEntries()] - return [Bcfg2.Client.XML.Element('Service', type='chkconfig', name=name) \ + return [Bcfg2.Client.XML.Element('Service', + type='chkconfig', + name=name) \ for name in allsrv if name not in specified] diff --git a/src/lib/Client/Tools/DebInit.py b/src/lib/Client/Tools/DebInit.py index 0185f420c..aee8ffd65 100644 --- a/src/lib/Client/Tools/DebInit.py +++ b/src/lib/Client/Tools/DebInit.py @@ -1,16 +1,18 @@ """Debian Init Support for Bcfg2""" __revision__ = '$Revision$' -import glob, os, re +import glob +import os +import re import Bcfg2.Client.Tools - # Debian squeeze and beyond uses a dependecy based boot sequence DEBIAN_OLD_STYLE_BOOT_SEQUENCE = ( 'etch', '4.0', 'lenny', '5.0', '5.0.1', '5.0.2', '5.0.3', '5.0.4', '5.0.4', '5.0.5', ) + class DebInit(Bcfg2.Client.Tools.SvcTool): """Debian Service Support for Bcfg2.""" name = 'DebInit' @@ -35,7 +37,9 @@ class DebInit(Bcfg2.Client.Tools.SvcTool): start_sequence = int(entry.get('sequence')) kill_sequence = 100 - start_sequence else: - self.logger.warning("Your debian version boot sequence is dependency based \"sequence\" attribute wil be ignored.") + self.logger.warning("Your debian version boot sequence is " + "dependency based \"sequence\" attribute " + "will be ignored.") else: start_sequence = None diff --git a/src/lib/Client/Tools/FreeBSDInit.py b/src/lib/Client/Tools/FreeBSDInit.py index e597a294b..10f0f2e93 100644 --- a/src/lib/Client/Tools/FreeBSDInit.py +++ b/src/lib/Client/Tools/FreeBSDInit.py @@ -8,6 +8,7 @@ __revision__ = '$Rev$' import os import Bcfg2.Client.Tools + class FreeBSDInit(Bcfg2.Client.Tools.SvcTool): """FreeBSD service support for Bcfg2.""" name = 'FreeBSDInit' diff --git a/src/lib/Client/Tools/FreeBSDPackage.py b/src/lib/Client/Tools/FreeBSDPackage.py index 49cfa5bd2..04c05adaa 100644 --- a/src/lib/Client/Tools/FreeBSDPackage.py +++ b/src/lib/Client/Tools/FreeBSDPackage.py @@ -8,6 +8,7 @@ __revision__ = '$Rev$' import re import Bcfg2.Client.Tools + class FreeBSDPackage(Bcfg2.Client.Tools.PkgTool): """The FreeBSD toolset implements package operations and inherits the rest from Toolset.Toolset.""" @@ -24,7 +25,7 @@ class FreeBSDPackage(Bcfg2.Client.Tools.PkgTool): pattern = re.compile('(.*)-(\d.*)') for pkg in packages: if pattern.match(pkg): - name = pattern.match(pkg).group(1) + name = pattern.match(pkg).group(1) version = pattern.match(pkg).group(2) self.installed[name] = version diff --git a/src/lib/Client/Tools/IPS.py b/src/lib/Client/Tools/IPS.py index d7d84617f..9afd23143 100644 --- a/src/lib/Client/Tools/IPS.py +++ b/src/lib/Client/Tools/IPS.py @@ -1,11 +1,12 @@ """This is the Bcfg2 support for OpenSolaris packages.""" __revision__ = '$Revision$' -import Bcfg2.Client.Tools - import pkg.client.image as image import pkg.client.progress as progress +import Bcfg2.Client.Tools + + class IPS(Bcfg2.Client.Tools.PkgTool): """The IPS driver implements OpenSolaris package operations.""" name = 'IPS' diff --git a/src/lib/Client/Tools/MacPorts.py b/src/lib/Client/Tools/MacPorts.py index 70285fa3a..23b536451 100644 --- a/src/lib/Client/Tools/MacPorts.py +++ b/src/lib/Client/Tools/MacPorts.py @@ -3,6 +3,7 @@ __revision__ = '$Revision$' import Bcfg2.Client.Tools + class MacPorts(Bcfg2.Client.Tools.PkgTool): """macports package support.""" name = 'MacPorts' diff --git a/src/lib/Client/Tools/POSIX.py b/src/lib/Client/Tools/POSIX.py index 9d37b77d3..a4417297a 100644 --- a/src/lib/Client/Tools/POSIX.py +++ b/src/lib/Client/Tools/POSIX.py @@ -44,6 +44,7 @@ def calcPerms(initial, perms): tempperms |= perm return tempperms + def normUid(entry): """ This takes a user name or uid and @@ -58,6 +59,7 @@ def normUid(entry): log.error('UID normalization failed for %s' % (entry.get('name'))) return False + def normGid(entry): """ This takes a group name or gid and @@ -75,6 +77,7 @@ def normGid(entry): text_chars = "".join([chr(y) for y in range(32, 127)] + list("\n\r\t\b")) notrans = string.maketrans("", "") + def isString(strng): """Returns true if a string contains no binary chars.""" if "\0" in strng: @@ -85,6 +88,7 @@ def isString(strng): return len(strng.translate(notrans, text_chars)) == 0 + class POSIX(Bcfg2.Client.Tools.Tool): """POSIX File support code.""" name = 'POSIX' @@ -161,7 +165,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): else: os.unlink(entry.get('name')) except OSError: - self.logger.info("Symlink %s cleanup failed" % (entry.get('name'))) + self.logger.info("Symlink %s cleanup failed" %\ + (entry.get('name'))) try: os.symlink(entry.get('to'), entry.get('name')) return True @@ -205,7 +210,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): pruneTrue = True ex_ents = [] if entry.get('prune', 'false') == 'true' \ - and (entry.tag == 'Directory' or entry.get('type') == 'directory'): + and (entry.tag == 'Directory' or + entry.get('type') == 'directory'): # FIXME: need to verify both old and new POSIX types try: entries = ['/'.join([entry.get('name'), ent]) \ @@ -217,7 +223,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.get('name')) self.logger.debug(ex_ents) nqtext = entry.get('qtext', '') + '\n' - nqtext += "Directory %s contains extra entries:" % entry.get('name') + nqtext += "Directory %s contains extra entries:" % \ + entry.get('name') nqtext += ":".join(ex_ents) entry.set('qtest', nqtext) [entry.append(XML.Element('Prune', path=x)) for x in ex_ents] diff --git a/src/lib/Client/Tools/Portage.py b/src/lib/Client/Tools/Portage.py index 765e981fe..58d2aad29 100644 --- a/src/lib/Client/Tools/Portage.py +++ b/src/lib/Client/Tools/Portage.py @@ -4,9 +4,10 @@ __revision__ = '$Revision$' import re import Bcfg2.Client.Tools + class Portage(Bcfg2.Client.Tools.PkgTool): - """The Gentoo toolset implements package and service operations and inherits - the rest from Toolset.Toolset.""" + """The Gentoo toolset implements package and service operations and + inherits the rest from Toolset.Toolset.""" name = 'Portage' __execs__ = ['/usr/bin/emerge', '/usr/bin/equery'] __handles__ = [('Package', 'ebuild')] @@ -47,8 +48,7 @@ class Portage(Bcfg2.Client.Tools.PkgTool): if self.installed[entry.attrib['name']] == entry.attrib['version']: if not self.setup['quick'] and \ entry.get('verify', 'true') == 'true': - output = self.cmd.run \ - ("/usr/bin/equery check '=%s-%s' 2>&1 |grep '!!!' | awk '{print $2}'" \ + output = self.cmd.run("/usr/bin/equery check '=%s-%s' 2>&1 |grep '!!!' | awk '{print $2}'" \ % (entry.get('name'), entry.get('version')))[1] if [filename for filename in output \ if filename not in modlist]: diff --git a/src/lib/Client/Tools/RcUpdate.py b/src/lib/Client/Tools/RcUpdate.py index a91562c30..159172b78 100644 --- a/src/lib/Client/Tools/RcUpdate.py +++ b/src/lib/Client/Tools/RcUpdate.py @@ -87,5 +87,7 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool): self.logger.debug('Found active services:') self.logger.debug(allsrv) specified = [srv.get('name') for srv in self.getSupportedEntries()] - return [Bcfg2.Client.XML.Element('Service', type='rc-update', name=name) \ + return [Bcfg2.Client.XML.Element('Service', + type='rc-update', + name=name) \ for name in allsrv if name not in specified] diff --git a/src/lib/Client/Tools/SMF.py b/src/lib/Client/Tools/SMF.py index 733228d18..f0bc6bd05 100644 --- a/src/lib/Client/Tools/SMF.py +++ b/src/lib/Client/Tools/SMF.py @@ -1,15 +1,18 @@ """SMF support for Bcfg2""" __revision__ = '$Revision$' -import glob, os +import glob +import os + import Bcfg2.Client.Tools + class SMF(Bcfg2.Client.Tools.SvcTool): """Support for Solaris SMF Services.""" __handles__ = [('Service', 'smf')] __execs__ = ['/usr/sbin/svcadm', '/usr/bin/svcs'] name = 'SMF' - __req__ = {'Service':['name', 'status']} + __req__ = {'Service': ['name', 'status']} __ireq__ = {'Service': ['name', 'status', 'FMRI']} def get_svc_command(self, service, action): @@ -53,7 +56,8 @@ class SMF(Bcfg2.Client.Tools.SvcTool): (entry.get("FMRI"), ":".join(files))) return entry.get('status') == 'on' else: - self.logger.debug("No service matching %s" % (entry.get("FMRI"))) + self.logger.debug("No service matching %s" % \ + (entry.get("FMRI"))) return entry.get('status') == 'off' try: srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % \ @@ -79,7 +83,8 @@ class SMF(Bcfg2.Client.Tools.SvcTool): os.rename(loc, loc.replace('/S', '/DISABLED.S')) return True except OSError: - self.logger.error("Failed to rename init script %s" % (loc)) + self.logger.error("Failed to rename init script %s" % \ + (loc)) return False else: cmdrc = self.cmd.run("/usr/sbin/svcadm disable %s" % \ @@ -94,8 +99,8 @@ class SMF(Bcfg2.Client.Tools.SvcTool): os.rename(loc.replace('/S', '/DISABLED.S'), loc) cmdrc = 0 except OSError: - self.logger.debug("Failed to rename %s to %s" \ - % (loc.replace('/S', '/DISABLED.S'), loc)) + self.logger.debug("Failed to rename %s to %s" % \ + (loc.replace('/S', '/DISABLED.S'), loc)) cmdrc = 1 else: srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % diff --git a/src/lib/Client/Tools/SYSV.py b/src/lib/Client/Tools/SYSV.py index c2b0eb2cc..b5e1f1c59 100644 --- a/src/lib/Client/Tools/SYSV.py +++ b/src/lib/Client/Tools/SYSV.py @@ -2,7 +2,10 @@ """This provides bcfg2 support for Solaris SYSV packages.""" __revision__ = '$Revision$' -import tempfile, Bcfg2.Client.Tools, Bcfg2.Client.XML +import tempfile + +import Bcfg2.Client.Tools +import Bcfg2.Client.XML noask = ''' @@ -19,6 +22,7 @@ action=nocheck basedir=default ''' + class SYSV(Bcfg2.Client.Tools.PkgTool): """Solaris SYSV package support.""" __execs__ = ["/usr/sbin/pkgadd", "/usr/bin/pkginfo"] diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Client/Tools/YUMng.py index 077d71508..9fc776471 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Client/Tools/YUMng.py @@ -4,7 +4,6 @@ __revision__ = '$Revision$' import ConfigParser import copy import os.path -import sys import yum import yum.packages import yum.rpmtrans @@ -21,6 +20,7 @@ try: except NameError: from sets import Set as set + def build_yname(pkgname, inst): """Build yum appropriate package name.""" d = {} @@ -39,6 +39,7 @@ def build_yname(pkgname, inst): d['arch'] = inst.get('arch') return d + def short_yname(nevra): d = nevra.copy() if 'version' in d: @@ -49,17 +50,19 @@ def short_yname(nevra): del d['release'] return d + def nevraString(p): if isinstance(p, yum.packages.PackageObject): return str(p) else: ret = "" - for i, j in [('epoch','%s:'), ('name','%s'), ('version','-%s'), - ('release','-%s'), ('arch','.%s')]: + for i, j in [('epoch', '%s:'), ('name', '%s'), ('version', '-%s'), + ('release', '-%s'), ('arch', '.%s')]: if i in p: ret = "%s%s" % (ret, j % p[i]) return ret + class Parser(ConfigParser.ConfigParser): def get(self, section, option, default): @@ -73,6 +76,7 @@ class Parser(ConfigParser.ConfigParser): except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): return default + class RPMDisplay(yum.rpmtrans.RPMBaseCallback): """We subclass the default RPM transaction callback so that we can control Yum's verbosity and pipe it through the right logger.""" @@ -83,11 +87,11 @@ class RPMDisplay(yum.rpmtrans.RPMBaseCallback): self.state = None self.package = None - def event(self, package, action, te_current, te_total, + def event(self, package, action, te_current, te_total, ts_current, ts_total): - """ + """ @param package: A yum package object or simple string of a package name - @param action: A yum.constant transaction set state or in the obscure + @param action: A yum.constant transaction set state or in the obscure rpm repackage case it could be the string 'repackaging' @param te_current: Current number of bytes processed in the transaction element being processed @@ -114,6 +118,7 @@ class RPMDisplay(yum.rpmtrans.RPMBaseCallback): """Deal with error reporting.""" self.logger.error(msg) + class YumDisplay(yum.callbacks.ProcessTransBaseCallback): """Class to handle display of what step we are in the Yum transaction such as downloading packages, etc.""" @@ -141,10 +146,10 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): def __init__(self, logger, setup, config): self.yb = yum.YumBase() Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - self.ignores = [ entry.get('name') for struct in config \ - for entry in struct \ - if entry.tag == 'Path' and \ - entry.get('type') == 'ignore' ] + self.ignores = [entry.get('name') for struct in config \ + for entry in struct \ + if entry.tag == 'Path' and \ + entry.get('type') == 'ignore'] self.instance_status = {} self.extra_instances = [] self.modlists = {} @@ -168,7 +173,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): except yum.Errors.YumBaseError, e: self.logger.error("YUMng error: %s" % e) raise Bcfg2.Client.Tools.toolInstantiationError - + yup = self.yb.doPackageLists(pkgnarrow='updates') if hasattr(self.yb.rpmdb, 'pkglist'): yinst = self.yb.rpmdb.pkglist @@ -201,7 +206,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): in truth self.doInstall = CP.get(self.name, "installed_action", "install").lower() == "install" - self.doUpgrade = CP.get(self.name, + self.doUpgrade = CP.get(self.name, "version_fail_action", "upgrade").lower() == "upgrade" self.doReinst = CP.get(self.name, "verify_fail_action", "reinstall").lower() == "reinstall" @@ -254,7 +259,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): instance = Bcfg2.Client.XML.SubElement(entry, 'Package') for attrib in list(entry.attrib.keys()): instance.attrib[attrib] = entry.attrib[attrib] - instances = [ instance ] + instances = [instance] return instances @@ -287,18 +292,20 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): else: results = verify(po) self.verifyCache[key] = results - if not rpmUtils.arch.isMultiLibArch(): return results + if not rpmUtils.arch.isMultiLibArch(): + return results # Okay deal with a buggy yum multilib and verify packages = self.yb.rpmdb.searchNevra(name=po.name, epoch=po.epoch, ver=po.version, rel=po.release) # find all arches of pkg - if len(packages) == 1: + if len(packages) == 1: return results # No mathcing multilib packages files = set(po.returnFileEntries()) # Will be the list of common fns common = {} for p in packages: - if p != po: files = files & set(p.returnFileEntries()) + if p != po: + files = files & set(p.returnFileEntries()) for p in packages: k = (p.name, p.epoch, p.version, p.release, p.arch) self.logger.debug("Multilib Verify: comparing %s to %s" \ @@ -320,10 +327,11 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): # this fn had verify problems in all but one of the multilib # packages. That means its correct in the package that's # "on top." Therefore, this is a fake verify problem. - if fn in results: del results[fn] + if fn in results: + del results[fn] return results - + def RefreshPackages(self): """ Creates self.installed{} which is a dict of installed packages. @@ -407,12 +415,13 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): virtPkg = True self.logger.info("%s appears to be provided by:" \ % entry.get('name')) - for p in POs: self.logger.info(" %s" % p) + for p in POs: + self.logger.info(" %s" % p) for inst in instances: nevra = build_yname(entry.get('name'), inst) snevra = short_yname(nevra) - if nevra in packageCache: + if nevra in packageCache: continue # Ignore duplicate instances else: packageCache.append(nevra) @@ -436,15 +445,16 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): qtext_versions.append("I(%s)" % nevra) continue - if not pkg_checks: continue + if not pkg_checks: + continue # Check EVR if virtPkg: self.logger.debug(" Not checking version for virtual package") - _POs = [ po for po in POs ] # Make a copy + _POs = [po for po in POs] # Make a copy elif entry.get('name') == 'gpg-pubkey': - _POs = [ p for p in POs if p.version == nevra['version'] \ - and p.release == nevra['release'] ] + _POs = [p for p in POs if p.version == nevra['version'] \ + and p.release == nevra['release']] else: _POs = self.yb.rpmdb.searchNevra(**snevra) if len(_POs) == 0: @@ -452,7 +462,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): stat['version_fail'] = True # Just chose the first pkg for the error message self.logger.info(" Wrong version installed. "\ - "Want %s, but have %s" % (nevraString(nevra), + "Want %s, but have %s" % (nevraString(nevra), nevraString(POs[0]))) qtext_versions.append("U(%s)" % str(POs[0])) continue @@ -485,7 +495,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): [ig.get('name') for ig in inst.findall('Ignore')] + \ self.ignores for fn, probs in vResult.items(): - if fn in modlist: + if fn in modlist: self.logger.debug(" %s in modlist, skipping" % fn) continue if fn in ignores: @@ -499,18 +509,19 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): tmp.append((p.type, p.message)) if tmp != []: stat['verify'][fn] = tmp - + if stat['verify'] != {}: stat['verify_fail'] = True package_fail = True self.logger.debug(" Verify Problems:") for fn, probs in stat['verify'].items(): self.logger.debug(" %s" % fn) - for p in probs: self.logger.debug(" %s: %s" % p) + for p in probs: + self.logger.debug(" %s: %s" % p) if len(POs) > 0: # Is this an install only package? We just look at the first one - provides = set([ p[0] for p in POs[0].provides ] + [ POs[0].name ]) + provides = set([p[0] for p in POs[0].provides] + [POs[0].name]) install_only = len(set(self.installOnlyPkgs) & provides) > 0 else: install_only = False @@ -530,7 +541,6 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): return not package_fail - def FindExtraInstances(self, entry, POs): """ Check for installed instances that are not in the config. @@ -538,12 +548,13 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): are no Instances to remove. """ - if len(POs) == 0: return None + if len(POs) == 0: + return None name = entry.get('name') - extra_entry = Bcfg2.Client.XML.Element('Package', name=name, + extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) instances = self._buildInstances(entry) - _POs = [ p for p in POs ] # Shallow copy + _POs = [p for p in POs] # Shallow copy # Algorythm is sensitive to duplicates, check for them checked = [] @@ -565,19 +576,19 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): epoch=p.epoch, name=p.name, version=p.version, release=p.release, arch=p.arch) - if _POs == []: + if _POs == []: return None else: return extra_entry def FindExtraPackages(self): """Find extra packages.""" - packages = [ e.get('name') for e in self.getSupportedEntries() ] + packages = [e.get('name') for e in self.getSupportedEntries()] extras = [] for p in self.installed.keys(): if p not in packages: - entry = Bcfg2.Client.XML.Element('Package', name=p, + entry = Bcfg2.Client.XML.Element('Package', name=p, type=self.pkgtype) for i in self.installed[p]: inst = Bcfg2.Client.XML.SubElement(entry, 'Instance', \ @@ -608,16 +619,16 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): rel = yum.misc.keyIdToRPMVer(gpg['timestamp']) if not (ver == inst.get('version') and rel == inst.get('release')): self.logger.info("GPG key file %s does not match gpg-pubkey-%s-%s"\ - % (key_file, inst.get('version'), + % (key_file, inst.get('version'), inst.get('release'))) return False - if not yum.misc.keyInstalled(ts, gpg['keyid'], + if not yum.misc.keyInstalled(ts, gpg['keyid'], gpg['timestamp']) == 0: result = ts.pgpImportPubkey(yum.misc.procgpgkey(rawkey)) else: self.logger.debug("gpg-pubkey-%s-%s already installed"\ - % (inst.get('version'), + % (inst.get('version'), inst.get('release'))) return True diff --git a/src/lib/Client/Tools/launchd.py b/src/lib/Client/Tools/launchd.py index a8b785016..db6d94c1b 100644 --- a/src/lib/Client/Tools/launchd.py +++ b/src/lib/Client/Tools/launchd.py @@ -2,20 +2,23 @@ __revision__ = '$Revision$' import os -import Bcfg2.Client.Tools import popen2 +import Bcfg2.Client.Tools + + class launchd(Bcfg2.Client.Tools.Tool): """Support for Mac OS X launchd services.""" __handles__ = [('Service', 'launchd')] __execs__ = ['/bin/launchctl', '/usr/bin/defaults'] name = 'launchd' - __req__ = {'Service':['name', 'status']} + __req__ = {'Service': ['name', 'status']} ''' Currently requires the path to the plist to load/unload, and Name is acually a reverse-fqdn (or the label). ''' + def __init__(self, logger, setup, config): Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) @@ -77,7 +80,6 @@ class launchd(Bcfg2.Client.Tools.Tool): return 'on' return False - def InstallService(self, entry): """Enable or disable launchd item.""" name = entry.get('name') @@ -95,18 +97,19 @@ class launchd(Bcfg2.Client.Tools.Tool): """Remove Extra launchd entries.""" pass - - def FindExtra(self): """Find Extra launchd services.""" try: - allsrv = self.cmd.run("/bin/launchctl list")[1] + allsrv = self.cmd.run("/bin/launchctl list")[1] except IndexError: allsrv = [] [allsrv.remove(svc) for svc in [entry.get("name") for entry in self.getSupportedEntries()] if svc in allsrv] - return [Bcfg2.Client.XML.Element("Service", type='launchd', name=name, status='on') for name in allsrv] + return [Bcfg2.Client.XML.Element("Service", + type='launchd', + name=name, + status='on') for name in allsrv] def BundleUpdated(self, bundle, states): """Reload launchd plist.""" @@ -126,4 +129,3 @@ class launchd(Bcfg2.Client.Tools.Tool): #only if necessary.... self.cmd.run("/bin/launchctl stop %s" % name) self.cmd.run("/bin/launchctl unload -w %s" % (self.FindPlist(entry))) - -- cgit v1.2.3-1-g7c22 From eda7e602795e6f3302529105a7c8be217acd907f Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Mon, 18 Oct 2010 11:11:18 -0500 Subject: web_reports: remove web_prefix, add wsgi handler --- src/lib/Server/Reports/reports.wsgi | 4 ++++ src/lib/Server/Reports/urls.py | 26 +++++++++----------------- 2 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 src/lib/Server/Reports/reports.wsgi diff --git a/src/lib/Server/Reports/reports.wsgi b/src/lib/Server/Reports/reports.wsgi new file mode 100644 index 000000000..232650485 --- /dev/null +++ b/src/lib/Server/Reports/reports.wsgi @@ -0,0 +1,4 @@ +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.Server.Reports.settings' +import django.core.handlers.wsgi +application = django.core.handlers.wsgi.WSGIHandler() diff --git a/src/lib/Server/Reports/urls.py b/src/lib/Server/Reports/urls.py index 5d298c974..85bad72fb 100644 --- a/src/lib/Server/Reports/urls.py +++ b/src/lib/Server/Reports/urls.py @@ -3,24 +3,16 @@ from django.http import HttpResponsePermanentRedirect handler500 = 'Bcfg2.Server.Reports.reports.views.server_error' -from ConfigParser import ConfigParser, NoSectionError, NoOptionError -c = ConfigParser() -c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']) - -# web_prefix should have a trailing slash, but no leading slash -# e.g. web_prefix = bcfg2/ -# web_prefix_root is a workaround for the index -if c.has_option('statistics', 'web_prefix'): - web_prefix = c.get('statistics', 'web_prefix').lstrip('/') -else: - web_prefix = '' +#from ConfigParser import ConfigParser, NoSectionError, NoOptionError +#c = ConfigParser() +#c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']) urlpatterns = patterns('', - (r'^%s' % web_prefix, include('Bcfg2.Server.Reports.reports.urls')) + (r'^', include('Bcfg2.Server.Reports.reports.urls')) ) -urlpatterns += patterns("django.views", - url(r"media/(?P.*)$", "static.serve", { - "document_root": '/Users/tlaszlo/svn/bcfg2/reports/site_media/', - }) -) +#urlpatterns += patterns("django.views", +# url(r"media/(?P.*)$", "static.serve", { +# "document_root": '/Users/tlaszlo/svn/bcfg2/reports/site_media/', +# }) +#) -- cgit v1.2.3-1-g7c22 From e19cb1b654b393396d0c6f94691cb97e281a3408 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Mon, 18 Oct 2010 11:13:46 -0500 Subject: web_reports: remove comments --- src/lib/Server/Reports/urls.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/Server/Reports/urls.py b/src/lib/Server/Reports/urls.py index 85bad72fb..d7ff1eee5 100644 --- a/src/lib/Server/Reports/urls.py +++ b/src/lib/Server/Reports/urls.py @@ -3,10 +3,6 @@ from django.http import HttpResponsePermanentRedirect handler500 = 'Bcfg2.Server.Reports.reports.views.server_error' -#from ConfigParser import ConfigParser, NoSectionError, NoOptionError -#c = ConfigParser() -#c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']) - urlpatterns = patterns('', (r'^', include('Bcfg2.Server.Reports.reports.urls')) ) -- cgit v1.2.3-1-g7c22 From 16807d819b63384a0f588b4ea881c7b043571875 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Mon, 18 Oct 2010 11:34:24 -0500 Subject: web_reports: use PATH_INFO instead of path in the resolver. fixes prefix problems. --- src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py index 2c27aab04..8285915bd 100644 --- a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py +++ b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py @@ -18,7 +18,7 @@ def page_navigator(context): """ fragment = dict() try: - path = context['request'].path + path = context['request'].META['PATH_INFO'] total_pages = int(context['total_pages']) records_per_page = int(context['records_per_page']) except KeyError, e: @@ -96,7 +96,7 @@ def page_navigator(context): @register.inclusion_tag('widgets/filter_bar.html', takes_context=True) def filter_navigator(context): try: - path = context['request'].path + path = context['request'].META['PATH_INFO'] view, args, kwargs = resolve(path) # Strip any page limits and numbers @@ -179,7 +179,7 @@ class AddUrlFilter(template.Node): def render(self, context): link = '#' try: - path = context['request'].path + path = context['request'].META['PATH_INFO'] view, args, kwargs = resolve(path) filter_value = self.filter_value.resolve(context, True) if filter_value: -- cgit v1.2.3-1-g7c22 From 8a70dbabd08308403ad8296979f50c379c707df9 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Mon, 18 Oct 2010 11:43:37 -0500 Subject: POSIX: Remove client-side support for old POSIX types Signed-off-by: Sol Jerome --- schemas/rules.xsd | 26 -- src/lib/Client/Frame.py | 14 - src/lib/Client/Tools/APT.py | 2 +- src/lib/Client/Tools/POSIX.py | 636 ++++++++++++++++++---------------- src/lib/Client/Tools/YUMng.py | 2 +- src/lib/Client/Tools/__init__.py | 3 +- src/lib/Server/Admin/Bundle.py | 2 +- src/lib/Server/Plugins/POSIXCompat.py | 38 -- src/lib/Server/Plugins/__init__.py | 1 - src/sbin/bcfg2-repo-validate | 41 ++- 10 files changed, 355 insertions(+), 410 deletions(-) delete mode 100644 src/lib/Server/Plugins/POSIXCompat.py diff --git a/schemas/rules.xsd b/schemas/rules.xsd index 207eb65e5..80036834a 100644 --- a/schemas/rules.xsd +++ b/schemas/rules.xsd @@ -54,14 +54,6 @@ - - - - - - - - @@ -70,11 +62,6 @@ - - - - - @@ -89,21 +76,11 @@ - - - - - - - - - - @@ -117,11 +94,8 @@ - - - diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py index 6cfb19732..545d4b584 100644 --- a/src/lib/Client/Frame.py +++ b/src/lib/Client/Frame.py @@ -110,20 +110,6 @@ class Frame: self.logger.info("Loaded tool drivers:") self.logger.info([tool.name for tool in self.tools]) if not self.dryrun and not self.setup['bundle']: - for cfile in [cfl for cfl in config.findall(".//ConfigFile") \ - if cfl.get('name') in self.__important__]: - tl = [t for t in self.tools if t.handlesEntry(cfile) \ - and t.canVerify(cfile)] - if tl: - if not tl[0].VerifyConfigFile(cfile, []): - if self.setup['interactive'] and not \ - promptFilter("Install %s: %s? (y/N):", [cfile]): - continue - try: - self.states[cfile] = tl[0].InstallConfigFile(cfile) - except: - self.logger.error("Unexpected tool failure", - exc_info=1) for cfile in [cfl for cfl in config.findall(".//Path") \ if cfl.get('name') in self.__important__ and \ cfl.get('type') == 'file']: diff --git a/src/lib/Client/Tools/APT.py b/src/lib/Client/Tools/APT.py index 9dc2c5bba..2afe2eab7 100644 --- a/src/lib/Client/Tools/APT.py +++ b/src/lib/Client/Tools/APT.py @@ -55,7 +55,7 @@ class APT(Bcfg2.Client.Tools.Tool): '%s/apt/apt.conf' % etc_path, '%s/dpkg/dpkg.cfg' % etc_path] + \ [entry.get('name') for struct in config for entry in struct \ - if entry.tag in ['Path', 'ConfigFile'] and \ + if entry.tag == 'Path' and \ entry.get('name').startswith('%s/apt/sources.list' % etc_path)] self.nonexistent = [entry.get('name') for struct in config for entry in struct \ if entry.tag == 'Path' and entry.get('type') == 'nonexistent'] diff --git a/src/lib/Client/Tools/POSIX.py b/src/lib/Client/Tools/POSIX.py index a4417297a..d2611130c 100644 --- a/src/lib/Client/Tools/POSIX.py +++ b/src/lib/Client/Tools/POSIX.py @@ -1,11 +1,11 @@ """All POSIX Type client support for Bcfg2.""" __revision__ = '$Revision$' -from datetime import datetime from stat import S_ISVTX, S_ISGID, S_ISUID, S_IXUSR, S_IWUSR, S_IRUSR, S_IXGRP from stat import S_IWGRP, S_IRGRP, S_IXOTH, S_IWOTH, S_IROTH, ST_MODE, S_ISDIR from stat import S_IFREG, ST_UID, ST_GID, S_ISREG, S_IFDIR, S_ISLNK, ST_MTIME import binascii +from datetime import datetime import difflib import errno import grp @@ -14,7 +14,6 @@ import os import pwd import shutil import stat -import string import time import Bcfg2.Client.Tools import Bcfg2.Options @@ -45,21 +44,6 @@ def calcPerms(initial, perms): return tempperms -def normUid(entry): - """ - This takes a user name or uid and - returns the corresponding uid or False. - """ - try: - try: - return int(entry.get('owner')) - except: - return int(pwd.getpwnam(entry.get('owner'))[2]) - except (OSError, KeyError): - log.error('UID normalization failed for %s' % (entry.get('name'))) - return False - - def normGid(entry): """ This takes a group name or gid and @@ -74,40 +58,33 @@ def normGid(entry): log.error('GID normalization failed for %s' % (entry.get('name'))) return False -text_chars = "".join([chr(y) for y in range(32, 127)] + list("\n\r\t\b")) -notrans = string.maketrans("", "") - -def isString(strng): - """Returns true if a string contains no binary chars.""" - if "\0" in strng: +def normUid(entry): + """ + This takes a user name or uid and + returns the corresponding uid or False. + """ + try: + try: + return int(entry.get('owner')) + except: + return int(pwd.getpwnam(entry.get('owner'))[2]) + except (OSError, KeyError): + log.error('UID normalization failed for %s' % (entry.get('name'))) return False - if not strng: - return True - - return len(strng.translate(notrans, text_chars)) == 0 - class POSIX(Bcfg2.Client.Tools.Tool): """POSIX File support code.""" name = 'POSIX' - __handles__ = [('ConfigFile', None), - ('Directory', None), - ('Path', 'device'), + __handles__ = [('Path', 'device'), ('Path', 'directory'), ('Path', 'file'), ('Path', 'hardlink'), ('Path', 'nonexistent'), ('Path', 'permissions'), - ('Path', 'symlink'), - ('Permissions', None), - ('SymLink', None)] - __req__ = {'ConfigFile': ['name', 'owner', 'group', 'perms'], - 'Directory': ['name', 'owner', 'group', 'perms'], - 'Path': ['name', 'type'], - 'Permissions': ['name', 'owner', 'group', 'perms'], - 'SymLink': ['name', 'to']} + ('Path', 'symlink')] + __req__ = {'Path': ['name', 'type']} # grab paranoid options from /etc/bcfg2.conf opts = {'ppath': Bcfg2.Options.PARANOID_PATH, @@ -120,64 +97,150 @@ class POSIX(Bcfg2.Client.Tools.Tool): def canInstall(self, entry): """Check if entry is complete for installation.""" if Bcfg2.Client.Tools.Tool.canInstall(self, entry): - if (entry.tag, entry.text, entry.get('empty', 'false')) == \ - ('ConfigFile', None, 'false'): + if (entry.tag, + entry.get('type'), + entry.text, + entry.get('empty', 'false')) == ('Path', + 'file', + None, + 'false'): return False return True else: return False - def VerifySymLink(self, entry, _): - """Verify SymLink Entry.""" - try: - sloc = os.readlink(entry.get('name')) - if sloc == entry.get('to'): - return True - self.logger.debug("Symlink %s points to %s, should be %s" % \ - (entry.get('name'), sloc, entry.get('to'))) - entry.set('current_to', sloc) - entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), - entry.get('to'))) + def gatherCurrentData(self, entry): + if entry.tag == 'Path' and entry.get('type') == 'file': + try: + ondisk = os.stat(entry.get('name')) + except OSError: + entry.set('current_exists', 'false') + self.logger.debug("%s %s does not exist" % + (entry.tag, entry.get('name'))) + return False + try: + entry.set('current_owner', str(ondisk[ST_UID])) + entry.set('current_group', str(ondisk[ST_GID])) + except (OSError, KeyError): + pass + entry.set('perms', str(oct(ondisk[ST_MODE])[-4:])) + try: + content = open(entry.get('name')).read() + entry.set('current_bfile', binascii.b2a_base64(content)) + except IOError, error: + self.logger.error("Failed to read %s: %s" % (error.filename, + error.strerror)) + + def Verifydevice(self, entry, _): + """Verify device entry.""" + if entry.get('dev_type') == None or \ + entry.get('owner') == None or \ + entry.get('group') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) return False + if entry.get('dev_type') in ['block', 'char']: + # check if major/minor are properly specified + if entry.get('major') == None or \ + entry.get('minor') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + try: + # check for file existence + filestat = os.stat(entry.get('name')) except OSError: entry.set('current_exists', 'false') - entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), - entry.get('to'))) + self.logger.debug("%s %s does not exist" % + (entry.tag, entry.get('name'))) return False - def InstallSymLink(self, entry): - """Install SymLink entry.""" - self.logger.info("Installing Symlink %s" % (entry.get('name'))) - if os.path.lexists(entry.get('name')): - try: - fmode = os.lstat(entry.get('name'))[ST_MODE] - if S_ISREG(fmode) or S_ISLNK(fmode): - self.logger.debug("Non-directory entry already exists at " - "%s. Unlinking entry." % \ - (entry.get('name'))) - os.unlink(entry.get('name')) - elif S_ISDIR(fmode): - self.logger.debug("Directory entry already exists at %s" %\ - (entry.get('name'))) - self.cmd.run("mv %s/ %s.bak" % \ - (entry.get('name'), - entry.get('name'))) - else: - os.unlink(entry.get('name')) - except OSError: - self.logger.info("Symlink %s cleanup failed" %\ - (entry.get('name'))) try: - os.symlink(entry.get('to'), entry.get('name')) - return True + # attempt to verify device properties as specified in config + dev_type = entry.get('dev_type') + mode = calcPerms(device_map[dev_type], + entry.get('mode', '0600')) + owner = normUid(entry) + group = normGid(entry) + if dev_type in ['block', 'char']: + # check for incompletely specified entries + if entry.get('major') == None or \ + entry.get('minor') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + major = int(entry.get('major')) + minor = int(entry.get('minor')) + if major == os.major(filestat.st_rdev) and \ + minor == os.minor(filestat.st_rdev) and \ + mode == filestat.st_mode and \ + owner == filestat.st_uid and \ + group == filestat.st_gid: + return True + else: + return False + elif dev_type == 'fifo' and \ + mode == filestat.st_mode and \ + owner == filestat.st_uid and \ + group == filestat.st_gid: + return True + else: + self.logger.info('Device properties for %s incorrect' % \ + entry.get('name')) + return False except OSError: + self.logger.debug("%s %s failed to verify" % + (entry.tag, entry.get('name'))) return False - def VerifyDirectory(self, entry, modlist): - """Verify Directory entry.""" + def Installdevice(self, entry): + """Install device entries.""" + try: + # check for existing paths and remove them + os.lstat(entry.get('name')) + try: + os.unlink(entry.get('name')) + exists = False + except OSError: + self.logger.info('Failed to unlink %s' % \ + entry.get('name')) + return False + except OSError: + exists = False + + if not exists: + try: + dev_type = entry.get('dev_type') + mode = calcPerms(device_map[dev_type], + entry.get('mode', '0600')) + if dev_type in ['block', 'char']: + # check if major/minor are properly specified + if entry.get('major') == None or \ + entry.get('minor') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + major = int(entry.get('major')) + minor = int(entry.get('minor')) + device = os.makedev(major, minor) + os.mknod(entry.get('name'), mode, device) + else: + os.mknod(entry.get('name'), mode) + os.chown(entry.get('name'), normUid(entry), normGid(entry)) + return True + except KeyError: + self.logger.error('Failed to install %s' % entry.get('name')) + except OSError: + self.logger.error('Failed to install %s' % entry.get('name')) + return False + + def Verifydirectory(self, entry, modlist): + """Verify Path type='directory' entry.""" if entry.get('perms') == None or \ entry.get('owner') == None or \ entry.get('group') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) return False while len(entry.get('perms', '')) < 4: entry.set('perms', '0' + entry.get('perms', '')) @@ -210,9 +273,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): pruneTrue = True ex_ents = [] if entry.get('prune', 'false') == 'true' \ - and (entry.tag == 'Directory' or - entry.get('type') == 'directory'): - # FIXME: need to verify both old and new POSIX types + and (entry.tag == 'Path' and entry.get('type') == 'directory'): + # check for any extra entries when prune='true' attribute is set try: entries = ['/'.join([entry.get('name'), ent]) \ for ent in os.listdir(entry.get('name'))] @@ -227,7 +289,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.get('name') nqtext += ":".join(ex_ents) entry.set('qtest', nqtext) - [entry.append(XML.Element('Prune', path=x)) for x in ex_ents] + [entry.append(XML.Element('Prune', path=x)) \ + for x in ex_ents] except OSError: ex_ents = [] pruneTrue = True @@ -243,7 +306,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.set('qtext', nqtext) if group != str(normGid(entry)): entry.set('current_group', group) - self.logger.debug("%s %s group wrong" % (entry.tag, entry.get('name'))) + self.logger.debug("%s %s group wrong" % \ + (entry.tag, entry.get('name'))) nqtext = entry.get('qtext', '') + '\n' nqtext += "%s group is %s should be %s" % \ (entry.get('name'), group, entry.get('group')) @@ -251,10 +315,16 @@ class POSIX(Bcfg2.Client.Tools.Tool): if perms != entry.get('perms'): entry.set('current_perms', perms) self.logger.debug("%s %s permissions are %s should be %s" % - (entry.tag, entry.get('name'), perms, entry.get('perms'))) + (entry.tag, + entry.get('name'), + perms, + entry.get('perms'))) nqtext = entry.get('qtext', '') + '\n' - nqtext += "%s perms are %s should be %s" % \ - (entry.get('name'), perms, entry.get('perms')) + nqtext += "%s %s perms are %s should be %s" % \ + (entry.tag, + entry.get('name'), + perms, + entry.get('perms')) entry.set('qtext', nqtext) if mtime != entry.get('mtime', '-1'): entry.set('current_mtime', mtime) @@ -265,21 +335,23 @@ class POSIX(Bcfg2.Client.Tools.Tool): nqtext += "%s mtime is %s should be %s" % \ (entry.get('name'), mtime, entry.get('mtime')) entry.set('qtext', nqtext) - if entry.tag != 'ConfigFile': + if entry.get('type') != 'file': nnqtext = entry.get('qtext') - nnqtext += '\nInstall %s %s: (y/N) ' % (entry.tag, entry.get('name')) + nnqtext += '\nInstall %s %s: (y/N) ' % (entry.get('type'), + entry.get('name')) entry.set('qtext', nnqtext) return pTrue and pruneTrue - def InstallDirectory(self, entry): - """Install Directory entry.""" + def Installdirectory(self, entry): + """Install Path type='directory' entry.""" if entry.get('perms') == None or \ entry.get('owner') == None or \ entry.get('group') == None: self.logger.error('Entry %s not completely specified. ' - 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) return False - self.logger.info("Installing Directory %s" % (entry.get('name'))) + self.logger.info("Installing directory %s" % (entry.get('name'))) try: fmode = os.lstat(entry.get('name')) if not S_ISDIR(fmode[ST_MODE]): @@ -289,7 +361,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): os.unlink(entry.get('name')) exists = False except OSError: - self.logger.info("Failed to unlink %s" % (entry.get('name'))) + self.logger.info("Failed to unlink %s" % \ + (entry.get('name'))) return False else: self.logger.debug("Found a pre-existing directory at %s" % \ @@ -334,7 +407,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): pname = pent.get('path') ulfailed = False if os.path.isdir(pname): - self.logger.info("Not removing extra directory %s, please check and remove manually" % pname) + self.logger.info("Not removing extra directory %s, " + "please check and remove manually" % pname) continue try: self.logger.debug("Unlinking file %s" % pname) @@ -344,188 +418,12 @@ class POSIX(Bcfg2.Client.Tools.Tool): ulfailed = True if ulfailed: return False - return self.InstallPermissions(entry) - - def VerifyhardLink(self, entry, _): - """Verify HardLink entry.""" - try: - if os.path.samefile(entry.get('name'), entry.get('to')): - return True - self.logger.debug("Hardlink %s is incorrect" % \ - entry.get('name')) - entry.set('qtext', "Link %s to %s? [y/N] " % \ - (entry.get('name'), - entry.get('to'))) - return False - except OSError: - entry.set('current_exists', 'false') - entry.set('qtext', "Link %s to %s? [y/N] " % \ - (entry.get('name'), - entry.get('to'))) - return False - - def InstallhardLink(self, entry): - """Install HardLink entry.""" - self.logger.info("Installing Hardlink %s" % (entry.get('name'))) - if os.path.lexists(entry.get('name')): - try: - fmode = os.lstat(entry.get('name'))[ST_MODE] - if S_ISREG(fmode) or S_ISLNK(fmode): - self.logger.debug("Non-directory entry already exists at " - "%s. Unlinking entry." % (entry.get('name'))) - os.unlink(entry.get('name')) - elif S_ISDIR(fmode): - self.logger.debug("Directory entry already exists at %s" % \ - (entry.get('name'))) - self.cmd.run("mv %s/ %s.bak" % \ - (entry.get('name'), - entry.get('name'))) - else: - os.unlink(entry.get('name')) - except OSError: - self.logger.info("Hardlink %s cleanup failed" % (entry.get('name'))) - try: - os.link(entry.get('to'), entry.get('name')) - return True - except OSError: - return False - - def VerifyPermissions(self, entry, _): - """Verify Permissions entry""" - return self.VerifyDirectory(entry, _) - - def InstallPermissions(self, entry): - """Install POSIX permissions""" - if entry.get('perms') == None or \ - entry.get('owner') == None or \ - entry.get('group') == None: - self.logger.error('Entry %s not completely specified. ' - 'Try running bcfg2-repo-validate.' % (entry.get('name'))) - return False - try: - os.chown(entry.get('name'), normUid(entry), normGid(entry)) - os.chmod(entry.get('name'), calcPerms(S_IFDIR, entry.get('perms'))) - return True - except (OSError, KeyError): - self.logger.error('Permission fixup failed for %s' % \ - (entry.get('name'))) - return False - - def Verifydevice(self, entry, _): - """Verify device entry.""" - try: - # check for file existence - filestat = os.stat(entry.get('name')) - except OSError: - entry.set('current_exists', 'false') - self.logger.debug("%s %s does not exist" % - (entry.tag, entry.get('name'))) - return False - - try: - # attempt to verify device properties as specified in config - dev_type = entry.get('dev_type') - mode = calcPerms(device_map[dev_type], - entry.get('mode', '0600')) - owner = entry.get('owner') - group = entry.get('group') - if dev_type in ['block', 'char']: - major = int(entry.get('major')) - minor = int(entry.get('minor')) - if major == os.major(filestat.st_rdev) and \ - minor == os.minor(filestat.st_rdev) and \ - mode == filestat.st_mode and \ - owner == filestat.st_uid and \ - group == filestat.st_gid: - return True - else: - return False - elif dev_type == 'fifo' and \ - mode == filestat.st_mode and \ - owner == filestat.st_uid and \ - group == filestat.st_gid: - return True - else: - self.logger.info('Device properties for %s incorrect' % \ - entry.get('name')) - return False - except OSError: - self.logger.debug("%s %s failed to verify" % - (entry.tag, entry.get('name'))) - return False - - def Installdevice(self, entry): - """Install device entries.""" - try: - # check for existing paths and remove them - filestat = os.lstat(entry.get('name')) - try: - os.unlink(entry.get('name')) - exists = False - except OSError: - self.logger.info('Failed to unlink %s' % \ - entry.get('name')) - return False - except OSError: - exists = False - - if not exists: - try: - dev_type = entry.get('dev_type') - mode = calcPerms(device_map[dev_type], - entry.get('mode', '0600')) - if dev_type in ['block', 'char']: - major = int(entry.get('major')) - minor = int(entry.get('minor')) - device = os.makedev(major, minor) - os.mknod(entry.get('name'), mode, device) - else: - os.mknod(entry.get('name'), mode) - os.chown(entry.get('name'), normUid(entry), normGid(entry)) - return True - except OSError: - self.logger.error('Failed to install %s' % entry.get('name')) - return False + return self.Installpermissions(entry) - def Verifynonexistent(self, entry, _): - """Verify nonexistent entry.""" - # return true if path does _not_ exist - return not os.path.lexists(entry.get('name')) - - def Installnonexistent(self, entry): - '''Remove nonexistent entries''' - try: - os.remove(entry.get('name')) - return True - except OSError: - self.logger.error('Failed to remove %s' % entry.get('name')) - return False - - def gatherCurrentData(self, entry): - if entry.tag == 'ConfigFile': - try: - ondisk = os.stat(entry.get('name')) - except OSError: - entry.set('current_exists', 'false') - self.logger.debug("%s %s does not exist" % - (entry.tag, entry.get('name'))) - return False - try: - entry.set('current_owner', str(ondisk[ST_UID])) - entry.set('current_group', str(ondisk[ST_GID])) - except (OSError, KeyError): - pass - entry.set('perms', str(oct(ondisk[ST_MODE])[-4:])) - try: - content = open(entry.get('name')).read() - entry.set('current_bfile', binascii.b2a_base64(content)) - except IOError, error: - self.logger.error("Failed to read %s: %s" % (error.filename, error.strerror)) - - def VerifyConfigFile(self, entry, _): - """Install ConfigFile entry.""" - # configfile verify is permissions check + content check - permissionStatus = self.VerifyDirectory(entry, _) + def Verifyfile(self, entry, _): + """Verify Path type='file' entry.""" + # permissions check + content check + permissionStatus = self.Verifydirectory(entry, _) tbin = False if entry.get('encoding', 'ascii') == 'base64': tempdata = binascii.a2b_base64(entry.text) @@ -534,8 +432,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): tempdata = '' else: if entry.text == None: - self.logger.error("Cannot verify incomplete ConfigFile %s" % \ - (entry.get('name'))) + self.logger.error("Cannot verify incomplete Path type='%s' %s" % \ + (entry.get('type'), entry.get('name'))) return False tempdata = entry.text if type(tempdata) == unicode: @@ -558,7 +456,12 @@ class POSIX(Bcfg2.Client.Tools.Tool): # md5sum so it would be faster for big binary files contentStatus = content == tempdata if not contentStatus: - if tbin or not isString(content): + try: + content.decode('ascii') + isstring = True + except: + isstring = False + if tbin or not isstring: entry.set('current_bfile', binascii.b2a_base64(content)) nqtext = entry.get('qtext', '') nqtext += '\nBinary file, no printable diff' @@ -567,7 +470,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): rawdiff = [] start = time.time() longtime = False - for x in difflib.ndiff(content.split('\n'), tempdata.split('\n')): + for x in difflib.ndiff(content.split('\n'), + tempdata.split('\n')): now = time.time() rawdiff.append(x) if now - start > 5 and not longtime: @@ -606,9 +510,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.set('qtext', qtxt) return contentStatus and permissionStatus - def InstallConfigFile(self, entry): - """Install ConfigFile entry.""" - self.logger.info("Installing ConfigFile %s" % (entry.get('name'))) + def Installfile(self, entry): + """Install Path type='file' entry.""" + self.logger.info("Installing file %s" % (entry.get('name'))) parent = "/".join(entry.get('name').split('/')[:-1]) if parent: @@ -642,9 +546,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): self.setup.get("paranoid", False) and not \ (entry.get('current_exists', 'true') == 'false'): bkupnam = entry.get('name').replace('/', '_') - # current list of backups for this ConfigFile + # current list of backups for this file bkuplist = [f for f in os.listdir(self.ppath) if - f.startswith(bkupnam)] + f.startswith(bkupnam)] bkuplist.sort() if len(bkuplist) == int(self.max_copies): # remove the oldest backup available @@ -663,7 +567,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): self.logger.info("Backup of %s saved to %s" % (entry.get('name'), self.ppath)) except IOError, e: - self.logger.error("Failed to create backup file for ConfigFile %s" % \ + self.logger.error("Failed to create backup file for %s" % \ (entry.get('name'))) self.logger.error(e) return False @@ -694,7 +598,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): os.utime(entry.get('name'), (int(entry.get('mtime')), int(entry.get('mtime')))) except: - self.logger.error("ConfigFile %s mtime fix failed" \ + self.logger.error("File %s mtime fix failed" \ % (entry.get('name'))) return False return True @@ -705,42 +609,158 @@ class POSIX(Bcfg2.Client.Tools.Tool): print(err) return False - def Verifydirectory(self, entry, _): - ret = getattr(self, 'VerifyDirectory') - return ret(entry, _) + def Verifyhardlink(self, entry, _): + """Verify HardLink entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + try: + if os.path.samefile(entry.get('name'), entry.get('to')): + return True + self.logger.debug("Hardlink %s is incorrect" % \ + entry.get('name')) + entry.set('qtext', "Link %s to %s? [y/N] " % \ + (entry.get('name'), + entry.get('to'))) + return False + except OSError: + entry.set('current_exists', 'false') + entry.set('qtext', "Link %s to %s? [y/N] " % \ + (entry.get('name'), + entry.get('to'))) + return False - def Installdirectory(self, entry): - ret = getattr(self, 'InstallDirectory') - return ret(entry) + def Installhardlink(self, entry): + """Install HardLink entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + self.logger.info("Installing Hardlink %s" % (entry.get('name'))) + if os.path.lexists(entry.get('name')): + try: + fmode = os.lstat(entry.get('name'))[ST_MODE] + if S_ISREG(fmode) or S_ISLNK(fmode): + self.logger.debug("Non-directory entry already exists at " + "%s. Unlinking entry." % (entry.get('name'))) + os.unlink(entry.get('name')) + elif S_ISDIR(fmode): + self.logger.debug("Directory already exists at %s" % \ + (entry.get('name'))) + self.cmd.run("mv %s/ %s.bak" % \ + (entry.get('name'), + entry.get('name'))) + else: + os.unlink(entry.get('name')) + except OSError: + self.logger.info("Hardlink %s cleanup failed" % \ + (entry.get('name'))) + try: + os.link(entry.get('to'), entry.get('name')) + return True + except OSError: + return False - def Verifyfile(self, entry, _): - ret = getattr(self, 'VerifyConfigFile') - return ret(entry, _) + def Verifynonexistent(self, entry, _): + """Verify nonexistent entry.""" + # return true if path does _not_ exist + return not os.path.lexists(entry.get('name')) - def Installfile(self, entry): - ret = getattr(self, 'InstallConfigFile') - return ret(entry) + def Installnonexistent(self, entry): + '''Remove nonexistent entries''' + try: + os.remove(entry.get('name')) + return True + except OSError: + self.logger.error('Failed to remove %s' % entry.get('name')) + return False def Verifypermissions(self, entry, _): - ret = getattr(self, 'VerifyPermissions') - return ret(entry, _) + """Verify Path type='permissions' entry""" + return self.Verifydirectory(entry, _) def Installpermissions(self, entry): - ret = getattr(self, 'InstallPermissions') - return ret(entry) + """Install POSIX permissions""" + if entry.get('perms') == None or \ + entry.get('owner') == None or \ + entry.get('group') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + try: + os.chown(entry.get('name'), normUid(entry), normGid(entry)) + os.chmod(entry.get('name'), calcPerms(S_IFDIR, entry.get('perms'))) + return True + except (OSError, KeyError): + self.logger.error('Permission fixup failed for %s' % \ + (entry.get('name'))) + return False def Verifysymlink(self, entry, _): - ret = getattr(self, 'VerifySymLink') - return ret(entry, _) + """Verify Path type='symlink' entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + try: + sloc = os.readlink(entry.get('name')) + if sloc == entry.get('to'): + return True + self.logger.debug("Symlink %s points to %s, should be %s" % \ + (entry.get('name'), sloc, entry.get('to'))) + entry.set('current_to', sloc) + entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), + entry.get('to'))) + return False + except OSError: + entry.set('current_exists', 'false') + entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), + entry.get('to'))) + return False def Installsymlink(self, entry): - ret = getattr(self, 'InstallSymLink') - return ret(entry) + """Install Path type='symlink' entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + self.logger.info("Installing symlink %s" % (entry.get('name'))) + if os.path.lexists(entry.get('name')): + try: + fmode = os.lstat(entry.get('name'))[ST_MODE] + if S_ISREG(fmode) or S_ISLNK(fmode): + self.logger.debug("Non-directory entry already exists at " + "%s. Unlinking entry." % \ + (entry.get('name'))) + os.unlink(entry.get('name')) + elif S_ISDIR(fmode): + self.logger.debug("Directory already exists at %s" %\ + (entry.get('name'))) + self.cmd.run("mv %s/ %s.bak" % \ + (entry.get('name'), + entry.get('name'))) + else: + os.unlink(entry.get('name')) + except OSError: + self.logger.info("Symlink %s cleanup failed" %\ + (entry.get('name'))) + try: + os.symlink(entry.get('to'), entry.get('name')) + return True + except OSError: + return False def InstallPath(self, entry): + """Dispatch install to the proper method according to type""" ret = getattr(self, 'Install%s' % entry.get('type')) return ret(entry) def VerifyPath(self, entry, _): + """Dispatch verify to the proper method according to type""" ret = getattr(self, 'Verify%s' % entry.get('type')) return ret(entry, _) diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Client/Tools/YUMng.py index 9fc776471..ce8832395 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Client/Tools/YUMng.py @@ -157,7 +157,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): self.__important__ = self.__important__ + \ [entry.get('name') for struct in config \ for entry in struct \ - if entry.tag in ['Path', 'ConfigFile'] and \ + if entry.tag in == 'Path' and \ (entry.get('name').startswith('/etc/yum.d') \ or entry.get('name').startswith('/etc/yum.repos.d')) \ or entry.get('name') == '/etc/yum.conf'] diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py index 1d89e6d02..8a90e130c 100644 --- a/src/lib/Client/Tools/__init__.py +++ b/src/lib/Client/Tools/__init__.py @@ -172,8 +172,7 @@ class Tool: '''Build a list of potentially modified POSIX paths for this entry''' return [entry.get('name') for struct in self.config.getchildren() \ for entry in struct.getchildren() \ - if entry.tag in ['ConfigFile', 'SymLink', 'Directory', - 'Permissions', 'Ignore', 'Path']] + if entry.tag in ['Ignore', 'Path']] def gatherCurrentData(self, entry): """Default implementation of the information gathering routines.""" diff --git a/src/lib/Server/Admin/Bundle.py b/src/lib/Server/Admin/Bundle.py index e293c6a4f..41cd5727e 100644 --- a/src/lib/Server/Admin/Bundle.py +++ b/src/lib/Server/Admin/Bundle.py @@ -91,7 +91,7 @@ class Bundle(Bcfg2.Server.Admin.MetadataCore): tree = lxml.etree.parse(bundle_list[int(lineno)]) #Prints bundle content #print lxml.etree.tostring(tree) - names = ['Action', 'ConfigFile', 'Directory', 'Package', 'Permission', 'Service', 'SymLink'] + names = ['Action', 'Package', 'Path', 'Service'] for name in names: for node in tree.findall("//" + name): print "%s:\t%s" % (name, node.attrib["name"]) diff --git a/src/lib/Server/Plugins/POSIXCompat.py b/src/lib/Server/Plugins/POSIXCompat.py deleted file mode 100644 index fc16e4b48..000000000 --- a/src/lib/Server/Plugins/POSIXCompat.py +++ /dev/null @@ -1,38 +0,0 @@ -"""This plugin provides a compatibility layer which turns new-style -POSIX entries into old-style entries. -""" - -__revision__ = '$Revision$' - -import Bcfg2.Server.Plugin - -COMPAT_DICT = {'file': 'ConfigFile', - 'directory': 'Directory', - 'permissions': 'Permissions', - 'symlink': 'SymLink'} - - -class POSIXCompat(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.GoalValidator): - """POSIXCompat is a goal validator plugin for POSIX entries.""" - name = 'POSIXCompat' - __version__ = '$Id$' - __author__ = 'bcfg-dev@mcs.anl.gov' - - def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.GoalValidator.__init__(self) - - def validate_goals(self, metadata, goals): - """Verify that we are generating correct old - Cfg/Directory/Symlink entries. - """ - for goal in goals: - for entry in goal.getchildren(): - if entry.tag == 'Path' and \ - entry.get('type') not in ['nonexistent', 'device']: - # Use new entry 'type' attribute to map old entry tags - oldentry = entry - entry.tag = COMPAT_DICT[entry.get('type')] - del entry.attrib['type'] - goal.replace(oldentry, entry) diff --git a/src/lib/Server/Plugins/__init__.py b/src/lib/Server/Plugins/__init__.py index 758f1219d..c69c37452 100644 --- a/src/lib/Server/Plugins/__init__.py +++ b/src/lib/Server/Plugins/__init__.py @@ -22,7 +22,6 @@ __all__ = [ 'Properties', 'Probes', 'Pkgmgr', - 'POSIXCompat', 'Rules', 'SSHbase', 'Snapshots', diff --git a/src/sbin/bcfg2-repo-validate b/src/sbin/bcfg2-repo-validate index 1889c9892..3d5efb093 100755 --- a/src/sbin/bcfg2-repo-validate +++ b/src/sbin/bcfg2-repo-validate @@ -88,14 +88,14 @@ if __name__ == '__main__': # (as defined in doc/server/configurationentries) # TODO: See if it is possible to do this in the schema instead required_configuration_attrs = { - 'device':['name', 'owner', 'group', 'dev_type'], - 'directory':['name', 'owner', 'group', 'perms'], - 'file':['name', 'owner', 'group', 'perms'], - 'hardlink':['name', 'to'], - 'symlink':['name', 'to'], - 'ignore':['name'], - 'nonexist':['name'], - 'permissions':['name', 'owner', 'group', 'perms']} + 'device': ['name', 'owner', 'group', 'dev_type'], + 'directory': ['name', 'owner', 'group', 'perms'], + 'file': ['name', 'owner', 'group', 'perms'], + 'hardlink': ['name', 'to'], + 'symlink': ['name', 'to'], + 'ignore': ['name'], + 'nonexist': ['name'], + 'permissions': ['name', 'owner', 'group', 'perms']} for rfile in rules_list: try: xdata = lxml.etree.parse(rfile) @@ -110,6 +110,11 @@ if __name__ == '__main__': + ['type']) except KeyError: continue + if 'dev_type' in required_attrs: + dev_type = posixpath.get('dev_type') + if dev_type in ['block', 'char']: + # check if major/minor are specified + required_attrs |= set(['major', 'minor']) if pathset.issuperset(required_attrs): continue else: @@ -146,16 +151,16 @@ if __name__ == '__main__': else: pset.add(ptuple) - filesets = {'metadata':(metadata_list, "%s/metadata.xsd"), - 'clients':(clients_list, "%s/clients.xsd"), - 'info':(info_list, "%s/info.xsd"), - 'bundle':(bundle_list, "%s/bundle.xsd"), - 'pkglist':(pkg_list, "%s/pkglist.xsd"), - 'base':(base_list, "%s/base.xsd"), - 'rules':(rules_list, "%s/rules.xsd"), - 'imageinfo':(imageinfo_list, "%s/report-configuration.xsd"), - 'services':(services_list, "%s/services.xsd"), - 'deps':(deps_list, "%s/deps.xsd"), + filesets = {'metadata': (metadata_list, "%s/metadata.xsd"), + 'clients': (clients_list, "%s/clients.xsd"), + 'info': (info_list, "%s/info.xsd"), + 'bundle': (bundle_list, "%s/bundle.xsd"), + 'pkglist': (pkg_list, "%s/pkglist.xsd"), + 'base': (base_list, "%s/base.xsd"), + 'rules': (rules_list, "%s/rules.xsd"), + 'imageinfo': (imageinfo_list, "%s/report-configuration.xsd"), + 'services': (services_list, "%s/services.xsd"), + 'deps': (deps_list, "%s/deps.xsd"), 'decisions': (dec_list, "%s/decisions.xsd"), 'packages': (pkgcfg_list, "%s/packages.xsd"), 'grouppatterns': (gp_list, "%s/grouppatterns.xsd"), -- cgit v1.2.3-1-g7c22 From 785724e84f5ecefcdbda789dfadf7f08e29f1303 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Mon, 18 Oct 2010 12:16:39 -0500 Subject: web_reports: update setup.py to include wsgi file --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a1499e474..6d5aae417 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,8 @@ setup(cmdclass=cmdclass, package_dir = {'Bcfg2':'src/lib'}, package_data = {'Bcfg2.Server.Reports.reports':['fixtures/*.xml', 'templates/*.html', 'templates/*/*.html', - 'templates/*/*.inc' ] }, + 'templates/*/*.inc' ], + 'Bcfg2.Server.Reports':['reports.wsgi'] }, scripts = glob('src/sbin/*'), data_files = [('share/bcfg2/schemas', glob('schemas/*.xsd')), -- cgit v1.2.3-1-g7c22 From 9e9edecaee3e0cdddd3d386c9fe5246b057aa0a6 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Mon, 18 Oct 2010 12:40:54 -0500 Subject: web_reports: more PATH_INFO fixes --- src/lib/Server/Reports/reports/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Server/Reports/reports/views.py index a061a3964..00d35c092 100644 --- a/src/lib/Server/Reports/reports/views.py +++ b/src/lib/Server/Reports/reports/views.py @@ -46,7 +46,7 @@ def timeview(fn): if cal_date.find(' ') > -1: fmt += " %H:%M" timestamp = datetime(*strptime(cal_date, fmt)[0:6]) - view, args, kw = resolve(request.path) + view, args, kw = resolve(request.META['PATH_INFO']) kw['year'] = "%0.4d" % timestamp.year kw['month'] = "%02.d" % timestamp.month kw['day'] = "%02.d" % timestamp.day @@ -367,7 +367,7 @@ def prepare_paginated_list(request, context, paged_list, page=1, max_results=25) if page > total_pages: # If we passed beyond the end send back try: - view, args, kwargs = resolve(request.path) + view, args, kwargs = resolve(request.META['PATH_INFO']) kwargs['page_number'] = total_pages raise PaginationError, HttpResponseRedirect( reverse(view, kwargs=kwargs) ) except (Resolver404, NoReverseMatch, ValueError): -- cgit v1.2.3-1-g7c22 From 770d9daac6b0d8ee47471133a1f64378f0b6c406 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Mon, 18 Oct 2010 12:40:58 -0500 Subject: Reports: Fix typo Signed-off-by: Sol Jerome --- src/lib/Server/Reports/reports/templates/config_items/item.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Server/Reports/reports/templates/config_items/item.html b/src/lib/Server/Reports/reports/templates/config_items/item.html index 41474922b..58aed1684 100644 --- a/src/lib/Server/Reports/reports/templates/config_items/item.html +++ b/src/lib/Server/Reports/reports/templates/config_items/item.html @@ -88,7 +88,7 @@ div.entry_list h3 {
-

Occurances on {{ timestamp|date:"Y-m-d" }}

+

Occurences on {{ timestamp|date:"Y-m-d" }}

{% if associated_list %} -- cgit v1.2.3-1-g7c22 From 1b56006774c2f835e2826f49f26fcb4cbe15414b Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Mon, 18 Oct 2010 13:06:07 -0500 Subject: README: Update URLs to point to bcfg2.org Signed-off-by: Sol Jerome --- README | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README b/README index bac8e30ae..beb0d3303 100644 --- a/README +++ b/README @@ -23,17 +23,17 @@ Installation For details about the installation of Bcfg2 please refer to the following pages in the Bcfg2 wiki. -* Prerequisites: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Prereqs -* Download: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Download -* Installation: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Install +* Prerequisites: http://bcfg2.org/wiki/Prereqs +* Download: http://bcfg2.org/wiki/Download +* Installation: http://bcfg2.org/wiki/Install Need help --------- -* FAQ: http://trac.mcs.anl.gov/projects/bcfg2/wiki/FAQ +* FAQ: http://bcfg2.org/wiki/FAQ * IRC: #bcfg2 on chat.freenode.net * Mailing list: https://lists.mcs.anl.gov/mailman/listinfo/bcfg-dev -* Bug tracker: http://trac.mcs.anl.gov/projects/bcfg2/report +* Bug tracker: http://bcfg2.org/report Documentation ------------- @@ -41,8 +41,8 @@ Documentation A lot of documentation is available in the Bcfg2 wiki and the Bcfg2 manual. -Wiki: http://trac.mcs.anl.gov/projects/bcfg2/wiki/ -Manual: http://doc.bcfg2.fourkitchens.com/ +Wiki: http://bcfg2.org/wiki/ +Manual: http://docs.bcfg2.org/ Bcfg2 is licensed under BSD, for more details check COPYING. -- cgit v1.2.3-1-g7c22 From 9a9d3a56907f2bd4af12e3ea3961aa7b93d8a966 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Mon, 18 Oct 2010 13:33:51 -0500 Subject: YUMng: syntax error --- src/lib/Client/Tools/YUMng.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Client/Tools/YUMng.py index ce8832395..9cdcdca40 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Client/Tools/YUMng.py @@ -157,7 +157,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): self.__important__ = self.__important__ + \ [entry.get('name') for struct in config \ for entry in struct \ - if entry.tag in == 'Path' and \ + if entry.tag == 'Path' and \ (entry.get('name').startswith('/etc/yum.d') \ or entry.get('name').startswith('/etc/yum.repos.d')) \ or entry.get('name') == '/etc/yum.conf'] -- cgit v1.2.3-1-g7c22 From d80879415ba4f2b8697d9e2809f3b9e4f0cb159c Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 19 Oct 2010 11:45:53 -0500 Subject: web_reports: determine static prefix automatically --- misc/bcfg2.spec | 54 +++++++++++++++++++++- src/lib/Server/Reports/reports/templates/base.html | 16 ++++--- .../Reports/reports/templatetags/bcfg2_tags.py | 35 ++++++++++++++ 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec index 72008435a..c38e52c3b 100644 --- a/misc/bcfg2.spec +++ b/misc/bcfg2.spec @@ -13,7 +13,7 @@ %define lxmldep %(rpm -q %{alt_lxml} 2>&1 > /dev/null && echo %{alt_lxml} || echo %{dfl_lxml}) Name: bcfg2 -Version: 1.1.0 +Version: 1.2.0 Release: %{release} Summary: Configuration management system @@ -93,6 +93,39 @@ systems are constantly changing; if required in your environment, Bcfg2 can enable the construction of complex change management and deployment strategies. +%package -n bcfg2-web +Version: %{version} +Summary: Bcfg2 Web Reporting Interface +Group: System Tools +Requires: bcfg2-server +Requires: httpd,mod_wsgi,Django + +%description -n bcfg2-web +Bcfg2 helps system administrators produce a consistent, reproducible, +and verifiable description of their environment, and offers +visualization and reporting tools to aid in day-to-day administrative +tasks. It is the fifth generation of configuration management tools +developed in the Mathematics and Computer Science Division of Argonne +National Laboratory. + +It is based on an operational model in which the specification can be +used to validate and optionally change the state of clients, but in a +feature unique to bcfg2 the client's response to the specification can +also be used to assess the completeness of the specification. Using +this feature, bcfg2 provides an objective measure of how good a job an +administrator has done in specifying the configuration of client +systems. Bcfg2 is therefore built to help administrators construct an +accurate, comprehensive specification. + +Bcfg2 has been designed from the ground up to support gentle +reconciliation between the specification and current client states. It +is designed to gracefully cope with manual system modifications. + +Finally, due to the rapid pace of updates on modern networks, client +systems are constantly changing; if required in your environment, +Bcfg2 can enable the construction of complex change management and +deployment strategies. + %prep %setup -q -n bcfg2-%{version} @@ -117,6 +150,11 @@ deployment strategies. %{__install} -m 755 debian/bcfg2.cron.hourly %{buildroot}%{_sysconfdir}/cron.hourly/bcfg2 %{__install} -m 755 tools/bcfg2-cron %{buildroot}%{_prefix}/lib/bcfg2/bcfg2-cron +%if "%{_vendor}" == "redhat" +%{__install} -d %{buildroot}%{_sysconfdir}/httpd/conf.d +%{__install} -m 644 misc/apache/bcfg2.conf %{buildroot}%{_sysconfdir}/httpd/conf.d/bcfg2.conf +%endif + %clean [ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} || exit 2 @@ -147,7 +185,10 @@ deployment strategies. %{python_sitelib}/*egg-info %endif -%{_datadir}/bcfg2 +%dir %{_datadir}/bcfg2 +%{_datadir}/bcfg2/Hostbase +%{_datadir}/bcfg2/schemas +%{_datadir}/bcfg2/xsl-transforms %config(noreplace) %{_sysconfdir}/default/bcfg2-server %{_sbindir}/bcfg2-admin %{_sbindir}/bcfg2-build-reports @@ -160,6 +201,15 @@ deployment strategies. %{_mandir}/man8/*.8* %dir %{_prefix}/lib/bcfg2 +%files -n bcfg2-web +%defattr(-,root,root,-) + +%{_datadir}/bcfg2/site_media + +%if "%{_vendor}" == "redhat" +%config(noreplace) %{_sysconfdir}/httpd/conf.d/bcfg2.conf +%endif + %changelog * Mon Jun 21 2010 Fabian Affolter - 1.1.0rc3-0.1 - Changed source0 in order that it works with spectool diff --git a/src/lib/Server/Reports/reports/templates/base.html b/src/lib/Server/Reports/reports/templates/base.html index 7a36c9893..9bd9da218 100644 --- a/src/lib/Server/Reports/reports/templates/base.html +++ b/src/lib/Server/Reports/reports/templates/base.html @@ -1,3 +1,5 @@ +{% load bcfg2_tags %} + @@ -10,19 +12,19 @@ - - - - - - + + + + + + {% block extra_header_info %}{% endblock %} diff --git a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py index 8285915bd..7fffe289d 100644 --- a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py +++ b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py @@ -237,3 +237,38 @@ def sortwell(value): configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) return configItems +class MediaTag(template.Node): + def __init__(self, filter_value): + self.filter_value = filter_value + + def render(self, context): + base = context['MEDIA_URL'] + try: + request = context['request'] + try: + base = request.environ['bcfg2.media_url'] + except: + if request.path != request.META['PATH_INFO']: + offset = request.path.find(request.META['PATH_INFO']) + if offset > 0: + base = "%s/%s" % (request.path[:offset], \ + context['MEDIA_URL'].strip('/')) + except: + pass + return "%s/%s" % (base, self.filter_value) + +@register.tag +def to_media_url(parser, token): + """ + Return a url relative to the media_url. + + {% to_media_url /bcfg2.css %} + """ + try: + tag_name, filter_value = token.split_contents() + filter_value = parser.compile_filter(filter_value) + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0] + + return MediaTag(filter_value) + -- cgit v1.2.3-1-g7c22 From 9a981769c88e8478693688087eb0f4b99d0188a6 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 19 Oct 2010 12:00:34 -0500 Subject: web_reports: updated spec to build web reports rpm --- misc/apache/bcfg2.conf | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 misc/apache/bcfg2.conf diff --git a/misc/apache/bcfg2.conf b/misc/apache/bcfg2.conf new file mode 100644 index 000000000..2963091ec --- /dev/null +++ b/misc/apache/bcfg2.conf @@ -0,0 +1,28 @@ +LoadModule wsgi_module modules/mod_wsgi.so + + + # + # If the root is changed update the static content alias as well + # + WSGIScriptAlias /bcfg2 "/usr/lib/python2.4/site-packages/Bcfg2/Server/Reports/reports.wsgi" + + WSGISocketPrefix run + WSGIDaemonProcess Bcfg2.Server.Reports processes=1 threads=10 + WSGIProcessGroup Bcfg2.Server.Reports + + # + # Manually set this to override the static content + # + #SetEnv bcfg2.media_url /bcfg2/site_media/ + + # + # This should have the same prefix as WSGIScriptAlias + # + Alias "/bcfg2/site_media/" "/usr/share/bcfg2/site_media/" + + Options None + AllowOverride None + Order allow,deny + Allow from all + + -- cgit v1.2.3-1-g7c22 From 21ede8dae0e372a797f546ad8c0e4ace72cb9ec5 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 19 Oct 2010 16:06:35 -0500 Subject: web_reports: debian reports package --- debian/bcfg2-server.install | 4 +++- debian/bcfg2-web.install | 2 ++ debian/control | 9 +++++++++ debian/rules | 9 +++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 debian/bcfg2-web.install diff --git a/debian/bcfg2-server.install b/debian/bcfg2-server.install index 859806fa0..91b1b2aef 100644 --- a/debian/bcfg2-server.install +++ b/debian/bcfg2-server.install @@ -1,5 +1,7 @@ debian/bcfg2-server.default usr/share/bcfg2 debian/tmp/usr/bin/bcfg2-* usr/sbin debian/tmp/usr/lib/python*/*-packages/Bcfg2/Server/* -debian/tmp/usr/share/bcfg2/* +debian/tmp/usr/share/bcfg2/Hostbase/* +debian/tmp/usr/share/bcfg2/schemas/* +debian/tmp/usr/share/bcfg2/xsl-transforms/* debian/tmp/usr/share/man/man8/* diff --git a/debian/bcfg2-web.install b/debian/bcfg2-web.install new file mode 100644 index 000000000..bfa55a925 --- /dev/null +++ b/debian/bcfg2-web.install @@ -0,0 +1,2 @@ +misc/apache/bcfg2.conf etc/apache2/conf.d/ +debian/tmp/usr/share/bcfg2/site_media/* diff --git a/debian/control b/debian/control index acfc6cc9c..726958c85 100644 --- a/debian/control +++ b/debian/control @@ -29,3 +29,12 @@ Description: Configuration management server for clients bound by client profiles. bcfg2-server is the server for bcfg2 clients, which generates configuration sets and stores statistics of client system states. + +Package: bcfg2-web +Architecture: all +Depends: ${python:Depends}, ${misc:Depends}, bcfg2 (= ${binary:Version}), python-django, libapache2-mod-wsgi +XB-Python-Version: >= 2.4 +Description: Configuration management web interface + Bcfg2 is a configuration management system that generates configuration sets + for clients bound by client profiles. + bcfg2-web is the reporting server for bcfg2. diff --git a/debian/rules b/debian/rules index 928b3d2d3..928880859 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,7 @@ #!/usr/bin/make -f + +WSGI_LOC = $(shell find debian/bcfg2-server/ -name reports.wsgi | perl -p -e 's|debian/bcfg2-server||') + %: dh --with python-support $@ @@ -14,3 +17,9 @@ override_dh_installinit: dh_installinit --package=bcfg2 --no-start # Install bcfg2-server initscript without starting it on postinst dh_installinit --package=bcfg2-server --no-start + +override_dh_installdeb: + dh_installdeb + perl -pi -e 's/^.*LoadModule.*//' debian/bcfg2-web/etc/apache2/conf.d/bcfg2.conf + perl -pi -e 's|/usr.*/reports.wsgi|$(WSGI_LOC)|' debian/bcfg2-web/etc/apache2/conf.d/bcfg2.conf + -- cgit v1.2.3-1-g7c22 From c3c735f161628b349aafdb1331224350591f11cc Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 21 Oct 2010 09:30:34 -0500 Subject: spec: updated to handle opensuse and web reports --- misc/bcfg2.spec | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec index c38e52c3b..e9798c39f 100644 --- a/misc/bcfg2.spec +++ b/misc/bcfg2.spec @@ -98,7 +98,14 @@ Version: %{version} Summary: Bcfg2 Web Reporting Interface Group: System Tools Requires: bcfg2-server -Requires: httpd,mod_wsgi,Django +Requires: httpd,Django +%if "%{_vendor}" == "redhat" +Requires: mod_wsgi +%define apache_conf %{_sysconfdir}/httpd +%else +Requires: apache2-mod_wsgi +%define apache_conf %{_sysconfdir}/apache2 +%endif %description -n bcfg2-web Bcfg2 helps system administrators produce a consistent, reproducible, @@ -150,10 +157,8 @@ deployment strategies. %{__install} -m 755 debian/bcfg2.cron.hourly %{buildroot}%{_sysconfdir}/cron.hourly/bcfg2 %{__install} -m 755 tools/bcfg2-cron %{buildroot}%{_prefix}/lib/bcfg2/bcfg2-cron -%if "%{_vendor}" == "redhat" -%{__install} -d %{buildroot}%{_sysconfdir}/httpd/conf.d -%{__install} -m 644 misc/apache/bcfg2.conf %{buildroot}%{_sysconfdir}/httpd/conf.d/bcfg2.conf -%endif +%{__install} -d %{buildroot}%{apache_conf}/conf.d +%{__install} -m 644 misc/apache/bcfg2.conf %{buildroot}%{apache_conf}/conf.d/bcfg2.conf %clean [ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} || exit 2 @@ -206,9 +211,7 @@ deployment strategies. %{_datadir}/bcfg2/site_media -%if "%{_vendor}" == "redhat" -%config(noreplace) %{_sysconfdir}/httpd/conf.d/bcfg2.conf -%endif +%config(noreplace) %{apache_conf}/conf.d/bcfg2.conf %changelog * Mon Jun 21 2010 Fabian Affolter - 1.1.0rc3-0.1 -- cgit v1.2.3-1-g7c22 From 4fa804bafe8a39135db76a7dca1917d22a60e1a4 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 21 Oct 2010 12:51:12 -0500 Subject: web_reports: Moved location of reports.wsgi to a more stable location --- debian/bcfg2-web.install | 1 + debian/control | 1 + debian/rules | 5 ----- misc/apache/bcfg2.conf | 4 +--- misc/bcfg2.spec | 5 +++-- reports/reports.wsgi | 4 ++++ setup.py | 4 ++-- src/lib/Server/Reports/reports.wsgi | 4 ---- 8 files changed, 12 insertions(+), 16 deletions(-) create mode 100644 reports/reports.wsgi delete mode 100644 src/lib/Server/Reports/reports.wsgi diff --git a/debian/bcfg2-web.install b/debian/bcfg2-web.install index bfa55a925..68caa98fa 100644 --- a/debian/bcfg2-web.install +++ b/debian/bcfg2-web.install @@ -1,2 +1,3 @@ misc/apache/bcfg2.conf etc/apache2/conf.d/ +debian/tmp/usr/share/bcfg2/reports.wsgi debian/tmp/usr/share/bcfg2/site_media/* diff --git a/debian/control b/debian/control index 726958c85..4db926f77 100644 --- a/debian/control +++ b/debian/control @@ -33,6 +33,7 @@ Description: Configuration management server Package: bcfg2-web Architecture: all Depends: ${python:Depends}, ${misc:Depends}, bcfg2 (= ${binary:Version}), python-django, libapache2-mod-wsgi +Suggests: python-mysqldb, python-psycopg2, python-sqlite XB-Python-Version: >= 2.4 Description: Configuration management web interface Bcfg2 is a configuration management system that generates configuration sets diff --git a/debian/rules b/debian/rules index 928880859..1638b8415 100755 --- a/debian/rules +++ b/debian/rules @@ -18,8 +18,3 @@ override_dh_installinit: # Install bcfg2-server initscript without starting it on postinst dh_installinit --package=bcfg2-server --no-start -override_dh_installdeb: - dh_installdeb - perl -pi -e 's/^.*LoadModule.*//' debian/bcfg2-web/etc/apache2/conf.d/bcfg2.conf - perl -pi -e 's|/usr.*/reports.wsgi|$(WSGI_LOC)|' debian/bcfg2-web/etc/apache2/conf.d/bcfg2.conf - diff --git a/misc/apache/bcfg2.conf b/misc/apache/bcfg2.conf index 2963091ec..b9b4b0452 100644 --- a/misc/apache/bcfg2.conf +++ b/misc/apache/bcfg2.conf @@ -1,10 +1,8 @@ -LoadModule wsgi_module modules/mod_wsgi.so - # # If the root is changed update the static content alias as well # - WSGIScriptAlias /bcfg2 "/usr/lib/python2.4/site-packages/Bcfg2/Server/Reports/reports.wsgi" + WSGIScriptAlias /bcfg2 "/usr/share/bcfg2/reports.wsgi" WSGISocketPrefix run WSGIDaemonProcess Bcfg2.Server.Reports processes=1 threads=10 diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec index e9798c39f..8e77ad908 100644 --- a/misc/bcfg2.spec +++ b/misc/bcfg2.spec @@ -158,7 +158,7 @@ deployment strategies. %{__install} -m 755 tools/bcfg2-cron %{buildroot}%{_prefix}/lib/bcfg2/bcfg2-cron %{__install} -d %{buildroot}%{apache_conf}/conf.d -%{__install} -m 644 misc/apache/bcfg2.conf %{buildroot}%{apache_conf}/conf.d/bcfg2.conf +%{__install} -m 644 misc/apache/bcfg2.conf %{buildroot}%{apache_conf}/conf.d/wsgi_bcfg2.conf %clean [ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} || exit 2 @@ -209,9 +209,10 @@ deployment strategies. %files -n bcfg2-web %defattr(-,root,root,-) +%{_datadir}/bcfg2/reports.wsgi %{_datadir}/bcfg2/site_media -%config(noreplace) %{apache_conf}/conf.d/bcfg2.conf +%config(noreplace) %{apache_conf}/conf.d/wsgi_bcfg2.conf %changelog * Mon Jun 21 2010 Fabian Affolter - 1.1.0rc3-0.1 diff --git a/reports/reports.wsgi b/reports/reports.wsgi new file mode 100644 index 000000000..232650485 --- /dev/null +++ b/reports/reports.wsgi @@ -0,0 +1,4 @@ +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.Server.Reports.settings' +import django.core.handlers.wsgi +application = django.core.handlers.wsgi.WSGIHandler() diff --git a/setup.py b/setup.py index 6d5aae417..37a6b9e36 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,7 @@ setup(cmdclass=cmdclass, package_dir = {'Bcfg2':'src/lib'}, package_data = {'Bcfg2.Server.Reports.reports':['fixtures/*.xml', 'templates/*.html', 'templates/*/*.html', - 'templates/*/*.inc' ], - 'Bcfg2.Server.Reports':['reports.wsgi'] }, + 'templates/*/*.inc' ] }, scripts = glob('src/sbin/*'), data_files = [('share/bcfg2/schemas', glob('schemas/*.xsd')), @@ -39,6 +38,7 @@ setup(cmdclass=cmdclass, glob('reports/xsl-transforms/*.xsl')), ('share/bcfg2/xsl-transforms/xsl-transform-includes', glob('reports/xsl-transforms/xsl-transform-includes/*.xsl')), + ('share/bcfg2', ['reports/reports.wsgi']), ('share/man/man1', glob("man/bcfg2.1")), ('share/man/man5', glob("man/*.5")), ('share/man/man8', glob("man/*.8")), diff --git a/src/lib/Server/Reports/reports.wsgi b/src/lib/Server/Reports/reports.wsgi deleted file mode 100644 index 232650485..000000000 --- a/src/lib/Server/Reports/reports.wsgi +++ /dev/null @@ -1,4 +0,0 @@ -import os -os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.Server.Reports.settings' -import django.core.handlers.wsgi -application = django.core.handlers.wsgi.WSGIHandler() -- cgit v1.2.3-1-g7c22 From 7e7529c1f14bf7c421d78a0ad93958504d728f92 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 21 Oct 2010 13:48:44 -0500 Subject: Plugin.py: Reverted broken regex --- src/lib/Server/Plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index 0458a4ce0..95569e3ac 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -33,8 +33,8 @@ info_regex = re.compile( \ 'encoding:(\s)*(?P\w+)|' + 'group:(\s)*(?P\S+)|' + 'important:(\s)*(?P\S+)|' + - 'mtime:(\s)*(?P\w+)$' + - '^owner:(\s)*(?P\S+)|' + + 'mtime:(\s)*(?P\w+)|' + + 'owner:(\s)*(?P\S+)|' + 'paranoid:(\s)*(?P\S+)|' + 'perms:(\s)*(?P\w+)|') -- cgit v1.2.3-1-g7c22 From cc1d65d91aa00cdc91637c2e9afa36e11b5cc6a0 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Fri, 22 Oct 2010 12:11:57 -0500 Subject: Extend client metadata to include group category information --- src/lib/Server/Plugins/Metadata.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/Server/Plugins/Metadata.py b/src/lib/Server/Plugins/Metadata.py index 3161a69e3..81fd3e173 100644 --- a/src/lib/Server/Plugins/Metadata.py +++ b/src/lib/Server/Plugins/Metadata.py @@ -39,14 +39,21 @@ class ClientMetadata(object): """Test to see if client is a member of group.""" return group in self.groups + def group_in_category(self, category): + for grp in self.query.all_groups_in_category(category): + if grp in self.groups: + return grp + return '' + class MetadataQuery(object): - def __init__(self, by_name, get_clients, by_groups, by_profiles, all_groups): + def __init__(self, by_name, get_clients, by_groups, by_profiles, all_groups, all_groups_in_category): # resolver is set later self.by_name = by_name self.names_by_groups = by_groups self.names_by_profiles = by_profiles self.all_clients = get_clients self.all_groups = all_groups + self.all_groups_in_category = all_groups_in_category def by_groups(self, groups): return [self.by_name(name) for name in self.names_by_groups(groups)] @@ -105,7 +112,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, lambda:self.clients.keys(), self.get_client_names_by_groups, self.get_client_names_by_profiles, - self.get_all_group_names) + self.get_all_group_names, + self.get_all_groups_in_category) @classmethod def init_repo(cls, repo, groups, os_selection, clients): @@ -592,6 +600,12 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, [all_groups.update(g[1]) for g in self.groups.values()] return all_groups + def get_all_groups_in_category(self, category): + all_groups = set() + [all_groups.add(g) for g in self.categories \ + if self.categories[g] == category] + return all_groups + def get_client_names_by_profiles(self, profiles): return [client for client, profile in self.clients.iteritems() \ if profile in profiles] -- cgit v1.2.3-1-g7c22 From 4be21b7fbb93c3552713ab9914114fbe0068b93d Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Wed, 27 Oct 2010 10:58:56 -0500 Subject: Guppy.py: New plugin to help trace memory leaks --- src/lib/Server/Plugins/Guppy.py | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/lib/Server/Plugins/Guppy.py diff --git a/src/lib/Server/Plugins/Guppy.py b/src/lib/Server/Plugins/Guppy.py new file mode 100644 index 000000000..b217378d6 --- /dev/null +++ b/src/lib/Server/Plugins/Guppy.py @@ -0,0 +1,63 @@ +""" +This plugin is used to trace memory leaks within the bcfg2-server +process using Guppy. By default the remote debugger is started +when this plugin is enabled. The debugger can be shutoff in a running +process using "bcfg2-admin xcmd Guppy.Disable" and reenabled using +"bcfg2-admin xcmd Guppy.Enable". + +To attach the console run: + +python -c "from guppy import hpy;hpy().monitor()" + +For example: + +# python -c "from guppy import hpy;hpy().monitor()" + +*** Connection 1 opened *** + lc +CID PID ARGV + 1 25063 ['/usr/sbin/bcfg2-server', '-D', '/var/run/bcfg2-server.pid'] + sc 1 +Remote connection 1. To return to Monitor, type or . + int +Remote interactive console. To return to Annex, type '-'. +>>> hp.heap() +... + + +""" +import re +import Bcfg2.Server.Plugin + +class Guppy(Bcfg2.Server.Plugin.Plugin): + """Guppy is a debugging plugin to help trace memory leaks""" + name = 'Guppy' + __version__ = '$Id$' + __author__ = 'bcfg-dev@mcs.anl.gov' + + experimental = True + __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['Enable','Disable'] + + def __init__(self, core, datastore): + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) + + self.Enable() + + def Enable(self): + """Enable remote debugging""" + try: + from guppy.heapy import Remote + Remote.on() + except: + self.logger.error("Failed to create Heapy context") + raise Bcfg2.Server.Plugin.PluginInitError + + def Disable(self): + """Disable remote debugging""" + try: + from guppy.heapy import Remote + Remote.off() + except: + self.logger.error("Failed to disable Heapy") + raise Bcfg2.Server.Plugin.PluginInitError + -- cgit v1.2.3-1-g7c22 From b50f7745652d144e6746129f927be80ab5054823 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 28 Oct 2010 14:20:15 -0500 Subject: Pacman: Use logging infrastructure for printing messages Signed-off-by: Sol Jerome --- src/lib/Client/Tools/Pacman.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lib/Client/Tools/Pacman.py b/src/lib/Client/Tools/Pacman.py index a9edc4d65..85068ddfc 100644 --- a/src/lib/Client/Tools/Pacman.py +++ b/src/lib/Client/Tools/Pacman.py @@ -1,8 +1,7 @@ """This is the bcfg2 support for pacman""" import Bcfg2.Client.Tools -import Bcfg2.Options -import Bcfg2.Client.Tools + class Pacman(Bcfg2.Client.Tools.PkgTool): '''Archlinux package support''' @@ -31,7 +30,8 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): def VerifyPackage(self, entry, modlist): '''Verify Package status for entry''' - print "VerifyPackage : " + entry.get('name')+ " : " + entry.get('version') + self.logger.info("VerifyPackage : %s : %s" % entry.get('name'), + entry.get('version')) if not 'version' in entry.attrib: self.logger.info("Cannot verify unversioned package %s" % @@ -44,8 +44,8 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): elif self.installed[entry.attrib['name']] == entry.attrib['version']: #if not self.setup['quick'] and \ # entry.get('verify', 'true') == 'true': - #FIXME: We should be able to check this once - # http://trac.macports.org/ticket/15709 is implemented + #FIXME: need to figure out if pacman + # allows you to verify packages return True else: entry.set('current_version', self.installed[entry.get('name')]) @@ -76,11 +76,7 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): print "packages : " + pkgline try: - self.logger.debug('Running Pacman.Install()') - print "running : %s -S %s" % (self.pkgtool, pkgline) - s = self.cmd.run("%s -S %s" % (self.pkgtool, pkgline)) - print "pacman : " + str(s) - except Exception as ex: - print "error in cmd.run ", ex - - self.logger.debug('Running Pacman.Install()') + self.logger.debug("Running : %s -S %s" % (self.pkgtool, pkgline)) + self.cmd.run("%s -S %s" % (self.pkgtool, pkgline)) + except Exception as e: + self.logger.error("Error occurred during installation: %s" % e) -- cgit v1.2.3-1-g7c22 From 636adaa1b3c0109e0f77be55ff0e8b3b18d65600 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 28 Oct 2010 14:37:08 -0500 Subject: Reports: Fix default time zone settings (Resolves Ticket #957) We were previously defaulting to America/Chicago instead of leaving this option up to the user. Setting the default to None will cause Django to go by the system time. Note also that Django sets the os.environ['TZ'] variable when this is not none, so we want to be careful not to set that if we don't have to. Signed-off-by: Sol Jerome --- man/bcfg2.conf.5 | 5 +++++ src/lib/Server/Hostbase/settings.py | 7 +++++-- src/lib/Server/Reports/settings.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5 index 394bb347d..f2e47b7ac 100644 --- a/man/bcfg2.conf.5 +++ b/man/bcfg2.conf.5 @@ -325,6 +325,11 @@ Host for database connections. Not used for sqlite3. .B database_port Port for database connections. Not used for sqlite3. +.TP +.B time_zone +Specify a time zone other than that used on the system. (Note that this +will cause the bcfg2 server to log messages in this time zone as well). + .SH COMMUNICATION OPTIONS Specified in the [communication] section. These options define diff --git a/src/lib/Server/Hostbase/settings.py b/src/lib/Server/Hostbase/settings.py index dadf98d24..a42fd5b2e 100644 --- a/src/lib/Server/Hostbase/settings.py +++ b/src/lib/Server/Hostbase/settings.py @@ -44,8 +44,11 @@ DATABASE_HOST = options['database_host'] # Set to empty string for default. Not used with sqlite3. DATABASE_PORT = int(options['database_port']) # Local time zone for this installation. All choices can be found here: -# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -TIME_ZONE = 'America/Chicago' +# http://docs.djangoproject.com/en/dev/ref/settings/#time-zone +try: + TIME_ZONE = c.get('statistics', 'time_zone') +except: + TIME_ZONE = None # enter the defauly MX record machines will get in Hostbase # this setting may move elsewhere eventually diff --git a/src/lib/Server/Reports/settings.py b/src/lib/Server/Reports/settings.py index 81220c0e3..b725783ca 100644 --- a/src/lib/Server/Reports/settings.py +++ b/src/lib/Server/Reports/settings.py @@ -49,7 +49,7 @@ if DATABASE_ENGINE == 'sqlite3' and DATABASE_NAME == '': try: TIME_ZONE = c.get('statistics', 'time_zone') except: - TIME_ZONE = 'America/Chicago' + TIME_ZONE = None # Language code for this installation. All choices can be found here: # http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes -- cgit v1.2.3-1-g7c22 From a44304d3fcb3fa4ec186e7ebbeb4d4e72f25e9de Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 28 Oct 2010 17:09:10 -0500 Subject: doc: Move Properties plugin documentation under connectors Signed-off-by: Sol Jerome --- doc/server/plugins/connectors/properties.txt | 46 ++++++++++++++++++++++++++++ doc/server/plugins/index.txt | 10 +++++- doc/server/plugins/properties.txt | 46 ---------------------------- 3 files changed, 55 insertions(+), 47 deletions(-) create mode 100644 doc/server/plugins/connectors/properties.txt delete mode 100644 doc/server/plugins/properties.txt diff --git a/doc/server/plugins/connectors/properties.txt b/doc/server/plugins/connectors/properties.txt new file mode 100644 index 000000000..fa8bfd884 --- /dev/null +++ b/doc/server/plugins/connectors/properties.txt @@ -0,0 +1,46 @@ +.. -*- mode: rst -*- + +.. _server-plugins-properties: + +========== +Properties +========== + +The Properties plugin is a connector plugin that adds information from +properties files into client metadata instances. + +Enabling Properties +=================== + +First, ``mkdir /var/lib/bcfg2/Properties``. Each property XML file goes +in this directory. Each will automatically be cached by the server, +and reread/reparsed upon changes. Add **Properties** to your ``plugins`` +line in ``/etc/bcfg2.conf``. + +Data Structures +=============== + +Properties adds a new dictionary to client metadata instances that maps +property file names to PropertyFile instances. PropertyFile instances +contain parsed XML data as the "data" attribute. + +Usage +===== + +Specific property files can be referred to in +templates as metadata.Properties[]. The +data attribute is an LXML element object. (Documented +`here `_) + +Currently, no access methods are defined for this data, but as we +formulate common use cases, we will add them to the !PropertyFile class +as methods. This will simplify templates. + +Accessing Properties contest from TGenshi +========================================= + +Access contents of ``Properties/auth.xml`` + +:: + + ${metadata.Properties['auth.xml'].data.find('file').find('bcfg2.key').text} diff --git a/doc/server/plugins/index.txt b/doc/server/plugins/index.txt index 126331325..61f91da86 100644 --- a/doc/server/plugins/index.txt +++ b/doc/server/plugins/index.txt @@ -68,6 +68,15 @@ Literal Configuration (Generators) Each of these plugins has a corresponding subdirectory with the same name in the Bcfg2 repository. +Connector Plugins +----------------- + +.. toctree:: + :maxdepth: 2 + :glob: + + connectors/* + Statistics Plugins ------------------ @@ -103,5 +112,4 @@ More details can be found in :ref:`server-plugins-plugin-roles` plugin-roles probes/index - properties trigger diff --git a/doc/server/plugins/properties.txt b/doc/server/plugins/properties.txt deleted file mode 100644 index fa8bfd884..000000000 --- a/doc/server/plugins/properties.txt +++ /dev/null @@ -1,46 +0,0 @@ -.. -*- mode: rst -*- - -.. _server-plugins-properties: - -========== -Properties -========== - -The Properties plugin is a connector plugin that adds information from -properties files into client metadata instances. - -Enabling Properties -=================== - -First, ``mkdir /var/lib/bcfg2/Properties``. Each property XML file goes -in this directory. Each will automatically be cached by the server, -and reread/reparsed upon changes. Add **Properties** to your ``plugins`` -line in ``/etc/bcfg2.conf``. - -Data Structures -=============== - -Properties adds a new dictionary to client metadata instances that maps -property file names to PropertyFile instances. PropertyFile instances -contain parsed XML data as the "data" attribute. - -Usage -===== - -Specific property files can be referred to in -templates as metadata.Properties[]. The -data attribute is an LXML element object. (Documented -`here `_) - -Currently, no access methods are defined for this data, but as we -formulate common use cases, we will add them to the !PropertyFile class -as methods. This will simplify templates. - -Accessing Properties contest from TGenshi -========================================= - -Access contents of ``Properties/auth.xml`` - -:: - - ${metadata.Properties['auth.xml'].data.find('file').find('bcfg2.key').text} -- cgit v1.2.3-1-g7c22 From ed7386814afdf3ca70bb9454ab7fb09a664e1657 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 28 Oct 2010 18:00:55 -0500 Subject: Pacman: Fix traceback for exception handler Signed-off-by: Sol Jerome --- src/lib/Client/Tools/Pacman.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Client/Tools/Pacman.py b/src/lib/Client/Tools/Pacman.py index 85068ddfc..be3fb0c94 100644 --- a/src/lib/Client/Tools/Pacman.py +++ b/src/lib/Client/Tools/Pacman.py @@ -78,5 +78,5 @@ class Pacman(Bcfg2.Client.Tools.PkgTool): try: self.logger.debug("Running : %s -S %s" % (self.pkgtool, pkgline)) self.cmd.run("%s -S %s" % (self.pkgtool, pkgline)) - except Exception as e: + except Exception, e: self.logger.error("Error occurred during installation: %s" % e) -- cgit v1.2.3-1-g7c22 From 106248d2315704efdee5e352b207a62b5cf697d5 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Thu, 28 Oct 2010 19:36:48 -0500 Subject: Reports: Don't set TIME_ZONE unless it's supported Django added the TIME_ZONE = None bit in 1.2 and we are still supporting distros with 1.0 installed by default. Signed-off-by: Sol Jerome --- src/lib/Server/Reports/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/Server/Reports/settings.py b/src/lib/Server/Reports/settings.py index b725783ca..9efe38552 100644 --- a/src/lib/Server/Reports/settings.py +++ b/src/lib/Server/Reports/settings.py @@ -49,7 +49,8 @@ if DATABASE_ENGINE == 'sqlite3' and DATABASE_NAME == '': try: TIME_ZONE = c.get('statistics', 'time_zone') except: - TIME_ZONE = None + if django.VERSION[0] == 1 and django.VERSION[1] > 2: + TIME_ZONE = None # Language code for this installation. All choices can be found here: # http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes -- cgit v1.2.3-1-g7c22 From 890a6f79dd857bdfe03a3fbf4e79f2901d8b88d7 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Fri, 29 Oct 2010 11:46:45 -0500 Subject: bcfg2: implement -Q option (bundle-quick mode) Implement the -Q option for the bcfg2 client. This option only verifies and installs the entries in bundles specified with -b. Considerably improves runtime performance when package checksums are being checked. This option prevents the client from sending statistics to the server, and is incompatible with -r. --- man/bcfg2.1 | 7 +++++++ src/lib/Options.py | 2 ++ src/sbin/bcfg2 | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/man/bcfg2.1 b/man/bcfg2.1 index bf20649c3..938d41dfe 100644 --- a/man/bcfg2.1 +++ b/man/bcfg2.1 @@ -113,6 +113,13 @@ Run bcfg2 in quick mode. Package checksum verification won't be performed. This mode relaxes the constraints of correctness, and thus should only be used in safe conditions. +.TP +.BR "\-Q" +Run bcfg2 in "bundle quick" mode, where only entries in a bundle are +or installed. This runs much faster than -q, but doesn't provide +statistics to the server at all. In order for this option to work, the +-b option must also be provided. This option is incompatible with -r. + .TP .BR "\-r " Cause bcfg2 to remove extra configuration elements it detects. Mode is diff --git a/src/lib/Options.py b/src/lib/Options.py index b467a776d..1dcad6427 100644 --- a/src/lib/Options.py +++ b/src/lib/Options.py @@ -290,6 +290,8 @@ CLIENT_REMOVE = Option('force removal of additional configuration items', default=False, cmd='-r', odesc="") CLIENT_BUNDLE = Option('only configure the given bundle(s)', default=[], cmd='-b', odesc='', cook=colon_split) +CLIENT_BUNDLEQUICK = Option('only verify/configure the given bundle(s)', default=False, + cmd='-Q') CLIENT_INDEP = Option('only configure the given bundle(s)', default=False, cmd='-z') CLIENT_KEVLAR = Option('run in kevlar (bulletproof) mode', default=False, diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index 3407a1c53..10262e4a9 100755 --- a/src/sbin/bcfg2 +++ b/src/sbin/bcfg2 @@ -49,6 +49,7 @@ class Client: 'dryrun': Bcfg2.Options.CLIENT_DRYRUN, 'paranoid': Bcfg2.Options.CLIENT_PARANOID, 'bundle': Bcfg2.Options.CLIENT_BUNDLE, + 'bundle-quick': Bcfg2.Options.CLIENT_BUNDLEQUICK, 'indep': Bcfg2.Options.CLIENT_INDEP, 'file': Bcfg2.Options.CLIENT_FILE, 'interactive': Bcfg2.Options.INTERACTIVE, @@ -62,7 +63,6 @@ class Client: 'password': Bcfg2.Options.SERVER_PASSWORD, 'retries': Bcfg2.Options.CLIENT_RETRIES, 'kevlar': Bcfg2.Options.CLIENT_KEVLAR, - 'key': Bcfg2.Options.SERVER_KEY, 'decision-list': DECISION_LIST, 'encoding': Bcfg2.Options.ENCODING, 'omit-lock-check': Bcfg2.Options.OMIT_LOCK_CHECK, @@ -93,6 +93,13 @@ class Client: to_file=self.setup['filelog']) self.logger = logging.getLogger('bcfg2') self.logger.debug(self.setup) + if self.setup['bundle-quick']: + if self.setup['bundle'] == []: + self.logger.error("-Q option requires -b") + raise SystemExit(1) + elif self.setup['remove'] != False: + self.logger.error("-Q option incompatible with -r") + raise SystemExit(1) if 'drivers' in self.setup and self.setup['drivers'] == 'help': self.logger.info("The following drivers are available:") self.logger.info(Bcfg2.Client.Tools.drivers) @@ -251,6 +258,12 @@ class Client: self.fatal_error("Server error: %s" % (self.config.text)) return(1) + if self.setup['bundle-quick']: + newconfig = Bcfg2.Client.XML.XML('') + [newconfig.append(bundle) for bundle in self.config.getchildren() if \ + bundle.tag == 'Bundle' and bundle.get('name') in self.setup['bundle']] + self.config = newconfig + self.tools = Bcfg2.Client.Frame.Frame(self.config, self.setup, times, self.setup['drivers'], @@ -281,7 +294,7 @@ class Client: except OSError: self.logger.error("Failed to unlock lockfile %s" % lockfile.name) - if not self.setup['file']: + if not self.setup['file'] and not self.setup['bundle-quick']: # upload statistics feedback = self.tools.GenerateStats() -- cgit v1.2.3-1-g7c22 From d7ce5d6926dffab8c167d41f4068d0f71c9beda7 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 2 Nov 2010 13:03:05 -0500 Subject: bcfg2-info: showentries matches argument length incorrectly --- src/sbin/bcfg2-info | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index d3a9bf8be..497c39174 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -303,7 +303,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): def do_showentries(self, args): """Show abstract configuration entries for a given host.""" arglen = len(args.split()) - if arglen not in [2, 3]: + if arglen not in [1, 2]: print("Usage: showentries ") return client = args.split()[0] -- cgit v1.2.3-1-g7c22 From b75139eb492ca3485c4bc6328265f3e75e9624fe Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 2 Nov 2010 13:52:19 -0500 Subject: docs: Added more information for the metadata object used in TGenshi and TCheetah --- doc/server/plugins/generators/tcheetah.txt | 16 +----- doc/server/plugins/generators/tgenshi/index.txt | 6 ++- doc/server/plugins/grouping/metadata.txt | 67 +++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/doc/server/plugins/generators/tcheetah.txt b/doc/server/plugins/generators/tcheetah.txt index e1ad600a2..52a0f3264 100644 --- a/doc/server/plugins/generators/tcheetah.txt +++ b/doc/server/plugins/generators/tcheetah.txt @@ -27,7 +27,7 @@ located in in a ``TCheetah`` subdirectory of your repository, usually files, ``template`` and ``info``. The template is a standard Cheetah template with two additions: -* `self.metadata` is the client's metadata +* `self.metadata` is the client's :ref:`metadata ` * `self.properties` is an xml document of unstructured data The ``info`` file is formatted like ``:info`` files from Cfg. @@ -44,19 +44,7 @@ Permissions entry and a Path entry to handle the same file. self.metadata variables ======================= -The following variables are available for self.metadata: - -* hostname -* bundles -* groups -* categories -* probes -* uuid -* password - -self.metadata is an instance of the class -ClientMetadata of file `Bcfg2/Server/Plugins/Metadata.py -`_. +self.metadata is an instance of the class ClientMetadata and documented :ref:`here `. self.properties =============== diff --git a/doc/server/plugins/generators/tgenshi/index.txt b/doc/server/plugins/generators/tgenshi/index.txt index cd9bcf152..425b3a289 100644 --- a/doc/server/plugins/generators/tgenshi/index.txt +++ b/doc/server/plugins/generators/tgenshi/index.txt @@ -48,8 +48,10 @@ supported. Inside of templates =================== -* metadata is the client's metadata -* properties.properties is an xml document of unstructured data +* **metadata** is the client's :ref:`metadata ` +* **properties.properties** is an xml document of unstructured data +* **name** is the path name specified in bcfg +* **path** is the path to the TGenshi template See the genshi `documentation `_ for examples of diff --git a/doc/server/plugins/grouping/metadata.txt b/doc/server/plugins/grouping/metadata.txt index 96fc70ff5..22c967262 100644 --- a/doc/server/plugins/grouping/metadata.txt +++ b/doc/server/plugins/grouping/metadata.txt @@ -225,3 +225,70 @@ Probes The metadata plugin includes client-side probing functionality. This is fully documented :ref:`here `. + +.. _server-plugins-grouping-metadata-clientmetadata: + +ClientMetadata +============== + +A special client metadata class is available to the TGenshi and TCheetah +plugins. + ++----------------------+------------------------------------------------+-------------------+ +| Attribute | Description | Value | ++======================+================================================+===================+ +| hostname | Client hostname | String | ++----------------------+------------------------------------------------+-------------------+ +| profile | Client profile | String | ++----------------------+------------------------------------------------+-------------------+ +| aliases | Client aliases | List | ++----------------------+------------------------------------------------+-------------------+ +| addresses | Adresses this client is known by | List | ++----------------------+------------------------------------------------+-------------------+ +| groups | Groups this client is a member of | List | ++----------------------+------------------------------------------------+-------------------+ +| categories | Categories of this clients groups | List | ++----------------------+------------------------------------------------+-------------------+ +| uuid | uuid identifier for this client | String | ++----------------------+------------------------------------------------+-------------------+ +| password | bcfg password for this client | String | ++----------------------+------------------------------------------------+-------------------+ +| connectors | connector plugins known to this client | List | ++----------------------+------------------------------------------------+-------------------+ +| query | MetadataQuery object | MetadataQuery | ++----------------------+------------------------------------------------+-------------------+ + +| + ++------------------------------+------------------------------------------------+-------------------+ +| Method | Description | Value | ++==============================+================================================+===================+ +| inGroup(group) | True if this client is a memnber of 'group' | Boolean | ++------------------------------+------------------------------------------------+-------------------+ +| group_in_category(category) | Returns the group in 'category' if the client | String | +| | is a member of 'category', otherwise '' | | ++------------------------------+------------------------------------------------+-------------------+ + +MetadataQuery +------------- + +This class provides query routines for the servers Metadata. + ++------------------------------+------------------------------------------------+-------------------+ +| Method | Description | Value | ++==============================+================================================+===================+ +| by_name(client) | Get ClientMetadata object for 'client' | ClientMetadata | ++------------------------------+------------------------------------------------+-------------------+ +| names_by_groups(group) | | | ++------------------------------+------------------------------------------------+-------------------+ +| names_by_profiles(profile) | All clients names in 'profile' | List | ++------------------------------+------------------------------------------------+-------------------+ +| all_clients() | All known client hostnames | List | ++------------------------------+------------------------------------------------+-------------------+ +| all_groups() | All known group names | List | ++------------------------------+------------------------------------------------+-------------------+ +| all_groups_in_category(cat) | All groups in category 'cat' | List | ++------------------------------+------------------------------------------------+-------------------+ +| all() | Get ClientMetadata for all clients | List | ++------------------------------+------------------------------------------------+-------------------+ + -- cgit v1.2.3-1-g7c22 From 53a383ab2afcbcac748b5387cbfb786feb5f7acf Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Tue, 2 Nov 2010 18:46:23 -0500 Subject: YUMng: Sync Joe Digilio's patch over from svn Jack Neely's commit message: Add a patch from Joe Digilio to include a verify_flags knob. I've added a line to also load this from the config file, and log in debug mode. Specifying 'verify_flags' with an Instance will override the values from the config file. Signed-off-by: Sol Jerome --- src/lib/Client/Tools/YUMng.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Client/Tools/YUMng.py index 9cdcdca40..f0d906717 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Client/Tools/YUMng.py @@ -210,6 +210,8 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): "version_fail_action", "upgrade").lower() == "upgrade" self.doReinst = CP.get(self.name, "verify_fail_action", "reinstall").lower() == "reinstall" + self.verifyFlags = CP.get(self.name, "verify_flags", + "").lower().replace(' ', ',') self.installOnlyPkgs = self.yb.conf.installonlypkgs if 'gpg-pubkey' not in self.installOnlyPkgs: @@ -225,6 +227,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): % self.doReinst) self.logger.debug("YUMng: installOnlyPkgs: %s" \ % str(self.installOnlyPkgs)) + self.logger.debug("YUMng: verify_flags: %s" % self.verifyFlags) def _fixAutoVersion(self, entry): # old style entry; synthesize Instances from current installed @@ -436,6 +439,8 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): stat['verify_fail'] = False stat['pkg'] = entry stat['modlist'] = modlist + verify_flags = inst.get('verify_flags', self.verifyFlags) + verify_flags = verify_flags.lower().replace(' ', ',').split(',') if len(POs) == 0: # Package not installed @@ -505,6 +510,8 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): for p in probs: if p.type == 'missing' and os.path.islink(fn): continue + elif 'no' + p.type in verify_flags: + continue if p.type not in ['missingok', 'ghost']: tmp.append((p.type, p.message)) if tmp != []: -- cgit v1.2.3-1-g7c22 From 6079a4b01ee4d500fb79cd5552f00e987ed65485 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 2 Nov 2010 19:39:24 -0500 Subject: reports: fix bcfg2-admin reports init when db is nonexistent (cherry picked from commit feed7f02aeda0b8abe4a3a521e6ab13081c84232) --- src/lib/Server/Reports/updatefix.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/Server/Reports/updatefix.py b/src/lib/Server/Reports/updatefix.py index 6d9b5e952..f8fca1f90 100644 --- a/src/lib/Server/Reports/updatefix.py +++ b/src/lib/Server/Reports/updatefix.py @@ -139,8 +139,12 @@ def dosync(): fresh = True # ensure database connection are close, so that the management can do it's job right - cursor.close() - connection.close() + try: + cursor.close() + connection.close() + except: + # ignore any errors from missing/invalid dbs + pass # Do the syncdb according to the django version if "call_command" in dir(django.core.management): # this is available since django 1.0 alpha. -- cgit v1.2.3-1-g7c22 From e2b8d674fe5a38906fcd07e760303178e2fe5c30 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Tue, 2 Nov 2010 20:31:51 -0500 Subject: doc: Fix broken link to help pages Signed-off-by: Sol Jerome --- doc/getting_started/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/getting_started/index.txt b/doc/getting_started/index.txt index fe52807d4..2c05c5238 100644 --- a/doc/getting_started/index.txt +++ b/doc/getting_started/index.txt @@ -9,7 +9,7 @@ Using Bcfg2 These are many of the resources available to help new users get started. * For the impatient there is the :ref:`quickstart-index` page. -* :ref:`gettinghelp` has information when you are troubleshooting or need to ask a question. +* :ref:`help-index` has information when you are troubleshooting or need to ask a question. * If you find anything wrong or missing please :ref:`report-a-bug` to let us know. .. toctree:: -- cgit v1.2.3-1-g7c22 From 5fe38f0054cf392829b69961b864f284e34057f4 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Tue, 2 Nov 2010 19:39:24 -0500 Subject: reports: fix bcfg2-admin reports init when db is nonexistent (cherry picked from commit feed7f02aeda0b8abe4a3a521e6ab13081c84232) --- src/lib/Server/Reports/updatefix.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/Server/Reports/updatefix.py b/src/lib/Server/Reports/updatefix.py index 6d9b5e952..f8fca1f90 100644 --- a/src/lib/Server/Reports/updatefix.py +++ b/src/lib/Server/Reports/updatefix.py @@ -139,8 +139,12 @@ def dosync(): fresh = True # ensure database connection are close, so that the management can do it's job right - cursor.close() - connection.close() + try: + cursor.close() + connection.close() + except: + # ignore any errors from missing/invalid dbs + pass # Do the syncdb according to the django version if "call_command" in dir(django.core.management): # this is available since django 1.0 alpha. -- cgit v1.2.3-1-g7c22