Add build_image step.

This adds the implementation of the build_image step with unit tests.

Tested: I ran unit tests with pytest to test for function behaviors
of git_metadata step. All test cases passed. I also ran an end-to-end
test with the sstate-cache, build was successful.

Signed-off-by: Simon-Lii <thesimonli@google.com>
Change-Id: I3b0d2b2125973419a638eee70dfff5ec039a280f
diff --git a/build.py b/build.py
index 5aeff45..a1e937b 100644
--- a/build.py
+++ b/build.py
@@ -1,11 +1,13 @@
-from steps import metadata_setup
+from steps import metadata_setup, build_image
 
 class Build():
     def __init__(self):
         self.metadata_setup_step = metadata_setup.MetadataSetup()
+        self.build_image_step = build_image.BuildImage()
 
     def run_build(self):
         self.metadata_setup_step.run_script()
+        self.build_image_step.run_script()
 
 def main():
     build = Build()
diff --git a/cloudbuild.yaml b/cloudbuild.yaml
index e7360a1..3489926 100644
--- a/cloudbuild.yaml
+++ b/cloudbuild.yaml
@@ -36,6 +36,7 @@
       - FIRMWARE_VERSION=${_FIRMWARE_VERSION}
       - METADATA_OBMC_REPO=${_METADATA_OBMC_REPO}
       - METADATA_REPO=${_METADATA_REPO}
+      - GBMC_CONFIG=${_GBMC_CONFIG}
     args:
       - python3
       - /workspace/build.py
diff --git a/steps/build_image.py b/steps/build_image.py
new file mode 100644
index 0000000..c2eefcf
--- /dev/null
+++ b/steps/build_image.py
@@ -0,0 +1,33 @@
+from steps import step
+import subprocess
+
+class BuildImage(step.Step):
+    def bitbake(self):
+        """Performs the openbmc build using bitbake"""
+        return "bitbake -k obmc-phosphor-image"
+
+    def export_gbmc_config(self):
+        """Sets the bitbake environment variable for GBMC_CONFIG"""
+        return f"export GBMC_CONFIG={self.gbmc_config}"
+
+    def export_gbmc_version(self):
+        """Sets the bitbake environment variable for GBMC_VERSION"""
+        return f"export GBMC_VERSION={self.firmware_version}"
+
+    def export_bb_env_extrawhite(self):
+        """Adds GBMC_CONFIG and GBMC_VERSION to BB_ENV_EXTRAWHITE whitelist"""
+        return "export BB_ENV_EXTRAWHITE=${BB_ENV_EXTRAWHITE} GBMC_CONFIG GBMC_VERSION"
+
+    def run_script(self):
+
+        # command =
+        # """export MACHINE=machine0 && source setup machine0 && umask 002
+        # && export GBMC_CONFIG=dev && export GBMC_VERSION=x.y.z
+        # && export BB_ENV_EXTRAWHITE=${BB_ENV_EXTRAWHITE} GBMC_CONFIG GBMC_VERSION
+        # && bitbake -k obmc-phosphor-image
+        # """
+        command = " && ".join([
+            self.export_machine(), self.source_setup(), self.umask(), self.export_gbmc_config(),
+            self.export_gbmc_version(), self.export_bb_env_extrawhite(), self.bitbake()])
+
+        subprocess.run(["bash", "-c", command])
diff --git a/steps/step.py b/steps/step.py
index 0bfff4a..f596ea9 100644
--- a/steps/step.py
+++ b/steps/step.py
@@ -12,6 +12,7 @@
         self.firmware_version = os.getenv("FIRMWARE_VERSION")
         self.metadata_obmc_repo = os.getenv("METADATA_OBMC_REPO")
         self.metadata_repo = os.getenv("METADATA_REPO")
+        self.gbmc_config = os.getenv("GBMC_CONFIG")
 
     def parse_repos(self, repos_string):
         """Parses a string representation of an array of triples"""
diff --git a/unit_tests/test_build_image.py b/unit_tests/test_build_image.py
new file mode 100644
index 0000000..a735583
--- /dev/null
+++ b/unit_tests/test_build_image.py
@@ -0,0 +1,69 @@
+import subprocess
+import unittest
+from unittest.mock import MagicMock
+from steps import build_image
+import inspect
+
+class TestBuildImage(unittest.TestCase):
+    def setUp(self):
+        self.step = build_image.BuildImage()
+        self.step.export_machine = MagicMock(return_value="")
+        self.step.source_setup = MagicMock(return_value="")
+        self.step.umask = MagicMock(return_value="")
+        self.step.export_gbmc_config = MagicMock(return_value="")
+        self.step.export_gbmc_version = MagicMock(return_value="")
+        self.step.export_bb_env_extrawhite = MagicMock(return_value="")
+        self.step.bitbake = MagicMock(return_value="")
+        subprocess.run = MagicMock()
+
+        self.step.run_script()
+
+    def test_export_machine(self):
+        """Tests that export_machine method is called"""
+        self.step.export_machine.assert_called_with()
+
+    def test_source_setup(self):
+        """Tests that source_setup method is called"""
+        self.step.source_setup.assert_called_with()
+
+    def test_umask(self):
+        """Tests that umask method i called"""
+        self.step.umask.assert_called_with()
+
+    def test_export_gbmc_config(self):
+        """Tests that export_gbmc_config is called"""
+        self.step.export_gbmc_config.assert_called_with()
+
+    def test_export_gbmc_version(self):
+        """Tests that export_gbmc_version is called"""
+        self.step.export_gbmc_version.assert_called_with()
+
+    def test_export_bb_env_extrawhite(self):
+        """Tests that export_bb_env_extrawhite is called"""
+        self.step.export_bb_env_extrawhite.assert_called_with()
+
+    def test_bitbake(self):
+        """Tests that bitbake method is called"""
+        self.step.bitbake.assert_called_with()
+
+    def test_subprocess_run(self):
+        """Tests subprocess's arguments, tests the order in which all the previous
+        tests are called
+        """
+        self.step = build_image.BuildImage()
+        self.step.firmware_machine = "machine0"
+        self.step.firmware_version = "x.y.z"
+        self.step.gbmc_config = "test"
+        subprocess.run = MagicMock()
+
+        self.step.run_script()
+
+        command =   "export MACHINE=machine0 " \
+                    "&& source setup machine0 " \
+                    "&& umask 002 " \
+                    "&& export GBMC_CONFIG=test " \
+                    "&& export GBMC_VERSION=x.y.z " \
+                    "&& export BB_ENV_EXTRAWHITE=${BB_ENV_EXTRAWHITE} GBMC_CONFIG GBMC_VERSION " \
+                    "&& bitbake -k obmc-phosphor-image"
+
+        subprocess.run.assert_called_with(["bash", "-c", command])