/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: 2014-01-05 11:51:35 UTC
  • Revision ID: tim@ed.am-20140105115135-6ses87ggwyjrxzfj
added global objects (the.repo, the.program), deployment object and implemented
init command

Show diffs side-by-side

added added

removed removed

1
1
# deployment.py
2
2
#
3
 
# Copyright (C) 2013 to 2014 Tim Marston <tim@edm.am>
 
3
# Copyright (C) 2013 Tim Marston <tim@edm.am>
4
4
#
5
5
# This file is part of stdhome (hereafter referred to as "this program").
6
6
# See http://ed.am/dev/stdhome for more information.
19
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
20
 
21
21
 
22
 
import os, re, shutil, filecmp, json
23
 
import the, util
24
 
from walker.copy_in import CopyInWalker
25
 
from walker.conflict import ConflictWalker
26
 
from walker.copy_out import CopyOutWalker
 
22
import os
 
23
import the
27
24
 
28
25
 
29
26
class Deployment:
30
27
 
31
28
 
32
29
        def __init__( self ):
33
 
                if the.repo is None:
34
 
                        raise RuntimeError( 'logic error: Deployment initialised when '
35
 
                                                                'the.repo is unset' )
36
 
                self.load_deployment_state()
37
 
                self.conflicts_checked = False
38
 
 
39
 
 
40
 
        def load_deployment_state( self ):
41
 
                """Load any deployment state.  If one is found then a deployment will be
42
 
                considered to be ongoing.
43
 
                """
44
 
 
45
 
                # list of files that were copied-in (or at least given the opportunity
46
 
        # to be) and updated through the vcs update.  This means that, while
47
 
        # there may have been conflicts during the update (which the user will
48
 
        # have to deal with in the repo), any conflicts arising with these files
49
 
        # in the home directory are no longer important and can be ignored.  In
50
 
        # short, this is a list of files that can safely be deployed, regardless
51
 
        # of the state of the home directory.
52
 
                self.deploy_files = None
53
 
 
54
 
                # list of files that were affected by a recent vcs update (so only these
55
 
                # files need to be checked for deployment conflicts or copied-out)
56
 
                self.affected_files = None
57
 
 
58
 
                # the revno that the repo was as prior to a recent update
59
 
                self.initial_revno = None
60
 
 
61
 
                # do we have a repo?
62
 
                if not os.path.exists( the.repo.full_dir ): return
63
 
 
64
 
                # if no file list exists, we're done
65
 
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
66
 
                if not os.path.isfile( file ):
 
30
 
 
31
                # initialise the file list
 
32
                self.file_list = None
 
33
                if not os.path.exists( the.repo.expanded_dir ):
 
34
                        self.file_list = []
 
35
                else:
 
36
                        self.load_file_list()
 
37
 
 
38
                assert False # not implemented
 
39
 
 
40
 
 
41
        def load_file_list( self ):
 
42
 
 
43
                # if no file list, don't load one
 
44
                if .....
67
45
                        return
68
46
 
69
 
                # read the file list
70
 
                if the.verbose: print "deployment state found; loading"
71
 
                f = open( file, 'r' )
72
 
                state = json.loads( f.read() )
73
 
 
74
 
                # unpack deployment state
75
 
                self.deploy_files = state['deploy_files'];
76
 
                self.initial_revno = state['initial_revno'];
77
 
                self.affected_files = state['affected_files'];
78
 
 
79
 
 
80
 
        def save_deployment_state( self ):
81
 
                """Save the current deployment state (so there will be a deployment ongoing).
82
 
                """
83
 
 
84
 
                if the.verbose: print "saving deployment state"
85
 
 
86
 
                # create metadata directory, as necessary
87
 
                if not os.path.isdir( the.full_mddir ):
88
 
                        os.mkdir( the.full_mddir )
89
 
 
90
 
                # pack deployment state
91
 
                state = {
92
 
                        'deploy_files': self.deploy_files,
93
 
                        'initial_revno': self.initial_revno,
94
 
                        'affected_files': self.affected_files,
95
 
                }
96
 
 
97
 
                # create file
98
 
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
99
 
                f = open( file, 'w' )
100
 
                f.write( json.dumps( state ) );
101
 
 
102
 
 
103
 
        def remove_deployment_state( self ):
104
 
                """Remove the current deployment state (so no deployment will be ongoing).
105
 
                """
106
 
 
107
 
                # check it exists
108
 
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
109
 
                if( os.path.isfile( file ) ):
110
 
 
111
 
                        # delete it
112
 
                        if the.verbose: print "removing deployment state"
113
 
                        os.unlink( file )
114
 
 
115
 
 
116
 
        def is_ongoing( self ):
117
 
                """Is there a deployment currently ongoing?
118
 
                """
119
 
 
120
 
                return False if self.deploy_files is None else True
121
 
 
122
 
 
123
 
        def check_ongoing( self, ongoing = True ):
124
 
                """Check that a deployment either is or is not ongoing and raise an error if
125
 
                not.
126
 
                """
