Project: sparky-rocky

Build now

Configuration

sparrowdo:
  localhost: true
  no_sudo: true
  tags: stage=main 
allow_manual_run: true  
vars:
  -
      name: version
      type: select
      values: [ 8, 9, 10, 9aarch64, 10aarch64 ]
      group: [ qemu ]
  -
      name: use_case_repo
      values: "%qemu_test.use_case_repo%"
      type: select
      group: [ orb, qemu, qemu_with_kickstart ]
  -
      name: qemu_binary
      type: select
      values: "%qemu_test.qemu.binary%"
      group: [ qemu, qemu_with_kickstart ]
  -
      name: qemu_machine
      type: input
      default: "%qemu_test.qemu.machine%"
      group: [ qemu, qemu_with_kickstart ]

  -
      name: ssh_key_path
      values: "%qemu_test.ssh_key_path%"
      type: select
      group: [ qemu, qemu_with_kickstart ]
  -
      name: bootstrap
      type: checkbox
      default: false
      group: [ orb, qemu ]
  -
      name: qemu_shut
      type: checkbox
      default: false
      group: [ qemu, qemu_with_kickstart ]
  -
      name: skip_test
      type: checkbox
      default: false
      group: [ qemu, qemu_with_kickstart, orb ]
  -
      name: kickstart_filter
      type: input
      default: ""
      group: [ qemu_with_kickstart ]
  -
      name: boot_iso
      values: "%qemu_test.boot_iso%"
      type: select
      group: [ qemu_with_kickstart ]
  -
      name: kickstart_url
      values: "%qemu_test.kickstart_url%"
      type: select
      group: [ qemu_with_kickstart ]
  -
      name: dump_task_code
      type: checkbox
      default: false
      group: [ orb, qemu, qemu_with_kickstart ]
  -
      name: test_env
      values: orb
      type: select
      group: [ orb ]
  -
      name: test_env
      values: qemu
      type: select
      group: [ qemu ]
  -
      name: test_env
      values: qemu_with_kickstart
      type: select
      group: [ qemu_with_kickstart ]

group_vars:
  - qemu
  - qemu_with_kickstart
  - orb

Job

#!raku

use Sparky::JobApi;

class Pipeline

does Sparky::JobApi::Role

