Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 

# 

# Copyright (C) 2015 Canonical Ltd 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License version 3 as 

# published by the Free Software Foundation. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program.  If not, see <http://www.gnu.org/licenses/>. 

 

import os 

import logging 

import re 

import shlex 

import shutil 

import tempfile 

import yaml 

 

from snapcraft import common 

 

logger = logging.getLogger(__name__) 

 

 

_MANDATORY_PACKAGE_KEYS = [ 

    'name', 

    'version', 

    'vendor', 

    'icon', 

] 

 

_OPTIONAL_PACKAGE_KEYS = [ 

    'frameworks', 

    'type', 

] 

 

 

def create(config_data, arches=None): 

    ''' 

    Create  the meta directory and provision it with package.yaml and readme.md 

    in the snap dir using information from config_data and arches. 

    If provided arches, is a list of arches. 

 

    Returns meta_dir. 

    ''' 

    # TODO keys for using apparmor, setting an icon missing. 

 

    meta_dir = os.path.join(common.get_snapdir(), 'meta') 

    os.makedirs(meta_dir, exist_ok=True) 

 

    config_data['icon'] = _copy(meta_dir, config_data['icon']) 

 

59    if 'framework-policy' in config_data: 

        _copy(meta_dir, config_data['framework-policy'], 'framework-policy') 

 

    _write_package_yaml(meta_dir, config_data, arches) 

    _write_readme_md(meta_dir, config_data) 

 

    if 'config' in config_data: 

        _setup_config_hook(meta_dir, config_data['config']) 

 

    return meta_dir 

 

 

def _write_package_yaml(meta_dir, config_data, arches): 

    package_yaml_path = os.path.join(meta_dir, 'package.yaml') 

    package_yaml = _compose_package_yaml(meta_dir, config_data, arches) 

 

    with open(package_yaml_path, 'w') as f: 

        yaml.dump(package_yaml, stream=f, default_flow_style=False) 

 

 

def _write_readme_md(meta_dir, config_data): 

    readme_md_path = os.path.join(meta_dir, 'readme.md') 

    readme_md = _compose_readme(config_data) 

 

    with open(readme_md_path, 'w') as f: 

        f.write(readme_md) 

 

 

def _setup_config_hook(meta_dir, config): 

    hooks_dir = os.path.join(meta_dir, 'hooks') 

 

    os.makedirs(hooks_dir) 

 

    execparts = shlex.split(config) 

    args = execparts[1:] if len(execparts) > 1 else [] 

 

    config_hook_path = os.path.join(hooks_dir, 'config') 

    _write_wrap_exe(execparts[0], config_hook_path, args=args, 

                    cwd='$SNAP_APP_PATH') 

 

 

def _copy(meta_dir, relpath, new_relpath=None): 

    new_base = new_relpath or os.path.basename(relpath) 

    new_relpath = os.path.join(meta_dir, new_base) 

 

104    if os.path.isdir(relpath): 

        shutil.copytree(relpath, new_relpath) 

    else: 

        shutil.copyfile(relpath, new_relpath) 

 

    return os.path.join('meta', os.path.basename(relpath)) 

 

 

def _copy_security_profiles(meta_dir, runnables): 

    for runnable in runnables: 

        for entry in ('security-policy', 'security-override'): 

            if entry in runnable: 

                runnable[entry]['apparmor'] = \ 

                    _copy(meta_dir, runnable[entry]['apparmor']) 

                runnable[entry]['seccomp'] = \ 

                    _copy(meta_dir, runnable[entry]['seccomp']) 

 

    return runnables 

 

 

def _compose_package_yaml(meta_dir, config_data, arches): 

    ''' 

    Creates a dictionary that can be used to yaml.dump a package.yaml using 

    config_data. 

    If provided arches, is a list of arches. 

 

    Missing key exceptions will be raised if config_data does not hold 

    MANDATORY_KEYS, config_data can be validated against the snapcraft schema. 

    ''' 

    package_yaml = {} 

 

    for key_name in _MANDATORY_PACKAGE_KEYS: 

        package_yaml[key_name] = config_data[key_name] 

 

    for key_name in _OPTIONAL_PACKAGE_KEYS: 

        if key_name in config_data: 

            package_yaml[key_name] = config_data[key_name] 

 

    if arches: 

        package_yaml['architectures'] = arches 

 

    if 'binaries' in config_data: 

        binaries = config_data['binaries'] 

        binaries = _wrap_binaries(binaries) 

        package_yaml['binaries'] = \ 

            _copy_security_profiles(meta_dir, _repack_names(binaries)) 

 

    if 'services' in config_data: 

        services = config_data['services'] 

        services = _wrap_services(services) 

        package_yaml['services'] = \ 

            _copy_security_profiles(meta_dir, _repack_names(services)) 

 

    return package_yaml 

 

 

