Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion openc3-cosmos-cmd-tlm-api/app/controllers/plugins_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,26 @@ def update
create(true)
end

# Dry run before an upgrade: returns which modified files differ from the
# incoming plugin's rendered content so the UI can warn the user that those
# modifications will be superseded (a new Version History entry is created).
def modified_diff
return unless authorization('admin')
begin
scope = sanitize_params([:scope])
return unless scope
scope = scope[0]
plugin_hash = JSON.parse(params[:plugin_hash])
files = OpenC3::PluginModel.modified_diff(plugin_hash, scope: scope)
render json: { files: files }
rescue JSON::ParserError => error
render json: { status: 'error', message: error.message }, status: :bad_request
rescue Exception => error
logger.error(error.formatted)
render json: { status: 'error', message: error.message }, status: :internal_server_error
end
end

def install
return unless authorization('admin')
begin
Expand All @@ -194,8 +214,22 @@ def install
temp_dir = Dir.mktmpdir
plugin_hash_filename = Dir::Tmpname.create(['plugin-instance-', '.json']) {}
plugin_hash_file_path = File.join(temp_dir, File.basename(plugin_hash_filename))
# Stamp the installing user server-side (never trust a client-supplied
# username) so Version History can attribute plugin-upgrade versions of
# modified files. Falls back to writing the body verbatim if it isn't
# the expected JSON object.
plugin_hash_body = params[:plugin_hash]
begin
parsed = JSON.parse(params[:plugin_hash])
if parsed.is_a?(Hash)
parsed['username'] = username()
plugin_hash_body = JSON.generate(parsed)
end
rescue JSON::ParserError
# leave plugin_hash_body as the original string
end
File.open(plugin_hash_file_path, 'wb') do |file|
file.write(params[:plugin_hash])
file.write(plugin_hash_body)
end

gem_name = sanitize_params([:id])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ def delete_modified
return unless authorization('system')
scope, id = sanitize_params([:scope, :id], require_params: true)
return unless scope
# Optional: delete only specific modified files (e.g. plugin upgrade
# removing non-script modifications while keeping versioned scripts).
files = params[:files]
files = nil unless files.is_a?(Array)
begin
@model_class.delete_modified(id, scope: scope)
@model_class.delete_modified(id, scope: scope, files: files)
head :ok
rescue Exception => e
log_error(e)
Expand Down
1 change: 1 addition & 0 deletions openc3-cosmos-cmd-tlm-api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
resources :permissions, only: [:index]

post '/plugins/install/:id', to: 'plugins#install', id: /[^\/]+/
post '/plugins/modified_diff', to: 'plugins#modified_diff'
resources :plugins, only: [:index, :create]
get '/plugins/:id', to: 'plugins#show', id: /[^\/]+/
match '/plugins/:id', to: 'plugins#update', id: /[^\/]+/, via: [:patch, :put]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,30 @@
end
end

describe "POST modified_diff" do
it "returns the list of modified files that differ from the plugin" do
allow(OpenC3::PluginModel).to receive(:modified_diff)
.with({"name" => "x"}, scope: "DEFAULT").and_return(["INST/screen.txt"])

post :modified_diff, params: {scope: "DEFAULT", plugin_hash: '{"name":"x"}'}
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["files"]).to eq(["INST/screen.txt"])
end

it "returns bad_request on invalid plugin_hash JSON" do
post :modified_diff, params: {scope: "DEFAULT", plugin_hash: "not json"}
expect(response).to have_http_status(:bad_request)
json = JSON.parse(response.body)
expect(json["status"]).to eq("error")
end

it "returns nothing without authorization" do
post :modified_diff, params: {plugin_hash: "{}"}
expect(response).to have_http_status(:unauthorized)
end
end

describe "POST create" do
before(:each) do
@file = Tempfile.new(["test-plugin", ".gem"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,27 @@
expect(ret['message']).to eql('Invalid scope: ../DEFAULT')
end
end

describe "delete_modified" do
it "forwards a files list to the model" do
expect(OpenC3::TargetModel).to receive(:delete_modified)
.with("INST", scope: "DEFAULT", files: ["INST/screens/a.txt"])
post :delete_modified, params: { scope: "DEFAULT", id: "INST", files: ["INST/screens/a.txt"] }
expect(response).to have_http_status(:ok)
end

it "passes files: nil when no files param is given" do
expect(OpenC3::TargetModel).to receive(:delete_modified)
.with("INST", scope: "DEFAULT", files: nil)
post :delete_modified, params: { scope: "DEFAULT", id: "INST" }
expect(response).to have_http_status(:ok)
end

it "passes files: nil when files is not an array" do
expect(OpenC3::TargetModel).to receive(:delete_modified)
.with("INST", scope: "DEFAULT", files: nil)
post :delete_modified, params: { scope: "DEFAULT", id: "INST", files: "oops" }
expect(response).to have_http_status(:ok)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# See LICENSE.md for more details.

# Modified by OpenC3, Inc.
# All changes Copyright 2022, OpenC3, Inc.
# All changes Copyright 2026, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license
Expand All @@ -27,6 +27,7 @@ const request = async function (
noAuth = false,
noScope = false,
onUploadProgress = false,
responseType,
} = {},
) {
if (!noAuth) {
Expand Down Expand Up @@ -56,6 +57,7 @@ const request = async function (
params,
headers,
onUploadProgress,
responseType,
})
}

Expand All @@ -77,6 +79,7 @@ export default {
noScope,
noAuth,
onUploadProgress,
responseType,
} = {},
) {
return request('get', path, {
Expand All @@ -85,6 +88,7 @@ export default {
noScope,
noAuth,
onUploadProgress,
responseType,
})
},

Expand Down Expand Up @@ -118,6 +122,7 @@ export default {
noScope,
noAuth,
onUploadProgress,
responseType,
} = {},
) {
return request('post', path, {
Expand All @@ -127,6 +132,7 @@ export default {
noScope,
noAuth,
onUploadProgress,
responseType,
})
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"date-fns-tz": "3.2.0",
"dompurify": "3.4.7",
"lodash": "4.18.1",
"monaco-editor": "0.54.0",
"pinia": "3.0.4",
"sass": "1.100.0",
"semver": "7.8.1",
Expand Down
Loading
Loading