Passed
Push — dpa_web17 ( ac852e )
by David
09:15
created

variant_action()   F

Complexity

Conditions 13
Paths 868

Size

Total Lines 68
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 43
c 1
b 0
f 0
nc 868
nop 1
dl 0
loc 68
rs 2.6333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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      <executable/>\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)) {
243
        error_page(filename_rules());
244
    }
245
    $app = get_str('app');
246
    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...
247
    $dockerfile = get_str('dockerfile');
248
    if (!is_valid_filename($dockerfile)) {
249
        error_page("Invalid dockerfile name: ".filename_rules());
250
    }
251
    $app_files = get_array('app_files');
252
    foreach ($app_files as $fname) {
253
        if (!is_valid_filename($fname)) {
254
            error_page("Invalid app file name: ".filename_rules());
255
        }
256
    }
257
    $input_file_names = explode(' ', get_str('input_file_names'));
258
    $output_file_names = explode(' ', get_str('output_file_names'));
259
    foreach ($input_file_names as $fname) {
260
        if (!is_valid_filename($fname)) {
261
            error_page("Invalid input file name: ".filename_rules());
262
        }
263
    }
264
    foreach ($output_file_names as $fname) {
265
        if (!is_valid_filename($fname)) {
266
            error_page("Invalid output file name: ".filename_rules());
267
        }
268
    }
269
270
    if (file_exists("$buda_root/$app/$variant")) {
271
        error_page("Variant '$variant' already exists.");
272
    }
273
    $dir = "$buda_root/$app/$variant";
274
    mkdir($dir);
275
    mkdir("$dir/.md5");
276
277
    // create variant description JSON file
278
    //
279
    $desc = new StdClass;
280
    $desc->dockerfile = $dockerfile;
281
    $desc->app_files = $app_files;
282
    $desc->input_file_names = $input_file_names;
283
    $desc->output_file_names = $output_file_names;
284
285
    // copy files from sandbox to variant dir
286
    //
287
    $pname = copy_and_stage_file($user, $dockerfile, $dir, $app, $variant);
288
    $desc->dockerfile_phys = $pname;
289
    $desc->app_files_phys = [];
290
    foreach ($app_files as $fname) {
291
        $pname = copy_and_stage_file($user, $fname, $dir, $app, $variant);
292
        $desc->app_files_phys[] = $pname;
293
    }
294
295
    file_put_contents(
296
        "$dir/variant.json",
297
        json_encode($desc, JSON_PRETTY_PRINT)
298
    );
299
300
    create_templates($variant, $desc, $dir);
301
302
    // Note: we don't currently allow indirect file access.
303
    // If we did, we'd need to create job.toml to mount project dir
304
305
    app_list("Variant $variant added for app $app.");
306
}
307
308
function variant_delete() {
309
    global $buda_root;
310
    $app = get_str('app');
311
    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...
312
    $variant = get_str('variant');
313
    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...
314
    $confirmed = get_str('confirmed', true);
315
    if ($confirmed) {
316
        $dir = "$buda_root/$app/$variant";
317
        if (!file_exists($dir)) error_page('no such variant');
318
        // delete staged files
319
        //
320
        foreach (scandir("$dir/.md5") as $fname) {
321
            if ($fname[0] == '.') continue;
322
            [$md5, $size] = parse_info_file("$dir/.md5/$fname");
323
            $phys_name = buda_file_phys_name($app, $variant, $md5);
324
            $phys_path = download_hier_path($phys_name);
325
            unlink($phys_path);
326
            unlink("$phys_path.md5");
327
        }
328
        system("rm -r $buda_root/$app/$variant", $ret);
329
        if ($ret) {
330
            error_page("delete failed");
331
        }
332
        $notice = "Variant $variant of app $app removed.";
333
        app_list($notice);
334
    } else {
335
        page_head("Confirm");
336
        echo "Are you sure you want to delete variant $variant of app $app?  <p>";
337
        show_button(
338
            "buda.php?action=variant_delete&app=$app&variant=$variant&confirmed=yes",
339
            "Yes"
340
        );
341
        page_tail();
342
    }
343
}
344
345
function app_delete() {
346
    global $buda_root;
347
    $app = get_str('app');
348
    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...
349
    $confirmed = get_str('confirmed', true);
350
    if ($confirmed) {
351
        $dir = "$buda_root/$app";
352
        if (!file_exists($dir)) error_page('no such app');
353
        foreach (scandir($dir) as $fname) {
354
            if ($fname[0] == '.') continue;
355
            error_page("You must delete all variants first.");
356
        }
357
        system("rmdir $buda_root/$app", $ret);
358
        if ($ret) {
359
            error_page('delete failed');
360
        }
361
        $notice = "App $app removed.";
362
        app_list($notice);
363
    } else {
364
        page_head('Confirm');
365
        echo "Are you sure you want to delete app $app?  <p>";
366
        show_button(
367
            "buda.php?action=app_delete&app=$app&confirmed=yes",
368
            "Yes"
369
        );
370
        page_tail();
371
    }
372
}
373
374
function app_form() {
375
    page_head('Create Docker app');
376
    form_start('buda.php');
377
    form_input_hidden('action', 'app_action');
378
    form_input_text('Name', 'name');
379
    form_submit('OK');
380
    form_end();
381
    page_tail();
382
}
383
384
function app_action() {
385
    global $buda_root;
386
    $name = get_str('name');
387
    if (!is_valid_filename($name)) {
388
        error_page(filename_rules());
389
    }
390
    $dir = "$buda_root/$name";
391
    if (file_exists($dir)) {
392
        error_page("App $name already exists.");
393
    }
394
    mkdir($dir);
395
    header("Location: buda.php");
396
}
397
398
function view_file() {
399
    global $buda_root;
400
    $app = get_str('app');
401
    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...
402
    $variant = get_str('variant');
403
    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...
404
    $fname = get_str('fname');
405
    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...
406
    echo "<pre>\n";
407
    readfile("$buda_root/$app/$variant/$fname");
408
    echo "</pre>\n";
409
}
410
411
// check access.
412
// Anyone with submit access to BUDA can add/delete apps and variants.
413
// Might want to refine this at some point
414
415
$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...
416
$buda_app = BoincApp::lookup("name='buda'");
417
if (!$buda_app) error_page('no buda app');
418
if (!has_submit_access($user, $buda_app->id)) {
419
    error_page('no access');
420
}
421
422
$action = get_str('action', true);
423
switch ($action) {
424
case 'app_form':
425
    app_form(); break;
426
case 'app_action':
427
    app_action(); break;
428
case 'app_delete':
429
    app_delete(); break;
430
case 'variant_view':
431
    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

431
    /** @scrutinizer ignore-call */ 
432
    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...
432
case 'variant_form':
433
    variant_form($user); break;
434
case 'variant_action':
435
    variant_action($user);
436
    write_plan_class_file();
437
    break;
438
case 'variant_delete':
439
    variant_delete();
440
    write_plan_class_file();
441
    break;
442
case 'view_file':
443
    view_file(); break;
444
case null:
445
    app_list(); break;
446
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...
447
    error_page("unknown action");
448
}
449
450
?>
451