{

  method stage-main {

    directory "scm";

    my $test_env = tags()<test_env> || "qemu";

    my $version = tags()<version> || "10";

    my $ssh_user = ( $test_env eq "orb" ) ??  %*ENV<USER> !! "admin";

    my $ssh_port = ( $test_env eq "orb" ) ?? 22 !! tags()<ssh_port> || 10022;

    my $host = ( $test_env eq "orb" ) ?? 'rocky@orb' !! "127.0.0.1";

    my $api = ( $test_env eq "orb" ) ?? "http://host.orb.internal:4000" !! "http://10.0.2.2:4000";

    my $bootstrap-mode = tags()<bootstrap>  ?? True !! False;

    my $delegate = tags()<delegate> || "";

    my $delegated = tags()<delegated> || False;

    my $skip-test = tags()<skip_test> || False;

    my $current-version = "";

    my $distro;

    my $prefix = tags()<prefix> || "default";

    my $base-dir = "{%*ENV<HOME>}/rocky-linux-distro/$prefix";

    my $job-desc;

    my $ssh_key_path = tags()<ssh_key_path> ??
    tags()<ssh_key_path>.subst(/^^ \s* "~/" /,%*ENV<HOME> ~ "/") !!
    %*ENV<HOME> ~ '/.ssh/id_rsa.pub';

    my $ssh_private_key = $ssh_key_path.subst(/".pub" $$/,"");

    if $test_env eq "qemu" {
      if "{$base-dir}/.version".IO ~~ :f {
          $current-version = "{$base-dir}/.version".IO.slurp();
      }
      $distro = config()<distros><qemu>{"version{$version}"};
      if $current-version.chomp ne $version {
        say "current version ($current-version) ne required version ($version), forcing bootstrap";
        $bootstrap-mode = True;
      }
    }

    my $kickstart-mode = ( $test_env eq "qemu_with_kickstart" && ! tags()<kickstart_file> ) ?? True !! False;
    my $ks-file = tags()<kickstart_file> || "";
    my $kickstart-filter = tags()<kickstart_filter> || "";

    say "=====";  
    say "base-dir: $base-dir";
    say "test_env: $test_env";
    say "version: $version" if $test_env eq "qemu";
    say "current-version: $current-version" if $test_env eq "qemu";
    say "distro: $distro" if $test_env eq "qemu";
    say "testbox: $host";
    say "ssh_user: $ssh_user";
    say "ssh_key_path: $ssh_key_path";
    say "ssh_private_key: $ssh_private_key";
    say "prefix: $prefix";
    say "ssh_port: $ssh_port";
    say "api: $api";
    say "delegate: $delegate";
    say "delegated: $delegated";
    say "skip_test: $skip-test";
    say "qemu_shut: {tags()<qemu_shut> || False}";
    say "bootstrap-mode: $bootstrap-mode" unless $test_env eq "qemu_with_kickstart";
    say "kickstart-mode: $kickstart-mode" if $test_env eq "qemu_with_kickstart";
    say "kickstart_file: $ks-file" if $test_env eq "qemu_with_kickstart";
    say "=====";  

    my $repo-dir;
    
    if tags()<delegated> {

      my $me = Sparky::JobApi.new: :mine;

      my $blob = $me.get-file("tasks.tar");

      directory "tasks";

      my $fh = open "tasks/tasks.tar", :w, :bin;
      $fh.write($blob);
      $fh.close;

      bash "tar -xf tasks.tar  && ls -l", %(
        cwd => "{$*CWD}/tasks",
        description => "unpack tasks.tar",
      );

      $job-desc = tags()<name>;

    } else {

      my $use_case_repo = tags()<use_case_repo>.subst(/^^ \s* "~/" /,%*ENV<HOME> ~ "/");

      if $ks-file {

          my $me = Sparky::JobApi.new: :mine;

          my $blob = $me.get-file("tasks.tar");

          directory "tasks";

          my $fh = open "tasks/tasks.tar", :w, :bin;
          $fh.write($blob);
          $fh.close;

          bash "tar -xf tasks.tar  && ls -l", %(
            cwd => "{$*CWD}/tasks",
            description => "unpack tasks.tar",
          );

      }

      file-delete "tasks.tar";

      say "archiving tasks/ to tasks.tar";

      bash "tar cf {$*CWD}/tasks.tar -C tasks/ .";

      directory "use_case_repo";

      file-delete "use_case_repo.tar";
      
      if $use_case_repo.IO ~~ :d {
        $repo-dir = $use_case_repo;
        say "archiving {$use_case_repo} files to use_case_repo.tar";
        bash "tar cf {$*CWD}/use_case_repo.tar -C {$use_case_repo} .";
        $job-desc = "local: $use_case_repo";
      } else {
        $repo-dir = "use_case_repo";
        git-scm tags()<use_case_repo>, %(
          to => "use_case_repo",
          branch => "HEAD",
        );
        say "(git) archiving {tags()<use_case_repo>} files to use_case_repo.tar";
        bash "git archive --format=tar  -o {$*CWD}/use_case_repo.tar HEAD", %(
          cwd => "{$*CWD}/use_case_repo",
        );
        $job-desc = "git: {tags()<use_case_repo>}";
      }

      if $ks-file {
        $job-desc = "{$job-desc} kickstart, file={$ks-file}";
        say "use kickstart file: {$repo-dir}/{$ks-file}, set bootstrap_mode to true";
        say "distro set from boot_iso: {tags()<boot_iso>}";
        say "kickstart_url: {tags()<kickstart_url>}";
        $bootstrap-mode = True;
        my $me = Sparky::JobApi.new: :mine;

        my $c = "{$repo-dir}/{$ks-file}".IO.slurp;
        $c = "$c\n%post\ndnf install cloud-init tar -y\nsystemctl enable cloud-init.service\nsystemctl start cloud-init.service\n%end\n";
        $c.= subst(/^^ url .*? $$/,"");
        $c = "$c\n{tags()<kickstart_url>}";
        "ks.tmp".IO.spurt($c);
        $me.put-file("ks.tmp","ks.cfg");
        $distro = tags()<boot_iso>;
      }

      if tags()<delegate> {
        my $me = Sparky::JobApi.new: :mine;
        my $project = $me.info.<project>;
        my $job-id = $me.info.<job-id>;
        my $rj = Sparky::JobApi.new(:$project,:$job-id, :api(tags()<delegate>));
        $rj.put-file("{$*CWD}/use_case_repo.tar","use_case_repo.tar");
        $rj.put-file("{$*CWD}/tasks.tar","tasks.tar");
        my $tags = %( :delegated );
        for tags().kv -> $k, $v {
          next if $k ~~ /^^ SPARKY_/;
          next if $k eq "delegate";
          $tags{$k} = $v;
        }
        $rj.queue({
          description => tags()<name>,
          :$tags,
        }),
        return;
      } else {

        my $me = Sparky::JobApi.new: :mine;
        $me.put-file("{$*CWD}/use_case_repo.tar","use_case_repo.tar");
        $me.put-file("{$*CWD}/tasks.tar","tasks.tar");

        if $kickstart-mode {
          my $i = 0;
          for dir($repo-dir, test => /'.ks' $$/) -> $f {
            if $kickstart-filter {
              next unless $f.basename eq $kickstart-filter
            }
            $i++;
            my $project = $me.info.<project>;
            my $job-id = "{$me.info.<job-id>}_ks_{$i}";
            my $ks = Sparky::JobApi.new(:$project,:$job-id);

            $ks.put-file("{$*CWD}/use_case_repo.tar","use_case_repo.tar");
            $ks.put-file("{$*CWD}/tasks.tar","tasks.tar");

            my $tags = %( kickstart_file => $f.basename );
            for tags().kv -> $k, $v {
              next if $k ~~ /^^ SPARKY_/;
              $tags{$k} = $v;
            }
            $ks.queue({
              description => "$job-desc kickstart, file={$f.basename}",
              :$tags,
            }),
          }
          return
        }
      }
    }

    directory $base-dir;

    if $bootstrap-mode and $test_env ne "orb" {

      say "bootstrap qemu VM";

      say "====";

      task-run "tasks/stop-qemu-box", %(
        prefix => $prefix,
      );

      # spawns a child job

      my $project = "qemu-session.$prefix";
      my $j = self.new-job: :$project;

      my $tags = {
          stage => "qemu-session",
          version => $version,
          distro_url => $distro,
          qemu_binary => tags()<qemu_binary>,
          ssh_key_path => $ssh_key_path,
          qemu_machine => tags()<qemu_machine>,
          parent_job_id => tags()<SPARKY_JOB_ID>,
          parent_job_name => tags()<SPARKY_PROJECT>,
          api => $api,
          prefix => $prefix,
          ssh_port => $ssh_port,
          ks_file => $ks-file,
      };
      $tags<use-kick-start> = True if $ks-file;
      $j.queue({
        description => $job-desc,
        tags => $tags,
      });

      unless $ks-file {
        say "save current version to {$base-dir}/.version";
        "{$base-dir}/.version".IO.spurt($version);
      }

      say "queue qemu-session job, ",$j.info.raku;

      say "wait till qemu box is ready, timeout 12 minutes";
      
      my $ssh-ok = False;

      for 1 .. 120 -> $i {
        my $s = task-run "tasks/check-ssh", %(
          :$host,
          :$ssh_port,
          :$ssh_user,
          :$ssh_private_key,
        ); 

        if $s<state> eq "alive" {
          $ssh-ok = True;
          last;
        }
        sleep(10);

        say "[$i] ...";

        say "qemu-session job status: ", $j.status;

        if $j.status eq "FAIL" {
          say "qemu-session job failed, stopping qemu vm and exiting ...";
          task-run "tasks/stop-qemu-box", %(
            prefix => $prefix,
          );
          exit(1); 
        }
      }

      if $ssh-ok == False {
        say "qemu-session job timeouted - exiting ...";
        if tags()<qemu_shut> {
          task-run "tasks/stop-qemu-box", %(
            prefix => $prefix,
          );
        }
        exit(1);
      }
    } elsif $bootstrap-mode and $test_env eq "orb" {

      say "bootstrap orb VM";

      say "====";

      bash "orb delete rocky -f||:";

      bash "orb create rocky";

      bash "orb start rocky";

    }
    
    if $skip-test {
      say "skip_test is enabled, exiting ...";
      return;
    }
    say "run use case scenario on vm box";

    my $project = "test-use-case.$prefix";

    my $j = Sparky::JobApi.new: :$project;
    
    my $bootstrap = $bootstrap-mode;

    $j.queue({
      description => $job-desc,
      tags => %(
        stage => "use-case",
        dump_task_code => tags()<dump_task_code> ?? "on" !! "off",
        parent_job_id => tags()<SPARKY_JOB_ID>,
        parent_job_name => tags()<SPARKY_PROJECT>,
        api => $api,
        ks_file => $ks-file,
      ),
      sparrowdo => %(
        :$ssh_user,
        :$ssh_port,
        :$ssh_private_key,
        :$host,
        :no_sudo,
        :$bootstrap,
        #:debug,
      ),
    });

    say "queue use case job, ",$j.info.raku;

    say "wait up to 15 minutes till use case scenario has finished";

    my $st = self.wait-job($j, %( :900timeout ) );

    #say $j.report();

    # check if child job send us any message
    my @meta = $j.meta;

    say @meta.raku;

    if @meta && @meta[0]<reboot> eq "need" {
       say "reboot signal recieved from child job, reboot qemu box";
       $project = "vm-reboot.$prefix";
       my $j = Sparky::JobApi.new: :$project;
       $j.queue({
          description => $job-desc,
          tags => %(
            :stage<vm-reboot>,
            :$test_env,
            :$api,
          ),
          sparrowdo => %(
            :$ssh_user,
            :$ssh_port,
            :$host,
            :no_sudo,
            #:debug,
          ),
        });

        say "queue vm-reboot job, ",$j.info.raku;

        say "wait up to 7 minutes till reboot has finished";

        my $st = self.wait-job($j, %( :420timeout ) );

        unless $st<OK> {
          if $test_env ne "orb" {
            say "qemu box is not good, stopping qemu session";
            task-run "tasks/stop-qemu-box", %(
              prefix => $prefix,
            );
          }  
          say "vm box is not good, exiting ...";
          exit(1);      
        }

        sleep(15); # wait till VM has rebooted 

        my $ssh-ok = False;

        for 1 .. 40 -> $i {
          my $s = task-run "tasks/check-ssh", %(
            :$ssh_user,
            :$ssh_port,
            :$host,
            :$ssh_private_key,
          ); 
          if $s<state> eq "alive" {
            $ssh-ok = True;
            last;
          }
          sleep(10);
          say "[$i] ...";
        }

        if $ssh-ok == True {

          say "machine successfully rebooted, run use case scenario on qemu box";

          $project = "test-use-case.$prefix";

          my $j = Sparky::JobApi.new: :$project;
      
          $j.queue({
            description => $job-desc,
            tags => %(
                stage => "use-case",
                dump_task_code => tags()<dump_task_code> ?? "on" !! "off",
                parent_job_id => tags()<SPARKY_JOB_ID>,
                parent_job_name => tags()<SPARKY_PROJECT>,
                api => $api,
                ks_file => $ks-file,
                :reboot-ok,
            ),
            sparrowdo => %(
              :$ssh_user,
              :$ssh_port,
              :$host,
              :no_sudo,
              :$ssh_private_key,
              #:debug,
            ),
          });

          say "queue use case job, ",$j.info.raku;

          say "wait up to 15 minutes till use case scenario has finished";

          my $st = self.wait-job($j, %( :900timeout ) );

          if tags()<qemu_shut> {
            task-run "tasks/stop-qemu-box", %(
              prefix => $prefix,
            );
          }
        } else {
          # handle case when vm has not showed up after reboot
          say "vm box is not good, stopping qemu session";
          if $test_env ne "orb" {
            task-run "tasks/stop-qemu-box", %(
              prefix => $prefix,
            );
          }
          say "vm box is not good, exiting";
          exit(1); 
        }
      # reboot is not required, just stop VM in case qemu_shut is enabled  
    } elsif tags()<qemu_shut> {
        task-run "tasks/stop-qemu-box", %(
          prefix => $prefix,
        );
    }
  }

  method stage-qemu-session {

    my $job = Sparky::JobApi.new(
      project  => tags()<parent_job_name>,
      job-id => tags()<parent_job_id>,
    );

    my $blob = $job.get-file("tasks.tar");

    directory "tasks";

    my $fh = open "tasks/tasks.tar", :w, :bin;
    $fh.write($blob);
    $fh.close;

    bash "tar -xf tasks.tar  && ls -l", %(
      cwd => "{$*CWD}/tasks",
      description => "unpack tasks.tar",
    );

    if tags()<use-kick-start> {

      my $ks_url = "http://10.0.2.2:4000/file_view/{tags()<parent_job_name>}/{tags()<parent_job_id>}/ks.cfg";

      my $ks-version = tags()<distro_url>.subst("/","_",:g).
      subst(":","_",:g).subst(".","_").tail.chomp;

      task-run "tasks/kickstart-bootstrap", %(
        distro_url => tags()<distro_url>,
        ssh_key_path => tags()<ssh_key_path>,
        prefix => tags()<prefix>,
        machine => tags()<qemu_machine>,
        qemu_binary => tags()<qemu_binary>,
        version => $ks-version,
        ks_url  => $ks_url,
        ssh_port => tags()<ssh_port>,
      );

    }

    unless tags()<use-kick-start> {
      task-run "tasks/setup-qemu-image", %(
        distro_url => tags()<distro_url>,
        ssh_key_path => tags()<ssh_key_path>,
        prefix => tags()<prefix>,
        version => tags()<version>,
      );
    }

    task-run "tasks/run-qemu-box", %(
      iso => "{%*ENV<HOME>}/rocky-linux-distro/{tags()<prefix>}/distro.qcow2",
      seed => "/tmp/init.iso",
      qemu_binary => tags()<qemu_binary>,
      machine => tags()<qemu_machine>,
      prefix => tags()<prefix>,
      ssh_port => tags()<ssh_port>,
    );

  }

  method stage-use-case {

    directory "scm";
    
    bash "echo OK";

    my $job = Sparky::JobApi.new(
      api => tags()<api>,
      project  => tags()<parent_job_name>,
      job-id => tags()<parent_job_id>,
    );

    my $blob = $job.get-file("use_case_repo.tar");

    my $fh = open "scm/use_case_repo.tar", :w, :bin;
    $fh.write($blob);
    $fh.close;

    bash "tar -xf use_case_repo.tar  && ls -l", %(
      cwd => "{$*CWD}/scm",
      description => "unpack use_case_repo.tar",
    );

    if "scm/main.raku".IO ~~ :e {
      say "load scenario from main.raku";
      chdir "scm/";
      if tags()<dump_task_code> eq "on" {
        say "enable dump_task_code ...";
        %*ENV<SP6_DUMP_TASK_CODE> = 1;
      }
      EVALFILE "main.raku";
    } else {
        warn "no file main.raku found, nothing to do"
    }

  }

  method stage-vm-reboot {

    say "rebooting machine ...";

    task-run "reboot", "reboot";
    
  }
 
}

Pipeline.new.run;