From 77bd0b2bc35dc8ca4cdba9af23f944f4cd03af62 Mon Sep 17 00:00:00 2001 From: "N. Harrison Ripps" Date: Tue, 16 Sep 2014 11:00:17 -0400 Subject: [PATCH 1/3] Modified README to reflect new branch --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 4bd887f658ba..3cec9be8ecb7 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -= OpenShift Documentation += OpenShift Documentation (ONLINE BRANCH) This repo contains the documentation for From 686b3ab27f88a8dd3ecbac3b3eb08b00e4e4c6e5 Mon Sep 17 00:00:00 2001 From: Bilhar Date: Mon, 22 Sep 2014 15:33:34 +1000 Subject: [PATCH 2/3] Beginning migration of UG topics --- _build_cfg.yml | 15 +++ user_guide/apps.adoc | 186 ++++++++++++++++++++++++++++++ user_guide/auth_tokens.adoc | 61 ++++++++++ user_guide/cartridges.adoc | 154 +++++++++++++++++++++++++ user_guide/domain_membership.adoc | 29 +++++ user_guide/domains.adoc | 112 ++++++++++++++++++ user_guide/images/2747.png | Bin 0 -> 25555 bytes user_guide/images/3167.png | Bin 0 -> 24334 bytes user_guide/overview.adoc | 17 +++ user_guide/ssh_keys.adoc | 93 +++++++++++++++ 10 files changed, 667 insertions(+) create mode 100644 user_guide/apps.adoc create mode 100644 user_guide/auth_tokens.adoc create mode 100644 user_guide/cartridges.adoc create mode 100644 user_guide/domain_membership.adoc create mode 100644 user_guide/domains.adoc create mode 100644 user_guide/images/2747.png create mode 100644 user_guide/images/3167.png create mode 100644 user_guide/overview.adoc create mode 100644 user_guide/ssh_keys.adoc diff --git a/_build_cfg.yml b/_build_cfg.yml index 782fa511b59e..b52a06bb20a7 100644 --- a/_build_cfg.yml +++ b/_build_cfg.yml @@ -41,6 +41,21 @@ Topics: - Name: Updating Client Tools File: updating_client_tools +--- +Name: User Guide +Dir: user_guide +Topics: + - Name: Overview + File: overview + - Name: Authorization Tokens + File: auth_tokens + - Name: SSH Keys + File: ssh_keys + - Name: Domains + File: domains + - Name: Applications + File: apps + --- Name: Developing Cartridges Dir: cartridge_specification_guide diff --git a/user_guide/apps.adoc b/user_guide/apps.adoc new file mode 100644 index 000000000000..290b63de1a0a --- /dev/null +++ b/user_guide/apps.adoc @@ -0,0 +1,186 @@ += Applications +{product-author} +{product-version} +:data-uri: +:icons: +:toc: +:toc-placement: preamble + +All OpenShift applications consist of one web framework cartridge that serves web requests, and additional cartridges that provide other capabilities, such as databases, scheduled jobs, or continuous integration. + +== Overview + +When a new application is created, a URL with name of the application and the name of the domain is registered in DNS. A copy of the application code is checked out locally into a folder with the same name as the application. Note that different types of applications may require different folder structures. Application components are run on gears. + +With each new application that is created with the client tools, a remote Git repository is populated with the selected cartridge, which is then cloned to the current directory on the local machine. The host name and IP address of the application are also added to the list of known hosts in the [filename]#~/.ssh/known_hosts# directory. + +The following table describes each component that makes up an OpenShift application. + +.Application Components +[cols="2,8",options="header"] +|=== +|Component|Description + +|Domain +|The domain provides a unique group identifier for all the applications of a specific user. The domain is not directly related to DNS; instead, it is appended to the application name to form a final application URL of the form http://_app_name-domain.example.com_ + +|Application Name +|The name of the application is selected by a user. The final URL to access the application is of the form http://_app_name-domain.example.com_. + +|Alias +|DNS names can be provided for the application by registering an alias with OpenShift and pointing the DNS entry to the OpenShift servers. + +|Git repository +|A Git repository is used to modify application code locally. After the code is applied, the `git push` command is required to deploy the revised code. +|=== + +OpenShift provides dedicated [filename]#/var/tmp# and [filename]#/tmp# directories for each user application. The [filename]#/var/tmp# directory is a symbolic link to [filename]#/tmp#. Each [filename]#/tmp# directory is completely isolated from the [filename]#/tmp# directories of all other applications. Files that are untouched for any period of ten days are automatically deleted from these directories. + +== Application Life Cycle + +The following table describes the general life cycle of most OpenShift applications. +[cols="2,8",options="header"] +|=== +|Process|Description + +|Code +|Develop the application code with the desired language and tools. Continuously push the application code to the applications remote Git source code repository. + +|Build +|OpenShift supports various build mechanisms, whether it is a simple script, a personal Jenkins continuous integration server, or an external build system. + +|Deploy +|Every application is composed of cartridges that simplify server maintenance and configuration. OpenShift supports various technologies to provision the required services automatically. + +|Manage +|OpenShift allows real-time monitoring, debugging, and tuning of applications. Applications are scaled automatically depending on web traffic. +|=== + +== Scalable Applications + +Applications can be created as either scalable or not scalable. An application that is not scalable only requires one of the quota of gears assigned to run an application, whereas a scalable application consumes two of the available gears; one for the actual application and one for the high-availability proxy (HAProxy). When an add-on cartridge, such as MySQL, is added to an application, it is installed in its own dedicated gear. Scalable applications have the advantage of automatically allocating resources based on demand. + +Scalable applications can be scaled automatically or manually. New applications are automatically scaled based on the number of requests by default. However, you can adjust the minimum and maximum number of gears to be consumed by an application within the defined limits to manually scale it. + +== Creating Applications +You can create new applications with the `rhc app create` command and the available options to supply the required information, such as the type of web framework to be used with the new application. Note that if multiple versions of the specified web framework are available, you are prompted to specify the version number. + +There are some factors that must be considered beforehand because certain aspects of the application cannot be changed after it is created. For example, whether an application is scalable or not must be specified when it is created. An application that is not scalable cannot be changed to scalable after it is created, and vice versa. The web framework of a cartridge also cannot be changed after an application is created. If you want to change the web framework you must delete the application and all of its data and then create a new one with the desired web framework. + + +The command syntax to create new applications with the `rhc app create` command is as follows: + +---- +rhc app create [] +---- + +[NOTE] +==== +Before you attempt to create an application, ensure that you have link:../client_tools_install_guide/overview.html[installed and configured] the client tools on your workstation. +==== + +== Command Quick Reference +[cols="8,5",options="header"] +|=== + +|Task |Command + +|Create application +|`rhc app create __ __` + +|Create scalable application +|`rhc app create __ __ -s` + +|Create application in specified domain +|`rhc app create __ __ -n __` + +|Create application from downloadable cartridge +|`rhc app create __ __` + +|Create application from custom code +|`rhc app create __ --from-code __` + +|Delete application +|`rhc app delete __` +|=== + +[WARNING] +==== +Deleting an application deletes all remote data associated with the application, which cannot be recovered. +==== + +== Tutorial: Creating Applications +The following tutorial shows you how to create applications with available options. + +*To create an application* + +Use the `rhc app create` command to create the application, specifying the name of the application and the Python web framework. Note that because multiple versions of the Python framework are supported, you must specify the version you want to create: + +---- +$ rhc app create mypython python-3.3 +Application Options +------------------- +Domain: mydomain +Cartridges: python-3.3 +Gear Size: default +Scaling: no + +Creating application 'mypython' ... done + + +Waiting for your DNS name to be available ... done + +Cloning into 'mypython'... +Warning: Permanently added 'mypython-mydomain.rhcloud.com' (RSA) to the list of known hosts. + +Your application 'mypython' is now available. + + URL: http://mypython-mydomain.rhcloud.com/ + SSH to: 54052e482587c84787000ad7@mypython-mydomain.rhcloud.com + Git remote: ssh://54052e482587c84787000ad7@mypython-mydomain.rhcloud.com/~/git/mypython.git/ + Cloned to: /home/User/mypython + +Run 'rhc show-app mypython' for more details about your app. +---- + +Although this example shows a Python application, you can substitute any web framework to create an application to suit your requirements. + +*To make the application scalable* + +Add the `-s` option to the `rhc app create` command to make the application scalable: + +---- +$ rhc app create mypython python-3.3 -s +---- + +When you make an application scalable, the automatic scaling feature is enabled by default. However, it is possible to scale an application manually by controlling the number of gears that are used. + +[NOTE] +==== +At the time of this writing, if a scalable application is created, the scaling function of that application cannot be disabled. However, it is possible to clone a non-scalable application and all its associated data and create a new scalable application using the application clone command. +==== + +*To specify the domain where to create the application* + +If there are multiple domains in your account, the application is created in the default domain. However, you can specify the domain where the application gets created by adding the `-n` option: + +---- +$ rhc app create mypython python-3.3 -s -n mydomain +---- + +*To create an application from a downloadable cartridge* + +You can create an application from a downloadable cartridge by specifying the manifest URL of the hosted cartridge: + +---- +$ rhc app create mypython http://www.example.com/manifest.yml +---- + +*To create an application with custom code* + +You can create an application by using custom code from a Git repository, which becomes the initial contents of the application. To create an application with custom code, specify the URL of the Git repository: + +---- +$ rhc app create mypython --from-code giturl.git +---- + diff --git a/user_guide/auth_tokens.adoc b/user_guide/auth_tokens.adoc new file mode 100644 index 000000000000..e26620e5314d --- /dev/null +++ b/user_guide/auth_tokens.adoc @@ -0,0 +1,61 @@ += Authorization Tokens +{product-author} +{product-version} +:data-uri: +:icons: + +An authorization token is used to automatically log in to an OpenShift account without having to enter login credentials each time. A token is also used to grant another user full or partial access to an account, which is determined by the _scope_ of the token, which are described in the following table. + +.Available Scopes for Authorization Tokens +[cols="2,7,2",options="header"] +|=== +|Scope |Description |Validity + +|session |Access to all API functions against an account |1 day +|read |Read-only access to account resources, but cannot view authorization tokens |1 month +|userinfo |Access to login name, unique id, and user capabilities |1 month +|=== + +When the client tools are installed and the `rhc setup` command is initially run to link:../client_tools_install_guide/configuring_client_tools.html[configure] the client tools, the setup wizard prompts you to create an authorization token. If you answer [userinput]#YES#, the wizard creates a session token in the [filename]#~/.openshift# directory. With this token, all client tool commands can be run without entering login credentials each time. When the token expires you are automatically prompted to reenter login information to renew the existing token. + +If an authorization token was not created when the client tools were installed, you can run the setup wizard again with the `rhc setup` command to create one. + +When you attempt to run an `rhc` command for the first time or after an authorization token has expired, you are prompted to enter your password. Entering your password generates a _session_ token automatically and the command executes as normal. + +== Command Quick Reference +[cols="8,5",options="header"] +|=== + +|Task |Command + +|Create a token +|`rhc authorization add --scopes __ --note __` + +|View tokens +|`rhc authorization list` + +|Delete one or more tokens +|`rhc authorization delete __, __` + +|Delete all tokens +|`rhc authorization delete-all` +|=== + +If an existing authorization token is no longer required and you do not want to be prompted for token renewal, run the `rhc logout` command to delete the token. + +//== Examples +//In the following example we attempt to create an application without having an authorization token. When the command is run, you are prompted to enter a password. + +//.Example Without Authorization Token +//==== + +//---- +//$ rhc app create myapp php-5.3 +//Your authorization token has expired. Please sign in now to continue on +//openshift.redhat.com. +//Password: +//---- +//==== + +//In this case when you enter your password an authorization token is created and the command proceeds to create the specified application. All subsequent commands are run //without requiring your password. + diff --git a/user_guide/cartridges.adoc b/user_guide/cartridges.adoc new file mode 100644 index 000000000000..0066a2a75047 --- /dev/null +++ b/user_guide/cartridges.adoc @@ -0,0 +1,154 @@ += Cartridges +{product-author} +{product-version} +:data-uri: +:icons: +:toc: +:toc-placement: preamble + +All OpenShift applications consist of one web framework cartridge that serves web requests, and additional cartridges that provide other capabilities, such as databases, scheduled jobs, or continuous integration. + +== Overview + +When a new application is created, a URL with name of the application and the name of the domain is registered in DNS. A copy of the application code is checked out locally into a folder with the same name as the application. Note that different types of applications may require different folder structures. Application components are run on gears. + +With each new application that is created with the client tools, a remote Git repository is populated with the selected cartridge, which is then cloned to the current directory on the local machine. The host name and IP address of the application are also added to the list of known hosts in the [filename]#~/.ssh/known_hosts# directory. + +The following table describes each component that makes up an OpenShift application. + +.Application Components +[cols="2,8",options="header"] +|=== +|Component|Description + +|Domain +|The domain provides a unique group identifier for all the applications of a specific user. The domain is not directly related to DNS; instead, it is appended to the application name to form a final application URL of the form http://_app_name-domain.example.com_ + +|Application Name +|The name of the application is selected by a user. The final URL to access the application is of the form http://_app_name-domain.example.com_. + +|Alias +|DNS names can be provided for the application by registering an alias with OpenShift and pointing the DNS entry to the OpenShift servers. + +|Git repository +|A Git repository is used to modify application code locally. After the code is applied, the `git push` command is required to deploy the revised code. +|=== + +OpenShift provides dedicated [filename]#/var/tmp# and [filename]#/tmp# directories for each user application. The [filename]#/var/tmp# directory is a symbolic link to [filename]#/tmp#. Each [filename]#/tmp# directory is completely isolated from the [filename]#/tmp# directories of all other applications. Files that are untouched for any period of ten days are automatically deleted from these directories. + +== Application Life Cycle + +The following table describes the general life cycle of most OpenShift applications. +[cols="2,8",options="header"] +|=== +|Process|Description + +|Code +|Develop the application code with the desired language and tools. Continuously push the application code to the applications remote Git source code repository. + +|Build +|OpenShift supports various build mechanisms, whether it is a simple script, a personal Jenkins continuous integration server, or an external build system. + +|Deploy +|Every application is composed of cartridges that simplify server maintenance and configuration. OpenShift supports various technologies to provision the required services automatically. + +|Manage +|OpenShift allows real-time monitoring, debugging, and tuning of applications. Applications are scaled automatically depending on web traffic. +|=== + +== Scalable Applications + +Applications can be created as either scalable or not scalable. An application that is not scalable only requires one of the quota of gears assigned to run an application, whereas a scalable application consumes two of the available gears; one for the actual application and one for the high-availability proxy (HAProxy). When an add-on cartridge, such as MySQL, is added to an application, it is installed in its own dedicated gear. Scalable applications have the advantage of automatically allocating resources based on demand. + +Scalable applications can be scaled automatically or manually. New applications are automatically scaled based on the number of requests by default. However, you can adjust the minimum and maximum number of gears to be consumed by an application within the defined limits to manually scale it. + +== Creating Applications +You can create new applications with the `rhc app create` command and the available options to supply the required information, such as the type of web framework to be used with the new application. Note that if multiple versions of the specified web framework are available, you are prompted to specify the version number. + +The command syntax to create new applications with the `rhc app create` command is as follows: + +---- +rhc app create +---- + +[NOTE] +==== +Before you attempt to create an application, ensure that you have link:../client_tools_install_guide/overview.html[installed and configured] the client tools on your workstation. +==== + +== Command Quick Reference +[cols="8,5",options="header"] +|=== + +|Task |Command + +|<> +|`rhc app create __ __` + +|Create scalable application +|`rhc app create __ __ -s` + +|Create application from downloadable cartridge +|`rhc app create __ __` + +|Create application in specified domain +|`rhc app create __ __ -n __` + +|Configure application properties +|`rhc authorization list` + +|Deploy application Git reference or binary artifact +|`rhc authorization delete __, __` + +a|Clean up application log files and [filename]#tmp# directories +|`rhc authorization delete-all` +|=== + +== Tutorial: Creating Applications +The following tutorial shows you how to create an application, and use the available options. + +*To create a Python application* + +Use the `rhc app create` command to create the application, specifying the name of the application and the Python web framework. Note that because multiple versions of the Python framework are supported, you must specify the version you want to create: + +---- +$ rhc app create mypython python-3.3 +Application Options +------------------- +Domain: mydomain +Cartridges: python-3.3 +Gear Size: default +Scaling: no + +Creating application 'mypython' ... done + + +Waiting for your DNS name to be available ... done + +Cloning into 'mypython'... +Warning: Permanently added 'mypython-mydomain.rhcloud.com' (RSA) to the list of known hosts. + +Your application 'mypython' is now available. + + URL: http://mypython-mydomain.rhcloud.com/ + SSH to: 54052e482587c84787000ad7@mypython-mydomain.rhcloud.com + Git remote: ssh://54052e482587c84787000ad7@mypython-mydomain.rhcloud.com/~/git/mypython.git/ + Cloned to: /home/User/mypython + +Run 'rhc show-app mypython' for more details about your app. +---- + +*To make the application scalable* + +Add the `-s` option to the `rhc app create` command to make the application scalable: + +---- +$ rhc app create mypython python-3.3 -s +---- + +When you make an application scalable, the automatic scaling feature is enabled by default. However, it is possible to scale an application manually by controlling the number of gears that are used. + +[NOTE] +==== +At the time of this writing, if a scalable application is created, the scaling function of that application cannot be disabled. However, it is possible to clone a non-scalable application and all its associated data and create a new scalable application using the application clone command. +==== \ No newline at end of file diff --git a/user_guide/domain_membership.adoc b/user_guide/domain_membership.adoc new file mode 100644 index 000000000000..844e0f9670b2 --- /dev/null +++ b/user_guide/domain_membership.adoc @@ -0,0 +1,29 @@ += Domain Membership +{product-author} +{product-version} +:data-uri: +:icons: + + +== Command Quick Reference +[cols="8,5",options="header"] + +|=== +|Task |Command + +|Create a domain +|`rhc domain create __` + +|View domains +|`rhc domain list` or `rhc domains` + +|View information about domain +|`rhc domain show [-n __]` + +|Rename a domain footnoteref:[1,You must first link:applications.html[delete all applications] that exist under the domain before you can rename the domain.] +|`rhc domain rename __ __` + +|Delete a domain footnoteref:[2,You must first link:applications.html[delete all applications] that exist under the domain before you can delete the domain.] +|`rhc domain delete __` +|=== + diff --git a/user_guide/domains.adoc b/user_guide/domains.adoc new file mode 100644 index 000000000000..0db0420ed0a9 --- /dev/null +++ b/user_guide/domains.adoc @@ -0,0 +1,112 @@ += Creating and Managing Domains +{product-author} +{product-version} +:data-uri: +:icons: +:toc: +:toc-placement: preamble + +All OpenShift applications must belong to a _domain_, which forms part of an application's public URL. + +== Overview +The format for an application URL is _application-domain_.example.com. Each account can have access to one or more domains that are shared by others. Depending on the type of account you have, you might be able to create more than one domain. + +ifdef::openshift-online[] +A blacklist restricts the domain names that are available. A warning message informs you if a blacklisted domain name has been selected when you attempt to create a domain. Domain names consist of a maximum of 16 alphanumeric characters and cannot contain spaces or symbols. Note that a domain must be created before you can create applications. +endif::[] + +If you created a domain when you initially link:../client_tools_install_guide/configuring_client_tools.html[configured] the client tools, you can begin creating applications. Otherwise, you can create a new domain by running the interactive setup wizard with the `rhc setup` command, or you can create a domain with the `rhc domain create` command. + +== Command Quick Reference +[cols="8,5",options="header"] + +|=== +|Task |Command + +|Create a domain +|`rhc domain create __` + +|View domains +|`rhc domain list` or `rhc domains` + +|View information about domain +|`rhc domain show [-n __]` + +|Rename a domain footnoteref:[1,You must first link:apps.html[delete all applications] that exist under the domain before you can rename the domain.] +|`rhc domain rename __ __` + +|Delete a domain footnoteref:[2,You must first link:apps.html[delete all applications] that exist under the domain before you can delete the domain.] +|`rhc domain delete __` +|=== + +//== Renaming a Domain +//When you rename a domain, the old domain is deleted and a new one is created. Therefore, all applications that exist under a domain must be deleted before you can rename a domain. + +//[WARNING] +//==== +//Deleting an application deletes all remote application data. This operation cannot be reversed and the data cannot be recovered. +//==== + +//Follow these steps to rename a domain: + +//. Ensure that the domain does not contain any applications: +//+ +//---- +//rhc apps +//---- + +//. Delete all applications that exist in that domain, if necessary: +//+ +//---- +//rhc app delete +//---- + +//. Rename the domain: +//+ +//---- +//rhc domain rename +//---- + +== Tutorial: Creating and Managing Domains +The following tutorial shows you how to create, rename, and delete domains. + +*To create a domain* + +---- +rhc domain create mydomain +---- + +*To rename a domain* + +When renaming a domain, you must first ensure that no applications exist in the domain you want to rename. If there are applications in that domain, you must first delete all applications before you can rename the domain. + +. Determine if there are applications in the domain: ++ +---- +$ rhc apps +---- + +. Delete each application that exists in that domain: ++ +---- +$ rhc delete myapp +---- ++ +[WARNING] +==== +You must first delete all applications from the domain before you can rename it. Understand that deleting an application deletes all remote data associated with that application, which cannot be recovered. +==== + +. With all applications deleted from that domain, rename the domain: ++ +---- +$ rhc domain rename myolddomain mynewdomain +---- + +*To delete a domain* + +Deleting a domain is similar to renaming it, where all applications that exist in that domain must first be deleted. When all applications from the domain have been deleted, you can then delete the domain: + +---- +$ rhc domain delete mydomain +---- \ No newline at end of file diff --git a/user_guide/images/2747.png b/user_guide/images/2747.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2e794d303335c19e497e72847ec2024864d798 GIT binary patch literal 25555 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 MqNQA^_&EIk0VQsYm;e9( literal 0 HcmV?d00001 diff --git a/user_guide/images/3167.png b/user_guide/images/3167.png new file mode 100644 index 0000000000000000000000000000000000000000..3440a528c8e93af5aae0f6e7e7ea34f6640d7358 GIT binary patch literal 24334 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 literal 0 HcmV?d00001 diff --git a/user_guide/overview.adoc b/user_guide/overview.adoc new file mode 100644 index 000000000000..706aed6b2abd --- /dev/null +++ b/user_guide/overview.adoc @@ -0,0 +1,17 @@ += Overview +{product-author} +{product-version} +:data-uri: +:icons: + +These topics help developers manage their cloud environment to develop and deploy OpenShift applications with a command-line interface (CLI), more commonly known as the OpenShift client tools. + +The information and examples included in these topics will help developers: + +* Create and manage domains and SSL certificates +* Create, build, and deploy applications +* Manage applications and cartridges +* Monitor and manage application storage and resources + +== Before You Begin +It is assumed that you have already link:../client_tools_installation_guide/overview.html[installed and configured] the client tools on your workstation. If not, you must do so before you can create and manage OpenShift applications with the client tools. \ No newline at end of file diff --git a/user_guide/ssh_keys.adoc b/user_guide/ssh_keys.adoc new file mode 100644 index 000000000000..76206bbaf3af --- /dev/null +++ b/user_guide/ssh_keys.adoc @@ -0,0 +1,93 @@ += SSH Keys +{product-author} +{product-version} +:data-uri: +:icons: +:experimental: +:toc: +:toc-placement: preamble + +OpenShift uses the Secure Shell (SSH) network protocol to authenticate account credentials with the remote servers for secure communication, and supports both RSA and DSA keys for SSH authentication. + +== Overview +SSH utilizes key cryptography for both the connection and for authentication. OpenShift uses SSH for performing Git operations and to provide remote access to your application gear. + +When the client tools are installed and the `rhc setup` command is initially run to link:../client_tools_install_guide/configuring_client_tools.html[configure] the client tools, the setup wizard generates a new pair of SSH keys in the default [filename]#.ssh# folder of your home directory. The SSH key pair consists of the public key, [filename]#id_rsa.pub#, and the private key, [filename]#id_rsa#. As part of the initial configuration, you have the option of automatically uploading the public key, [filename]#id_rsa.pub#, to the remote server. Your account can have one or more public SSH keys associated with it, and you can access your account from any workstation that has the private SSH key on it. + +[NOTE] +==== +Red Hat recommends that you use the interactive setup wizard to create and configure SSH keys so that your workstation can authenticate and communicate with the remote server. Run the interactive setup wizard with the `rhc setup` command. +==== + +== Supported SSH Keys +|=== +|ssh-rsa +|ssh-dss +|++ecdsa-sha2-nistp256-cert-v01@openssh.com++ +|++ecdsa-sha2-nistp384-cert-v01@openssh.com++ +|++ecdsa-sha2-nistp521-cert-v01@openssh.com++ +|++ssh-rsa-cert-v01@openssh.com++ +|++ssh-dss-cert-v01@openssh.com++ +|++ssh-rsa-cert-v00@openssh.com++ +|++ssh-dss-cert-v00@openssh.com++ +|ecdsa-sha2-nistp256 +|ecdsa-sha2-nistp384 +|ecdsa-sha2-nistp521 +|=== + +== Command Quick Reference +[cols="8,5",options="header"] +|=== + +|Task |Command + +|Add a key to remote server +|`rhc sshkey add __ __` + +|Add key contents to remote server +|`rhc sshkey add __ --type __ --content __` + +|View all keys on remote server +|`rhc sshkey list` + +|View a specific key on remote server +|`rhc sshkey show __` + +|Generate SSH keys manually +|`ssh-keygen -t __` + +|Delete a key from remote server +|`rhc sshkey remove __` +|=== + +== Tutorial: Creating and Uploading SSH Keys +This tutorial will show you how to generate SSH keys manually, and then upload the public key or its contents to the remote server. However Red Hat recommends that you use the interactive setup wizard to create and upload the public SSH key to the remote server. + +*To generate SSH keys manually* + +Generate an SSH key pair, specifying the type of key to generate, which could be either RSA or DSA: + +---- +$ ssh keygen -t rsa +---- + +Press kbd:[Enter] when prompted to save the SSH keys in the default location, which typically is in the [filename]#~/.ssh# directory. + +*To upload public key to remote server* + +Specify the name of the key and the path to where it was saved: + +---- +$ rhc sshkey add mysshkey ~/.ssh/id_rsa.pub +---- + +*To upload the actual key contents to remote server* + +Specify the name of the key, type of key, and supply the key contents: + +---- +$ rhc sshkey add mysshkey --type rsa --content AAAAB3NzaC1yc2EhyuiBIwAAAQEA14PDPWsaZMDspZNK7ABsppzwy++Ih2tRwjBkxzC2KEcQi7v8IcyODb7qLJ72tgx3G90zRm7vQ6wuyy7rkYLIvTYiDnchy68ikjyt7wuBuSCgFcHLUdon7xn7VrskjhMN4pae6bjaY1+o4Knpfm3N72+9q/6+T52QIWCE1+Ku6UYYuOGy8qWynddw24bp4jGEKAXqTXcALuBoukC3uB+hrxvZYH1fbek6aEAQPYzO6sGqJqV1UoF0ascelhtyui8kadrKPr/5uJsPS+kGZguU16ykQb2k9K03JMSfvPP4rLe50Q9G4dSZFbUOQXdC3n13CqvsEVzizUGl0HyT8MhRqw +---- + +== Resolving Authentication Issues +If you experience authentication issues, or if there is a mismatch between the local and remote keys, Red Hat recommends resolving authentication issues with the interactive setup wizard. The interactive setup wizard also provides the option to automatically upload a new public key to the remote server. Launch the interactive setup wizard with the `rhc setup` command and follow the onscreen instructions. \ No newline at end of file From be7aa26fc4df4b31eefb3a92e802a9cf2923eb49 Mon Sep 17 00:00:00 2001 From: "N. Harrison Ripps" Date: Wed, 24 Sep 2014 13:12:37 -0400 Subject: [PATCH 3/3] Fixed generator logic, improved Guard --- Guardfile | 12 +- Rakefile | 440 +---------------------- _builder_lib/docsitebuilder/helpers.rb | 470 +++++++++++++++++++++++++ 3 files changed, 479 insertions(+), 443 deletions(-) create mode 100644 _builder_lib/docsitebuilder/helpers.rb diff --git a/Guardfile b/Guardfile index c6bfff0a742e..69b613a87ff0 100644 --- a/Guardfile +++ b/Guardfile @@ -1,20 +1,14 @@ -require 'asciidoctor' -require 'erb' -require 'haml' -require 'tilt' - guard 'shell' do watch(/^.*\.adoc$/) { |m| if not m[0].start_with?('_preview') src_group_path = m[0].split('/')[0] filename = m[0].split('/')[-1][0..-6] - tgt_file_path = "_preview/#{src_group_path}" - Asciidoctor.render_file m[0], :in_place => true, :safe => :unsafe, :template_dir => '_template', :attributes => ['source-highlighter=coderay','coderay-css=style',"stylesdir=_preview/stylesheets","imagesdir=#{src_group_path}/images",'stylesheet=origin.css','linkcss!','icons=font','idprefix=','idseparator=-','sectanchors'] - system('mv', "#{src_group_path}/#{filename}.html", "#{tgt_file_path}/") + system("bundle exec rake refresh_page['#{src_group_path}:#{filename}']") end } end guard 'livereload' do - watch(%r{^_preview\.(css|js|html)$}) + watch(%r{^_preview/.+\.(css|js|html)$}) + watch(%r{^_preview/.+\/.+\/.+\.(css|js|html)$}) end diff --git a/Rakefile b/Rakefile index 08ac44a47e5a..0a075c143583 100644 --- a/Rakefile +++ b/Rakefile @@ -1,445 +1,17 @@ -require 'asciidoctor' -require 'git' -require 'logger' -require 'pandoc-ruby' -require 'pathname' +require "#{File.join(Dir.pwd,'_builder_lib/docsitebuilder/helpers')}" require 'rake' -require 'yaml' -BUILD_FILENAME = '_build_cfg.yml' -BUILDER_DIRNAME = '_build_system' -PREVIEW_DIRNAME = '_preview' -PACKAGE_DIRNAME = '_package' -BLANK_STRING_RE = Regexp.new('^\s*$') -PRODUCT_AUTHOR = "OpenShift Documentation Project " -ANALYTICS_SHIM = '' - -def source_dir - @source_dir ||= File.expand_path(Dir.pwd) -end - -def template_dir - @template_dir ||= File.join(source_dir,'_templates') -end - -def preview_dir - @preview_dir ||= begin - preview_dir = File.join(source_dir,PREVIEW_DIRNAME) - if not File.exists?(preview_dir) - Dir.mkdir(preview_dir) - end - preview_dir - end -end - -def package_dir - @package_dir ||= begin - package_dir = File.join(source_dir,PACKAGE_DIRNAME) - if not File.exists?(package_dir) - Dir.mkdir(package_dir) - end - package_dir - end -end - -def build_date - Time.now.utc -end - -def git - @git ||= Git.open(source_dir) -end - -def git_checkout branch_name - target_branch = git.branches.local.select{ |b| b.name == branch_name }[0] - if not target_branch.current - target_branch.checkout - end -end - -# Returns the local git branches; current branch is always first -def local_branches - @local_branches ||= begin - branches = [] - branches << git.branches.local.select{ |b| b.current }[0].name - branches << git.branches.local.select{ |b| not b.current }.map{ |b| b.name } - branches.flatten - end -end - -def build_config_file - @build_config_file ||= File.join(source_dir,BUILD_FILENAME) -end - -def build_config - @build_config ||= validate_config(YAML.load_stream(open(build_config_file))) -end - -def distro_map - @distro_map ||= begin - { 'openshift-origin' => { - :name => 'OpenShift Origin', - :branches => { - 'master' => { - :name => 'Nightly Build', - :dir => 'latest', - }, - 'origin-4' => { - :name => 'Version 4', - :dir => 'stable', - }, - }, - }, - 'openshift-online' => { - :name => 'OpenShift Online', - :branches => { - 'online' => { - :name => 'Latest Release', - :dir => 'online', - }, - }, - }, - 'openshift-enterprise' => { - :name => 'OpenShift Enterprise', - :branches => { - 'enterprise-2.2' => { - :name => 'Version 2.2', - :dir => 'enterprise/v2.2', - }, - }, - } - } - end -end - -def distro_branches(use_distro='') - @distro_branches ||= begin - use_distro_list = use_distro == '' ? distro_map.keys : [use_distro] - distro_map.select{ |dkey,dval| use_distro_list.include?(dkey) }.map{ |distro,dconfig| dconfig[:branches].keys }.flatten - end -end - -def page(args) - page_css = '' - args[:css].each do |sheet| - sheet_href = args[:css_path] + sheet - page_css << "\n" - end - page_head = < - - -#{args[:distro]} #{args[:version]} | #{args[:group_title]} | #{args[:topic_title]} -#{page_css} - - - - -
-

