Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: report unsupported AutoYaST elements #1976

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e21a294
refactor(ruby): split profile fetching and conversion
imobachgs Feb 4, 2025
25d328a
feat(ruby): find unsupported elements in the AutoYaST profile
imobachgs Feb 4, 2025
84915a2
feat(ruby): report AutoYaST unsupported elements
imobachgs Feb 5, 2025
682381b
feat: extend the AutoYaST profile definition
imobachgs Feb 6, 2025
f8abcb6
feat(ruby): improve support level calculation
imobachgs Feb 6, 2025
a1e8ff3
feat(ruby): add network elements to AutoYaST compatibility
imobachgs Feb 6, 2025
bc0fbd4
fix(ruby): make RuboCop happy
imobachgs Feb 6, 2025
1e8ecaf
chore(ruby): remove useless shebang lines
imobachgs Feb 6, 2025
56fd289
fix(ruby): rake task to the top-level Rakefile
imobachgs Feb 6, 2025
d5ec2b6
fix(ruby): fix handling of unknown AutoYaST elements
imobachgs Feb 6, 2025
b621c12
feat(web): add an specific question to handle AutoYaST unsupported el…
imobachgs Feb 6, 2025
dcdab34
chore(ruby): drop ProfileFetcher#copy_files (unused)
imobachgs Feb 7, 2025
fb9039f
fix(ruby): do not read the profile if it was not fetched
imobachgs Feb 7, 2025
f33c81e
fix(ruby): use a slash (/) for AutoYaST element path separator
imobachgs Feb 7, 2025
5b14f30
feat(web): improve UnsupportedAutoYaST component
imobachgs Feb 7, 2025
2b110d6
chore(ruby): allow disabling the AutoYaST check
imobachgs Feb 7, 2025
744c297
fix(ruby): fix ProfileFetcher documentation
imobachgs Feb 7, 2025
9ee3b4a
docs: update changes files
imobachgs Feb 7, 2025
a9e9dd7
Merge branch 'master' into profile-checker
imobachgs Feb 7, 2025
6795940
fix(ruby): fix location of commands.rb
imobachgs Feb 7, 2025
eb8ead1
fix(ruby): add unit tests for the AgamaAutoYaST command
imobachgs Feb 7, 2025
be389d4
fix(web): improve wording in the UnsupportedAutoYaST component
imobachgs Feb 7, 2025
e5519e6
docs: describe the autoyast.unsupported question
imobachgs Feb 7, 2025
9044b34
autoyast compat: declare "bootproto" unsupported
mvidner Feb 7, 2025
3fefbf5
Added JSON schema for autoyast-compat.json
mvidner Feb 7, 2025
1086efc
feat(web): explain how to disable AutoYaST checks
imobachgs Feb 10, 2025
6b4cfb1
fix(ruby): disable AutoYaST XML validation in agama-autoyast
imobachgs Feb 10, 2025
35fa4dd
Merge branch 'master' into profile-checker
imobachgs Feb 10, 2025
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
11 changes: 11 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

$LOAD_PATH.unshift File.expand_path("service/lib", __dir__)

require "shellwords"
require "fileutils"
require "yast/rake"
require_relative "service/lib/tasks/autoyast"

# Infers the gem name from the source code
#
Expand Down Expand Up @@ -209,3 +212,11 @@ if ENV["YUPDATE_FORCE"] == "1" || File.exist?("/.packages.initrd") || live_iso?
end
end
end

desc "Documentation tasks"
namespace :doc do
desc "Generate the AutoYaST compatibility documentation"
task :autoyast_compat do
puts Agama::Tasks::AutoYaSTCompatGenerator.new.generate
end
end
2 changes: 2 additions & 0 deletions doc/questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ Sensitive answers or params will be replaced, so the user has to explicitly spec

| class | description | possible answers | available data | notes |
|--- |--- |--- |--- |--- |
| `autoyast.unsupported` | When there are unsupported elements in an AutoYaST profile | `Abort` `Continue` | `planned` elements to be supported in the future, `unsupported` unsupported elements | |
| `software.medium_error` | When there is issue with access to medium | `Retry` `Skip` | `url` with url where failed access happen | |
| `software.unsigned_file` | When file from repository is not digitally signed. If it should be used | `Yes` `No` | `filename` with name of file | |
| `software.import_gpg` | When signature is sign with unknown GPG key | `Trust` `Skip` | `id` of key `name` of key and `fingerprint` of key | |
| `storage.activate_multipath` | When it looks like system has multipath and if it should be activated | `yes` `no` | | Here it is used lower case. It should be unified. |
| `storage.commit_error` | When some storage actions failed and if it should continue | `yes` `no` | | Also here it is lowercase |
| `storage.luks_activation` | When LUKS encrypted device is detected and it needs password to probe it | `skip` `decrypt` | `device` name, `label` of device, `size` of device and `attempt` the number of attempt | Answer contain additional field password that has to be filled if answer is `decrypt`. Attempt data can be used to limit passing wrong password. |

19 changes: 13 additions & 6 deletions service/bin/agama-autoyast
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ require "rubygems"
Dir.chdir(__dir__) do
require "bundler/setup"
end
require "agama/autoyast/converter"

require "agama/commands/agama_autoyast"

if ARGV.length != 2
warn "Usage: #{$PROGRAM_NAME} URL DIRECTORY"
Expand All @@ -42,9 +43,15 @@ end

