From f97ac86dc0dadc20b53d66dade311e4b4e50b7fc Mon Sep 17 00:00:00 2001
From: "A. Wilcox" <AWilcox@Wilcox-Tech.com>
Date: Thu, 31 Oct 2019 18:03:00 -0500
Subject: hscript: Implement fs, add tests

---
 hscript/disk.cc                                 | 52 +++++++++++++++++++++++++
 hscript/disk.hh                                 | 15 +++++++
 hscript/script.cc                               | 27 ++++++++++++-
 tests/fixtures/0179-fs-basic.installfile        |  9 +++++
 tests/fixtures/0180-fs-without-fs.installfile   |  9 +++++
 tests/fixtures/0181-fs-invalid-dev.installfile  |  9 +++++
 tests/fixtures/0182-fs-invalid-type.installfile |  9 +++++
 tests/spec/validator_spec.rb                    | 23 +++++++++++
 8 files changed, 152 insertions(+), 1 deletion(-)
 create mode 100644 tests/fixtures/0179-fs-basic.installfile
 create mode 100644 tests/fixtures/0180-fs-without-fs.installfile
 create mode 100644 tests/fixtures/0181-fs-invalid-dev.installfile
 create mode 100644 tests/fixtures/0182-fs-invalid-type.installfile

diff --git a/hscript/disk.cc b/hscript/disk.cc
index a54b36d..72604d9 100644
--- a/hscript/disk.cc
+++ b/hscript/disk.cc
@@ -13,6 +13,7 @@
 #include <algorithm>
 #include <cstring>          /* strerror */
 #include <fstream>
+#include <set>
 #include <string>
 #ifdef HAS_INSTALL_ENV
 #   include <assert.h>         /* assert */
@@ -606,6 +607,57 @@ bool LVMVolume::execute(ScriptOptions) const {
 }
 
 
