How to Inject Dynamic Data in a Solve File at Test Time
‍Modern software has become so big and complex that one small change in a minor module or a change in an external API used by the code can result in system-wide failures if left undetected. One of the best ways to address the risk of failure is through automated testing conducted on an ongoing basis. Any line of code that goes untested is a hazard. Instruqt challenges are no exception.
Fortunately, automated testing capabilities are built into Instruqt. All you need to do to test an Instruqt track is run the Instruqt CLI command instruqt track test <ORGANIZATION/CHALLENGE_SLUG> from a client terminal window. The command will create the VM for the challenge and then execute the solve script for each challenge in the track followed up by running the corresponding check script.
The purpose of a solve script is to emulate a challenge’s commands that a user executes in a terminal window of the Instruqt interactive learning environment. The purpose of the check script is to verify that the user or the solve script has indeed executed the expected commands as directed.
Most times user behavior can be hard-coded into a solve script. But, there are situations in which specific aspects of a solve script are only known at test time. In such cases, special measures need to be taken.
For example, imagine an Instruqt challenge in which a user needs to provide a username/password pair to log in to a third-party website that provides a particular piece of required data that’s dynamically generated at the moment the data is needed. There is no way that such “just-in-time” data can be hardcoded into a solve script. And, it’s bad business from a security standpoint to hard code the username/password pair into the solve script in order to let the script's intelligence do data retrieval. Rather, a better approach is to have some sort of external intelligence log into the third-party website, get the just-in-time data, and then inject that data into the solve script before executing the instruqt track test command. It’s a viable approach, but one that's not as simple as it seems. There are two tasks that need to be addressed.
The first task is to identify the external intelligence that will log into the third-party website and get the just-in-time data. The second task is to come up with a way that enables the external intelligence to do the actual data injection into the Instruqt solve script. Tackling these tasks is tricky, but it can be done. This article shows you how.
🎥 Prefer Video? Scroll down to watch the video version of this blog post. 🎥
The following sections describe how to inject dynamic data into an Instruqt solve script at test time. The external intelligence we’ll use to do the injection is a Jenkins pipeline script. I’ll demonstrate how to do the injection using a demonstration Instruqt challenge that gets a random word from an online resource and then injects that random word into a solve script within the challenge. You can think of the random word as the just-in-time data that gets injected at test time.
The online resource that provides the random word is an API published by wordnik.com.
In order to work with the Wordnik API, users need to provide a special key dedicated to their account. A user gets the API key by registering with wordnik.com. This is secret information analogically similar to a username/password pair. When users create a random word manually in the Instruqt challenge’s terminal window using the Wordnik API, they will assign their secret API key to an environment variable from the command line. The value in the environment variable is then appended to the URL that a curl command uses to get the random word for the wordnik.com API, as shown in Figure 1 below.
(The actual Instruqt track shown in Figure 1 above can be found here.)
In terms of the solve script, the random word will be injected in a just-in-time manner by the Jenkins pipeline script. The pipeline script calls the wordnik.com API using a curl command to get a random word that’s injected into the corresponding solve script hosted on Instruqt. As with the manual user interaction, the Jenkins pipeline script appends the API key to the worknik.com API URL.
The following section describes at a conceptual level how the Jenkins pipeline script injects just-in-time data at test time. The actual pipeline script, written in Groovy, is stored as a gist file on GitHub that is to be found here. With regard to the pipeline script, I assume that you know how to create pipeline jobs under Jenkins and have prior knowledge of how to write pipeline scripts. Also, I assume you have some familiarity working with Groovy commands, particularly using the withCredentials() function.
Injecting Just-In-Time Data Into a Solve Script Using a Jenkins Pipeline
As mentioned above, just-in-time data is injected into the Jenkins pipeline job that is controlling the execution of Instruqt testing. The job requires that the Instruqt CLI tool is installed on Jenkins. The pipeline script uses the Instruqt CLI tool to copy the source code for the track in question from Instruqt onto the Jenkins file system using the command instruqt track pull. (See Figure 2, callout (1).)
Once the source code is pulled down from Instruqt, the solve file into which data will be injected is backed up as a precaution in case of any malfunction in Jenkins as the pipeline script is run. (Figure 2, callout (2). Then, the pipeline script inspects the solve script of interest substituting a predefined “replacement” string with the actual runtime string to inject. The replacement string is XXXXXXXXX. Thus, if the value to inject is cheese, the pipeline script looks for the string XXXXXXXXX and replaces it with the string, cheese. (See Figure 2, callout (3).)
After the just-in-time string is injected into the solve script, the pipeline executes the CLI command Instruqt track push. The command uploads the updated solve script to Instruqt. (See Figure 2, callout (4).) Then the pipeline script executes the CLI command Instruqt track test to run all the solve and check scripts in the track.
After the testing is completed, the backup file replaces the updated solve script effectively resetting the track files to their original state. Once again, the Instruqt track push is called in the pipeline script. Should an error occur at any time, the backup file replaces the updated solve script too and Instruqt track push is called.
Listing 1 below shows the content of the solve script into which just-in-time data is injected. Notice the placeholder string XXXXXXXXX at Line 7. The pipeline script substitutes the WordNik.com API key for the placeholder string. Then the solve script calls the WordNik API to get a random word. (A copy of the assignment.md and check files are available for viewing as GitHub gist files. You can find the assignment.md here. You can find the check file here.)
Listing 2 below shows the “Injecting data'' stage from the Jenkins pipeline script that does the actual data injection. Bear in mind that the values for the environment variables $WORDNIK_API_KEY and $INSTRUQT_TOKEN are set in an earlier stage using the withCredentials() pipeline function. withCredentials() extracts the values of the environment variables from secrets stored as Jenkins credentials.
Notice that the excerpt shown in Listing 2 downloads the source code from the Instruqt track of interest (Line 9) and injects the Wordnik API key into the solve file at Line 28. The solve file is reset to its original state in the stage that follows and uploaded to the Instruqt server after the test is run on the Instruqt server. Again, as mentioned above, you can view the entire pipeline script on the corresponding GitHub gist here.
Putting it All Together
Being able to Inject data at test time is useful for those tracks in Instruqt that require users to get and input data from a third party source that uses security credentials. The technique described in this article shows the basic technique that can be applied to a wide variety of situations.
Moving forward you might find it useful to do the demonstration track that corresponds to this article. Then take the time to review both the solve script shown above in Listing 1 and pipeline script shown in the GitHub gist to get a sense of how it all works.
Using Jenkins to implement a continuous testing process is a reliable way to ensure the quality and accuracy of your Instruqt tracks. Furthermore, being able to implement data injection into an Instruqt track at test time enhances the breadth and power of your testing initiatives. Hopefully the content presented in the article has given you the basic understanding you need to implement data injection at test time on Instruqt tracks using Jenkins.
‍
‍Modern software has become so big and complex that one small change in a minor module or a change in an external API used by the code can result in system-wide failures if left undetected. One of the best ways to address the risk of failure is through automated testing conducted on an ongoing basis. Any line of code that goes untested is a hazard. Instruqt challenges are no exception.
Fortunately, automated testing capabilities are built into Instruqt. All you need to do to test an Instruqt track is run the Instruqt CLI command instruqt track test <ORGANIZATION/CHALLENGE_SLUG> from a client terminal window. The command will create the VM for the challenge and then execute the solve script for each challenge in the track followed up by running the corresponding check script.
The purpose of a solve script is to emulate a challenge’s commands that a user executes in a terminal window of the Instruqt interactive learning environment. The purpose of the check script is to verify that the user or the solve script has indeed executed the expected commands as directed.
Most times user behavior can be hard-coded into a solve script. But, there are situations in which specific aspects of a solve script are only known at test time. In such cases, special measures need to be taken.
For example, imagine an Instruqt challenge in which a user needs to provide a username/password pair to log in to a third-party website that provides a particular piece of required data that’s dynamically generated at the moment the data is needed. There is no way that such “just-in-time” data can be hardcoded into a solve script. And, it’s bad business from a security standpoint to hard code the username/password pair into the solve script in order to let the script's intelligence do data retrieval. Rather, a better approach is to have some sort of external intelligence log into the third-party website, get the just-in-time data, and then inject that data into the solve script before executing the instruqt track test command. It’s a viable approach, but one that's not as simple as it seems. There are two tasks that need to be addressed.
The first task is to identify the external intelligence that will log into the third-party website and get the just-in-time data. The second task is to come up with a way that enables the external intelligence to do the actual data injection into the Instruqt solve script. Tackling these tasks is tricky, but it can be done. This article shows you how.
🎥 Prefer Video? Scroll down to watch the video version of this blog post. 🎥
The following sections describe how to inject dynamic data into an Instruqt solve script at test time. The external intelligence we’ll use to do the injection is a Jenkins pipeline script. I’ll demonstrate how to do the injection using a demonstration Instruqt challenge that gets a random word from an online resource and then injects that random word into a solve script within the challenge. You can think of the random word as the just-in-time data that gets injected at test time.
The online resource that provides the random word is an API published by wordnik.com.
In order to work with the Wordnik API, users need to provide a special key dedicated to their account. A user gets the API key by registering with wordnik.com. This is secret information analogically similar to a username/password pair. When users create a random word manually in the Instruqt challenge’s terminal window using the Wordnik API, they will assign their secret API key to an environment variable from the command line. The value in the environment variable is then appended to the URL that a curl command uses to get the random word for the wordnik.com API, as shown in Figure 1 below.
(The actual Instruqt track shown in Figure 1 above can be found here.)
In terms of the solve script, the random word will be injected in a just-in-time manner by the Jenkins pipeline script. The pipeline script calls the wordnik.com API using a curl command to get a random word that’s injected into the corresponding solve script hosted on Instruqt. As with the manual user interaction, the Jenkins pipeline script appends the API key to the worknik.com API URL.
The following section describes at a conceptual level how the Jenkins pipeline script injects just-in-time data at test time. The actual pipeline script, written in Groovy, is stored as a gist file on GitHub that is to be found here. With regard to the pipeline script, I assume that you know how to create pipeline jobs under Jenkins and have prior knowledge of how to write pipeline scripts. Also, I assume you have some familiarity working with Groovy commands, particularly using the withCredentials() function.
Injecting Just-In-Time Data Into a Solve Script Using a Jenkins Pipeline
As mentioned above, just-in-time data is injected into the Jenkins pipeline job that is controlling the execution of Instruqt testing. The job requires that the Instruqt CLI tool is installed on Jenkins. The pipeline script uses the Instruqt CLI tool to copy the source code for the track in question from Instruqt onto the Jenkins file system using the command instruqt track pull. (See Figure 2, callout (1).)
Once the source code is pulled down from Instruqt, the solve file into which data will be injected is backed up as a precaution in case of any malfunction in Jenkins as the pipeline script is run. (Figure 2, callout (2). Then, the pipeline script inspects the solve script of interest substituting a predefined “replacement” string with the actual runtime string to inject. The replacement string is XXXXXXXXX. Thus, if the value to inject is cheese, the pipeline script looks for the string XXXXXXXXX and replaces it with the string, cheese. (See Figure 2, callout (3).)
After the just-in-time string is injected into the solve script, the pipeline executes the CLI command Instruqt track push. The command uploads the updated solve script to Instruqt. (See Figure 2, callout (4).) Then the pipeline script executes the CLI command Instruqt track test to run all the solve and check scripts in the track.
After the testing is completed, the backup file replaces the updated solve script effectively resetting the track files to their original state. Once again, the Instruqt track push is called in the pipeline script. Should an error occur at any time, the backup file replaces the updated solve script too and Instruqt track push is called.
Listing 1 below shows the content of the solve script into which just-in-time data is injected. Notice the placeholder string XXXXXXXXX at Line 7. The pipeline script substitutes the WordNik.com API key for the placeholder string. Then the solve script calls the WordNik API to get a random word. (A copy of the assignment.md and check files are available for viewing as GitHub gist files. You can find the assignment.md here. You can find the check file here.)
Listing 2 below shows the “Injecting data'' stage from the Jenkins pipeline script that does the actual data injection. Bear in mind that the values for the environment variables $WORDNIK_API_KEY and $INSTRUQT_TOKEN are set in an earlier stage using the withCredentials() pipeline function. withCredentials() extracts the values of the environment variables from secrets stored as Jenkins credentials.
Notice that the excerpt shown in Listing 2 downloads the source code from the Instruqt track of interest (Line 9) and injects the Wordnik API key into the solve file at Line 28. The solve file is reset to its original state in the stage that follows and uploaded to the Instruqt server after the test is run on the Instruqt server. Again, as mentioned above, you can view the entire pipeline script on the corresponding GitHub gist here.
Putting it All Together
Being able to Inject data at test time is useful for those tracks in Instruqt that require users to get and input data from a third party source that uses security credentials. The technique described in this article shows the basic technique that can be applied to a wide variety of situations.
Moving forward you might find it useful to do the demonstration track that corresponds to this article. Then take the time to review both the solve script shown above in Listing 1 and pipeline script shown in the GitHub gist to get a sense of how it all works.
Using Jenkins to implement a continuous testing process is a reliable way to ensure the quality and accuracy of your Instruqt tracks. Furthermore, being able to implement data injection into an Instruqt track at test time enhances the breadth and power of your testing initiatives. Hopefully the content presented in the article has given you the basic understanding you need to implement data injection at test time on Instruqt tracks using Jenkins.
‍
You might also like
Try Instruqt Yourself
Get a closer look at how Instruqt can help you sell smarter and train better.
Take a self-guided tour of Instruqt