Passed
Pull Request — master (#5960)
by David
29:08 queued 18:37
created

write_plan_class_file()   B

Complexity

Conditions 8
Paths 2

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 16
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 20
rs 8.4444
1
<?php
2
// This file is part of BOINC.
3
// https://boinc.berkeley.edu
4
// Copyright (C) 2024 University of California
5
//
6
// BOINC is free software; you can redistribute it and/or modify it
7
// under the terms of the GNU Lesser General Public License
8
// as published by the Free Software Foundation,
9
// either version 3 of the License, or (at your option) any later version.
10
//
11
// BOINC is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
// See the GNU Lesser General Public License for more details.
15
//
16
// You should have received a copy of the GNU Lesser General Public License
17
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
18
19
// web interface for managing BUDA science apps
20
//
21
// in the following, 'app' means BUDA science app
22
// and 'variant' means a variant of one of these (e.g. CPU, GPU)
23
24
require_once('../inc/util.inc');
25
require_once('../inc/sandbox.inc');
26
require_once('../inc/submit_util.inc');
27
28
display_errors();
29
30
$buda_root = "../../buda_apps";
31
32
// scan BUDA apps and variants, and write a file 'buda_plan_classes'
33
// in the project dir with list of plan classes
34
//
35
function write_plan_class_file() {
36
    $pcs = [];
37
    global $buda_root;
38
    if (is_dir($buda_root)) {
39
        $apps = scandir($buda_root);
40
        foreach ($apps as $app) {
41
            if ($app[0] == '.') continue;
42
            if (!is_dir("$buda_root/$app")) continue;
43
            $vars = scandir("$buda_root/$app");
44
            foreach ($vars as $var) {
45
                if ($var[0] == '.') continue;
46
                if (!is_dir("$buda_root/$app/$var")) continue;
47
                $pcs[] = $var;
48
            }
49
        }
50
    }
51
    $pcs = array_unique($pcs);
52
    file_put_contents(
53
        "../../buda_plan_classes",
54
        implode("\n", $pcs)."\n"
55
    );
56
}
57
58
// show list of BUDA apps and variants,
59
// w/ buttons for adding and deleting
60
//
61
function app_list($notice=null) {
62
    global $buda_root;
63
    if (!is_dir($buda_root)) {
64
        mkdir($buda_root);
65
    }
66
    page_head('BUDA science apps');
67
    if ($notice) {
68
        echo "$notice <p>\n";
69
    }
70
    text_start();
71
    echo "
72
        <p>BUDA (BOINC Universal Docker App)
73
        lets you submit Docker jobs using a web interface;
74
        you don't need to log into the BOINC server.
75
        <p>
76
        <a href=https://github.com/BOINC/boinc/wiki/BUDA-overview>BUDA overview</a>.
77
    ";
78
79
    echo "<h2>Science apps</h2>";
80
    $dirs = scandir($buda_root);
81
    foreach ($dirs as $dir) {
82
        if ($dir[0] == '.') continue;
83
        show_app($dir);
84
    }
85
    echo '<hr>';
86
    show_button_small('buda.php?action=app_form', 'Add science app');
87
    text_end();
88
    page_tail();
89
}
90
91
function show_app($dir) {
92
    global $buda_root;
93
    echo "<hr><font size=+3>$dir</font>\n";
94
    start_table('table-striped');
95
    table_header('Variant name<br><small>click for details</small>', 'Submit jobs');
96
    $pcs = scandir("$buda_root/$dir");
97
    foreach ($pcs as $pc) {
98
        if ($pc[0] == '.') continue;
99
        table_row(
100
            "<a href=buda.php?action=variant_view&app=$dir&variant=$pc>$pc</href>",
101
            button_text(
102
                "buda_submit.php?app=$dir&variant=$pc", "Submission form"
103
            )
104
        );
105
    }
106
    end_table();
107
    echo "<p>";
108
    show_button_small("buda.php?action=variant_form&app=$dir", 'Add variant');
109
    echo "<p>";
110
    show_button_small(
111
        "buda.php?action=app_delete&app=$dir", "Delete science app '$dir'"
112
    );
113
}
114
115
function variant_view() {
116
    global $buda_root;
117
    $app = get_str('app');
118
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
119
    $variant = get_str('variant');
120
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
121
    page_head("App $app variant $variant");
122
    $dir = "$buda_root/$app/$variant";
123
    start_table();
124
    table_header('name', 'size', 'md5');
125
    foreach(scandir($dir) as $f) {
126
        if ($f[0] == '.') continue;
127
        [$md5, $size] = parse_info_file("$dir/.md5/$f");
128
        table_row(
129
            "<a href=buda.php?action=view_file&app=$app&variant=$variant&fname=$f>$f</a>",
130
            $size,
131
            $md5
132
        );
133
    }
134
    end_table();
135
    show_button_small(
136
        "buda.php?action=variant_delete&app=$app&variant=$variant",
137
        'Delete variant'
138
    );
139
    page_tail();
140
}
141
142
// form for creating app variant.
143
// Currently doesn't support indirect files.
144
// doing this would require checkboxes for indirect
145
//
146
// Could have other stuff like
147
//      - min_quorum, max_total_results
148
//      - rsc_disk_bound, rsc_memory_bound
149
// or does this belong in job submission?
150
//
151
function variant_form($user) {
152
    $sbitems = sandbox_select_items($user);
153
    $app = get_str('app');
154
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
155
156
    page_head("Create a variant of Docker app $app");
157
    echo "
158
        Details are <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission#adding-a-variant>here</a>.
159
    ";
160
    $sb = '<br><small>From your <a href=sandbox.php>file sandbox</a></small>';
161
    form_start('buda.php');
162
    form_input_hidden('app', $app);
163
    form_input_hidden('action', 'variant_action');
164
    form_input_text('Plan class', 'variant');
165
    form_select("Dockerfile$sb", 'dockerfile', $sbitems);
166
    form_select_multiple("Application files$sb", 'app_files', $sbitems);
167
    form_input_text(
168
        'Input file names<br><small>Space-separated</small>',
169
        'input_file_names'
170
    );
171
    form_input_text(
172
        'Output file names<br><small>Space-separated</small>',
173
        'output_file_names'
174
    );
175
    form_submit('OK');
176
    form_end();
177
    page_tail();
178
}
179
180
function buda_file_phys_name($app, $variant, $md5) {
181
    return sprintf('buda_%s_%s_%s', $app, $variant, $md5);
182
}
183
184
// copy file from sandbox to variant dir, and stage to download hier
185
//
186
function copy_and_stage_file($user, $fname, $dir, $app, $variant) {
187
    copy_sandbox_file($user, $fname, $dir);
188
    [$md5, $size] = parse_info_file("$dir/.md5/$fname");
189
    $phys_name = buda_file_phys_name($app, $variant, $md5);
190
    stage_file_aux("$dir/$fname", $md5, $size, $phys_name);
191
    return $phys_name;
192
}
193
194
// create templates and put them in variant dir
195
//
196
function create_templates($variant, $variant_desc, $dir) {
197
    // input template
198
    //
199
    $x = "<input_template>\n";
200
    $ninfiles = 1 + count($variant_desc->input_file_names) + count($variant_desc->app_files);
201
    for ($i=0; $i<$ninfiles; $i++) {
202
        $x .= "   <file_info>\n      <no_delete/>\n   </file_info>\n";
203
    }
204
    $x .= "   <workunit>\n";
205
    $x .= file_ref_in($variant_desc->dockerfile);
206
    foreach ($variant_desc->app_files as $fname) {
207
        $x .= file_ref_in($fname);
208
    }
209
    foreach ($variant_desc->input_file_names as $fname) {
210
        $x .= file_ref_in($fname);
211
    }
212
    if ($variant == 'cpu') {
213
        $x .= "      <plan_class></plan_class>\n";
214
    } else {
215
        $x .= "      <plan_class>$variant</plan_class>\n";
216
    }
217
    $x .= "   </workunit>\n<input_template>\n";
218
    file_put_contents("$dir/template_in", $x);
219
220
    // output template
221
    //
222
    $x = "<output_template>\n";
223
    $i = 0;
224
    foreach ($variant_desc->output_file_names as $fname) {
225
        $x .= file_info_out($i++);
226
    }
227
    $x .= "   <result>\n";
228
    $i = 0;
229
    foreach ($variant_desc->output_file_names as $fname) {
230
        $x .= file_ref_out($i++, $fname);
231
    }
232
    $x .= "   </result>\n</output_template>\n";
233
    file_put_contents("$dir/template_out", $x);
234
}
235
236
// create variant
237
//
238
function variant_action($user) {
239
    global $buda_root;
240
    $variant = get_str('variant');
241
    if (!$variant) $variant = 'cpu';
242
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
243
    $app = get_str('app');
244
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
245
    $dockerfile = get_str('dockerfile');
246
    if (!is_valid_filename($dockerfile)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
247
    $app_files = get_array('app_files');
248
    foreach ($app_files as $fname) {
249
        if (!is_valid_filename($fname)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
250
    }
251
    $input_file_names = explode(' ', get_str('input_file_names'));
252
    $output_file_names = explode(' ', get_str('output_file_names'));
253
    foreach ($input_file_names as $fname) {
254
        if (!is_valid_filename($fname)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
255
    }
256
    foreach ($output_file_names as $fname) {
257
        if (!is_valid_filename($fname)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
258
    }
259
260
    if (file_exists("$buda_root/$app/$variant")) {
261
        error_page("Variant '$variant' already exists.");
262
    }
263
    $dir = "$buda_root/$app/$variant";
264
    mkdir($dir);
265
    mkdir("$dir/.md5");
266
267
    // create variant description JSON file
268
    //
269
    $desc = new StdClass;
270
    $desc->dockerfile = $dockerfile;
271
    $desc->app_files = $app_files;
272
    $desc->input_file_names = $input_file_names;
273
    $desc->output_file_names = $output_file_names;
274
275
    // copy files from sandbox to variant dir
276
    //
277
    $pname = copy_and_stage_file($user, $dockerfile, $dir, $app, $variant);
278
    $desc->dockerfile_phys = $pname;
279
    $desc->app_files_phys = [];
280
    foreach ($app_files as $fname) {
281
        $pname = copy_and_stage_file($user, $fname, $dir, $app, $variant);
282
        $desc->app_files_phys[] = $pname;
283
    }
284
285
    file_put_contents(
286
        "$dir/variant.json",
287
        json_encode($desc, JSON_PRETTY_PRINT)
288
    );
289
290
    create_templates($variant, $desc, $dir);
291
292
    // Note: we don't currently allow indirect file access.
293
    // If we did, we'd need to create job.toml to mount project dir
294
295
    app_list("Variant $variant added for app $app.");
296
}
297
298
function variant_delete() {
299
    global $buda_root;
300
    $app = get_str('app');
301
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
302
    $variant = get_str('variant');
303
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
304
    $confirmed = get_str('confirmed', true);
305
    if ($confirmed) {
306
        $dir = "$buda_root/$app/$variant";
307
        if (!file_exists($dir)) error_page('no such variant');
308
        // delete staged files
309
        //
310
        foreach (scandir("$dir/.md5") as $fname) {
311
            if ($fname[0] == '.') continue;
312
            [$md5, $size] = parse_info_file("$dir/.md5/$fname");
313
            $phys_name = buda_file_phys_name($app, $variant, $md5);
314
            $phys_path = download_hier_path($phys_name);
315
            unlink($phys_path);
316
            unlink("$phys_path.md5");
317
        }
318
        system("rm -r $buda_root/$app/$variant", $ret);
319
        if ($ret) {
320
            error_page("delete failed");
321
        }
322
        $notice = "Variant $variant of app $app removed.";
323
        app_list($notice);
324
    } else {
325
        page_head("Confirm");
326
        echo "Are you sure you want to delete variant $variant of app $app?  <p>";
327
        show_button(
328
            "buda.php?action=variant_delete&app=$app&variant=$variant&confirmed=yes",
329
            "Yes"
330
        );
331
        page_tail();
332
    }
333
}
334
335
function app_delete() {
336
    global $buda_root;
337
    $app = get_str('app');
338
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
339
    $confirmed = get_str('confirmed', true);
340
    if ($confirmed) {
341
        $dir = "$buda_root/$app";
342
        if (!file_exists($dir)) error_page('no such app');
343
        foreach (scandir($dir) as $fname) {
344
            if ($fname[0] == '.') continue;
345
            error_page("You must delete all variants first.");
346
        }
347
        system("rmdir $buda_root/$app", $ret);
348
        if ($ret) {
349
            error_page('delete failed');
350
        }
351
        $notice = "App $app removed.";
352
        app_list($notice);
353
    } else {
354
        page_head('Confirm');
355
        echo "Are you sure you want to delete app $app?  <p>";
356
        show_button(
357
            "buda.php?action=app_delete&app=$app&confirmed=yes",
358
            "Yes"
359
        );
360
        page_tail();
361
    }
362
}
363
364
function app_form() {
365
    page_head('Create Docker app');
366
    form_start('buda.php');
367
    form_input_hidden('action', 'app_action');
368
    form_input_text('Name', 'name');
369
    form_submit('OK');
370
    form_end();
371
    page_tail();
372
}
373
374
function app_action() {
375
    global $buda_root;
376
    $name = get_str('name');
377
    if (!is_valid_filename($name)) die("bad arg: $name");
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
378
    $dir = "$buda_root/$name";
379
    if (file_exists($dir)) {
380
        error_page("App $name already exists.");
381
    }
382
    mkdir($dir);
383
    header("Location: buda.php");
384
}
385
386
function view_file() {
387
    global $buda_root;
388
    $app = get_str('app');
389
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
390
    $variant = get_str('variant');
391
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
392
    $fname = get_str('fname');
393
    if (!is_valid_filename($fname)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
394
    echo "<pre>\n";
395
    readfile("$buda_root/$app/$variant/$fname");
396
    echo "</pre>\n";
397
}
398
399
// check access.
400
// Anyone with submit access to BUDA can add/delete apps and variants.
401
// Might want to refine this at some point
402
403
$user = get_logged_in_user();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $user is correct as get_logged_in_user() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
404
$buda_app = BoincApp::lookup("name='buda'");
405
if (!$buda_app) error_page('no buda app');
406
if (!has_submit_access($user, $buda_app->id)) {
407
    error_page('no access');
408
}
409
410
$action = get_str('action', true);
411
switch ($action) {
412
case 'app_form':
413
    app_form(); break;
414
case 'app_action':
415
    app_action(); break;
416
case 'app_delete':
417
    app_delete(); break;
418
case 'variant_view':
419
    variant_view($user); break;
0 ignored issues
show
Unused Code introduced by
The call to variant_view() has too many arguments starting with $user. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

419
    /** @scrutinizer ignore-call */ 
420
    variant_view($user); break;

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
420
case 'variant_form':
421
    variant_form($user); break;
422
case 'variant_action':
423
    variant_action($user);
424
    write_plan_class_file();
425
    break;
426
case 'variant_delete':
427
    variant_delete();
428
    write_plan_class_file();
429
    break;
430
case 'view_file':
431
    view_file(); break;
432
case null:
433
    app_list(); break;
434
default:
0 ignored issues
show
Coding Style introduced by
DEFAULT keyword must be indented 4 spaces from SWITCH keyword
Loading history...
Coding Style introduced by
DEFAULT case must have a breaking statement
Loading history...
435
    error_page("unknown action $action");
436
}
437
438
?>
439