+const static std::set<std::string> valid_fses = {
+    "ext2", "ext3", "ext4", "jfs", "vfat", "xfs"
+};
+
+
+Key *Filesystem::parseFromData(const std::string &data, int lineno,
+                               int *errors, int *) {
+    if(std::count(data.begin(), data.end(), ' ') != 1) {
+        if(errors) *errors += 1;
+        output_error("installfile:" + std::to_string(lineno),
+                     "fs: expected exactly two elements",
+                     "syntax is: fs [device] [fstype]");
+        return nullptr;
+    }
+
+    std::string::size_type sep = data.find(' ');
+    std::string device(data.substr(0, sep));
+    std::string fstype(data.substr(sep + 1));
+
+    if(device.size() < 6 || device.compare(0, 5, "/dev/")) {
+        if(errors) *errors += 1;
+        output_error("installfile:" + std::to_string(lineno),
+                     "fs: element 1: expected device node",
+                     "'" + device + "' is not a valid device node");
+        return nullptr;
+    }
+
+    if(valid_fses.find(fstype) == valid_fses.end()) {
+        std::string fses;
+        for(auto &&fs : valid_fses) fses += fs + " ";
+
+        if(errors) *errors += 1;
+        output_error("installfile:" + std::to_string(lineno),
+                     "fs: element 2: expected filesystem type",
+                     "valid filesystems are: " + fses);
+        return nullptr;
+    }
+
+    return new Filesystem(lineno, device, fstype);
+}
+
+bool Filesystem::validate(ScriptOptions) const {
+    /* Validation is done during parsing. */
+    return true;
+}
+
+bool Filesystem::execute(ScriptOptions) const {
+    return false;
+}
+
+
 Key *Mount::parseFromData(const std::string &data, int lineno, int *errors,
                           int *warnings) {
     std::string dev, where, opt;
diff --git a/hscript/disk.hh b/hscript/disk.hh
index 43bb56d..53e2749 100644
--- a/hscript/disk.hh
+++ b/hscript/disk.hh
@@ -170,6 +170,21 @@ public:
 };
 
 class Filesystem : public Key {
+private:
+    const std::string _block;
+    const std::string _type;
+
+    Filesystem(int _line, const std::string &_b, const std::string &_t) :
+        Key(_line), _block(_b), _type(_t) {}
+public:
+    /*! Retrieve the block device on which to create the filesystem. */
+    const std::string device() const { return this->_block; }
+    /*! Retreive the type of filesystem to create. */
+    const std::string fstype() const { return this->_type; }
+
+    static Key *parseFromData(const std::string &, int, int*, int*);
+    bool validate(ScriptOptions) const override;
+    bool execute(ScriptOptions) const override;
 };
 
 class Mount : public Key {
diff --git a/hscript/script.cc b/hscript/script.cc
index dae014c..050bb01 100644
--- a/hscript/script.cc
+++ b/hscript/script.cc
@@ -118,6 +118,8 @@ struct Script::ScriptPrivate {
     std::vector< std::unique_ptr<LVMGroup> > lvm_vgs;
     /*! LVM logical volume keys */
     std::vector< std::unique_ptr<LVMVolume> > lvm_lvs;
+    /*! Filesystem creation keys */
+    std::vector< std::unique_ptr<Filesystem> > fses;
     /*! Target system's mountpoints. */
     std::vector< std::unique_ptr<Mount> > mounts;
 
@@ -196,6 +198,10 @@ struct Script::ScriptPrivate {
             std::unique_ptr<LVMVolume> lv(dynamic_cast<LVMVolume *>(obj));
             this->lvm_lvs.push_back(std::move(lv));
             return true;
+        } else if(key_name == "fs") {
+            std::unique_ptr<Filesystem> fs(dynamic_cast<Filesystem *>(obj));
+            this->fses.push_back(std::move(fs));
+            return true;
         } else if(key_name == "mount") {
             std::unique_ptr<Mount> mount(dynamic_cast<Mount *>(obj));
             this->mounts.push_back(std::move(mount));
@@ -687,7 +693,7 @@ bool add_default_repos(std::vector<std::unique_ptr<Keys::Repository>> &repos) {
 bool Script::validate() const {
     int failures = 0;
     std::set<std::string> seen_diskids, seen_labels, seen_parts, seen_pvs,
-            seen_vg_names, seen_vg_pvs, seen_lvs, seen_mounts;
+            seen_vg_names, seen_vg_pvs, seen_lvs, seen_fses, seen_mounts;
     std::map<const std::string, int> seen_iface;
 
     /* REQ: Runner.Validate.network */
@@ -913,6 +919,7 @@ bool Script::validate() const {
             continue;
         }
 
+        /* REQ: Runner.Validate.lvm_lv.Name */
         if(seen_lvs.find(lvpath) != seen_lvs.end()) {
             failures++;
             output_error("installfile:" + std::to_string(lv->lineno()),
@@ -921,6 +928,7 @@ bool Script::validate() const {
         }
         seen_lvs.insert(lvpath);
 
+        /* REQ: Runner.Validate.lvm_lv.VolumeGroup */
         if(seen_vg_names.find(lv->vg()) == seen_vg_names.end()) {
             /* Let's make sure it still exists, if we are running in the IE */
             if(opts.test(InstallEnvironment)) {
@@ -936,6 +944,23 @@ bool Script::validate() const {
         }
     }
 
+    /* REQ: Runner.Validate.fs */
+    for(auto &fs : this->internal->fses) {
+        if(!fs->validate(this->opts)) {
+            failures++;
+            continue;
+        }
+
+        /* REQ: Runner.Validate.fs.Unique */
+        if(seen_fses.find(fs->device()) != seen_fses.end()) {
+            failures++;
+            output_error("installfile:" + std::to_string(fs->lineno()),
+                         "fs: a filesystem is already scheduled to be "
+                         "created on " + fs->device());
+        }
+        seen_fses.insert(fs->device());
+    }
+
     /* REQ: Runner.Validate.mount */
     for(auto &mount : this->internal->mounts) {
         if(!mount->validate(this->opts)) {
diff --git a/tests/fixtures/0179-fs-basic.installfile b/tests/fixtures/0179-fs-basic.installfile
new file mode 100644
index 0000000..6990c07
--- /dev/null
+++ b/tests/fixtures/0179-fs-basic.installfile
@@ -0,0 +1,9 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+diskid /dev/sdb WDBNCE2500PNC
+disklabel /dev/sdb gpt
+partition /dev/sdb 1 fill
+fs /dev/sdb1 ext4
+mount /dev/sdb1 /
diff --git a/tests/fixtures/0180-fs-without-fs.installfile b/tests/fixtures/0180-fs-without-fs.installfile
new file mode 100644
index 0000000..f113018
--- /dev/null
+++ b/tests/fixtures/0180-fs-without-fs.installfile
@@ -0,0 +1,9 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+diskid /dev/sdb WDBNCE2500PNC
+disklabel /dev/sdb gpt
+partition /dev/sdb 1 fill
+fs /dev/sdb1
+mount /dev/sdb1 /
diff --git a/tests/fixtures/0181-fs-invalid-dev.installfile b/tests/fixtures/0181-fs-invalid-dev.installfile
new file mode 100644
index 0000000..5a3a67f
--- /dev/null
+++ b/tests/fixtures/0181-fs-invalid-dev.installfile
@@ -0,0 +1,9 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+diskid /dev/sdb WDBNCE2500PNC
+disklabel /dev/sdb gpt
+partition /dev/sdb 1 fill
+fs sdb1 ext4
+mount /dev/sdb1 /
diff --git a/tests/fixtures/0182-fs-invalid-type.installfile b/tests/fixtures/0182-fs-invalid-type.installfile
new file mode 100644
index 0000000..4a88048
--- /dev/null
+++ b/tests/fixtures/0182-fs-invalid-type.installfile
@@ -0,0 +1,9 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+diskid /dev/sdb WDBNCE2500PNC
+disklabel /dev/sdb gpt
+partition /dev/sdb 1 fill
+fs /dev/sdb1 ntfs
+mount /dev/sdb1 /
diff --git a/tests/spec/validator_spec.rb b/tests/spec/validator_spec.rb
index c4e5ac2..c36e5a1 100644
--- a/tests/spec/validator_spec.rb
+++ b/tests/spec/validator_spec.rb
@@ -873,6 +873,29 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do
                     expect(last_command_started).to have_output(/error: .*lvm_lv.*volume group/)
                 end
             end
+            context "for 'fs' key" do
+                it "succeeds with a simple value" do
+                    use_fixture '0179-fs-basic.installfile'
+                    run_validate
+                    expect(last_command_started).to have_output(PARSER_SUCCESS)
+                    expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+                end
+                it "requires a filesystem type" do
+                    use_fixture '0180-fs-without-fs.installfile'
+                    run_validate
+                    expect(last_command_started).to have_output(/error: .*fs.*expected/)
+                end
+                it "requires a valid absolute block device path" do
+                    use_fixture '0181-fs-invalid-dev.installfile'
+                    run_validate
+                    expect(last_command_started).to have_output(/error: .*fs.*device/)
+                end
+                it "requires a valid filesystem type" do
+                    use_fixture '0182-fs-invalid-type.installfile'
+                    run_validate
+                    expect(last_command_started).to have_output(/error: .*fs.*type/)
+                end
+            end
             context "for 'keymap' key" do
                 it "succeeds with a simple value" do
                     use_fixture '0178-keymap-basic.installfile'
-- 
cgit v1.2.3-70-g09d2