OpenShift Documentation #{args[:distro]} #{args[:version]}

-
-
-
-
- - - - - -
-

 

-
-EOF - - page_nav = ['
'] - groupidx = 0 - args[:navigation].each do |topic_group| - current_group = topic_group[:id] == args[:group_id] - page_nav << '
' - page_nav << '
' - page_nav << '

' - page_nav << " #{topic_group[:name]}" - page_nav << '

' - page_nav << '
' - page_nav << "
    " - topic_group[:topics].each do |topic| - current_topic = topic[:id] == args[:topic_id] - page_nav << "
  • #{topic[:name]}
  • " - end - page_nav << '
' - page_nav << '
' - groupidx = groupidx + 1 - end - page_nav << '
' - - page_body = < -
- - #{args[:content]} -
-
-
-#{ANALYTICS_SHIM} - - -EOF - - page_txt = '' - page_txt << page_head - page_txt << "\n" - page_txt << page_nav.join("\n") - page_txt << "\n" - page_txt << page_body - page_txt -end - -def parse_distros distros_string, for_validation=false - values = distros_string.split(',').map{ |v| v.strip } - return values if for_validation - return distro_map.keys if values.include?('all') - return values.uniq -end - -def validate_distros distros_string - return false if not distros_string.is_a?(String) - values = parse_distros(distros_string, true) - values.each do |v| - return false if not v == 'all' or not distro_map.keys.include?(v) - end - return true -end - -def validate_config config_data - # Validate/normalize the config file straight away - if not config_data.is_a?(Array) - raise "The configutaration in #{build_config_file} is malformed; the build system is expecting an array of topic groups." - end - config_data.each do |topic_group| - # Check for presence of topic group keys - ['Name','Dir','Topics'].each do |group_key| - if not topic_group.has_key?(group_key) - raise "One of the topic groups in #{build_config_file} is missing the '#{group_key}' key." - end - end - # Check for right format of topic group values - ['Name','Dir'].each do |group_key| - if not topic_group[group_key].is_a?(String) - raise "One of the topic groups in #{build_config_file} is not using a string for the #{group_key} setting; current value is #{topic_group[group_key].inspect}" - end - if topic_group[group_key].empty? or topic_group[group_key].match BLANK_STRING_RE - raise "One of the topic groups in #{build_config_file} is using a blank value for the #{group_key} setting." - end - end - if not File.exists?(File.join(source_dir,topic_group['Dir'])) - raise "In #{build_config_file}, the directory #{topic_group['Dir']} for topic group #{topic_group['Name']} does not exist under #{source_dir}" - end - # Validate the Distros setting - if topic_group.has_key?('Distros') - if not validate_distros(topic_group['Distros']) - key_list = distro_map.keys.map{ |k| "'#{k.to_s}'" }.sort.join(', ') - raise "In #{build_config_file}, the Distros value #{topic_group['Distros'].inspect} for topic group #{topic_group['Name']} is not valid. Legal values are 'all', #{key_list}, or a comma-separated list of legal values." - end - topic_group['Distros'] = parse_distros(topic_group['Distros']) - else - topic_group['Distros'] = parse_distros('all') - end - if not topic_group['Topics'].is_a?(Array) - raise "The #{topic_group['Name']} topic group in #{build_config_file} is malformed; the build system is expecting an array of 'Topic' definitions." - end - # Generate an ID for this topic group - topic_group['ID'] = camelize topic_group['Name'] - # Now buzz through the topics - topic_group['Topics'].each do |topic| - ['Name','File'].each do |topic_key| - if not topic[topic_key].is_a?(String) - raise "In #{build_config_file}, topic group #{topic_group['Name']}, one of the topics is not using a string for the '#{topic_key}' setting; current value is #{topic[topic_key].inspect}" - end - if topic[topic_key].empty? or topic[topic_key].match BLANK_STRING_RE - raise "In #{build_config_file}, topic group #{topic_group['Name']}, one of the topics is using a blank value for the '#{topic_key}' setting" - end - end - # Normalize the filenames - if topic['File'].end_with?('.adoc') - topic['File'] = topic['File'][0..-6] - end - if not File.exists?(File.join(source_dir,topic_group['Dir'],"#{topic['File']}.adoc")) - raise "In #{build_config_file}, could not find file #{topic['File']} under directory #{topic_group['Dir']} for topic #{topic['Name']} in topic group #{topic_group['Name']}." - end - if topic.has_key?('Distros') - if not validate_distros(topic['Distros']) - key_list = distro_map.keys.map{ |k| "'#{k.to_s}'" }.sort.join(', ') - raise "In #{build_config_file}, the Distros value #{topic_group['Distros'].inspect} for topic item #{topic['Name']} in topic group #{topic_group['Name']} is not valid. Legal values are 'all', #{key_list}, or a comma-separated list of legal values." - end - topic['Distros'] = parse_distros(topic['Distros']) - else - topic['Distros'] = parse_distros('all') - end - # Generate an ID for this topic - topic['ID'] = "#{topic_group['ID']}::#{camelize(topic['Name'])}" - end - end - config_data -end - -def camelize text - text.split(' ').map{ |t| t.capitalize }.join -end - -def nav_tree distro - @nav_tree ||= begin - navigation = [] - build_config.each do |topic_group| - next if not topic_group['Distros'].include?(distro) - next if topic_group['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0 - topic_list = [] - topic_group['Topics'].each do |topic| - next if not topic['Distros'].include?(distro) - topic_list << { - :path => "../#{topic_group['Dir']}/#{topic['File']}.html", - :name => topic['Name'], - :id => topic['ID'], - } - end - navigation << { :name => topic_group['Name'], :id => topic_group['ID'], :topics => topic_list } - end - navigation - end -end - -def asciidoctor_page_attrs(more_attrs=[]) - [ - 'source-highlighter=coderay', - 'coderay-css=style', - 'linkcss!', - 'icons=font', - 'idprefix=', - 'idseparator=-', - 'sectanchors', - ].concat(more_attrs) -end +include DocSiteBuilder::Helpers task :build, :build_distro do |task,args| # Figure out which distros we are building. # A blank value here == all distros build_distro = args[:build_distro] || '' + generate_docs(build_distro) +end - if not build_distro == '' - if not distro_map.has_key?(build_distro) - puts "Unrecognized distro '#{build_distro}'; cancelling build." - exit - else - puts "Building the #{distro_map[build_distro][:name]} distribution(s)." - end - else - puts "Building all available distributions." - end - - # First, notify the user of missing local branches - missing_branches = [] - distro_branches(build_distro).sort.each do |dbranch| - next if local_branches.include?(dbranch) - missing_branches << dbranch - end - if missing_branches.length > 0 - puts "\nNOTE: The following branches do not exist in your local git repo:" - missing_branches.each do |mbranch| - puts "- #{mbranch}" - end - puts "The build will proceed but these branches will not be generated." - end - distro_map.each do |distro,distro_config| - if not build_distro == '' and not build_distro == distro - next - end - puts "\nBuilding #{distro_config[:name]}" - distro_config[:branches].each do |branch,branch_config| - if missing_branches.include?(branch) - puts "- skipping #{branch}" - next - end - puts "- building #{branch}" - - # Put us on the correct branch - git_checkout(branch) - - # Create the target dir - branch_path = File.join(preview_dir,branch_config[:dir]) - system("mkdir -p #{branch_path}/stylesheets") - - # Copy stylesheets into preview area - system("cp -r _stylesheets/*css #{branch_path}/stylesheets") - - # Build the landing page - navigation = nav_tree(distro) - - # Build the topic files - build_config.each do |topic_group| - next if not topic_group['Distros'].include?(distro) - next if topic_group['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0 - src_group_path = File.join(source_dir,topic_group['Dir']) - tgt_group_path = File.join(branch_path,topic_group['Dir']) - if not File.exists?(tgt_group_path) - Dir.mkdir(tgt_group_path) - end - topic_group['Topics'].each do |topic| - next if not topic['Distros'].include?(distro) - src_file_path = File.join(src_group_path,"#{topic['File']}.adoc") - tgt_file_path = File.join(tgt_group_path,"#{topic['File']}.html") - puts " - #{File.join(topic_group['Dir'],topic['File'])}" - topic_adoc = File.open(src_file_path,'r').read - page_attrs = asciidoctor_page_attrs([ - "imagesdir=#{src_group_path}/images", - distro, - "product-title=#{distro_config[:name]}", - "product-version=#{branch_config[:name]}", - "product-author=#{PRODUCT_AUTHOR}" - ]) - topic_html = Asciidoctor.render topic_adoc, :header_footer => false, :safe => :unsafe, :attributes => page_attrs - full_file_text = page({ - :distro => distro_config[:name], - :version => branch_config[:name], - :group_title => topic_group['Name'], - :topic_title => topic['Name'], - :content => topic_html, - :navigation => navigation, - :group_id => topic_group['ID'], - :topic_id => topic['ID'], - :css_path => "../../#{branch_config[:dir]}/stylesheets/", - :css => [ - 'bootstrap_default.min.css', - 'font-awesome.min.css', - 'foundation.css', - 'origin.css', - ], - :nav_expanded => (topic_group['Dir'] == 'welcome' and topic['File'].start_with?('index')), - }) - File.write(tgt_file_path,full_file_text) - end - end - end - - # Create a distro landing page - # WARNING: if building mutiple distros, this file will be overwritten by each distro - src_file_path = File.join(source_dir,'index.adoc') - topic_adoc = File.open(src_file_path,'r').read - page_attrs = asciidoctor_page_attrs([ - "imagesdir=#{File.join(source_dir,'_site_images')}", - distro, - "product-title=#{distro_config[:name]}", - "product-version=Updated #{build_date}", - "product-author=#{PRODUCT_AUTHOR}" - ]) - topic_html = Asciidoctor.render topic_adoc, :header_footer => true, :safe => :unsafe, :attributes => page_attrs - File.write(File.join(preview_dir,'index.html'),topic_html) - - # Return to the original branch - git_checkout(local_branches[0]) - end - - puts "\nAll builds completed." +task :refresh_page, :single_page do |task,args| + generate_docs('',args[:single_page]) end task :clean do diff --git a/_builder_lib/docsitebuilder/helpers.rb b/_builder_lib/docsitebuilder/helpers.rb new file mode 100644 index 000000000000..8a927c30e392 --- /dev/null +++ b/_builder_lib/docsitebuilder/helpers.rb @@ -0,0 +1,470 @@ +require 'asciidoctor' +require 'git' +require 'logger' +require 'pandoc-ruby' +require 'pathname' +require 'yaml' + +module DocSiteBuilder + module Helpers + + BUILD_FILENAME = '_build_cfg.yml' + BUILDER_DIRNAME = '_build_system' + PREVIEW_DIRNAME = '_preview' + PACKAGE_DIRNAME = '_package' + BLANK_STRING_RE = Regexp.new('^\s*$') + PRODUCT_AUTHOR = "OpenShift Documentation Project " + ANALYTICS_SHIM = '' + + def source_dir + @source_dir ||= File.expand_path '../../../', __FILE__ + end + + def template_dir + @template_dir ||= File.join(source_dir,'_templates') + end + + def preview_dir + @preview_dir ||= begin + preview_dir = File.join(source_dir,PREVIEW_DIRNAME) + if not File.exists?(preview_dir) + Dir.mkdir(preview_dir) + end + preview_dir + end + end + + def package_dir + @package_dir ||= begin + package_dir = File.join(source_dir,PACKAGE_DIRNAME) + if not File.exists?(package_dir) + Dir.mkdir(package_dir) + end + package_dir + end + end + + def build_date + Time.now.utc + end + + def git + @git ||= Git.open(source_dir) + end + + def git_checkout branch_name + target_branch = git.branches.local.select{ |b| b.name == branch_name }[0] + if not target_branch.current + target_branch.checkout + end + end + + # Returns the local git branches; current branch is always first + def local_branches + @local_branches ||= begin + branches = [] + branches << git.branches.local.select{ |b| b.current }[0].name + branches << git.branches.local.select{ |b| not b.current }.map{ |b| b.name } + branches.flatten + end + end + + def working_branch + local_branches[0] + end + + def build_config_file + @build_config_file ||= File.join(source_dir,BUILD_FILENAME) + end + + # Protip: Don't cache this! It needs to be reread every time we change branches. + def build_config + validate_config(YAML.load_stream(open(build_config_file))) + end + + def distro_map + @distro_map ||= begin + { 'openshift-origin' => { + :name => 'OpenShift Origin', + :branches => { + 'master' => { + :name => 'Nightly Build', + :dir => 'latest', + }, + 'origin-4' => { + :name => 'Version 4', + :dir => 'stable', + }, + }, + }, + 'openshift-online' => { + :name => 'OpenShift Online', + :branches => { + 'online' => { + :name => 'Latest Release', + :dir => 'online', + }, + }, + }, + 'openshift-enterprise' => { + :name => 'OpenShift Enterprise', + :branches => { + 'enterprise-2.2' => { + :name => 'Version 2.2', + :dir => 'enterprise/v2.2', + }, + }, + } + } + end + end + + def distro_branches(use_distro='') + @distro_branches ||= begin + use_distro_list = use_distro == '' ? distro_map.keys : [use_distro] + distro_map.select{ |dkey,dval| use_distro_list.include?(dkey) }.map{ |distro,dconfig| dconfig[:branches].keys }.flatten + end + end + + def page(args) + page_css = '' + args[:css].each do |sheet| + sheet_href = args[:css_path] + sheet + page_css << "\n" + end + page_head = < + + +#{args[:distro]} #{args[:version]} | #{args[:group_title]} | #{args[:topic_title]} +#{page_css} + + + + +
+

OpenShift Documentation #{args[:distro]} #{args[:version]}

+
+
+
+
+ + + + + +
+

 

+
+EOF + + page_nav = ['
'] + groupidx = 0 + args[:navigation].each do |topic_group| + current_group = topic_group[:id] == args[:group_id] + page_nav << '
' + page_nav << '
' + page_nav << '

' + page_nav << " #{topic_group[:name]}" + page_nav << '

' + page_nav << '
' + page_nav << "
    " + topic_group[:topics].each do |topic| + current_topic = topic[:id] == args[:topic_id] + page_nav << "
  • #{topic[:name]}
  • " + end + page_nav << '
' + page_nav << '
' + groupidx = groupidx + 1 + end + page_nav << '
' + + page_body = < +
+ + #{args[:content]} +
+
+
+#{ANALYTICS_SHIM} + + +EOF + + page_txt = '' + page_txt << page_head + page_txt << "\n" + page_txt << page_nav.join("\n") + page_txt << "\n" + page_txt << page_body + page_txt + end + + def parse_distros distros_string, for_validation=false + values = distros_string.split(',').map{ |v| v.strip } + return values if for_validation + return distro_map.keys if values.include?('all') + return values.uniq + end + + def validate_distros distros_string + return false if not distros_string.is_a?(String) + values = parse_distros(distros_string, true) + values.each do |v| + return false if not v == 'all' or not distro_map.keys.include?(v) + end + return true + end + + def validate_config config_data + # Validate/normalize the config file straight away + if not config_data.is_a?(Array) + raise "The configutaration in #{build_config_file} is malformed; the build system is expecting an array of topic groups." + end + config_data.each do |topic_group| + # Check for presence of topic group keys + ['Name','Dir','Topics'].each do |group_key| + if not topic_group.has_key?(group_key) + raise "One of the topic groups in #{build_config_file} is missing the '#{group_key}' key." + end + end + # Check for right format of topic group values + ['Name','Dir'].each do |group_key| + if not topic_group[group_key].is_a?(String) + raise "One of the topic groups in #{build_config_file} is not using a string for the #{group_key} setting; current value is #{topic_group[group_key].inspect}" + end + if topic_group[group_key].empty? or topic_group[group_key].match BLANK_STRING_RE + raise "One of the topic groups in #{build_config_file} is using a blank value for the #{group_key} setting." + end + end + if not File.exists?(File.join(source_dir,topic_group['Dir'])) + raise "In #{build_config_file}, the directory #{topic_group['Dir']} for topic group #{topic_group['Name']} does not exist under #{source_dir}" + end + # Validate the Distros setting + if topic_group.has_key?('Distros') + if not validate_distros(topic_group['Distros']) + key_list = distro_map.keys.map{ |k| "'#{k.to_s}'" }.sort.join(', ') + raise "In #{build_config_file}, the Distros value #{topic_group['Distros'].inspect} for topic group #{topic_group['Name']} is not valid. Legal values are 'all', #{key_list}, or a comma-separated list of legal values." + end + topic_group['Distros'] = parse_distros(topic_group['Distros']) + else + topic_group['Distros'] = parse_distros('all') + end + if not topic_group['Topics'].is_a?(Array) + raise "The #{topic_group['Name']} topic group in #{build_config_file} is malformed; the build system is expecting an array of 'Topic' definitions." + end + # Generate an ID for this topic group + topic_group['ID'] = camelize topic_group['Name'] + # Now buzz through the topics + topic_group['Topics'].each do |topic| + ['Name','File'].each do |topic_key| + if not topic[topic_key].is_a?(String) + raise "In #{build_config_file}, topic group #{topic_group['Name']}, one of the topics is not using a string for the '#{topic_key}' setting; current value is #{topic[topic_key].inspect}" + end + if topic[topic_key].empty? or topic[topic_key].match BLANK_STRING_RE + raise "In #{build_config_file}, topic group #{topic_group['Name']}, one of the topics is using a blank value for the '#{topic_key}' setting" + end + end + # Normalize the filenames + if topic['File'].end_with?('.adoc') + topic['File'] = topic['File'][0..-6] + end + if not File.exists?(File.join(source_dir,topic_group['Dir'],"#{topic['File']}.adoc")) + raise "In #{build_config_file}, could not find file #{topic['File']} under directory #{topic_group['Dir']} for topic #{topic['Name']} in topic group #{topic_group['Name']}." + end + if topic.has_key?('Distros') + if not validate_distros(topic['Distros']) + key_list = distro_map.keys.map{ |k| "'#{k.to_s}'" }.sort.join(', ') + raise "In #{build_config_file}, the Distros value #{topic_group['Distros'].inspect} for topic item #{topic['Name']} in topic group #{topic_group['Name']} is not valid. Legal values are 'all', #{key_list}, or a comma-separated list of legal values." + end + topic['Distros'] = parse_distros(topic['Distros']) + else + topic['Distros'] = parse_distros('all') + end + # Generate an ID for this topic + topic['ID'] = "#{topic_group['ID']}::#{camelize(topic['Name'])}" + end + end + config_data + end + + def camelize text + text.split(' ').map{ |t| t.capitalize }.join + end + + def nav_tree distro, distro_build_config + navigation = [] + distro_build_config.each do |topic_group| + next if not topic_group['Distros'].include?(distro) + next if topic_group['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0 + topic_list = [] + topic_group['Topics'].each do |topic| + next if not topic['Distros'].include?(distro) + topic_list << { + :path => "../#{topic_group['Dir']}/#{topic['File']}.html", + :name => topic['Name'], + :id => topic['ID'], + } + end + navigation << { :name => topic_group['Name'], :id => topic_group['ID'], :topics => topic_list } + end + navigation + end + + def asciidoctor_page_attrs(more_attrs=[]) + [ + 'source-highlighter=coderay', + 'coderay-css=style', + 'linkcss!', + 'icons=font', + 'idprefix=', + 'idseparator=-', + 'sectanchors', + ].concat(more_attrs) + end + + def generate_docs(build_distro,single_page=nil) + single_page_dir = nil + single_page_file = nil + if not single_page.nil? + single_page_dir = single_page.split(':')[0] + single_page_file = single_page.split(':')[1] + puts "Rebuilding '#{single_page}' on branch '#{working_branch}'." + end + if not build_distro == '' + if not distro_map.has_key?(build_distro) + puts "Unrecognized distro '#{build_distro}'; cancelling build." + exit + else + puts "Building the #{distro_map[build_distro][:name]} distribution(s)." + end + elsif single_page.nil? + puts "Building all available distributions." + end + + # First, notify the user of missing local branches + missing_branches = [] + distro_branches(build_distro).sort.each do |dbranch| + next if local_branches.include?(dbranch) + missing_branches << dbranch + end + if missing_branches.length > 0 and single_page.nil? + puts "\nNOTE: The following branches do not exist in your local git repo:" + missing_branches.each do |mbranch| + puts "- #{mbranch}" + end + puts "The build will proceed but these branches will not be generated." + end + + distro_map.each do |distro,distro_config| + if not build_distro == '' and not build_distro == distro + next + end + first_branch = single_page.nil? + distro_config[:branches].each do |branch,branch_config| + if not single_page.nil? and not working_branch == branch + next + end + if first_branch + puts "\nBuilding #{distro_config[:name]}" + first_branch = false + end + if missing_branches.include?(branch) + puts "- skipping #{branch}" + next + end + if single_page.nil? + puts "- building #{branch}" + git_checkout(branch) + end + + # Create the target dir + branch_path = File.join(preview_dir,branch_config[:dir]) + system("mkdir -p #{branch_path}/stylesheets") + + # Copy stylesheets into preview area + system("cp -r _stylesheets/*css #{branch_path}/stylesheets") + + # Read the _build_config.yml for this distro + distro_build_config = build_config + + # Build the landing page + navigation = nav_tree(distro,distro_build_config) + + # Build the topic files + distro_build_config.each do |topic_group| + next if not topic_group['Distros'].include?(distro) + next if topic_group['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0 + next if not single_page.nil? and not single_page_dir == topic_group['Dir'] + src_group_path = File.join(source_dir,topic_group['Dir']) + tgt_group_path = File.join(branch_path,topic_group['Dir']) + if not File.exists?(tgt_group_path) + Dir.mkdir(tgt_group_path) + end + topic_group['Topics'].each do |topic| + next if not topic['Distros'].include?(distro) + next if not single_page.nil? and not topic['File'] == single_page_file + src_file_path = File.join(src_group_path,"#{topic['File']}.adoc") + tgt_file_path = File.join(tgt_group_path,"#{topic['File']}.html") + if single_page.nil? + puts " - #{File.join(topic_group['Dir'],topic['File'])}" + end + topic_adoc = File.open(src_file_path,'r').read + page_attrs = asciidoctor_page_attrs([ + "imagesdir=#{src_group_path}/images", + distro, + "product-title=#{distro_config[:name]}", + "product-version=#{branch_config[:name]}", + "product-author=#{PRODUCT_AUTHOR}" + ]) + topic_html = Asciidoctor.render topic_adoc, :header_footer => false, :safe => :unsafe, :attributes => page_attrs + full_file_text = page({ + :distro => distro_config[:name], + :version => branch_config[:name], + :group_title => topic_group['Name'], + :topic_title => topic['Name'], + :content => topic_html, + :navigation => navigation, + :group_id => topic_group['ID'], + :topic_id => topic['ID'], + :css_path => "../../#{branch_config[:dir]}/stylesheets/", + :css => [ + 'bootstrap_default.min.css', + 'font-awesome.min.css', + 'foundation.css', + 'origin.css', + ], + }) + File.write(tgt_file_path,full_file_text) + if not single_page.nil? + return + end + end + end + end + + # Create a distro landing page + # WARNING: if building mutiple distros, this file will be overwritten by each distro + src_file_path = File.join(source_dir,'index.adoc') + topic_adoc = File.open(src_file_path,'r').read + page_attrs = asciidoctor_page_attrs([ + "imagesdir=#{File.join(source_dir,'_site_images')}", + distro, + "product-title=#{distro_config[:name]}", + "product-version=Updated #{build_date}", + "product-author=#{PRODUCT_AUTHOR}" + ]) + topic_html = Asciidoctor.render topic_adoc, :header_footer => true, :safe => :unsafe, :attributes => page_attrs + File.write(File.join(preview_dir,'index.html'),topic_html) + + # Return to the original branch + git_checkout(local_branches[0]) + end + + puts "\nAll builds completed." + end + end +end