begin
url, directory = ARGV
converter = Agama::AutoYaST::Converter.new(url)
converter.to_agama(directory)
rescue RuntimeError => e
warn "Could not load the profile from #{url}: #{e}"
exit 2
result = Agama::Commands::AgamaAutoYaST.new(url, directory).run
if !result
warn "Did not convert the profile (canceled by the user)."
exit 2
end
rescue Agama::Commands::CouldNotFetchProfile
warn "Could not fetch the AutoYaST profile."
exit 3
rescue Agama::Commands::CouldNotWriteAgamaConfig
warn "Could not write the Agama configuration."
exit 4
end
1 change: 0 additions & 1 deletion service/lib/agama/autoyast/bond_reader.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
Expand Down
1 change: 0 additions & 1 deletion service/lib/agama/autoyast/connections_reader.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
Expand Down
77 changes: 1 addition & 76 deletions service/lib/agama/autoyast/converter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
Expand Down Expand Up @@ -50,73 +49,6 @@ module AutoYaST
# TODO: handle invalid profiles (YAST_SKIP_XML_VALIDATION).
# TODO: capture reported errors (e.g., via the Report.Error function).
class Converter
# @param profile_url [String] Profile URL
def initialize(profile_url)
@profile_url = profile_url
end

# Converts the profile into a set of files that Agama can process.
#
# @param dir [Pathname,String] Directory to write the profile.
def to_agama(dir)
path = Pathname(dir)
FileUtils.mkdir_p(path)
import_yast
profile = read_profile
File.write(path.join("autoinst.json"), export_profile(profile).to_json)
end

private

attr_reader :profile_url

def copy_profile; end

# @return [Hash] AutoYaST profile
def read_profile
FileUtils.mkdir_p(Yast::AutoinstConfig.profile_dir)

# fetch the profile
Yast::AutoinstConfig.ParseCmdLine(profile_url)
Yast::ProfileLocation.Process

# put the profile in the tmp directory
FileUtils.cp(
Yast::AutoinstConfig.xml_tmpfile,
tmp_profile_path
)

loop do
Yast::Profile.ReadXML(tmp_profile_path)
run_pre_scripts
break unless File.exist?(Yast::AutoinstConfig.modified_profile)

FileUtils.cp(Yast::AutoinstConfig.modified_profile, tmp_profile_path)
FileUtils.rm(Yast::AutoinstConfig.modified_profile)
end

Yast::Profile.current
end

def run_pre_scripts
pre_scripts = Yast::Profile.current.fetch_as_hash("scripts")
.fetch_as_array("pre-scripts")
.map { |h| Y2Autoinstallation::PreScript.new(h) }
script_runner = Y2Autoinstall::ScriptRunner.new

pre_scripts.each do |script|
script.create_script_file
script_runner.run(script)
end
end

def tmp_profile_path
@tmp_profile_path ||= File.join(
Yast::AutoinstConfig.profile_dir,
"autoinst.xml"
)
end

# Sections which have a corresponding reader. The reader is expected to be
# named in Pascal case and adding "Reader" as suffix (e.g., "L10nReader").
SECTIONS = ["l10n", "product", "root", "scripts", "software", "storage", "user"].freeze
Expand All @@ -126,21 +58,14 @@ def tmp_profile_path
# It goes through the list of READERS and merges the results of all of them.
#
# @return [Hash] Agama profile
def export_profile(profile)
def to_agama(profile)
SECTIONS.reduce({}) do |result, section|
require "agama/autoyast/#{section}_reader"
klass = "#{section}_reader".split("_").map(&:capitalize).join
reader = Agama::AutoYaST.const_get(klass).new(profile)
result.merge(reader.read)
end
end

def import_yast
Yast.import "AutoinstConfig"
Yast.import "AutoinstScripts"
Yast.import "Profile"
Yast.import "ProfileLocation"
end
end
end
end
1 change: 0 additions & 1 deletion service/lib/agama/autoyast/l10n_reader.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
Expand Down
1 change: 0 additions & 1 deletion service/lib/agama/autoyast/network_reader.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
Expand Down
1 change: 0 additions & 1 deletion service/lib/agama/autoyast/product_reader.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
Expand Down
69 changes: 69 additions & 0 deletions service/lib/agama/autoyast/profile_checker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License 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, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "agama/autoyast/profile_description"

module Agama
module AutoYaST
# This class checks an AutoYaST profile and determines which unsupported elements are used.
#
# It does not report unknown elements.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what happens with unknown elements? Are they silently ignored now or does a different part of Agama report them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to ignore them by now and report only the status of the known elements. The idea is to extend the compat file to cover all the AutoYaST elements, stating whether it is unsupported, supported or planned.

class ProfileChecker
# Finds unsupported profile elements.
#
# @param profile [Yast::ProfileHash] AutoYaST profile to check
# @return [Array<ProfileElement>] List of unsupported elements
def find_unsupported(profile)
description = ProfileDescription.load
elements = elements_from(profile)

elements.map do |e|
normalized_key = e.gsub(/\[\d+\]/, "[]")
element = description.find_element(normalized_key)
element unless element&.supported?
end.compact
end

private

# Returns the elements from the profile
#
# @return [Array<String>] List of element IDs (e.g., "networking/backend")
def elements_from(profile, parent = "")
return [] unless profile.is_a?(Hash)

profile.map do |k, v|
current = parent.empty? ? k : "#{parent}#{ProfileDescription::SEPARATOR}#{k}"

children = if v.is_a?(Array)
v.map.with_index do |e, i|
elements_from(e, "#{parent}#{ProfileDescription::SEPARATOR}#{k}[#{i}]")
end
else
elements_from(v, k)
end

[current, *children]
end.flatten
end
end
end
end
Loading