Skip to content

Instantly share code, notes, and snippets.

@drmuey
Last active January 20, 2021 20:24
Show Gist options
  • Save drmuey/3d5bc39d8efaf526e1df0f1d6fe47383 to your computer and use it in GitHub Desktop.
Save drmuey/3d5bc39d8efaf526e1df0f1d6fe47383 to your computer and use it in GitHub Desktop.
Design doc for os abstraction layer

NAME

Cpanel::OS - Abstract interface to the OS to obviate the need for if-this-os-do-this-elsif-elsif-else tech debt

SYNOPSIS

    my $os = Cpanel::OS->instance;
    my $pm = Cpanel::PackMan->instance;

    $pm->sys->install( $os->packages("bootstrap") );

as opposed to crufty tech debt version we’ve come to know and not love:

    if ($x eq "centos" && $y < 7) {
        @packages = …;
    }
    elsif ($x eq "cloudlinux" && $y > 6) {
        @packages = …;
    }
    elsif (…
        …

    $x eq "unbuntu" ? call_apt_to_install(@packages) : $y < 8 ? call_yum(@packages) : call_dnf(@packages);

DESCRIPTION

The goal is to make an abstract interface to OS specific things so our code can be as OS agnostic as possible.

In other words, there should be very few places normal code should be doing things based on the OS information.

Such places should contain an explanation justifying why it must be done that way.

DESIGN

Problem Space

It was historically very difficult to add support in cPanel for a new OS. The reason why is because OS variations are handled by logic checking the os name and version to determine what the thing in question needs.

That is undesirable for a number of reasons, including but not limited to:

  • We have to update hundreds if not thousands of places arbitrarily strewn through our code base
  • As we learn we need to make another adjustment we repeat the mass find and edit
  • It adds more complexity that needs tested and documented

These problems are expensive to address, result in bad UX, and limit us due to ever increasing tech debt.

Overall Approach

Instead of code asking about os names and numbers to figuring out what it needs depending on the OS, have it ask Cpanel::OS for it.

Put another way: In code like that we are really asking “What is it we need here?” so just ask that question.

We are rarely interested in the distro and version numbers; instead we are interested in finding the information I need, information that can change between OSs.

Cpanel::OS aims to let code ask the question we’re really asking: hey, what foo should I use? Without needing to understand anything about the OS.

For example, take this simple need:

    sub get_foo {
        my $foo;
        if ($dist eq "centos" && $major > 6) {
            $foo = "x";
        }
        elsif ($dist eq "centos" && $major < 6) {
            $foo = "y";
        }

        return $foo;
    }

    …

    my $foo = get_foo();
    process_foo($foo);

Did you see any of the problems? There are at least 8:

  1. Bug: does not set $foo for CentOS 6
  2. Would get more fragile is we needed a different foo on CentOS 8.2 and 8.3
  3. Does not differentiate between cloudinux and centos
  4. more code needs added to add a new OS, e.g. ubuntu
  5. we have to find this code when adding a new OS
  6. it can be undefined leading to unexpected result
  7. it contains logic for unsupported OSs
  8. ever more complex doc and tests need updated as get_foo() changes

Now take the Cpanel::OS version:

    my $foo = $os->value("foo");
    process_foo($foo);

All 8 (and the others) are non-issues now.

Some of the benefits:

  • A new OS can be added by defining its data.
  • An old OS can removed by removing its data.
  • An OS can define data that differs within itself
  • It results in far less code to write, test, and document.
  • As we switch code to support ubuntu we can remove systems that support the undesireable approach, for example, Cpanel::GenSysInfo and similar. Less code and more consistency.

Goals/Requirements

Make it easy to define an OS, in git, w/ the ability to differentiate from generic (v42) to specific (v42.2020.99)

Only load what is needed when it is needed

Fast/Light

Use as low level systems as possible.

Cache results in memory and on disk for other processes.

Rely on a minimum of dependencies.

Only do heavy calculations/deps once when needed and rely on cache after that.

methods we removed as they really shouldn’t be needed if we are approaching it correctly

  • A system command lookup

    Typically system commands should be encapsulated in a subclass for the system in question.

    For example, instead of defining package management commands in the OS, the package management class that implements functionality for the OS’s package system. e.g. for ubuntu Cpanel::PackMan needs an apt “driver” that it loads based on $os->package_manager.

  • A best OS path lookup for OS specific code.

    Similar to system commands. Since we are really asking what foo to use and not how do I do this on $dist $major $minor …

    If a need for it does exist and can be demonstarted we can re-instate that behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment