Passed
Pull Request — master (#5918)
by David
13:04
created

app_list()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 17
nc 12
nop 1
dl 0
loc 35
rs 9.3888
c 1
b 0
f 0
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
// show list of BUDA apps and variants,
33
// w/ buttons for adding and deleting
34
//
35
function app_list($notice=null) {
36
    global $buda_root;
37
    if (!is_dir($buda_root)) {
38
        mkdir($buda_root);
39
    }
40
    page_head('BUDA science apps');
41
    if ($notice) {
42
        echo "$notice <p>\n";
43
    }
44
    text_start();
45
    echo "
46
        <p>BUDA (BOINC Universal Docker App)
47
        lets you submit Docker jobs through a web interface;
48
        you don't need to log into the BOINC server.
49
        <p>
50
        To use BUDA, you set up a 'science app' and one or more 'variants'.
51
        Each variant includes a Dockerfile,
52
        a main program to run within the container,
53
        and any other files that are needed.
54
        <p>
55
        Typically there is a variant named 'cpu' that uses one CPU.
56
        The names of other variants are plan class names;
57
        these can be versions that use a GPU or multiple CPUs.
58
    ";
59
60
    echo "<h2>Science apps</h2>";
61
    $dirs = scandir($buda_root);
62
    foreach ($dirs as $dir) {
63
        if ($dir[0] == '.') continue;
64
        show_app($dir);
65
    }
66
    echo '<hr>';
67
    show_button_small('buda.php?action=app_form', 'Add science app');
68
    text_end();
69
    page_tail();
70
}
71
72
function show_app($dir) {
73
    global $buda_root;
74
    $indent = "&nbsp;&nbsp;&nbsp;&nbsp&nbsp;&nbsp;&nbsp;&nbsp";
75
    echo "<hr><font size=+3>$dir</font>\n";
76
    start_table('table-striped');
77
    table_header('Variant name (click for details)', 'Submit jobs');
78
    $pcs = scandir("$buda_root/$dir");
79
    foreach ($pcs as $pc) {
80
        if ($pc[0] == '.') continue;
81
        table_row(
82
            "<a href=buda.php?action=variant_view&app=$dir&variant=$pc>$pc</href>",
83
            button_text(
84
                "buda_submit.php?app=$dir&variant=$pc", "Submit"
85
            )
86
        );
87
    }
88
    end_table();
89
    echo "<p>";
90
    show_button("buda.php?action=variant_form&app=$dir", 'Add variant');
91
    echo "<p>";
92
    show_button_small(
93
        "buda.php?action=app_delete&app=$dir", "Delete science app '$dir'"
94
    );
95
}
96
97
function variant_view() {
98
    global $buda_root;
99
    $app = get_str('app');
100
    $variant = get_str('variant');
101
    page_head("App $app variant $variant");
102
    $dir = "$buda_root/$app/$variant";
103
    start_table();
104
    table_header('name', 'size', 'md5');
105
    foreach(scandir($dir) as $f) {
106
        if ($f[0] == '.') continue;
107
        [$md5, $size] = parse_info_file("$dir/.md5/$f");
108
        table_row(
109
            "<a href=buda.php?action=view_file&app=$app&variant=$variant&fname=$f>$f</a>",
110
            $size,
111
            $md5
112
        );
113
    }
114
    end_table();
115
    show_button_small(
116
        "buda.php?action=variant_delete&app=$dir&variant=$variant",
117
        'Delete variant'
118
    );
119
    page_tail();
120
}
121
122
// form for creating app variant.
123
// Currently doesn't support indirect files.
124
// doing this would require checkboxes for indirect
125
//
126
// Could have other stuff like
127
//      - min_quorum, max_total_results
128
//      - rsc_disk_bound, rsc_memory_bound
129
// or does this belong in job submission?
130
//
131
function variant_form($user) {
132
    $sbitems = sandbox_select_items($user);
133
    $app = get_str('app');
134
135
    page_head("Create variant of Docker app $app");
136
    form_start('buda.php');
137
    form_input_hidden('app', $app);
138
    form_input_hidden('action', 'variant_action');
139
    form_input_text('Plan class', 'variant');
140
    form_select('Dockerfile', 'dockerfile', $sbitems);
141
    form_select_multiple('Application files', 'app_files', $sbitems);
142
    form_input_text('Input file names', 'input_file_names');
143
    form_input_text('Output file names', 'output_file_names');
144
    form_submit('OK');
145
    form_end();
146
    page_tail();
147
}
148
149
function buda_file_phys_name($app, $variant, $md5) {
150
    return sprintf('buda_%s_%s_%s', $app, $variant, $md5);
151
}
152
153
// copy file from sandbox to variant dir, and stage to download hier
154
//
155
function copy_and_stage_file($user, $fname, $dir, $app, $variant) {
156
    copy_sandbox_file($user, $fname, $dir);
157
    [$md5, $size] = parse_info_file("$dir/.md5/$fname");
158
    $phys_name = buda_file_phys_name($app, $variant, $md5);
159
    stage_file_aux("$dir/$fname", $md5, $size, $phys_name);
160
    return $phys_name;
161
}
162
163
// create variant
164
//
165
function variant_action($user) {
166
    global $buda_root;
167
    $variant = get_str('variant');
168
    $app = get_str('app');
169
    $dockerfile = get_str('dockerfile');
170
    $app_files = get_array('app_files');
171
    $input_file_names = explode(' ', get_str('input_file_names'));
172
    $output_file_names = explode(' ', get_str('output_file_names'));
173
174
    if (file_exists("$buda_root/$app/$variant")) {
175
        error_page("Variant '$variant' already exists.");
176
    }
177
    $dir = "$buda_root/$app/$variant";
178
    mkdir($dir);
179
    mkdir("$dir/.md5");
180
181
    // create variant description JSON file
182
    //
183
    $desc = new StdClass;
184
    $desc->dockerfile = $dockerfile;
185
    $desc->app_files = $app_files;
186
    $desc->input_file_names = $input_file_names;
187
    $desc->output_file_names = $output_file_names;
188
189
    // copy files from sandbox to variant dir
190
    //
191
    $pname = copy_and_stage_file($user, $dockerfile, $dir, $app, $variant);
192
    $desc->dockerfile_phys = $pname;
193
    $desc->app_files_phys = [];
194
    foreach ($app_files as $fname) {
195
        $pname = copy_and_stage_file($user, $fname, $dir, $app, $variant);
196
        $desc->app_files_phys[] = $pname;
197
    }
198
199
    file_put_contents(
200
        "$dir/variant.json",
201
        json_encode($desc, JSON_PRETTY_PRINT)
202
    );
203
204
    // Note: we don't currently allow indirect file access.
205
    // If we did, we'd need to create job.toml to mount project dir
206
207
    app_list("Variant $variant added for app $app.");
208
}
209
210
function variant_delete() {
211
    global $buda_root;
212
    $app = get_str('app');
213
    $variant = get_str('variant');
214
    $confirmed = get_str('confirmed', true);
215
    if ($confirmed) {
216
        $dir = "$buda_root/$app/$variant";
217
        // delete staged files
218
        //
219
        foreach (scandir("$dir/.md5") as $fname) {
220
            if ($fname[0] == '.') continue;
221
            [$md5, $size] = parse_info_file("$dir/.md5/$fname");
222
            $phys_name = buda_file_phys_name($app, $variant, $md5);
223
            $phys_path = download_hier_path($phys_name);
224
            unlink($phys_path);
225
            unlink("$phys_path.md5");
226
        }
227
        system("rm -r $buda_root/$app/$variant", $ret);
228
        if ($ret) {
229
            error_page("delete failed");
230
        }
231
        $notice = "Variant $variant of app $app removed.";
232
        app_list($notice);
233
    } else {
234
        page_head("Confirm");
235
        echo "Are you sure want to delete variant $variant of app $app?
236
            <p>
237
        ";
238
        show_button(
239
            "buda.php?action=variant_delete&app=$app&variant=$variant&confirmed=yes",
240
            "Yes"
241
        );
242
        page_tail();
243
    }
244
}
245
246
function app_form() {
247
    page_head("Create Docker app");
248
    form_start();
0 ignored issues
show
Bug introduced by
The call to form_start() has too few arguments starting with action. ( Ignorable by Annotation )

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

248
    /** @scrutinizer ignore-call */ 
249
    form_start();

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
249
    form_input_text('Name', 'name');
250
    form_submit('OK');
251
    form_end();
252
    page_tail();
253
}
254
255
function app_action() {
256
    global $buda_root;
257
    $name = get_str('name');
258
    $dir = "$buda_root/$name";
259
    if (file_exists($dir)) {
260
        error_page("App $name already exists.");
261
    }
262
    mkdir($dir);
263
    header("Location: buda.php");
264
}
265
266
function view_file() {
267
    global $buda_root;
268
    $app = get_str('app');
269
    $variant = get_str('variant');
270
    $fname = get_str('fname');
271
    echo "<pre>\n";
272
    readfile("$buda_root/$app/$variant/$fname");
273
    echo "</pre>\n";
274
}
275
276
$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...
277
$action = get_str('action', true);
278
switch ($action) {
279
case 'app_form':
280
    app_form(); break;
281
case 'app_action':
282
    app_action(); break;
283
case 'app_delete':
284
    app_delete(); break;
285
case 'variant_view':
286
    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

286
    /** @scrutinizer ignore-call */ 
287
    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...
287
case 'variant_form':
288
    variant_form($user); break;
289
case 'variant_action':
290
    variant_action($user); break;
291
case 'variant_delete':
292
    variant_delete(); break;
293
case 'view_file':
294
    view_file(); break;
295
case null:
296
    app_list(); break;
297
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...
298
    error_page("unknown action $action");
299
}
300
301
?>
302