def _repack_names(names): 

    repack = [] 

    for name in names: 

        names[name].update({'name': name}) 

        repack.append(names[name]) 

    return repack 

 

 

def _compose_readme(config_data): 

    s = '{config[summary]}\n{config[description]}\n' 

    return s.format(config=config_data) 

 

 

def _replace_cmd(execparts, cmd): 

        newparts = [cmd] + execparts[1:] 

        return ' '.join([shlex.quote(x) for x in newparts]) 

 

 

def _write_wrap_exe(wrapexec, wrappath, shebang=None, args=[], cwd=None): 

    args = ' '.join(args) + ' $*' if args else '$*' 

    cwd = 'cd {}'.format(cwd) if cwd else '' 

 

    snap_dir = common.get_snapdir() 

    assembled_env = common.assemble_env().replace(snap_dir, '$SNAP_APP_PATH') 

    replace_path = r'{}/.*/install'.format(common.get_partsdir()) 

    assembled_env = re.sub(replace_path, '$SNAP_APP_PATH', assembled_env) 

    executable = '"{}"'.format(wrapexec) 

    if shebang is not None: 

        new_shebang = re.sub(replace_path, '$SNAP_APP_PATH', shebang) 

        if new_shebang != shebang: 

            # If the shebang was pointing to and executable within the 

            # local 'parts' dir, have the wrapper script execute it 

            # directly, since we can't use $SNAP_APP_PATH in the shebang 

            # itself. 

            executable = '"{}" "{}"'.format(new_shebang, wrapexec) 

    script = ('#!/bin/sh\n' + 

              '{}\n'.format(assembled_env) + 

              '{}\n'.format(cwd) + 

              'exec {} {}\n'.format(executable, args)) 

 

    with open(wrappath, 'w+') as f: 

        f.write(script) 

 

    os.chmod(wrappath, 0o755) 

 

 

def _wrap_exe(relexepath): 

    snap_dir = common.get_snapdir() 

    exepath = os.path.join(snap_dir, relexepath) 

    wrappath = exepath + '.wrapper' 

 

    # TODO talk to original author if the exception to be captured here is 

    # FileNotFoundError, the original code was a general catch all 

    try: 

        os.remove(wrappath) 

    except FileNotFoundError: 

        pass 

 

    wrapexec = '$SNAP_APP_PATH/{}'.format(relexepath) 

220    if not os.path.exists(exepath) and '/' not in relexepath: 

        # If it doesn't exist it might be in the path 

        logger.debug('Checking to see if "{}" is in the $PATH'.format( 

            relexepath)) 

        with tempfile.NamedTemporaryFile('w+') as tempf: 

            script = ('#!/bin/sh\n' + 

                      '{}\n'.format(common.assemble_env()) + 

                      'which "{}"\n'.format(relexepath)) 

            tempf.write(script) 

            tempf.flush() 

            common.run(['/bin/sh', tempf.name], cwd=snap_dir) 

            wrapexec = relexepath 

    else: 

        with open(exepath, 'rb') as exefile: 

            # If the file has a she-bang, the path might be pointing to 

            # the local 'parts' dir. Extract it so that _write_wrap_exe 

            # will have a chance to rewrite it. 

            if exefile.read(2) == b'#!': 

                shebang = exefile.readline().strip().decode('utf-8') 

            else: 

                shebang = None 

 

    _write_wrap_exe(wrapexec, wrappath, shebang=shebang) 

 

    return os.path.relpath(wrappath, snap_dir) 

 

 

def _wrap_binaries(binaries): 

    for name in binaries: 

        execparts = shlex.split(binaries[name]['exec']) 

        execwrap = _wrap_exe(execparts[0]) 

        binaries[name]['exec'] = _replace_cmd(execparts, execwrap) 

 

    return binaries 

 

 

def _wrap_services(services): 

    for name in services: 

        startpath = services[name].get('start') 

261        if startpath: 

            startparts = shlex.split(startpath) 

            startwrap = _wrap_exe(startparts[0]) 

            services[name]['start'] = _replace_cmd(startparts, startwrap) 

        stoppath = services[name].get('stop') 

        if stoppath: 

            stopparts = shlex.split(stoppath) 

            stopwrap = _wrap_exe(stopparts[0]) 

            services[name]['stop'] = _replace_cmd(stopparts, stopwrap) 

 

    return services