127
 
 
128
 
                if( ongoing ):
129
 
                        if self.deploy_files is None:
130
 
                                raise self.DeploymentOngoing( False )
131
 
                else:
132
 
                        if self.deploy_files is not None:
133
 
                                raise self.DeploymentOngoing( True )
134
 
 
135
 
 
136
 
        def get_initial_revno( self ):
137
 
                """Get the initial revision identifier from the deployment state.
138
 
                """
139
 
 
140
 
                return self.initial_revno
141
 
 
142
 
 
143
 
        def copy_in( self, initial_revno = None ):
144
 
                """Copy-in changes from the home directory to the repository.  When finished,
145
 
                the state of deployment is saved, meaning that a deployment is ongoing.
146
 
                """
 
47
                # load it
 
48
 
 
49
 
 
50
        def copy_in( self ):
147
51
 
148
52
                # check we don't already have a file list
149
 
                self.check_ongoing( False )
150
 
 
151
 
                # if the repo doesn't exist, we have an empty file list
152
 
                if not os.path.exists( the.repo.full_dir ):
153
 
                        self.deploy_files = list()
154
 
                else:
155
 
                        # copy in
156
 
                        if the.verbose: print "importing files..."
157
 
                        walker = CopyInWalker()
158
 
                        walker.walk()
159
 
                        self.deploy_files = walker.walk_list
160
 
 
161
 
                        # obtain initial revno
162
 
                        self.initial_revno = the.repo.vcs.get_revno()
163
 
 
164
 
                # save state
165
 
                self.save_deployment_state()
166
 
 
167
 
 
168
 
        def get_conflicts( self, affected_files = None ):
169
 
                """Check to see if there are any delpoyment conflicts.  If a list of affected
170
 
                files is supplied, then only those files are checked (and they are also
171
 
                saved with the deployment state).  Otherwise, all files in the
172
 
                repository are checked.
173
 
                """
174
 
 
175
 
                # check we have a file list
176
 
                self.check_ongoing( True )
177
 
 
178
 
                # set updated files
179
 
                if affected_files is not None:
180
 
                        self.affected_files = affected_files
181
 
                        self.save_deployment_state()
182
 
 
183
 
                # check for deployment conflicts
184
 
                walker = ConflictWalker( self.deploy_files, self.affected_files )
185
 
                walker.walk()
186
 
 
187
 
                self.conflicts_checked = True
188
 
                return walker.changed + walker.obstructed
 
53
                if self.file_list is not None:
 
54
                        raise the.FatalError( 'deployment in progress; ' + \
 
55
                                'see "%s resolve --help" for information' % the.program.name )
 
56
                        
 
57
                assert False # not implemented
189
58
 
190
59
 
191
60
        def copy_out( self ):
192
 
                """Copy-out changed files frmo the repository to the home directory.  If the
193
 
                deployment state incudes a list of affected files, then only those fiels
194
 
                are copied-out.
195
 
                """
196
 
 
197
 
                # check we have a file list
198
 
                self.check_ongoing( True )
199
 
 
200
 
                # check that deployment conflicts have been checked-for
201
 
                if not self.conflicts_checked:
202
 
                        raise RuntimeError('logic error: deployment conflicts unchecked' )
203
 
 
204
 
                # copy out
205
 
                if the.verbose: print "exporting files..."
206
 
                walker = CopyOutWalker( self.affected_files )
207
 
                walker.walk()
208
 
 
209
 
                # clear state
210
 
                self.remove_deployment_state()
211
 
 
212
 
 
213
 
        class DeploymentOngoing( the.program.FatalError ):
214
 
 
215
 
                def __init__( self, ongoing ):
216
 
                        if( ongoing ):
217
 
                                self.msg = "there is an ongoing deployment"
218
 
                        else:
219
 
                                self.msg = "there is no ongoing deployment"
220
 
 
221
 
 
222
 
        class CopyInConflicts( the.program.FatalError ):
223
 
 
224
 
                def __init__( self, conflicts ):
225
 
                        self.conflicts = conflicts
 
61
                if self.pre_copy_out_list is None:
 
62
                        return
 
63
 
 
64
                print "deploying files"
 
65
 
 
66
                # walk the repo
 
67
                for ( repodir, directories, files ) in os.walk( the.repo.expanded_dir ):
 
68
                        assert path[ : len( the.repo.expanded_dir ) ] == \
 
69
                                the.repo.expanded_dir
 
70
                        relative_path = path[ len( the.repo.expanded_dir ) : ]
 
71
                        fsdir = the.expanded_fsdir + relative_path
 
72
 
 
73
                        print relative_path
 
74
 
 
75
                        # check directories
 
76
                        for dir in directories:
 
77
                                if os.path.exists( fsdir + dir ) and \
 
78
                                   not os.path.isdir( fsdir + dir ):
 
79
 
 
80
                                        #
 
81
 
 
82
                        # if exists in repo as directory and also in fs as non-directory
 
83
                        if os.path.isdir( the.repo.expnaded_dir + relative_path +