-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add rudimentary windows support for bcfg2 #391
Open
chschenk
wants to merge
29
commits into
Bcfg2:master
Choose a base branch
from
chschenk:windows
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
b5ed496
Add basic windows support for bcfg2
chschenk 354df63
Fix PEP-8 Style issues
chschenk cb7c965
Return extra services in WinService
chschenk 63a45c2
Add import for binary Files
chschenk 7c7990c
Implement a locking mechanism which works on Windows and Linux
chschenk 6c5832d
Readd locked function for bcfg2-server
chschenk 0dc7c3f
Fix Style Issues
chschenk 19f05d5
More Style Fixes
chschenk 1a28369
Fix style issues
chschenk 4c907fe
Fix Error with lambda functions
chschenk 54c0c1e
Use compat to import modules which doesnt exists on windows
chschenk ef4aea5
Fix more Style Issues
chschenk a19f5c3
Fix Style Issues
chschenk ad69ab5
More Style Fixes
chschenk cdd5a7d
Fix more Style Issues
chschenk 4059e22
Better error handling in Flock class
chschenk 190d785
Fix style and error handling issues
chschenk 2bd891f
In Actions Shell should be False by default
chschenk c7e9e5a
Add WinFS support for nonexistent path types
chschenk c40da03
Fixing pylint errors
chschenk 03ad988
Fixing more pylint errors
chschenk 82dd867
Don't use "except as" because it only works in python 2.6 and newer
chschenk 17d8888
Don't use "except as" because it only works in python 2.6 and newer
chschenk 80efd67
Added the "real" logger to locking mechanism
chschenk febfafd
Added missing import for python3 compatibility
chschenk 460979c
Fixing exception Handling for Python 2.4
chschenk 73adee8
Fixing exception Handling for Python 2.4
chschenk ebc546d
Disable pylint W0622 in WinFS.py like in POSIX/File.py
chschenk 8815e8f
Added missing space to comment
chschenk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
"""WinAction driver""" | ||
import subprocess | ||
|
||
import Bcfg2.Client.Tools | ||
from Bcfg2.Utils import safe_input | ||
|
||
|
||
class WinAction(Bcfg2.Client.Tools.Tool): | ||
"""Implement Actions""" | ||
name = 'WinAction' | ||
__handles__ = [('Action', None)] | ||
__req__ = {'Action': ['name', 'timing', 'when', 'command', 'status']} | ||
|
||
def RunAction(self, entry): | ||
"""This method handles command execution and status return.""" | ||
shell = True | ||
shell_string = '' | ||
if entry.get('shell', 'false') == 'true': | ||
shell = True | ||
shell_string = '(in shell) ' | ||
|
||
if not Bcfg2.Options.setup.dry_run: | ||
if Bcfg2.Options.setup.interactive: | ||
prompt = ('Run Action %s%s, %s: (y/N): ' % | ||
(shell_string, entry.get('name'), | ||
entry.get('command'))) | ||
ans = safe_input(prompt) | ||
if ans not in ['y', 'Y']: | ||
return False | ||
if False: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if entry.get('build', 'true') == 'false': | ||
self.logger.debug("Action: Deferring execution of %s due " | ||
"to build mode" % entry.get('command')) | ||
return False | ||
self.logger.debug("Running Action %s %s" % | ||
(shell_string, entry.get('name'))) | ||
rv = self.cmd.run(entry.get('command'), shell=shell, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
close_fds=False) | ||
self.logger.debug("Action: %s got return code %s" % | ||
(entry.get('command'), rv.retval)) | ||
entry.set('rc', str(rv.retval)) | ||
return entry.get('status', 'check') == 'ignore' or rv.success | ||
else: | ||
self.logger.debug("In dryrun mode: not running action: %s" % | ||
(entry.get('name'))) | ||
return False | ||
|
||
def VerifyAction(self, dummy, _): | ||
"""Actions always verify true.""" | ||
return True | ||
|
||
def InstallAction(self, entry): | ||
"""Run actions as pre-checks for bundle installation.""" | ||
if entry.get('timing') != 'post': | ||
return self.RunAction(entry) | ||
return True | ||
|
||
def BundleUpdated(self, bundle): | ||
"""Run postinstalls when bundles have been updated.""" | ||
states = dict() | ||
for action in bundle.findall("Action"): | ||
if action.get('timing') in ['post', 'both']: | ||
if not self._install_allowed(action): | ||
continue | ||
states[action] = self.RunAction(action) | ||
return states | ||
|
||
def BundleNotUpdated(self, bundle): | ||
"""Run Actions when bundles have not been updated.""" | ||
states = dict() | ||
for action in bundle.findall("Action"): | ||
if (action.get('timing') in ['post', 'both'] and | ||
action.get('when') != 'modified'): | ||
if not self._install_allowed(action): | ||
continue | ||
states[action] = self.RunAction(action) | ||
return states |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
"""All Windows Type client support for Bcfg2.""" | ||
import sys | ||
import os | ||
import stat | ||
import tempfile | ||
import Bcfg2.Options | ||
import Bcfg2.Client.Tools | ||
from Bcfg2.Compat import b64decode | ||
|
||
|
||
class WinFS(Bcfg2.Client.Tools.Tool): | ||
"""Windows File support code.""" | ||
name = 'WinFS' | ||
__handles__ = [('Path', 'file')] | ||
|
||
def __init__(self, config): | ||
Bcfg2.Client.Tools.Tool.__init__(self, config) | ||
self.__req__ = dict(Path=dict()) | ||
self.__req__['Path']['file'] = ['name', 'mode', 'owner', 'group'] | ||
|
||
def _getFilePath(self, entry): | ||
"""Evaluates the enviroment Variables and returns the file path""" | ||
file_path = os.path.expandvars(os.path.normpath(entry.get('name')[1:])) | ||
if not file_path[1] == ':': | ||
self.logger.info( | ||
"Skipping \"%s\" because it doesnt look like a Windows Path" % | ||
file_path) | ||
return False | ||
return file_path | ||
|
||
def VerifyPath(self, entry, _): | ||
"""Path always verify true.""" | ||
file_path = self._getFilePath(entry) | ||
if(not file_path): | ||
return False | ||
ondisk = self._exists(file_path) | ||
tempdata = self._get_data(entry)[0] | ||
if isinstance(tempdata, str) and str != unicode: | ||
tempdatasize = len(tempdata) | ||
else: | ||
tempdatasize = len(tempdata.encode(Bcfg2.Options.setup.encoding)) | ||
|
||
different = False | ||
content = None | ||
if not ondisk: | ||
# first, see if the target file exists at all; if not, | ||
# they're clearly different | ||
different = True | ||
content = "" | ||
elif tempdatasize != ondisk[stat.ST_SIZE]: | ||
# next, see if the size of the target file is different | ||
# from the size of the desired content | ||
different = True | ||
else: | ||
# finally, read in the target file and compare them | ||
# directly. comparison could be done with a checksum, | ||
# which might be faster for big binary files, but slower | ||
# for everything else | ||
try: | ||
content = open(file_path).read() | ||
except UnicodeDecodeError: | ||
content = open(file_path, | ||
encoding=Bcfg2.Options.setup.encoding).read() | ||
except IOError: | ||
self.logger.error("Windows: Failed to read %s: %s" % | ||
(file_path, sys.exc_info()[1])) | ||
return False | ||
different = str(content) != str(tempdata) | ||
return not different | ||
|
||
def InstallPath(self, entry): | ||
"""Install device entries.""" | ||
file_path = self._getFilePath(entry) | ||
|
||
if not file_path: | ||
return False | ||
|
||
self.logger.debug("Installing: " + file_path) | ||
if not os.path.exists(os.path.dirname(file_path)): | ||
if not self._makedirs(path=file_path): | ||
return False | ||
newfile = self._write_tmpfile(entry, file_path) | ||
if not newfile: | ||
return False | ||
rv = True | ||
if not self._rename_tmpfile(newfile, file_path): | ||
return False | ||
|
||
return rv | ||
|
||
def _makedirs(self, path): | ||
""" os.makedirs helpfully creates all parent directories for us.""" | ||
rv = True | ||
try: | ||
os.makedirs(os.path.dirname(path)) | ||
except OSError: | ||
err = sys.exc_info()[1] | ||
self.logger.error('Windows: Failed to create directory %s: %s' % | ||
(path, err)) | ||
rv = False | ||
return rv | ||
|
||
def _write_tmpfile(self, entry, file_path): | ||
""" Write the file data to a temp file """ | ||
filedata = self._get_data(entry)[0] | ||
# get a temp file to write to that is in the same directory as | ||
# the existing file in order to preserve any permissions | ||
# protections on that directory, and also to avoid issues with | ||
# /tmp set nosetuid while creating files that are supposed to | ||
# be setuid | ||
try: | ||
(newfd, newfile) = \ | ||
tempfile.mkstemp(prefix=os.path.basename(file_path), | ||
dir=os.path.dirname(file_path)) | ||
except OSError: | ||
err = sys.exc_info()[1] | ||
self.logger.error( | ||
"Windows: Failed to create temp file in %s: %s" % | ||
(file_path, err)) | ||
return False | ||
try: | ||
if isinstance(filedata, str) and str != unicode: | ||
os.fdopen(newfd, 'w').write(filedata) | ||
else: | ||
os.fdopen(newfd, 'wb').write( | ||
filedata.encode(Bcfg2.Options.setup.encoding)) | ||
except (OSError, IOError): | ||
err = sys.exc_info()[1] | ||
self.logger.error( | ||
"Windows: Failed to open temp file %s for writing " | ||
"%s: %s" % | ||
(newfile, file_path, err)) | ||
return False | ||
return newfile | ||
|
||
def _get_data(self, entry): | ||
""" Get a tuple of (<file data>, <is binary>) for the given entry """ | ||
is_binary = entry.get('encoding', 'ascii') == 'base64' | ||
if entry.get('empty', 'false') == 'true' or not entry.text: | ||
tempdata = '' | ||
elif is_binary: | ||
tempdata = b64decode(entry.text) | ||
else: | ||
tempdata = entry.text | ||
if isinstance(tempdata, unicode) and unicode != str: | ||
try: | ||
tempdata = tempdata.encode(Bcfg2.Options.setup.encoding) | ||
except UnicodeEncodeError: | ||
err = sys.exc_info()[1] | ||
self.logger.error("Windows: Error encoding file %s: %s" % | ||
(entry.get('name'), err)) | ||
return (tempdata, is_binary) | ||
|
||
def _rename_tmpfile(self, newfile, file_path): | ||
""" Rename the given file to the appropriate filename for entry """ | ||
try: | ||
if(os.path.isfile(file_path)): | ||
os.unlink(file_path) | ||
os.rename(newfile, file_path) | ||
return True | ||
except OSError: | ||
err = sys.exc_info()[1] | ||
self.logger.error( | ||
"Windows: Failed to rename temp file %s to %s: %s" | ||
% (newfile, file_path, err)) | ||
try: | ||
os.unlink(newfile) | ||
except OSError: | ||
err = sys.exc_info()[1] | ||
self.logger.error( | ||
"Windows: Could not remove temp file %s: %s" % | ||
(newfile, err)) | ||
return False | ||
|
||
def _exists(self, file_path): | ||
""" check for existing paths and optionally remove them. if | ||
the path exists, return the lstat of it """ | ||
try: | ||
return os.lstat(file_path) | ||
except OSError: | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
"""WinService support for Bcfg2.""" | ||
|
||
import subprocess | ||
|
||
import Bcfg2.Client.Tools | ||
import Bcfg2.Client.XML | ||
|
||
|
||
class WinService(Bcfg2.Client.Tools.SvcTool): | ||
"""WinService service support for Bcfg2.""" | ||
name = 'WinService' | ||
__handles__ = [('Service', 'windows')] | ||
__req__ = {'Service': ['name', 'status']} | ||
|
||
def get_svc_command(self, service, action): | ||
return "powershell.exe %s-Service %s" % (action, service.get('name')) | ||
|
||
def VerifyService(self, entry, _): | ||
"""Verify Service status for entry""" | ||
|
||
if entry.get('status') == 'ignore': | ||
return True | ||
|
||
try: | ||
output = self.cmd.run('powershell.exe (Get-Service %s).Status' % | ||
(entry.get('name')), | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
close_fds=False).stdout.splitlines()[0] | ||
except IndexError: | ||
self.logger.error("Service %s not an Windows service" % | ||
entry.get('name')) | ||
return False | ||
|
||
if output is None: | ||
# service does not exist | ||
entry.set('current_status', 'off') | ||
status = False | ||
elif output.lower() == 'running': | ||
# service is running | ||
entry.set('current_status', 'on') | ||
if entry.get('status') == 'off': | ||
status = False | ||
else: | ||
status = True | ||
else: | ||
# service is not running | ||
entry.set('current_status', 'off') | ||
if entry.get('status') == 'on': | ||
status = False | ||
else: | ||
status = True | ||
|
||
return status | ||
|
||
def InstallService(self, entry): | ||
"""Install Service for entry.""" | ||
if entry.get('status') == 'on': | ||
cmd = "start" | ||
elif entry.get('status') == 'off': | ||
cmd = "stop" | ||
return self.cmd.run(self.get_svc_command(entry, cmd), | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
close_fds=False).success | ||
|
||
def restart_service(self, service): | ||
"""Restart a service. | ||
|
||
:param service: The service entry to modify | ||
:type service: lxml.etree._Element | ||
:returns: Bcfg2.Utils.ExecutorResult - The return value from | ||
:class:`Bcfg2.Utils.Executor.run` | ||
""" | ||
self.logger.debug('Restarting service %s' % service.get('name')) | ||
restart_target = service.get('target', 'restart') | ||
return self.cmd.run(self.get_svc_command(service, restart_target), | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
close_fds=False) | ||
|
||
def FindExtra(self): | ||
"""Locate extra Windows services.""" | ||
services = self.cmd.run("powershell.exe " | ||
"\"Get-WMIObject win32_service -Filter " | ||
"\\\"StartMode = 'auto'\\\"" | ||
" | Format-Table Name -HideTableHeaders\"", | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
close_fds=False).stdout.splitlines() | ||
return [Bcfg2.Client.XML.Element('Service', name=name, type='windows') | ||
for name in services] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this default to False? It seems to me like this block of code will always result in shell = True