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

Make the user visible patterns configurable, added MicroOS products #916

Merged
merged 15 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions .yupdate.post
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ function restart_service() {

if [ -n "$SERVICE_START" ]; then
SERVICE_START_UNIX_TIME=$(date -d "$SERVICE_START" +"%s")
# find the date of the latest file in the gem
NEWEST_FILE_TIME=$(find /usr/lib*/ruby/gems/*/gems/$SERVICE_NAME-* -exec stat --format %Y "{}" \; | sort -nr | head -n 1)
# find the date of the latest file in the product configuration or in the gem
NEWEST_FILE_TIME=$(find /usr/share/agama/products.d /usr/lib*/ruby/gems/*/gems/$SERVICE_NAME-* -exec stat --format %Y "{}" \; | sort -nr | head -n 1)

# when a file is newer than the start time then restart the service
if [ -n "$NEWEST_FILE_TIME" ] && [ "$SERVICE_START_UNIX_TIME" -lt "$NEWEST_FILE_TIME" ]; then
Expand Down
16 changes: 16 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,21 @@ if ENV["YUPDATE_FORCE"] == "1" || File.exist?("/.packages.initrd") || live_iso?
FileUtils.mkdir_p(File.join(destdir, "/usr/share"))
FileUtils.cp_r("playwright/.", File.join(destdir, "/usr/share/agama-playwright"))
end

if ENV["YUPDATE_SKIP_PRODUCTS"] != "1"
files = Dir.glob("products.d/*.y{a}ml")
files.each do |f|
# the sources contain several products, update only the existing files
oldfile = File.join("/usr/share/agama/", f)
if File.exist?(oldfile)
target = File.join(destdir, "/usr/share/agama/", f)
FileUtils.mkdir_p(File.dirname(target))
FileUtils.cp(f, target)
else
# if there is a new product file it needs to be copied manually
Copy link
Contributor

Choose a reason for hiding this comment

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

Without putting too much thinking into it... not sure if this is the behavior I would expect.

Copy link
Contributor

Choose a reason for hiding this comment

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

well, problem is that it is hard to know which product belongs to which iso

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem is that the Git repository contains YAML files for all product. If you are patching the openSUSE Agama installer then the Live medium contains only the openSUSE products.

If yupdate would install all files you would suddenly see the ALP Dolomite product in the list, that would be confusing. So I decided to patch only the existing files and to not blindly copy everything available. IMHO it is better to have a safer default behavior.

puts "Skipping product file: #{f}"
end
end
end
end
end
2 changes: 2 additions & 0 deletions products.d/ALP-Dolomite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ software:
- alp_cockpit
- alp_hardware
optional_patterns: null # no optional pattern shared
# no user selectable patterns, do not display the pattern selector
user_patterns: []
mandatory_packages:
- package: ppc64-diag # Needed for hardware-based installations
archs: ppc64
Expand Down
2 changes: 2 additions & 0 deletions products.d/leap16.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ software:
- alp-container_runtime
- alp_defaults
optional_patterns: null # no optional pattern shared
user_patterns:
- alp_selinux
mandatory_packages: null
optional_packages: null
base_product: Leap16
Expand Down
9 changes: 9 additions & 0 deletions products.d/tumbleweed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ software:
mandatory_patterns:
- enhanced_base # only pattern that is shared among all roles on TW
optional_patterns: null # no optional pattern shared
user_patterns:
- basic_desktop
- xfce
- kde
- gnome
- yast2_x11
- yast2_server
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure if it makes sense to have all three. Maybe just desktop and server yast modules will be enough?

Copy link
Contributor Author

@lslezak lslezak Dec 6, 2023

Choose a reason for hiding this comment

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

I'm not sure. If you want to have YaST just for the package management then the yast2_basis is enough. On the other hand the yast_desktop pattern adds just few megabytes more, not a big deal...

- multimedia
- office
mandatory_packages:
- NetworkManager
optional_packages: null
Expand Down
4 changes: 2 additions & 2 deletions service/lib/agama/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ def merge(config)
# @param property [Symbol|String|nil] Property to retrieve of the elements.
#
# @return [Array]
def arch_elements_from(*keys, property: nil)
def arch_elements_from(*keys, property: nil, default: [])
lslezak marked this conversation as resolved.
Show resolved Hide resolved
keys.map!(&:to_s)
elements = products.dig(*keys)
return [] unless elements.is_a?(Array)
return default unless elements.is_a?(Array)

elements.map do |element|
if !element.is_a?(Hash)
Expand Down
5 changes: 5 additions & 0 deletions service/lib/agama/software/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ def patterns(filtered)
patterns = Y2Packager::Resolvable.find({ kind: :pattern }, preload)
patterns = patterns.select(&:user_visible) if filtered

# only display the configured patterns
if product.user_patterns
patterns.select! {|p| product.user_patterns.include?(p.name)}
end

patterns
end

Expand Down
7 changes: 7 additions & 0 deletions service/lib/agama/software/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ class Product
# @return [Array<String>]
attr_accessor :optional_patterns

# Optional user selectable patterns
#
# @return [Array<String>]
attr_accessor :user_patterns

# Product translations.
#
# @example
Expand All @@ -94,6 +99,8 @@ def initialize(id)
@optional_packages = []
@mandatory_patterns = []
@optional_patterns = []
# nil = display all patterns, [] = display none patterns
lslezak marked this conversation as resolved.
Show resolved Hide resolved
@user_patterns = nil
@translations = {}
end

Expand Down
4 changes: 4 additions & 0 deletions service/lib/agama/software/product_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def build
product.optional_packages = data[:optional_packages]
product.mandatory_patterns = data[:mandatory_patterns]
product.optional_patterns = data[:optional_patterns]
product.user_patterns = data[:user_patterns]
product.translations = attrs["translations"] || {}
end
end
Expand Down Expand Up @@ -79,6 +80,9 @@ def product_data_from_config(id)
),
optional_patterns: config.arch_elements_from(
id, "software", "optional_patterns", property: :pattern
),
user_patterns: config.arch_elements_from(
id, "software", "user_patterns", property: :pattern, default: nil
)
}
end
Expand Down
11 changes: 7 additions & 4 deletions web/src/components/overview/SoftwareSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const initialState = {
errors: [],
errorsRead: false,
size: "",
patterns: {},
progress: { message: _("Reading software repositories"), current: 0, total: 0, finished: false }
};

Expand All @@ -51,8 +52,8 @@ const reducer = (state, action) => {
case "UPDATE_PROPOSAL": {
if (state.busy) return state;

const { errors, size } = action.payload;
return { ...state, errors, size, errorsRead: true };
const { errors, size, patterns } = action.payload;
return { ...state, errors, size, patterns, errorsRead: true };
}

default: {
Expand Down Expand Up @@ -82,8 +83,9 @@ export default function SoftwareSection({ showErrors }) {
const updateProposal = async () => {
const errors = await cancellablePromise(client.getIssues());
const size = await cancellablePromise(client.getUsedSpace());
const patterns = await cancellablePromise(client.patterns(true));

dispatch({ type: "UPDATE_PROPOSAL", payload: { errors, size } });
dispatch({ type: "UPDATE_PROPOSAL", payload: { errors, size, patterns } });
};

updateProposal();
Expand Down Expand Up @@ -142,7 +144,8 @@ export default function SoftwareSection({ showErrors }) {
icon="apps"
loading={state.busy}
errors={errors.map(toValidationError)}
path="/software"
// do not display the pattern selector when there are no patterns to display
path={Object.keys(state.patterns).length > 0 ? "/software" : null}
>
<SectionContent />
</Section>
Expand Down
35 changes: 35 additions & 0 deletions web/src/components/overview/SoftwareSection.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,20 @@ import { SoftwareSection } from "~/components/overview";

jest.mock("~/client");

const kdePattern = {
kde: [
"Graphical Environments",
"Packages providing the Plasma desktop environment and applications from KDE.",
"./pattern-kde",
"KDE Applications and Plasma 5 Desktop",
"1110"
]
};

let getStatusFn = jest.fn().mockResolvedValue(IDLE);
let getProgressFn = jest.fn().mockResolvedValue({});
let getIssuesFn = jest.fn().mockResolvedValue([]);
let patternsFn = jest.fn().mockResolvedValue(kdePattern);

beforeEach(() => {
createClient.mockImplementation(() => {
Expand All @@ -42,6 +53,7 @@ beforeEach(() => {
getIssues: getIssuesFn,
onStatusChange: noop,
onProgressChange: noop,
patterns: patternsFn,
getUsedSpace: jest.fn().mockResolvedValue("500 MB")
},
};
Expand All @@ -51,6 +63,7 @@ beforeEach(() => {
describe("when the proposal is calculated", () => {
beforeEach(() => {
getStatusFn = jest.fn().mockResolvedValue(IDLE);
patternsFn = jest.fn().mockResolvedValue(kdePattern);
});

it("renders the required space", async () => {
Expand All @@ -59,6 +72,28 @@ describe("when the proposal is calculated", () => {
await screen.findByText("500 MB");
});

describe("patterns are available", () => {
it("the header is a link", async () => {
const { container } = installerRender(<SoftwareSection showErrors />);
// wait until the component is fully rendered
await screen.findByText("Installation will take");
expect(container.querySelector("h2 a[href='/software']")).not.toBeNull();
});
});

describe("no patterns are available", () => {
beforeEach(() => {
patternsFn = jest.fn().mockResolvedValue({});
});

it("the header is a plain text", async () => {
const { container } = installerRender(<SoftwareSection showErrors />);
// wait until the component is fully rendered
await screen.findByText("Installation will take");
expect(container.querySelector("h2 a")).toBeNull();
});
});

describe("and there are errors", () => {
beforeEach(() => {
getIssuesFn = jest.fn().mockResolvedValue([{ description: "Could not install..." }]);
Expand Down
Loading