/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-02-26 19:15:30 UTC
  • Revision ID: tim@ed.am-20140226191530-6x21vlwto2xx80cd
renamed updated_files to affected_files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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