/stdhome

To get this branch, use:
bzr branch http://bzr.ed.am/stdhome

« back to all changes in this revision

Viewing changes to lib/stdhome/deployment.py

  • Committer: Tim Marston
  • Date: 2013-12-08 19:28:11 UTC
  • Revision ID: tim@ed.am-20131208192811-r20qj7cgmn4duw11
initial commit; basic app startup and initial command-line processing

Show diffs side-by-side

added added

removed removed

1
 
# deployment.py
2
 
#
3
 
# Copyright (C) 2013 to 2014 Tim Marston <tim@edm.am>
4
 
#
5
 
# This file is part of stdhome (hereafter referred to as "this program").
6
 
# See http://ed.am/dev/stdhome for more information.
7
 
#
8
 
# This program is free software: you can redistribute it and/or modify
9
 
# it under the terms of the GNU General Public License as published by
10
 
# the Free Software Foundation, either version 3 of the License, or
11
 
# (at your option) any later version.
12
 
#
13
 
# This program is distributed in the hope that it will be useful,
14
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 
# GNU General Public License for more details.
17
 
#
18
 
# You should have received a copy of the GNU General Public License
19
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 
 
21
 
 
22
 
import os, re, shutil, filecmp, json
23
 
import the, util
24
 
from walker.copy_in_walker import CopyInWalker
25
 
from walker.conflict_walker import ConflictWalker
26
 
from walker.copy_out_walker import CopyOutWalker
27
 
 
28
 
class Deployment:
29
 
 
30
 
 
31
 
        def __init__( self ):
32
 
                self.load_deployment_state()
33
 
                self.conflicts_checked = False
34
 
 
35
 
 
36
 
        def load_deployment_state( self ):
37
 
 
38
 
                # list of files that were copied-in (or at least given the opportunity
39
 
        # to be) and updated through the vcs update.  This means that, while
40
 
        # there may have been conflicts during the update (which will be dealt
41
 
        # with in the repo), any arising conflicts with the filesystem are now
42
 
        # assumed to be because of the vcs update and can be ignored (that is to
43
 
        # say, we no longer care about the file in the home directory).  In
44
 
        # short, this is a list of files that it is safe to deploy, regardless
45
 
        # of the state of the filesystem.
46
 
                self.deploy_files = None
47
 
 
48
 
                # list of files that were affected by a recent vcs update (so only these
49
 
                # need to be checked for deployment conflicts or copied-out)
50
 
                self.affected_files = None
51
 
 
52
 
                # do we have a repo?
53
 
                if not os.path.exists( the.repo.full_dir ): return
54
 
 
55
 
                # if no file list exists, we're done
56
 
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
57
 
                if not os.path.isfile( file ):
58
 
                        return
59
 
 
60
 
                # read the file list
61
 
                if the.verbose: print "loading deployment state"
62
 
                f = open( file, 'r' )
63
 
                state = json.loads( f.read() )
64
 
 
65
 
                # unpack deployment state
66
 
                self.deploy_files = state['deploy_files'];
67
 
                self.affected_files = state['affected_files'];
68
 
 
69
 
 
70
 
        def save_deployment_state( self ):
71
 
                if the.verbose: print "saving deployment state"
72
 
 
73
 
                # create metadata directory, as necessary
74
 
                if not os.path.isdir( the.full_mddir ):
75
 
                        os.mkdir( the.full_mddir )
76
 
 
77
 
                # pack deployment state
78
 
                state = {
79
 
                        'deploy_files': self.deploy_files,
80
 
                        'affected_files': self.affected_files,
81
 
                }
82
 
 
83
 
                # create file
84
 
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
85
 
                f = open( file, 'w' )
86
 
                f.write( json.dumps( state ) );
87
 
 
88
 
 
89
 
        def remove_deployment_state( self ):
90
 
 
91
 
                # check it exists
92
 
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
93
 
                if( os.path.isfile( file ) ):
94
 
 
95
 
                        # delete it
96
 
                        if the.verbose: print "removing deployment state"
97
 
                        os.unlink( file )
98
 
 
99
 
 
100
 
        def is_ongoing( self ):
101
 
                return False if self.deploy_files is None else True
102
 
 
103
 
 
104
 
        def check_ongoing( self, ongoing = True ):
105
 
                if( ongoing ):
106
 
                        if self.deploy_files is None:
107
 
                                raise self.DeploymentOngoing( False )
108
 
                else:
109
 
                        if self.deploy_files is not None:
110
 
                                raise self.DeploymentOngoing( True )
111
 
 
112
 
 
113
 
        def copy_in( self ):
114
 
 
115
 
                # check we don't already have a file list
116
 
                self.check_ongoing( False )
117
 
 
118
 
                # if the repo doesn't exist, we have an empty file list
119
 
                if not os.path.exists( the.repo.full_dir ):
120
 
                        self.deploy_files = list()
121
 
                else:
122
 
                        # copy in
123
 
                        if the.verbose: print "importing files..."
124
 
                        walker = CopyInWalker()
125
 
                        walker.walk()
126
 
                        if( walker.changed ):
127
 
                                raise self.CopyInConflicts( walker.changed )
128
 
                        self.deploy_files = walker.walk_list
129
 
 
130
 
                # save state
131
 
                self.save_deployment_state()
132
 
 
133
 
 
134
 
        def get_conflicts( self, affected_files = None ):
135
 
 
136
 
                # check we have a file list
137
 
                self.check_ongoing( True )
138
 
 
139
 
                # set updated files
140
 
                if affected_files is not None:
141
 
                        self.affected_files = affected_files
142
 
                        self.save_deployment_state()
143
 
 
144
 
                # check for deployment conflicts
145
 
                walker = ConflictWalker( self.deploy_files, self.affected_files )
146
 
                walker.walk()
147
 
 
148
 
                self.conflicts_checked = True
149
 
                return walker.changed
150
 
 
151
 
 
152
 
        def copy_out( self ):
153
 
 
154
 
                # check we have a file list
155
 
                self.check_ongoing( True )
156
 
 
157
 
                # check that deployment conflicts have been checked-for
158
 
                if not self.conflicts_checked:
159
 
                        raise RuntimeError('logic error: deployment conflicts unchecked' )
160
 
 
161
 
                # copy out
162
 
                if the.verbose: print "exporting files..."
163
 
                walker = CopyOutWalker( self.affected_files )
164
 
                walker.walk()
165
 
 
166
 
                # clear state
167
 
                self.remove_deployment_state()
168
 
 
169
 
 
170
 
        class DeploymentOngoing( the.program.FatalError ):
171
 
 
172
 
                def __init__( self, ongoing ):
173
 
                        if( ongoing ):
174
 
                                self.msg = "there is an ongoing deployment"
175
 
                        else:
176
 
                                self.msg = "there is no ongoing deployment"
177
 
 
178
 
 
179
 
        class CopyInConflicts( the.program.FatalError ):
180
 
 
181
 
                def __init__( self, conflicts ):
182
 
                        self.conflicts = conflicts