1
# Copyright 2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Archive Contents files generator."""
8
'GenerateContentsFiles',
11
from optparse import OptionValueError
14
from canonical.config import config
15
from canonical.launchpad.ftests.script import run_command
16
from lp.archivepublisher.config import getPubConfig
17
from lp.services.scripts.base import (
19
LaunchpadScriptFailure,
21
from lp.services.utils import file_exists
22
from lp.soyuz.scripts.ftpmaster import LpQueryDistro
33
def differ_in_content(one_file, other_file):
34
"""Do the two named files have different contents?"""
35
return file(one_file).read() != file(other_file).read()
39
"""Local helper for receiving `LpQueryDistro` results."""
41
def __call__(self, argument):
42
"""Store call argument."""
43
self.argument = argument
46
def get_template(template_name):
47
"""Return path of given template in this script's templates directory."""
49
config.root, "cronscripts", "publishing", "gen-contents",
53
def execute(logger, command_line):
54
description = ' '.join(command_line)
55
logger.debug("Execute: %s", description)
56
retval, stdout, stderr = run_command(command_line)
60
raise LaunchpadScriptFailure(
61
"Failure while running command: %s" % description)
64
class GenerateContentsFiles(LaunchpadScript):
66
def add_my_options(self):
67
self.parser.add_option(
68
"-d", "--distribution", dest="distribution", default=None,
69
help="Distribution to generate Contents files for.")
71
def setUpPrivateTree(self):
72
self.logger.debug("Ensuring that we have a private tree in place.")
73
for suffix in ['cache', 'misc']:
74
dirname = '%s-%s' % (self.distribution.name, suffix)
75
os.makedirs(os.path.join(self.content_archive, dirname))
77
def queryDistro(self, request, options=None):
78
"""Call the query-distro script about `self.distribution`."""
79
args = ['-d', self.distribution.name]
80
if options is not None:
83
query_distro = LpQueryDistro(test_args=args)
84
receiver = StoreArgument()
85
query_distro.runAction(presenter=receiver)
86
return receiver.argument
88
def getPocketSuffixes(self):
89
return self.queryDistro("pocket_suffixes").split()
92
return self.queryDistro("supported").split()
95
devel = self.queryDistro("development")
96
return self.queryDistro("archs", options=["-s", devel])
98
def getDirs(self, archs):
99
return ['source', 'debian-installer'] + [
100
'binary-%s' % arch.name for arch in archs]
102
def writeAptConf(self, suites, archs):
103
output_dirname = '%s-misc' % self.distribution.name
104
output_file = file(os.path.join(
105
self.content_archive, output_dirname, "apt-contents.conf"))
108
'architectures': archs,
109
'content_archive': self.content_archive,
110
'distribution': self.distribution.name,
113
header = get_template('apt_conf_header.template')
114
output_file.write(file(header).read() % parameters)
116
dist = get_template('apt_conf_dist.template')
117
dist_template = dist.read()
120
parameters['suite'] = suite
121
output_file.write(dist_template % parameters)
125
def createComponentDirs(self, suites, archs):
127
for component in COMPONENTS:
128
for directory in self.getDirs(archs):
130
self.content_archive, self.distribution.name, 'dists',
131
suite, component, directory)
132
if not file_exists(path):
133
self.logger.debug("Creating %s", path)
136
def writeContentsTop(self):
137
output_filename = os.path.join(
138
self.content_archive, '%s-misc' % self.distribution.name,
141
'distrotitle': self.distribution.title,
143
output_file = file(output_filename, 'w')
145
file(get_template('Contents.top')).read() % parameters)
148
def generateContentsFiles(self):
150
"Running apt in private tree to generate new contents.")
151
execute(self.logger, [
154
self.config.overrideroot,
155
"%s/" % self.content_archive,
157
self.writeContentsTop()
158
execute(self.logger, [
161
"%s/%s-misc/apt-contents.conf" % (
162
self.content_archive, self.distribution.name),
165
def installContentsFile(self, suite, arch):
166
contents_dir = os.path.join(
167
self.content_archive, self.distribution.name, 'dists', suite)
168
contents_filename = "Contents-%s" % arch
169
last_contents = os.path.join(contents_dir, ".%s" % contents_filename)
170
current_contents = os.path.join(contents_dir, contents_filename)
172
# Avoid rewriting unchanged files; mirrors would have to
173
# re-fetch them unnecessarily.
174
if differ_in_content(current_contents, last_contents):
176
"Installing new Contents file for %s/%s", suite, arch)
177
if file_exists(last_contents):
178
os.remove(last_contents)
179
os.rename(current_contents, last_contents)
181
new_contents = os.path.join(
182
contents_dir, "%s.gz" % contents_filename)
183
contents_dest = os.path.join(
184
self.config.distsroot, suite, "%s.gz" % contents_filename)
185
if file_exists(contents_dest):
186
os.remove(contents_dest)
187
os.rename(new_contents, contents_dest)
188
os.chmod(contents_dest, 0664)
191
"Skipping unmodified Contents file for %s/%s", suite, arch)
193
def compareAndUpdate(self, suites, archs):
194
self.logger.debug("Comparing contents files with public tree.")
197
self.installContentsFile(suite, arch)
200
if self.distribution is None:
201
raise OptionValueError("Specify a distribution using -d.")
203
self.config = getPubConfig(self.distribution.main_archive)
204
self.content_archive = os.path.join(
205
config.archivepublisher.content_archive_root,
206
self.distribution.name + "-contents")
208
self.setUpPrivateTree()
209
suites = self.getSuites()
210
archs = self.getArchs()
211
self.writeAptConf(suites, archs)
212
self.createComponentDirs(suites, archs)
213
self.generateContentsFiles()
214
self.compareAndUpdate(suites, archs)