Created
February 25, 2014 03:03
-
-
Save tbielawa/9201843 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Makefile b/Makefile | |
index be57177..5a59b31 100644 | |
--- a/Makefile | |
+++ b/Makefile | |
@@ -171,6 +171,7 @@ rpm: rpmcommon | |
@find rpm-build -maxdepth 2 -name 'juicer*.rpm' | awk '{print " " $$1}' | |
@echo "#############################################" | |
+# This makes an RPM for Openshift Online | |
oorpm: rpmcommon | |
@rpmbuild --define "_topdir %(pwd)/rpm-build" \ | |
--define "_builddir %{_topdir}" \ | |
@@ -191,7 +192,8 @@ koji: srpm | |
test: | |
. ./hacking/setup-env | |
if [ "$(LOG)" = "true" ]; then \ | |
- ./hacking/tests | tee /tmp/juicer_tests.log; \ | |
+ ./hacking/tests | tee -a /tmp/juicer_tests.log; \ | |
+ echo "Test results logged to /tmp/juicer_tests.log"; \ | |
else \ | |
./hacking/tests; \ | |
fi | |
diff --git a/bin/juicer b/bin/juicer | |
index d4ee78d..eddd743 100755 | |
--- a/bin/juicer | |
+++ b/bin/juicer | |
@@ -32,4 +32,7 @@ if __name__ == '__main__': | |
try: | |
main() | |
except JuicerError, e: | |
+ print "Juicer error happened:" | |
print e | |
+ except KeyboardInterrupt: | |
+ print "User killed via ^C" | |
diff --git a/bin/juicer-admin b/bin/juicer-admin | |
index aa2e468..0d7eeb8 100755 | |
--- a/bin/juicer-admin | |
+++ b/bin/juicer-admin | |
@@ -31,6 +31,7 @@ if __name__ == '__main__': | |
try: | |
main() | |
except JuicerError, e: | |
+ print "Juicer error happened:" | |
print e | |
- except KeyError, e: | |
- pass | |
+ except KeyboardInterrupt: | |
+ print "User killed via ^C" | |
diff --git a/docs/man/man1/juicer-admin.1 b/docs/man/man1/juicer-admin.1 | |
index ab773c8..8324fe1 100644 | |
--- a/docs/man/man1/juicer-admin.1 | |
+++ b/docs/man/man1/juicer-admin.1 | |
@@ -2,12 +2,12 @@ | |
.\" Title: juicer-admin | |
.\" Author: :doctype:manpage | |
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/> | |
-.\" Date: 02/14/2014 | |
+.\" Date: 02/19/2014 | |
.\" Manual: Pulp repos and release carts | |
.\" Source: Juicer 0.7.0 | |
.\" Language: English | |
.\" | |
-.TH "JUICER\-ADMIN" "1" "02/14/2014" "Juicer 0\&.7\&.0" "Pulp repos and release carts" | |
+.TH "JUICER\-ADMIN" "1" "02/19/2014" "Juicer 0\&.7\&.0" "Pulp repos and release carts" | |
.\" ----------------------------------------------------------------- | |
.\" * Define some portability stuff | |
.\" ----------------------------------------------------------------- | |
@@ -40,7 +40,9 @@ Manage pulp repositories, users, and roles\&. | |
\fB\-v\fR, \fB\-\-verbose\fR | |
.RS 4 | |
Increase the verbosity (up to 3x)\&. In usage specify | |
-\fI\-v\fR\fBbefore\fR\fICOMMAND\fR\&. | |
+\fI\-v\fR | |
+before | |
+\fICOMMAND\fR\&. | |
.RE | |
.PP | |
\fB\-V\fR, \fB\-\-version\fR | |
@@ -52,7 +54,8 @@ Print the version that you\(cqre using of juicer\-admin\&. | |
.RS 4 | |
Specify which environment(s) to perform the | |
\fISUBCOMMAND\fR | |
-in\&. In usage all | |
+in\&. In | |
+\fBjuicer\-admin\fR(1) usage, all | |
\fISUBCOMMAND\fRs accept this option when given after | |
\fISUBCOMMAND\fR\&. | |
.RE | |
@@ -61,12 +64,12 @@ in\&. In usage all | |
This section describes all of the \fIREPO\fR subcommands\&. | |
.SH "REPO CREATE" | |
.sp | |
-usage: juicer\-admin repo create REPONAME [\-\-arch ARCH] [\-\-feed FEED] [\-\-checksum\-type TYPE] | |
+usage: juicer\-admin repo create \fIREPONAME\fR [\-\-arch \fIARCH\fR] [\-\-feed \fIFEED\fR] [\-\-checksum\-type \fITYPE\fR] | |
.PP | |
\fBREPONAME\fR | |
.RS 4 | |
The name of the repository to be created\&. Only alphanumeric, \&., \-, and _ allowed\&. As a regular expression: | |
-\fB([a\-zA\-Z\-_\&.]+)\fR | |
+\fB([a\-zA\-Z0\-9\-_\&.]+)\fR | |
.RE | |
.PP | |
\fB\-\-arch=\fR\fIARCH\fR | |
@@ -89,6 +92,25 @@ Optional\&. The checksum type to use when generating meta\-data\&. Defaults to | |
\fBsha256\fR, also valid is | |
\fBsha\fR\&. | |
.RE | |
+.SH "REPO IMPORT" | |
+.sp | |
+usage: juicer\-admin repo import \-\-from\-file \fIREPO_DEF\fR [\-\-noop] | |
+.sp | |
+Create repositories matching the definitions in the repo def file\&. Repositories which already exist will be updated\&. | |
+.PP | |
+\fB\-\-from\-file=\fR\fIREPO_DEF\fR | |
+.RS 4 | |
+Repository definition file in JSON format\&. See | |
+\fIREPO DEF FORMAT\fR | |
+(below) for a quick review of the syntax\&. See the docs online or in /usr/share/doc/juicer*/repo_syntax\&.md for a detailed description\&. | |
+.RE | |
+.PP | |
+\fB\-\-noop\fR, \fB\-\-dry\-run\fR, \fB\-n\fR | |
+.RS 4 | |
+Don\(cqt create the repos, just show what would have happened\&. | |
+.RE | |
+.sp | |
+\fBNote:\fR This command does not respect \fB\-\-in\fR | |
.SH "REPO DELETE" | |
.sp | |
usage: juicer\-admin repo delete \fIREPONAME\fR | |
@@ -99,16 +121,30 @@ Name of repository which will be deleted | |
.RE | |
.SH "REPO LIST" | |
.sp | |
-usage: juicer\-admin repo list | |
+usage: juicer\-admin repo list [\-\-json] | |
+.sp | |
+List all of the repos in any or all environments\&. | |
+.PP | |
+\fB\-\-json\fR | |
+.RS 4 | |
+Optional\&. Return the data in JSON format\&. | |
+.RE | |
.SH "REPO SHOW" | |
.sp | |
-usage: juicer\-admin repo show \fIREPONAME\fR | |
+usage: juicer\-admin repo show \fIREPONAME\fR [\&...] [\-\-json] | |
.sp | |
-Show basic statistics about a repo in pulp\&. Currently this command just prints the number of packages in the specified repository\&. | |
+Show basic statistics in table format about one or more repos in pulp\&. This information includes: Name, Environment, RPM count, SRPM count, and Checksum Type\&. | |
.PP | |
\fBREPONAME\fR | |
.RS 4 | |
-Name of the repository to show | |
+Name of the repository to show\&. Separate multiple | |
+\fIREPONAME\fR | |
+parameters with spaces\&. | |
+.RE | |
+.PP | |
+\fB\-\-json\fR | |
+.RS 4 | |
+Optional\&. Return the data in JSON format\&. | |
.RE | |
.SH "REPO SYNC" | |
.sp | |
@@ -123,7 +159,7 @@ Name of repository to sync | |
This section describes all of the \fIUSER\fR subcommands\&. | |
.SH "USER CREATE" | |
.sp | |
-usage: juicer\-admin user create LOGIN \-\-name FULLNAME \-\-password PASSWORD | |
+usage: juicer\-admin user create \fILOGIN\fR \-\-name \fIFULLNAME\fR \-\-password \fIPASSWORD\fR | |
.sp | |
Create a user in the pulp system\&. | |
.PP | |
@@ -201,6 +237,146 @@ Login or username of user which will be added to role | |
.RE | |
.sp | |
See the Pulp User documentation (\fBSEE ALSO\fR) for more information on the specifics of role management\&. | |
+.SH "REPO DEF FORMAT" | |
+.sp | |
+\fBMandatory keys\fR: | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+name (string) | |
+.RE | |
+.sp | |
+\fBOptional Keys\fR: | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+feed (string) | |
+.RE | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+checksum_type (string, one of: | |
+\fBsha\fR, | |
+\fBsha256\fR) | |
+.RE | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+env (list of environment name strings) | |
+.RE | |
+.sp | |
+\fBExample:\fR | |
+.sp | |
+.if n \{\ | |
+.RS 4 | |
+.\} | |
+.nf | |
+[ | |
+ {"name": "repo01", "env": ["prod"]}, | |
+ {"name": "repo02"}, | |
+ { | |
+ "name": "fedora_mirror", | |
+ "feed": "http://download\&.fedoraproject\&.org/pub/fedora/linux/releases/20/Everything/x86_64/os/", | |
+ "checksum_type": "sha", | |
+ "env": ["dev", "prod"] | |
+ } | |
+] | |
+.fi | |
+.if n \{\ | |
+.RE | |
+.\} | |
+.sp | |
+\fBProtips\fR | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+Don\(cqt end lists or hashes with trailing commas | |
+.RE | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+Remember to close all of your braces and brackets: Each | |
+\fB[\fR | |
+has a matching | |
+\fB]\fR, each | |
+\fB{\fR | |
+has a matching | |
+\fB}\fR | |
+.RE | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+Use a javascript mode in your editor if it doesn\(cqt have a native json mode | |
+.RE | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+Setting | |
+\fBenv\fR | |
+to an empty list (\fB[]\fR) will | |
+\fInot\fR | |
+delete the repo from any environment | |
+.RE | |
+.sp | |
+.RS 4 | |
+.ie n \{\ | |
+\h'-04'\(bu\h'+03'\c | |
+.\} | |
+.el \{\ | |
+.sp -1 | |
+.IP \(bu 2.3 | |
+.\} | |
+Use a linting service if you\(cqre stuck, for example: | |
+\fBhttp://jsonlint\&.com/\fR | |
+.RE | |
.SH "FILES" | |
.sp | |
\fB~/\&.config/juicer/config\fR \(em Juicer configuration file | |
@@ -213,16 +389,18 @@ Juicer was written by GCA\-PC, Red Hat, Inc\&.\&. | |
This man page was written by Tim Bielawa <tbielawa@redhat\&.com> and Andrew Butcher <abutcher@redhat\&.com>\&. | |
.SH "COPYRIGHT" | |
.sp | |
-Copyright \(co 2012, Red Hat, Inc\&.\&. | |
+Copyright \(co 2012\-2014, Red Hat, Inc\&.\&. | |
.sp | |
Juicer is released under the terms of the GPLv3+ License\&. | |
.SH "SEE ALSO" | |
.sp | |
\fBjuicer\fR(1), \fBjuicer\&.conf\fR(5) | |
.sp | |
-\fBPulp User Documentation\fR \(em https://pulp\-user\-guide\&.readthedocs\&.org/en/pulp\-2\&.0/ | |
+\fBPulp User Documentation\fR \(em http://www\&.pulpproject\&.org/docs/ | |
+.sp | |
+\fBDetailed Repo Def Description\fR \(em https://github\&.com/juicer/juicer/blob/master/docs/markdown/repo_syntax\&.md | |
.sp | |
-The Juicer Homepage: https://github\&.com/juicer/juicer/ | |
+\fBThe Juicer Homepage\fR \(em https://github\&.com/juicer/juicer/ | |
.SH "AUTHOR" | |
.PP | |
\fB:doctype:manpage\fR | |
diff --git a/docs/man/man1/juicer-admin.1.asciidoc.in b/docs/man/man1/juicer-admin.1.asciidoc.in | |
index 4faf859..a918d5b 100644 | |
--- a/docs/man/man1/juicer-admin.1.asciidoc.in | |
+++ b/docs/man/man1/juicer-admin.1.asciidoc.in | |
@@ -26,15 +26,16 @@ COMMON OPTIONS | |
-------------- | |
*-v*, *--verbose*:: | |
-Increase the verbosity (up to 3x). In usage specify '-v' *before* 'COMMAND'. | |
+Increase the verbosity (up to 3x). In usage specify '-v' before 'COMMAND'. | |
*-V*, *--version*:: | |
Print the version that you're using of juicer-admin. | |
*--in* 'env' ...:: | |
-Specify which environment(s) to perform the 'SUBCOMMAND' in. In usage | |
-all __SUBCOMMAND__s accept this option when given after 'SUBCOMMAND'. | |
+Specify which environment(s) to perform the 'SUBCOMMAND' in. In | |
+*juicer-admin*(1) usage, all __SUBCOMMAND__s accept this option when | |
+given after 'SUBCOMMAND'. | |
@@ -45,17 +46,16 @@ This section describes all of the __REPO__ subcommands. | |
REPO CREATE | |
----------- | |
-usage: juicer-admin repo create REPONAME [--arch ARCH] [--feed FEED] [--checksum-type TYPE] | |
+usage: juicer-admin repo create 'REPONAME' [--arch 'ARCH'] [--feed 'FEED'] [--checksum-type 'TYPE'] | |
*REPONAME*:: | |
The name of the repository to be created. Only alphanumeric, ., -, and | |
-_ allowed. As a regular expression: *([a-zA-Z-_.]+)* | |
+_ allowed. As a regular expression: *([a-zA-Z0-9-_.]+)* | |
*--arch=*'ARCH':: | |
Optional. Repository package architecture. Defaults to *noarch*. Other | |
values might be: *i386*, *i686*, or *x86_64*. | |
- | |
*--feed=*'FEED':: | |
Optional. A feed url from which to synchronize yum repository packages. | |
@@ -64,6 +64,25 @@ Optional. The checksum type to use when generating meta-data. | |
Defaults to *sha256*, also valid is *sha*. | |
+REPO IMPORT | |
+----------- | |
+usage: juicer-admin repo import --from-file 'REPO_DEF' [--noop] | |
+ | |
+Create repositories matching the definitions in the repo def | |
+file. Repositories which already exist will be updated. | |
+ | |
+*--from-file=*'REPO_DEF':: | |
+ | |
+Repository definition file in JSON format. See __REPO DEF FORMAT__ | |
+(below) for a quick review of the syntax. See the docs online or in | |
+/usr/share/doc/juicer*/repo_syntax.md for a detailed description. | |
+ | |
+*--noop*, *--dry-run*, *-n*:: | |
+ | |
+Don't create the repos, just show what would have happened. | |
+ | |
+ | |
+*Note:* This command does not respect *--in* | |
REPO DELETE | |
@@ -77,18 +96,30 @@ Name of repository which will be deleted | |
REPO LIST | |
--------- | |
-usage: juicer-admin repo list | |
+usage: juicer-admin repo list [--json] | |
+ | |
+List all of the repos in any or all environments. | |
+ | |
+*--json*:: | |
+Optional. Return the data in JSON format. | |
REPO SHOW | |
--------- | |
-usage: juicer-admin repo show 'REPONAME' | |
+usage: juicer-admin repo show 'REPONAME' [...] [--json] | |
-Show basic statistics about a repo in pulp. Currently this command | |
-just prints the number of packages in the specified repository. | |
+Show basic statistics in table format about one or more repos in | |
+pulp. This information includes: Name, Environment, RPM count, SRPM | |
+count, and Checksum Type. | |
*REPONAME*:: | |
-Name of the repository to show | |
+Name of the repository to show. Separate multiple 'REPONAME' | |
+parameters with spaces. | |
+ | |
+*--json*:: | |
+ | |
+Optional. Return the data in JSON format. | |
+ | |
REPO SYNC | |
@@ -114,7 +145,7 @@ This section describes all of the __USER__ subcommands. | |
USER CREATE | |
----------- | |
-usage: juicer-admin user create LOGIN --name FULLNAME --password PASSWORD | |
+usage: juicer-admin user create 'LOGIN' --name 'FULLNAME' --password 'PASSWORD' | |
Create a user in the pulp system. | |
@@ -198,7 +229,40 @@ the specifics of role management. | |
+REPO DEF FORMAT | |
+--------------- | |
+ | |
+*Mandatory keys*: | |
+ | |
+- name (string) | |
+ | |
+*Optional Keys*: | |
+ | |
+- feed (string) | |
+- checksum_type (string, one of: *sha*, *sha256*) | |
+- env (list of environment name strings) | |
+*Example:* | |
+ | |
+ [ | |
+ {"name": "repo01", "env": ["prod"]}, | |
+ {"name": "repo02"}, | |
+ { | |
+ "name": "fedora_mirror", | |
+ "feed": "http://download.fedoraproject.org/pub/fedora/linux/releases/20/Everything/x86_64/os/", | |
+ "checksum_type": "sha", | |
+ "env": ["dev", "prod"] | |
+ } | |
+ ] | |
+ | |
+ | |
+*Protips* | |
+ | |
+- Don't end lists or hashes with trailing commas | |
+- Remember to close all of your braces and brackets: Each *[* has a matching *]*, each *{* has a matching *}* | |
+- Use a javascript mode in your editor if it doesn't have a native json mode | |
+- Setting *env* to an empty list (*[]*) will 'not' delete the repo from any environment | |
+- Use a linting service if you're stuck, for example: *http://jsonlint.com/* | |
@@ -224,7 +288,7 @@ Andrew Butcher <abutcher@redhat.com>. | |
COPYRIGHT | |
--------- | |
-Copyright © 2012, Red Hat, Inc.. | |
+Copyright © 2012-2014, Red Hat, Inc.. | |
Juicer is released under the terms of the GPLv3+ License. | |
@@ -234,6 +298,8 @@ SEE ALSO | |
-------- | |
*juicer*(1), *juicer.conf*(5) | |
-*Pulp User Documentation* -- <https://pulp-user-guide.readthedocs.org/en/pulp-2.0/> | |
+*Pulp User Documentation* -- <http://www.pulpproject.org/docs/> | |
+ | |
+*Detailed Repo Def Description* -- <https://github.com/juicer/juicer/blob/master/docs/markdown/repo_syntax.md> | |
-The Juicer Homepage: <https://github.com/juicer/juicer/> | |
+*The Juicer Homepage* -- <https://github.com/juicer/juicer/> | |
diff --git a/docs/man/man1/juicer.1 b/docs/man/man1/juicer.1 | |
index 21015dd..4111786 100644 | |
--- a/docs/man/man1/juicer.1 | |
+++ b/docs/man/man1/juicer.1 | |
@@ -2,12 +2,12 @@ | |
.\" Title: juicer | |
.\" Author: :doctype:manpage | |
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/> | |
-.\" Date: 02/14/2014 | |
+.\" Date: 02/19/2014 | |
.\" Manual: Pulp repos and release carts | |
.\" Source: Juicer 0.7.0 | |
.\" Language: English | |
.\" | |
-.TH "JUICER" "1" "02/14/2014" "Juicer 0\&.7\&.0" "Pulp repos and release carts" | |
+.TH "JUICER" "1" "02/19/2014" "Juicer 0\&.7\&.0" "Pulp repos and release carts" | |
.\" ----------------------------------------------------------------- | |
.\" * Define some portability stuff | |
.\" ----------------------------------------------------------------- | |
@@ -42,16 +42,30 @@ RPMs (\fIitems\fR) need not be stored on your local machine\&. Juicer can handle | |
\fB\-v\fR, \fB\-\-verbose\fR | |
.RS 4 | |
Increase the verbosity (up to 3x)\&. In usage specify | |
-\fI\-v\fR\fBbefore\fR\fICOMMAND\fR\&. | |
+\fI\-v\fR | |
+before | |
+\fICOMMAND\fR\&. | |
.RE | |
.PP | |
\fB\-V\fR, \fB\-\-version\fR | |
.RS 4 | |
Print the version that you\(cqre using of juicer\&. | |
.RE | |
+.PP | |
+\fB\-\-in\fR \fIenv\fR \&... | |
+.RS 4 | |
+In | |
+\fBjuicer\fR(1) many subcommands allow you to limit their scope to a specific environment\&. Use | |
+\fB\-\-in\fR | |
+to specify which environment(s) to perform the | |
+\fISUBCOMMAND\fR | |
+in\&. Commands which support this will show | |
+\fB[\-\-in]\fR | |
+in their usage line\&. | |
+.RE | |
.SH "CART CREATE" | |
.sp | |
-usage: juicer cart create \fICARTNAME\fR [\-f manifest] [\-r \fIREPONAME\fR \fIitems\fR \&... [ \-r \fIREPONAME\fR \fIitems\fR ]] | |
+usage: juicer cart create \fICARTNAME\fR [\-f \fImanifest\fR] [\-r \fIREPONAME\fR \fIitems\fR \&... [ \-r \fIREPONAME\fR \fIitems\fR ]] | |
.sp | |
Create a cart with the items specified\&. | |
.PP | |
@@ -63,9 +77,17 @@ The name of the new release cart\&. | |
\fB\-f\fR \fImanifest\fR | |
.RS 4 | |
Create a cart from a manifest file\&. A manifest file is written in the following format: | |
-.RE | |
.sp | |
+.if n \{\ | |
+.RS 4 | |
+.\} | |
+.nf | |
name: version\-release | |
+.fi | |
+.if n \{\ | |
+.RE | |
+.\} | |
+.RE | |
.PP | |
\fB\-r\fR \fIREPONAME\fR | |
.RS 4 | |
@@ -83,7 +105,7 @@ Items to add to the cart in repository | |
.RE | |
.SH "RPM DELETE" | |
.sp | |
-usage: juicer rpm delete \-r REPO\-NAME ITEM ITEM \&... \-\-in [ENV \&...] | |
+usage: juicer rpm delete \-r \fIREPO\-NAME\fR \fIITEM\fR \fIITEM\fR \&... [\-\-in] | |
.sp | |
Delete rpms in a repository\&. | |
.PP | |
@@ -96,24 +118,14 @@ The name of the repo rpms live in\&. | |
.RS 4 | |
Filename of rpm to delete\&. | |
.RE | |
-.PP | |
-\fB\-\-in\fR \fIENVIRONMENT\fR \&... | |
-.RS 4 | |
-The environments to delete rpms in\&. | |
-.RE | |
.SH "HELLO" | |
.sp | |
-usage: juicer hello [\-\-in \fIENVIRONMENT\fR \&...] | |
+usage: juicer hello [\-\-in] | |
.sp | |
Test connection settings in \fB~/\&.config/juicer/config\fR | |
-.PP | |
-\fBENVIRONMENT\fR | |
-.RS 4 | |
-The environments to limit connecting testing to\&. | |
-.RE | |
.SH "CART MERGE" | |
.sp | |
-usage: juicer cart merge CART1 [\&...] CARTN \-\-name NEWCARTNAME | |
+usage: juicer cart merge \fICART1\fR [\&...] \fICARTN\fR \-\-name \fINEWCARTNAME\fR | |
.sp | |
Merges the contents of N carts into \fINEWCARTNAME\fR\&. Defaults to updating \fICART1\fR\&. | |
.PP | |
@@ -130,7 +142,7 @@ Names of N carts to merge\&. | |
.sp | |
usage: juicer cart pull \fICARTNAME\fR | |
.sp | |
-Pulls a description of a cart from the pulp server and saves it on your local machine in ~/\&.config/juicer/carts/\&. | |
+Pulls a description of a cart from the pulp server and saves it on your local machine in \fB~/\&.config/juicer/carts/\fR\&. | |
.PP | |
\fBCARTNAME\fR | |
.RS 4 | |
@@ -138,7 +150,7 @@ The name of the cart to pull\&. | |
.RE | |
.SH "CART PUSH" | |
.sp | |
-usage: juicer cart push \fICARTNAME\fR [\-\-in \fIENVIRONMENT\fR \&...] | |
+usage: juicer cart push \fICARTNAME\fR [\-\-in] | |
.sp | |
Pushes/Updates a cart on the pulp server\&. | |
.PP | |
@@ -146,14 +158,9 @@ Pushes/Updates a cart on the pulp server\&. | |
.RS 4 | |
The name of the cart to push\&. | |
.RE | |
-.PP | |
-\fB\-\-in\fR \fIENVIRONMENT\fR \&... | |
-.RS 4 | |
-The environments to push the new/updated cart to\&. | |
-.RE | |
.SH "REPO PUBLISH" | |
.sp | |
-usage: juicer repo publish \fIREPO\fR [\-\-in \fIENVIRONMENT\fR \&...] | |
+usage: juicer repo publish \fIREPO\fR [\-\-in] | |
.sp | |
Publishes a repository, regenerating it\(cqs metadata\&. | |
.PP | |
@@ -161,11 +168,6 @@ Publishes a repository, regenerating it\(cqs metadata\&. | |
.RS 4 | |
The name of the repo to publish\&. | |
.RE | |
-.PP | |
-\fB\-\-in\fR \fIENVIRONMENT\fR \&... | |
-.RS 4 | |
-The environments to publish repository in\&. | |
-.RE | |
.SH "CART PROMOTE" | |
.sp | |
usage: juicer cart promote \fICARTNAME\fR | |
@@ -192,9 +194,17 @@ The name of the new release cart\&. | |
\fB\-f\fR \fIMANIFEST\fR | |
.RS 4 | |
Update a cart with a manifest file\&. A manifest file is written in the following format: | |
-.RE | |
.sp | |
+.if n \{\ | |
+.RS 4 | |
+.\} | |
+.nf | |
name: version\-release | |
+.fi | |
+.if n \{\ | |
+.RE | |
+.\} | |
+.RE | |
.PP | |
\fB\-r\fR \fIREPONAME\fR | |
.RS 4 | |
@@ -212,7 +222,7 @@ Items to add to the cart in repository | |
.RE | |
.SH "RPM SEARCH" | |
.sp | |
-usage: juicer rpm search \fIITEM\fR [\-r \fIREPO\fR \&...] [\-c] [\-\-in \fIENVIRONMENT\fR \&...] | |
+usage: juicer rpm search \fIITEM\fR [\-r \fIREPO\fR \&...] [\-c] [\-\-in] | |
.sp | |
Search for an RPM (\fIitem\fR) in pulp\&. | |
.PP | |
@@ -230,11 +240,6 @@ The repo(s) to limit search scope to\&. | |
.RS 4 | |
Search for the package in carts as well\&. | |
.RE | |
-.PP | |
-\fB\-\-in\fR \fIENVIRONMENT\fR \&... | |
-.RS 4 | |
-The environments to limit search scope to\&. | |
-.RE | |
.SH "CART SHOW" | |
.sp | |
usage: juicer cart show \fICARTNAME\fR | |
@@ -258,7 +263,7 @@ The pattern to match\&. Default: | |
.RE | |
.SH "RPM UPLOAD" | |
.sp | |
-usage: juicer rpm upload \-r \fIREPO\fR \fIITEM\fR \&... [\-\-in \fIENVIRONMENT\fR \&...] | |
+usage: juicer rpm upload \-r \fIREPO\fR \fIITEM\fR \&... [\-\-in] | |
.sp | |
Upload multiple RPMs or files (\fIITEM\fR) to \fIREPO\fR\&. | |
.PP | |
@@ -275,11 +280,6 @@ option may be given multiple times\&. | |
.RS 4 | |
Name of the RPM(s) or file(s) to upload\&. | |
.RE | |
-.PP | |
-\fB\-\-in\fR \fIENVIRONMENT\fR \&... | |
-.RS 4 | |
-The environments which items will be uploaded to\&. | |
-.RE | |
.SH "EXAMPLES" | |
.sp | |
\fIitems\fR given may be any number and combination of the following input resource types: | |
@@ -419,14 +419,14 @@ Juicer was written by GCA\-PC, Red Hat, Inc\&.\&. | |
This man page was written by Tim Bielawa <tbielawa@redhat\&.com>\&. | |
.SH "COPYRIGHT" | |
.sp | |
-Copyright \(co 2012, Red Hat, Inc\&.\&. | |
+Copyright \(co 2012\-2014, Red Hat, Inc\&.\&. | |
.sp | |
Juicer is released under the terms of the GPLv3+ License\&. | |
.SH "SEE ALSO" | |
.sp | |
\fBjuicer\-admin\fR(1), \fBjuicer\&.conf\fR(5), \fBfnmatch\fR(3) | |
.sp | |
-The Juicer Homepage: https://github\&.com/juicer/juicer/ | |
+\fBThe Juicer Homepage\fR \(em https://github\&.com/juicer/juicer/ | |
.SH "AUTHOR" | |
.PP | |
\fB:doctype:manpage\fR | |
diff --git a/docs/man/man1/juicer.1.asciidoc.in b/docs/man/man1/juicer.1.asciidoc.in | |
index 8f36f9b..8340080 100644 | |
--- a/docs/man/man1/juicer.1.asciidoc.in | |
+++ b/docs/man/man1/juicer.1.asciidoc.in | |
@@ -36,15 +36,23 @@ COMMON OPTIONS | |
-------------- | |
*-v*, *--verbose*:: | |
-Increase the verbosity (up to 3x). In usage specify '-v' *before* 'COMMAND'. | |
+Increase the verbosity (up to 3x). In usage specify '-v' before 'COMMAND'. | |
*-V*, *--version*:: | |
Print the version that you're using of juicer. | |
+*--in* 'env' ...:: | |
+ | |
+In *juicer*(1) many subcommands allow you to limit their scope to a | |
+specific environment. Use *--in* to specify which environment(s) to | |
+perform the 'SUBCOMMAND' in. Commands which support this will show | |
+*[--in]* in their usage line. | |
+ | |
+ | |
CART CREATE | |
----------- | |
-usage: juicer cart create 'CARTNAME' [-f manifest] [-r 'REPONAME' 'items' ... [ -r 'REPONAME' 'items' ]] | |
+usage: juicer cart create 'CARTNAME' [-f 'manifest'] [-r 'REPONAME' 'items' ... [ -r 'REPONAME' 'items' ]] | |
Create a cart with the items specified. | |
@@ -55,7 +63,7 @@ The name of the new release cart. | |
Create a cart from a manifest file. A manifest file is written in | |
the following format: | |
-name: version-release | |
+ name: version-release | |
*-r* 'REPONAME':: | |
Name of the reopsitory to install 'ITEMS' into. The '-r' option may be | |
@@ -64,28 +72,11 @@ given multiple times. | |
*ITEM* ...:: | |
Items to add to the cart in repository 'REPONAME'. | |
-//////////////////////////////////////////////////////////////////////// | |
-CREATE-LIKE | |
------------ | |
-usage: juicer create-like 'CARTNAME' 'OLDCARTNAME' 'ITEM' [ item2 [ ...] ] | |
- | |
-Create a new cart based off an existing one. | |
- | |
-*CARTNAME*:: | |
-The name of your new release cart. | |
- | |
-*OLDCARTNAME*:: | |
-Cart to copy from. | |
- | |
-*ITEMS*:: | |
-Items to update in the cart. | |
- | |
-//////////////////////////////////////////////////////////////////////// | |
RPM DELETE | |
---------- | |
-usage: juicer rpm delete -r REPO-NAME ITEM ITEM ... --in [ENV ...] | |
+usage: juicer rpm delete -r 'REPO-NAME' 'ITEM' 'ITEM' ... [--in] | |
Delete rpms in a repository. | |
@@ -95,35 +86,20 @@ The name of the repo rpms live in. | |
*ITEM*:: | |
Filename of rpm to delete. | |
-*--in* 'ENVIRONMENT' ... :: | |
-The environments to delete rpms in. | |
-//////////////////////////////////////// | |
-EDIT | |
----- | |
-usage: juicer edit CARTNAME | |
- | |
-Interactively edit a release cart. | |
- | |
-*CARTNAME*:: | |
-The name of your release cart. | |
-//////////////////////////////////////// | |
- | |
HELLO | |
------ | |
-usage: juicer hello [--in 'ENVIRONMENT' ...] | |
+usage: juicer hello [--in] | |
Test connection settings in *~/.config/juicer/config* | |
-*ENVIRONMENT*:: | |
-The environments to limit connecting testing to. | |
CART MERGE | |
---------- | |
-usage: juicer cart merge CART1 [...] CARTN --name NEWCARTNAME | |
+usage: juicer cart merge 'CART1' [...] 'CARTN' --name 'NEWCARTNAME' | |
Merges the contents of N carts into 'NEWCARTNAME'. Defaults to | |
updating 'CART1'. | |
@@ -141,7 +117,7 @@ CART PULL | |
usage: juicer cart pull 'CARTNAME' | |
Pulls a description of a cart from the pulp server and saves it on | |
-your local machine in ~/.config/juicer/carts/. | |
+your local machine in *~/.config/juicer/carts/*. | |
*CARTNAME*:: | |
The name of the cart to pull. | |
@@ -150,28 +126,24 @@ The name of the cart to pull. | |
CART PUSH | |
--------- | |
-usage: juicer cart push 'CARTNAME' [--in 'ENVIRONMENT' ...] | |
+usage: juicer cart push 'CARTNAME' [--in] | |
Pushes/Updates a cart on the pulp server. | |
*CARTNAME*:: | |
The name of the cart to push. | |
-*--in* 'ENVIRONMENT' ... :: | |
-The environments to push the new/updated cart to. | |
REPO PUBLISH | |
------------ | |
-usage: juicer repo publish 'REPO' [--in 'ENVIRONMENT' ...] | |
+usage: juicer repo publish 'REPO' [--in] | |
Publishes a repository, regenerating it's metadata. | |
*REPO*:: | |
The name of the repo to publish. | |
-*--in* 'ENVIRONMENT' ... :: | |
-The environments to publish repository in. | |
CART PROMOTE | |
@@ -201,7 +173,7 @@ The name of the new release cart. | |
Update a cart with a manifest file. A manifest file is written in | |
the following format: | |
-name: version-release | |
+ name: version-release | |
*-r* 'REPONAME':: | |
Name of the reopsitory to install 'ITEMS' into. The '-r' option may be | |
@@ -213,7 +185,7 @@ Items to add to the cart in repository 'REPONAME'. | |
RPM SEARCH | |
---------- | |
-usage: juicer rpm search 'ITEM' [-r 'REPO' ...] [-c] [--in 'ENVIRONMENT' ...] | |
+usage: juicer rpm search 'ITEM' [-r 'REPO' ...] [-c] [--in] | |
Search for an RPM ('item') in pulp. | |
@@ -226,8 +198,6 @@ The repo(s) to limit search scope to. | |
*-c*:: | |
Search for the package in carts as well. | |
-*--in* 'ENVIRONMENT' ...:: | |
-The environments to limit search scope to. | |
@@ -256,7 +226,7 @@ The pattern to match. Default: *** | |
RPM UPLOAD | |
---------- | |
-usage: juicer rpm upload -r 'REPO' 'ITEM' ... [--in 'ENVIRONMENT' ...] | |
+usage: juicer rpm upload -r 'REPO' 'ITEM' ... [--in] | |
Upload multiple RPMs or files ('ITEM') to 'REPO'. | |
@@ -267,8 +237,6 @@ multiple times. | |
*ITEM* ...:: | |
Name of the RPM(s) or file(s) to upload. | |
-*--in* 'ENVIRONMENT' ...:: | |
-The environments which items will be uploaded to. | |
@@ -359,7 +327,7 @@ This man page was written by Tim Bielawa <tbielawa@redhat.com>. | |
COPYRIGHT | |
--------- | |
-Copyright © 2012, Red Hat, Inc.. | |
+Copyright © 2012-2014, Red Hat, Inc.. | |
Juicer is released under the terms of the GPLv3+ License. | |
@@ -369,4 +337,4 @@ SEE ALSO | |
-------- | |
*juicer-admin*(1), *juicer.conf*(5), *fnmatch*(3) | |
-The Juicer Homepage: <https://github.com/juicer/juicer/> | |
+*The Juicer Homepage* -- <https://github.com/juicer/juicer/> | |
diff --git a/docs/man/man5/juicer.conf.5 b/docs/man/man5/juicer.conf.5 | |
index 8a85501..4c499ba 100644 | |
--- a/docs/man/man5/juicer.conf.5 | |
+++ b/docs/man/man5/juicer.conf.5 | |
@@ -2,12 +2,12 @@ | |
.\" Title: juicer.conf | |
.\" Author: :doctype:manpage | |
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/> | |
-.\" Date: 02/14/2014 | |
+.\" Date: 02/19/2014 | |
.\" Manual: Pulp repos and release carts | |
.\" Source: Juicer 0.7.0 | |
.\" Language: English | |
.\" | |
-.TH "JUICER\&.CONF" "5" "02/14/2014" "Juicer 0\&.7\&.0" "Pulp repos and release carts" | |
+.TH "JUICER\&.CONF" "5" "02/19/2014" "Juicer 0\&.7\&.0" "Pulp repos and release carts" | |
.\" ----------------------------------------------------------------- | |
.\" * Define some portability stuff | |
.\" ----------------------------------------------------------------- | |
@@ -265,14 +265,14 @@ section\&. | |
Juicer was written by GCA\-PC, Red Hat, Inc\&.\&. This man page was written by Tim Bielawa <tbielawa@redhat\&.com>\&. | |
.SH "COPYRIGHT" | |
.sp | |
-Copyright \(co 2012, Red Hat, Inc\&.\&. | |
+Copyright \(co 2012\-2014, Red Hat, Inc\&.\&. | |
.sp | |
Juicer is released under the terms of the GPLv3+ License\&. | |
.SH "SEE ALSO" | |
.sp | |
\fBjuicer\fR(1), \fBjuicer\-admin\fR(1) | |
.sp | |
-The Juicer Homepage: https://github\&.com/juicer/juicer/ | |
+\fBThe Juicer Homepage\fR \(em https://github\&.com/juicer/juicer/ | |
.SH "AUTHOR" | |
.PP | |
\fB:doctype:manpage\fR | |
diff --git a/docs/man/man5/juicer.conf.5.asciidoc.in b/docs/man/man5/juicer.conf.5.asciidoc.in | |
index 5c5b3cd..b378cd8 100644 | |
--- a/docs/man/man5/juicer.conf.5.asciidoc.in | |
+++ b/docs/man/man5/juicer.conf.5.asciidoc.in | |
@@ -182,7 +182,7 @@ by Tim Bielawa <tbielawa@redhat.com>. | |
COPYRIGHT | |
--------- | |
-Copyright © 2012, Red Hat, Inc.. | |
+Copyright © 2012-2014, Red Hat, Inc.. | |
Juicer is released under the terms of the GPLv3+ License. | |
@@ -191,4 +191,4 @@ SEE ALSO | |
-------- | |
*juicer*(1), *juicer-admin*(1) | |
-The Juicer Homepage: <https://github.com/juicer/juicer/> | |
+*The Juicer Homepage* -- <https://github.com/juicer/juicer/> | |
diff --git a/docs/markdown/repo_syntax.md b/docs/markdown/repo_syntax.md | |
new file mode 100644 | |
index 0000000..8ed19bf | |
--- /dev/null | |
+++ b/docs/markdown/repo_syntax.md | |
@@ -0,0 +1,101 @@ | |
+# Repo File Syntax | |
+ | |
+## Introduction | |
+ | |
+Repositories can be automatically created from a **json** document which | |
+follows a specific syntax. This document describes that syntax. | |
+ | |
+The overall structure of the json document reads like this: | |
+ | |
+- The base datastructure of the document is a list | |
+- Each item in the list is a repository definition (repo def) | |
+- Each repo def is a hash, or dictionary | |
+- Each hash MUST have a `name` key, which is the repository name | |
+- Each hash MAY have zero or more of the following **optional** keys | |
+ - `checksum_type` | |
+ - `feed` | |
+ - `env` | |
+ | |
+## Repository Definition Keys | |
+ | |
+- `name` | |
+ - **Required** - Yes | |
+ - **Description** - The repository name. Must match the regular expression `([a-zA-Z0-9-_.]+)` (alphanumeric characters, `.`, `-`, and `_`) | |
+ | |
+ | |
+- `checksum_type` | |
+ - **Description** - The checksum type to use when generating meta-data | |
+ - **Required** - No | |
+ - **Default Value** - `sha256` | |
+ - **Choices:** | |
+ - sha | |
+ - sha256 | |
+ | |
+- `feed` | |
+ - **Description** - A feed url from which to synchronize yum repository packages | |
+ - **Required** - No | |
+ - **Default Value** - None | |
+ | |
+- `env` | |
+ - **Description** - A list of environments this repository should exist in | |
+ - **Required** - No | |
+ - **Default Value** - By default repositories are created in all environments | |
+ | |
+## Example Repository Definition File | |
+ | |
+Here's a simple repo def with just two repositories defined: | |
+**repo01**, and **repo02**. **repo01** uses all the default values, | |
+while **repo02** sets `checksum_type` to `sha`. These repos would be | |
+created in all environments. | |
+ | |
+ [ | |
+ { | |
+ "name": "repo01" | |
+ }, | |
+ { | |
+ "name": "repo02", | |
+ "checksum_type": "sha" | |
+ } | |
+ ] | |
+ | |
+We could have also written it like this: | |
+ | |
+ [{"name": "repo01"}, {"name": "repo02", "checksum_type": "sha"}] | |
+ | |
+Or even like this: | |
+ | |
+ [ | |
+ {"name": "repo01"}, | |
+ {"name": "repo02", "checksum_type": "sha"} | |
+ ] | |
+ | |
+Here's an example with another repository definition. It exercises all | |
+of the optional keys, as well as demonstrates some more alternative | |
+syntax: | |
+ | |
+ | |
+ [ | |
+ {"name": "repo01", "env": ["prod"]}, | |
+ {"name": "repo02"}, | |
+ { | |
+ "name": "fedora_mirror", | |
+ "feed": "http://download.fedoraproject.org/pub/fedora/linux/releases/20/Everything/x86_64/os/", | |
+ "checksum_type": "sha", | |
+ "env": ["dev", "prod"] | |
+ } | |
+ ] | |
+ | |
+- **repo01** will only be created in *prod* | |
+- **repo02** is created in *all environments* | |
+ | |
+- **fedora_mirror** syncs content from it's feed of | |
+ *http://download.fedoraproject.org/pub/fedora/linux/releases/20/Everything/x86_64/os/*, | |
+ sets it's checksum type to *sha*, and only is created in the *dev* | |
+ and *prod* environments | |
+ | |
+# Protips (Troubleshooting) | |
+ | |
+- Don't end lists or hashes with trailing commas | |
+- Remember to close all of your braces and brackets: Each `[` has a matching `]`, each `{` has a matching `}` | |
+- Use a `javascript` mode in your editor if it doesn't have a native `json` mode | |
+- Setting `env` to an empty list (`[]`) will **not** delete the repo from any environment | |
diff --git a/hacking/repo_def/01_repo_def_example_good.json b/hacking/repo_def/01_repo_def_example_good.json | |
new file mode 120000 | |
index 0000000..4990664 | |
--- /dev/null | |
+++ b/hacking/repo_def/01_repo_def_example_good.json | |
@@ -0,0 +1 @@ | |
+../../share/juicer/repo_def_example.json | |
\ No newline at end of file | |
diff --git a/hacking/repo_def/02_env_not_list.json b/hacking/repo_def/02_env_not_list.json | |
new file mode 100644 | |
index 0000000..1d32e71 | |
--- /dev/null | |
+++ b/hacking/repo_def/02_env_not_list.json | |
@@ -0,0 +1,6 @@ | |
+[ | |
+ { | |
+ "name": "repodef03", | |
+ "env": "prod" | |
+ } | |
+] | |
diff --git a/hacking/repo_def/03_extra_keys.json b/hacking/repo_def/03_extra_keys.json | |
new file mode 100644 | |
index 0000000..368618e | |
--- /dev/null | |
+++ b/hacking/repo_def/03_extra_keys.json | |
@@ -0,0 +1,9 @@ | |
+[ | |
+ { | |
+ "name": "repodef03", | |
+ "checksum_type": "sha", | |
+ "feed": "http://foo", | |
+ "env": ["prod"], | |
+ "extra_key": "don't need this" | |
+ } | |
+] | |
diff --git a/hacking/repo_def/04_feed_not_string.json b/hacking/repo_def/04_feed_not_string.json | |
new file mode 100644 | |
index 0000000..2ccbf42 | |
--- /dev/null | |
+++ b/hacking/repo_def/04_feed_not_string.json | |
@@ -0,0 +1,6 @@ | |
+[ | |
+ { | |
+ "name": "repodef03", | |
+ "feed": ["list", "of", "feeds"] | |
+ } | |
+] | |
diff --git a/hacking/repo_def/05_invalid_checksum_type.json b/hacking/repo_def/05_invalid_checksum_type.json | |
new file mode 100644 | |
index 0000000..a6159e4 | |
--- /dev/null | |
+++ b/hacking/repo_def/05_invalid_checksum_type.json | |
@@ -0,0 +1,6 @@ | |
+[ | |
+ { | |
+ "name": "repodef03", | |
+ "checksum_type": "shu256" | |
+ } | |
+] | |
diff --git a/hacking/repo_def/06_invalid_json_document.json b/hacking/repo_def/06_invalid_json_document.json | |
new file mode 100644 | |
index 0000000..022ef24 | |
--- /dev/null | |
+++ b/hacking/repo_def/06_invalid_json_document.json | |
@@ -0,0 +1,5 @@ | |
+&[ | |
+ { | |
+ "name": "repodef03", | |
+ } | |
+] | |
diff --git a/hacking/repo_def/07_invalid_keys.json b/hacking/repo_def/07_invalid_keys.json | |
new file mode 100644 | |
index 0000000..e366272 | |
--- /dev/null | |
+++ b/hacking/repo_def/07_invalid_keys.json | |
@@ -0,0 +1,6 @@ | |
+[ | |
+ { | |
+ "name": "repodef03", | |
+ "invalid_key": "what is this?" | |
+ } | |
+] | |
diff --git a/hacking/repo_def/08_invalid_repo_name.json b/hacking/repo_def/08_invalid_repo_name.json | |
new file mode 100644 | |
index 0000000..3d8ba0c | |
--- /dev/null | |
+++ b/hacking/repo_def/08_invalid_repo_name.json | |
@@ -0,0 +1,5 @@ | |
+[ | |
+ { | |
+ "name": "$$r*ffpodef0@#3" | |
+ } | |
+] | |
diff --git a/hacking/repo_def/09_json_doc_not_list.json b/hacking/repo_def/09_json_doc_not_list.json | |
new file mode 100644 | |
index 0000000..5ef923d | |
--- /dev/null | |
+++ b/hacking/repo_def/09_json_doc_not_list.json | |
@@ -0,0 +1,3 @@ | |
+{ | |
+ "name": "repodef03" | |
+} | |
diff --git a/hacking/repo_def/10_missing_required_keys.json b/hacking/repo_def/10_missing_required_keys.json | |
new file mode 100644 | |
index 0000000..0dda711 | |
--- /dev/null | |
+++ b/hacking/repo_def/10_missing_required_keys.json | |
@@ -0,0 +1,5 @@ | |
+[ | |
+ { | |
+ "badkey": "is bad" | |
+ } | |
+] | |
diff --git a/hacking/repo_def/11_repo_not_not_dict.json b/hacking/repo_def/11_repo_not_not_dict.json | |
new file mode 100644 | |
index 0000000..6dc7bd4 | |
--- /dev/null | |
+++ b/hacking/repo_def/11_repo_not_not_dict.json | |
@@ -0,0 +1,3 @@ | |
+[ | |
+ "repo01" | |
+] | |
diff --git a/hacking/setup-env b/hacking/setup-env | |
index e8b7032..2a8cfd9 100644 | |
--- a/hacking/setup-env | |
+++ b/hacking/setup-env | |
@@ -1,6 +1,6 @@ | |
# -*- mode: shell-script -*- | |
-PREFIX_PYTHONPATH="`pwd`:`pwd`/Juicer/:`pwd`/JuicerAdmin/" | |
+PREFIX_PYTHONPATH="`pwd`:`pwd`/Juicer/:`pwd`/JuicerAdmin/:`pwd`/hacking/" | |
PREFIX_PATH="`pwd`/bin" | |
PREFIX_MANPATH="`pwd`/docs/man" | |
diff --git a/hacking/tests b/hacking/tests | |
index f865279..dab86d1 100755 | |
--- a/hacking/tests | |
+++ b/hacking/tests | |
@@ -1,7 +1,7 @@ | |
#!/bin/bash | |
# -*- coding: utf-8 -*- | |
# Juicer - Administer Pulp and Release Carts | |
-# Copyright © 2012, Red Hat, Inc. | |
+# Copyright © 2012-2014, Red Hat, Inc. | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
@@ -16,7 +16,51 @@ | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
-cd juicer/tests/ | |
+###################################################################### | |
+# Catch a ^C exit so the for loops don't keep running | |
+###################################################################### | |
+trap "{ exit 0; }" SIGINT SIGTERM | |
+###################################################################### | |
+ | |
+. ./hacking/setup-env | |
+ | |
+# Oh SWEET. Color codes! Ganked from http://stackoverflow.com/a/10466960/263969 | |
+RESTORE='\033[0m' | |
+RED='\033[00;31m' | |
+GREEN='\033[00;32m' | |
+ | |
+green() { | |
+ echo -en "${RESTORE}${GREEN}${1}${RESTORE}" | |
+} | |
+ | |
+red() { | |
+ echo -ne "${RESTORE}${RED}${1}${RESTORE}" | |
+} | |
+ | |
+###################################################################### | |
+ | |
+echo "#################################" | |
+echo "Running repo def validation tests" | |
+echo "#################################" | |
+ | |
+# Count how many tests we're running | |
+NUM_TESTS=`ls -1 ./hacking/repo_def/*.json | wc -l` | |
+# Only one of them should pass | |
+MUST_FAIL=$(( ${NUM_TESTS} - 1 )) | |
+echo "Expecting `green '1 PASS'` and `red \"${MUST_FAIL} FAIL\"`s" | |
+ | |
+python ./juicer/utils/ValidateRepoDef.py ./hacking/repo_def/*.json | |
+if [ "$?" == "${MUST_FAIL}" ]; then | |
+ echo "Repo def validation tests success: (`green '1 PASS'`/`red \"${MUST_FAIL} FAIL\"`s)" | |
+else | |
+ echo `red "Repo def validation failed"` | |
+fi | |
+ | |
+###################################################################### | |
+# Run the rest of the tests | |
+###################################################################### | |
+ | |
+pushd juicer/tests/ | |
for test in test_hello test_pull test_show test_workflow; | |
do | |
@@ -30,4 +74,4 @@ do | |
python -m unittest TestJuicerAdmin.TestJuicerAdmin.$test | |
done | |
-cd - | |
+popd | |
diff --git a/juicer/__init__.py b/juicer/__init__.py | |
index fe78243..41ab564 100644 | |
--- a/juicer/__init__.py | |
+++ b/juicer/__init__.py | |
@@ -17,4 +17,4 @@ | |
# Juicer package | |
-__version__ = '0.7.0-3a4a3cc' | |
+__version__ = '0.7.0-5e23949' | |
diff --git a/juicer/admin/JuicerAdmin.py b/juicer/admin/JuicerAdmin.py | |
index c806180..4103d35 100644 | |
--- a/juicer/admin/JuicerAdmin.py | |
+++ b/juicer/admin/JuicerAdmin.py | |
@@ -1,6 +1,6 @@ | |
# -*- coding: utf-8 -*- | |
# Juicer - Administer Pulp and Release Carts | |
-# Copyright © 2012,2013, Red Hat, Inc. | |
+# Copyright © 2012-2014, Red Hat, Inc. | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
@@ -17,10 +17,13 @@ | |
from juicer.common import Constants | |
from juicer.common.Errors import * | |
+from juicer.common.Repo import JuicerRepo, PulpRepo | |
import juicer.admin | |
import juicer.utils | |
import juicer.utils.Log | |
+import juicer.utils.ValidateRepoDef | |
import re | |
+import json | |
class JuicerAdmin(object): | |
@@ -43,6 +46,8 @@ class JuicerAdmin(object): | |
`repo_name` - Name of repository to create | |
`feed` - Repo URL to feed from | |
`checksum_type` - Used for generating meta-data | |
+ `from_file` - JSON file of repo definitions | |
+ `noop` - Boolean, if true don't actually create/update repos, just show what would have happened | |
Create repository in specified environments, associate the | |
yum_distributor with it and publish the repo | |
@@ -108,6 +113,87 @@ class JuicerAdmin(object): | |
_r.raise_for_status() | |
return True | |
+ def import_repo(self, from_file, noop=False, query='/repositories/'): | |
+ """ | |
+ `from_file` - JSON file of repo definitions | |
+ `noop` - Boolean, if true don't actually create/update repos, just show what would have happened | |
+ """ | |
+ try: | |
+ repo_defs = juicer.utils.ValidateRepoDef.validate_document(from_file) | |
+ except JuicerRepoDefError, e: | |
+ juicer.utils.Log.log_error("Could not load repo defs from %s:" % from_file) | |
+ raise e | |
+ else: | |
+ juicer.utils.Log.log_debug("Loaded and validated repo defs from %s" % from_file) | |
+ | |
+ # All our known envs, for cases where no value is supplied for 'env' | |
+ all_envs = juicer.utils.get_environments() | |
+ | |
+ # Repos to create/update, sorted by environment. | |
+ repo_objects_create = [] | |
+ repo_objects_update = {} | |
+ for env in all_envs: | |
+ repo_objects_update[env] = [] | |
+ | |
+ # All repo defs as Repo objects | |
+ all_repos = [JuicerRepo(repo['name'], repo_def=repo) for repo in repo_defs] | |
+ | |
+ # Detailed information on all existing repos | |
+ # | |
+ # TODO: Optimize this so we only call to pulp for environments | |
+ # we KNOW we need to operate in. Determine this by evaluating | |
+ # the 'env' property of each repo def. | |
+ juicer.utils.Log.log_info("Loading information on all existing repos (this could take a while)") | |
+ existing_repos = self.list_repos(envs=all_envs) | |
+ | |
+ # Use a cache to speed up testing | |
+ # juicer.utils.Log.log_info("BE AWARE: Currently reading repo list from local cache") | |
+ # existing_repos = juicer.utils.read_json_document('/tmp/repo_list.json') | |
+ | |
+ for repo in all_repos: | |
+ # 'env' is all environments if: 'env' is not defined; 'env' is an empty list | |
+ current_env = repo.get('env', []) | |
+ if current_env == []: | |
+ juicer.utils.Log.log_debug("Setting 'env' to all_envs for repo: %s" % repo['name']) | |
+ repo['env'] = all_envs | |
+ | |
+ # Assemble a set of all specified environments. | |
+ defined_envs = juicer.utils.unique_repo_def_envs(all_repos) | |
+ juicer.utils.Log.log_notice("Discovered environments: %s" % ", ".join(list(defined_envs))) | |
+ | |
+ # sort out new vs. existing | |
+ for repo in all_repos: | |
+ # Does the repo refer to environments in our juicer.conf file? | |
+ if juicer.utils.repo_in_defined_envs(repo, all_envs): | |
+ repo['reality_check_in_env'] = [] | |
+ repo['missing_in_env'] = [] | |
+ for env in repo['env']: | |
+ if juicer.utils.repo_exists_in_repo_list(repo, existing_repos[env]): | |
+ # Does the repo def match what exists already? | |
+ pulp_repo = self.show_repo(repo_names=[repo['name']], envs=[env]) | |
+ #juicer.utils.Log.log_debug(str(pulp_repo)) | |
+ repo_diff = juicer.utils.repo_def_matches_reality(repo, pulp_repo[env][0]) | |
+ if not repo_diff.diff()['distributor'] or repo_diff.diff()['importer']: | |
+ juicer.utils.Log.log_notice("Repo %s already exists, but reality does not the definition", repo['name']) | |
+ repo['reality_check_in_env'].append((env, repo_diff)) | |
+ else: | |
+ juicer.utils.Log.log_notice("Repo %s already exists and is correct", repo['name']) | |
+ else: | |
+ # The repo does not exist yet in reality | |
+ juicer.utils.Log.log_notice("Need to create %s in %s", repo['name'], env) | |
+ repo['missing_in_env'].append(env) | |
+ | |
+ # Do we need to create the repo anywhere? | |
+ if repo['missing_in_env']: | |
+ repo_objects_create.append(repo) | |
+ | |
+ # We we need to update the repo anywhere? | |
+ if repo['reality_check_in_env']: | |
+ for env,diff in repo['reality_check_in_env']: | |
+ repo_objects_update[env].append(repo) | |
+ | |
+ return (repo_objects_create, repo_objects_update) | |
+ | |
def create_user(self, login=None, password=None, user_name=None, envs=[], query='/users/'): | |
""" | |
`login` - Login or username for user | |
@@ -236,23 +322,19 @@ class JuicerAdmin(object): | |
juicer.utils.Log.log_debug( | |
"List Repos In: %s", ", ".join(envs)) | |
- count = 0 | |
- | |
+ repo_lists = {} | |
for env in envs: | |
- count += 1 | |
+ repo_lists[env] = [] | |
- juicer.utils.Log.log_info("%s:", env) | |
+ for env in envs: | |
_r = self.connectors[env].get(query) | |
if _r.status_code == Constants.PULP_GET_OK: | |
for repo in juicer.utils.load_json_str(_r.content): | |
if re.match(".*-{0}$".format(env), repo['id']): | |
- juicer.utils.Log.log_info(repo['display_name']) | |
- | |
- if count < len(envs): | |
- juicer.utils.Log.log_info("") | |
+ repo_lists[env].append(repo['display_name']) | |
else: | |
_r.raise_for_status() | |
- return True | |
+ return repo_lists | |
def list_users(self, envs=[], query="/users/"): | |
""" | |
@@ -304,41 +386,37 @@ class JuicerAdmin(object): | |
_r.raise_for_status() | |
return True | |
- def show_repo(self, repo_name=None, envs=[], query='/repositories/'): | |
+ def show_repo(self, repo_names=[], envs=[], query='/repositories/'): | |
""" | |
- `repo_name` - Name of repository to show | |
+ `repo_names` - Name of repository(s) to show | |
Show repositories in specified environments | |
""" | |
- juicer.utils.Log.log_debug("Show Repo: %s", repo_name) | |
- | |
- # keep track of which iteration of environment we're in | |
- count = 0 | |
+ juicer.utils.Log.log_debug("Show Repo(s): %s", str(repo_names)) | |
+ repo_objects = {} | |
for env in envs: | |
- count += 1 | |
- | |
- juicer.utils.Log.log_info("%s:", env) | |
- url = "%s%s-%s/" % (query, repo_name, env) | |
- _r = self.connectors[env].get(url) | |
- if _r.status_code == Constants.PULP_GET_OK: | |
- repo = juicer.utils.load_json_str(_r.content) | |
+ repo_objects[env] = [] | |
- juicer.utils.Log.log_info(repo['display_name']) | |
- try: | |
- juicer.utils.Log.log_info("%s packages" % repo['content_unit_counts']['rpm']) | |
- except: | |
- juicer.utils.Log.log_info("0 packages") | |
- | |
- if count < len(envs): | |
- # just want a new line | |
- juicer.utils.Log.log_info("") | |
- else: | |
- if _r.status_code == Constants.PULP_GET_NOT_FOUND: | |
- raise JuicerPulpError("repo '%s' was not found" % repo_name) | |
+ for env in envs: | |
+ juicer.utils.Log.log_debug("scanning environment: %s", env) | |
+ for repo_name in repo_names: | |
+ juicer.utils.Log.log_debug("looking for repo: %s", repo_name) | |
+ url = "%s%s-%s/?details=true" % (query, repo_name, env) | |
+ _r = self.connectors[env].get(url) | |
+ if _r.status_code == Constants.PULP_GET_OK: | |
+ juicer.utils.Log.log_debug("found repo: %s", repo_name) | |
+ repo = juicer.utils.load_json_str(_r.content) | |
+ repo_object = PulpRepo(repo_name, env, repo_def=repo) | |
+ repo_objects[env].append(repo_object) | |
else: | |
- _r.raise_for_status() | |
- return True | |
+ if _r.status_code == Constants.PULP_GET_NOT_FOUND: | |
+ juicer.utils.Log.log_warn("could not find repo '%s' in %s" % (repo_name, env)) | |
+ else: | |
+ _r.raise_for_status() | |
+ for k,v in repo_objects.iteritems(): | |
+ juicer.utils.Log.log_debug("environment %s: found %d repos" % (k, len(v))) | |
+ return repo_objects | |
def show_user(self, login=None, envs=[], query='/users/'): | |
""" | |
diff --git a/juicer/admin/Parser.py b/juicer/admin/Parser.py | |
index 190f9fb..ed43e44 100644 | |
--- a/juicer/admin/Parser.py | |
+++ b/juicer/admin/Parser.py | |
@@ -28,271 +28,305 @@ class Parser(object): | |
self._default_envs = juicer.utils.get_environments() | |
- self.parser.add_argument('-v', action='count', \ | |
- default=1, \ | |
+ self.parser.add_argument('-v', action='count', | |
+ default=1, | |
help='Increase the verbosity (up to 3x)') | |
- self.parser.add_argument('-V', '--version', action='version', \ | |
- version="juicer-admin-%s" \ | |
- % juicer.utils.juicer_version()) | |
+ self.parser.add_argument('-V', '--version', action='version', | |
+ version="juicer-admin-%s" | |
+ % juicer.utils.juicer_version()) | |
################################################################## | |
# Keep the different commands separate | |
- self.subparsers = self.parser.add_subparsers(title='Commands', \ | |
- dest='command', \ | |
- description='\'%(prog)s COMMAND -h\' for individual help topics') | |
+ self.subparsers = self.parser.add_subparsers(title='Commands', | |
+ dest='command', | |
+ description='\'%(prog)s COMMAND -h\' for individual help topics') | |
################################################################## | |
# Create the 'repo' sub-parser | |
- parser_repo = self.subparsers.add_parser('repo', \ | |
- help='Repo operations') | |
+ parser_repo = self.subparsers.add_parser('repo', | |
+ help='Repo operations') | |
subparser_repo = parser_repo.add_subparsers(dest='sub_command') | |
################################################################## | |
# Create the 'user' sub-parser | |
- parser_user = self.subparsers.add_parser('user', \ | |
- help='User operations') | |
+ parser_user = self.subparsers.add_parser('user', | |
+ help='User operations') | |
subparser_user = parser_user.add_subparsers(dest='sub_command') | |
################################################################## | |
# Create the 'role' sub-parser | |
- parser_role = self.subparsers.add_parser('role', \ | |
- help='Role operations') | |
+ parser_role = self.subparsers.add_parser('role', | |
+ help='Role operations') | |
subparser_role = parser_role.add_subparsers(dest='sub_command') | |
################################################################## | |
# Create the 'repo create' sub-parser | |
- parser_repo_create = subparser_repo.add_parser('create',\ | |
- help='Create pulp repository', \ | |
- usage='%(prog)s REPONAME --arch ARCH --feed FEED --checksum-type CHECKSUM-TYPE --in [ENV ...]') | |
+ parser_repo_create = subparser_repo.add_parser('create', | |
+ help='Create pulp repository', | |
+ usage='%(prog)s REPONAME { [--arch ARCH] [--feed FEED] [--checksum-type CHECKSUM-TYPE] | --from-file JSON_DEFS} [--in ENV [...]]') | |
- parser_repo_create.add_argument('name', metavar='name', \ | |
- help='The name of your repo') | |
+ parser_repo_create.add_argument('name', metavar='name', | |
+ help='The name of your repo') | |
- parser_repo_create.add_argument('--arch', metavar='arch', \ | |
- default='noarch', \ | |
+ parser_repo_create.add_argument('--arch', metavar='arch', | |
+ default='noarch', | |
help='The architecture of your repo (default: noarch)') | |
- parser_repo_create.add_argument('--feed', metavar='feed', \ | |
- default=None, \ | |
- help='A feed repo for your repo') | |
+ parser_repo_create.add_argument('--feed', metavar='feed', | |
+ default=None, | |
+ help='A feed repo for your repo') | |
- parser_repo_create.add_argument('--checksum-type', metavar='checksum_type', \ | |
- default='sha256', \ | |
- choices=['sha26', 'sha'], \ | |
- help='Checksum-type used for meta-data generation (one of: sha26, sha)') | |
+ parser_repo_create.add_argument('--checksum-type', metavar='checksum_type', | |
+ default='sha256', | |
+ choices=['sha26', 'sha'], | |
+ help='Checksum-type used for meta-data generation (one of: sha26, sha)') | |
- parser_repo_create.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to create your repository') | |
+ parser_repo_create.add_argument('--noop', '--dry-run', '-n', | |
+ default=False, action='store_true', | |
+ help="Don't create the repos, just show what would have happened") | |
+ | |
+ parser_repo_create.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to create your repository') | |
parser_repo_create.set_defaults(ja=juicer.admin.create_repo) | |
################################################################## | |
+ # Create the 'repo import' sub-parser | |
+ import_description = """This will create repositories matching the definitions in the repo | |
+def file. Repositories which already exist will be updated. See the | |
+"repo import" and "Repo Def Format" sections in juicer-admin(1) for | |
+instructions on how to write a proper repo def file.""" | |
+ | |
+ parser_repo_import = subparser_repo.add_parser('import', | |
+ help='Create pulp repositories from an imported definition', | |
+ usage='%(prog)s --from-file JSON_DEFS [--noop]', | |
+ description=import_description) | |
+ | |
+ parser_repo_import.add_argument('--from-file', metavar='json_defs', default=None, | |
+ help='Repository definition file in JSON format') | |
+ | |
+ parser_repo_import.add_argument('--noop', '--dry-run', '-n', | |
+ default=False, action='store_true', | |
+ help="Don't create the repos, just show what would have happened") | |
+ | |
+ parser_repo_import.set_defaults(ja=juicer.admin.import_repo) | |
+ | |
+ ################################################################## | |
# Create the 'user create' sub-parser | |
- parser_user_create = subparser_user.add_parser('create',\ | |
- help='Create pulp user', \ | |
- usage='%(prog)s LOGIN --name FULLNAME --password PASSWORD \ | |
- \n\nYou will be prompted if PASSWORD argument not supplied.') | |
- | |
- parser_user_create.add_argument('login', metavar='login', \ | |
- help='Login user id for user') | |
- | |
- parser_user_create.add_argument('--name', metavar='name', \ | |
- dest='name', \ | |
- required=True, \ | |
- help='Full name of user') | |
- | |
- parser_user_create.add_argument('--password', metavar='password', \ | |
- dest='password', \ | |
- nargs='*', \ | |
- required=True, \ | |
- action=PromptAction, \ | |
+ parser_user_create = subparser_user.add_parser('create', | |
+ help='Create pulp user', | |
+ usage='%(prog)s LOGIN --name FULLNAME --password PASSWORD \ | |
+ \n\nYou will be prompted if the PASSWORD argument not supplied.') | |
+ | |
+ parser_user_create.add_argument('login', metavar='login', | |
+ help='Login user id for user') | |
+ | |
+ parser_user_create.add_argument('--name', metavar='name', | |
+ dest='name', | |
+ required=True, | |
+ help='Full name of user') | |
+ | |
+ parser_user_create.add_argument('--password', metavar='password', | |
+ dest='password', | |
+ nargs='*', | |
+ required=True, | |
+ action=PromptAction, | |
help='Plain text password for user') | |
- parser_user_create.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to create pulp user') | |
+ parser_user_create.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to create pulp user') | |
parser_user_create.set_defaults(ja=juicer.admin.create_user) | |
################################################################## | |
# Create the 'user update' sub-parser | |
- parser_user_update = subparser_user.add_parser('update',\ | |
- help='Change user information', \ | |
- usage='%(prog)s LOGIN --name FULLNAME --password PASSWORD \ | |
- \n\nYou will be prompted if PASSWORD argument not supplied.') | |
- | |
- parser_user_update.add_argument('login', metavar='login', \ | |
- help='Login user id for user to update') | |
- | |
- parser_user_update.add_argument('--name', metavar='name', \ | |
- dest='name', \ | |
- required=False, \ | |
- help='Updated name of user') | |
- | |
- parser_user_update.add_argument('--password', metavar='password', \ | |
- dest='password', \ | |
- nargs='*', \ | |
- required=False, \ | |
- action=PromptAction, \ | |
+ parser_user_update = subparser_user.add_parser('update', | |
+ help='Change user information', | |
+ usage='%(prog)s LOGIN --name FULLNAME --password PASSWORD \ | |
+ \n\nYou will be prompted if the PASSWORD argument not supplied.') | |
+ | |
+ parser_user_update.add_argument('login', metavar='login', | |
+ help='Login user id for user to update') | |
+ | |
+ parser_user_update.add_argument('--name', metavar='name', | |
+ dest='name', | |
+ required=False, | |
+ help='Updated name of user') | |
+ | |
+ parser_user_update.add_argument('--password', metavar='password', | |
+ dest='password', | |
+ nargs='*', | |
+ required=False, | |
+ action=PromptAction, | |
help='Updated password for user') | |
- parser_user_update.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to create pulp user') | |
+ parser_user_update.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to create pulp user') | |
parser_user_update.set_defaults(ja=juicer.admin.update_user) | |
################################################################## | |
# Create the 'repo list' sub-parser | |
- parser_repo_list = subparser_repo.add_parser('list', \ | |
- help='List all repos') | |
+ parser_repo_list = subparser_repo.add_parser('list', | |
+ help='List all repos') | |
- parser_repo_list.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to list repos') | |
+ parser_repo_list.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to list repos') | |
+ | |
+ parser_repo_list.add_argument('--json', | |
+ action='store_true', default=False, | |
+ help='Dump everything in JSON format') | |
parser_repo_list.set_defaults(ja=juicer.admin.list_repos) | |
################################################################## | |
# Create the 'user list' sub-parser | |
- parser_user_list = subparser_user.add_parser('list', \ | |
- help='List all users') | |
+ parser_user_list = subparser_user.add_parser('list', | |
+ help='List all users') | |
- parser_user_list.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to list users') | |
+ parser_user_list.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to list users') | |
parser_user_list.set_defaults(ja=juicer.admin.list_users) | |
################################################################## | |
# Create the 'repo sync' sub-parser | |
- parser_repo_sync = subparser_repo.add_parser('sync', \ | |
- help='Sync pulp repository') | |
+ parser_repo_sync = subparser_repo.add_parser('sync', | |
+ help='Sync pulp repository') | |
- parser_repo_sync.add_argument('name', metavar='name', \ | |
- help='The name of your repo') | |
+ parser_repo_sync.add_argument('name', metavar='name', | |
+ help='The name of your repo') | |
- parser_repo_sync.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to sync your repository') | |
+ parser_repo_sync.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to sync your repository') | |
parser_repo_sync.set_defaults(ja=juicer.admin.sync_repo) | |
################################################################## | |
# Create the 'repo show' sub-parser | |
- parser_repo_show = subparser_repo.add_parser('show', \ | |
- usage='%(prog)s name --in [ENV ...]', \ | |
- help='Show pulp repository') | |
+ parser_repo_show = subparser_repo.add_parser('show', | |
+ usage='%(prog)s name [...] [--json] --in [ENV ...]', | |
+ help='Show pulp repository(s)') | |
+ | |
+ parser_repo_show.add_argument('name', metavar='name', | |
+ nargs="+", | |
+ help='The name of your repo(s)') | |
- parser_repo_show.add_argument('name', metavar='name', \ | |
- help='The name of your repo') | |
+ parser_repo_show.add_argument('--json', | |
+ action='store_true', default=False, | |
+ help='Dump everything in JSON format') | |
- parser_repo_show.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to show your repository') | |
+ parser_repo_show.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to show your repository') | |
parser_repo_show.set_defaults(ja=juicer.admin.show_repo) | |
################################################################## | |
# Create the 'user show' sub-parser | |
- parser_user_show = subparser_user.add_parser('show', \ | |
- usage='%(prog)s LOGIN --in [ENV ...]', \ | |
- help='Show pulp user') | |
+ parser_user_show = subparser_user.add_parser('show', | |
+ usage='%(prog)s LOGIN --in [ENV ...]', | |
+ help='Show pulp user') | |
- parser_user_show.add_argument('login', metavar='login', \ | |
- help='Login user id for user') | |
+ parser_user_show.add_argument('login', metavar='login', | |
+ help='Login user id for user') | |
- parser_user_show.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to show user') | |
+ parser_user_show.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to show user') | |
parser_user_show.set_defaults(ja=juicer.admin.show_user) | |
################################################################## | |
# Create the 'repo delete' sub-parser | |
- parser_repo_delete = subparser_repo.add_parser('delete', \ | |
- help='Delete pulp repository') | |
+ parser_repo_delete = subparser_repo.add_parser('delete', | |
+ help='Delete pulp repository') | |
- parser_repo_delete.add_argument('name', metavar='name', \ | |
- help='The name of your repo') | |
+ parser_repo_delete.add_argument('name', metavar='name', | |
+ help='The name of your repo') | |
- parser_repo_delete.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to delete your repository') | |
+ parser_repo_delete.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to delete your repository') | |
parser_repo_delete.set_defaults(ja=juicer.admin.delete_repo) | |
################################################################## | |
# Create the 'user delete' sub-parser | |
- parser_user_delete = subparser_user.add_parser('delete', \ | |
- help='Delete pulp user') | |
+ parser_user_delete = subparser_user.add_parser('delete', | |
+ help='Delete pulp user') | |
- parser_user_delete.add_argument('login', metavar='login', \ | |
- help='Login user id for user') | |
+ parser_user_delete.add_argument('login', metavar='login', | |
+ help='Login user id for user') | |
- parser_user_delete.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to delete user') | |
+ parser_user_delete.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to delete user') | |
parser_user_delete.set_defaults(ja=juicer.admin.delete_user) | |
################################################################## | |
# Create the 'role add' sub-parser | |
- parser_role_add = subparser_role.add_parser('add', \ | |
- help='Add user to role') | |
+ parser_role_add = subparser_role.add_parser('add', | |
+ help='Add user to role') | |
- parser_role_add.add_argument('--login', metavar='login', \ | |
- help='Login user id for user', \ | |
- required=True) | |
+ parser_role_add.add_argument('--login', metavar='login', | |
+ help='Login user id for user', | |
+ required=True) | |
- parser_role_add.add_argument('--role', metavar='role', \ | |
- help='Role to add user to', \ | |
- required=True) | |
+ parser_role_add.add_argument('--role', metavar='role', | |
+ help='Role to add user to', | |
+ required=True) | |
- parser_role_add.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to add user to role') | |
+ parser_role_add.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to add user to role') | |
parser_role_add.set_defaults(ja=juicer.admin.role_add) | |
################################################################## | |
# Create the 'role list' sub-parser | |
- parser_role_list = subparser_role.add_parser('list', \ | |
- help='List all roles') | |
- | |
- parser_role_list.add_argument('--in', metavar='envs', \ | |
- nargs="+", \ | |
- dest='envs', \ | |
- default=self._default_envs, \ | |
- help='The environments in which to list roles') | |
+ parser_role_list = subparser_role.add_parser('list', | |
+ help='List all roles') | |
+ | |
+ parser_role_list.add_argument('--in', metavar='envs', | |
+ nargs="+", | |
+ dest='envs', | |
+ default=self._default_envs, | |
+ help='The environments in which to list roles') | |
parser_role_list.set_defaults(ja=juicer.admin.list_roles) | |
diff --git a/juicer/admin/__init__.py b/juicer/admin/__init__.py | |
index 88c5d35..4127d46 100644 | |
--- a/juicer/admin/__init__.py | |
+++ b/juicer/admin/__init__.py | |
@@ -1,6 +1,6 @@ | |
# -*- coding: utf-8 -*- | |
# Juicer - Administer Pulp and Release Carts | |
-# Copyright © 2012,2013, Red Hat, Inc. | |
+# Copyright © 2012-2014, Red Hat, Inc. | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
@@ -16,22 +16,48 @@ | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
from juicer.admin.JuicerAdmin import JuicerAdmin as ja | |
- | |
+import juicer.utils | |
def create_repo(args): | |
pulp = ja(args) | |
pulp.create_repo(args.arch, args.name, args.feed, args.envs, args.checksum_type) | |
+def import_repo(args): | |
+ pulp = ja(args) | |
+ # Get our TODO specs from juicer-admin | |
+ (to_create, to_update) = pulp.import_repo(args.from_file, args.noop) | |
+ | |
+ if args.noop: | |
+ juicer.utils.Log.log_info("NOOP: Would have created repos with definitions:") | |
+ juicer.utils.Log.log_info("%s", juicer.utils.create_json_str(to_create, indent=4, cls=juicer.common.Repo.RepoEncoder)) | |
+ | |
+ juicer.utils.Log.log_info("NOOP: Would have updated repos with definitions:") | |
+ juicer.utils.Log.log_info("%s", juicer.utils.create_json_str(to_update, indent=4, cls=juicer.common.Repo.RepoEncoder)) | |
+ else: | |
+ for repo in to_create: | |
+ pulp.create_repo(repo_name=repo['name'], | |
+ feed=repo['feed'], | |
+ envs=repo['missing_in_env'], | |
+ checksum_type=repo['checksum_type']) | |
+ | |
+ for env,repos in to_update.iteritems(): | |
+ for repo in repos: | |
+ juicer.utils.Log.log_info("Would have updated %s-%s", repo, env) | |
+ | |
+ | |
def create_user(args): | |
pulp = ja(args) | |
pulp.create_user(args.login, args.password, args.name, args.envs) | |
- | |
def list_repos(args): | |
pulp = ja(args) | |
- pulp.list_repos(args.envs) | |
- | |
+ repo_lists = pulp.list_repos(args.envs) | |
+ if args.json: | |
+ print juicer.utils.create_json_str(repo_lists, indent=4) | |
+ else: | |
+ for env, repos in repo_lists.iteritems(): | |
+ print "%s(%d): %s" % (env, len(repos), ' '.join(repos)) | |
def sync_repo(args): | |
pulp = ja(args) | |
@@ -40,8 +66,33 @@ def sync_repo(args): | |
def show_repo(args): | |
pulp = ja(args) | |
- pulp.show_repo(args.name, args.envs) | |
- | |
+ repo_objects = pulp.show_repo(args.name, args.envs) | |
+ | |
+ if args.json: | |
+ # JSON output requested | |
+ print juicer.utils.create_json_str(repo_objects, indent=4, | |
+ cls=juicer.common.Repo.RepoEncoder) | |
+ else: | |
+ found_repos = 0 | |
+ for env, repos in repo_objects.iteritems(): | |
+ found_repos += len(repos) | |
+ if found_repos == 0: | |
+ print "Could not locate repo(s) in any environment" | |
+ return False | |
+ | |
+ # Human readable table-style output by default | |
+ rows = [['Repo', 'Env', 'RPMs', 'SRPMs', 'Checksum']] | |
+ for env,repos in repo_objects.iteritems(): | |
+ # 'repos' contains a list of hashes | |
+ for repo in repos: | |
+ # each hash represents a repo | |
+ repo_name = repo['name'] | |
+ repo_rpm_count = repo['rpm_count'] | |
+ repo_srpm_count = repo['srpm_count'] | |
+ repo_checksum = repo['checksum'] | |
+ rows.append([repo_name, env, repo_rpm_count, repo_srpm_count, repo_checksum]) | |
+ | |
+ print juicer.utils.table(rows) | |
def show_user(args): | |
pulp = ja(args) | |
diff --git a/juicer/common/Constants.py b/juicer/common/Constants.py | |
index 9b25196..4155247 100644 | |
--- a/juicer/common/Constants.py | |
+++ b/juicer/common/Constants.py | |
@@ -1,6 +1,6 @@ | |
# -*- coding: utf-8 -*- | |
# Juicer - Administer Pulp and Release Carts | |
-# Copyright © 2012, Red Hat, Inc. | |
+# Copyright © 2012-2014, Red Hat, Inc. | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
@@ -17,7 +17,6 @@ | |
import os.path | |
- | |
###################################################################### | |
# PULP RETURN CODES | |
###################################################################### | |
@@ -75,3 +74,16 @@ EXAMPLE_SYSTEM_CONFIG = '/usr/share/juicer/juicer.conf' | |
###################################################################### | |
# The version the server should be running | |
EXPECTED_SERVER_VERSION = '2.3' | |
+ | |
+###################################################################### | |
+# Repo def file defaults/attributes | |
+REPO_DEF_DEFAULTS = { | |
+ 'name': None, | |
+ 'feed': None, | |
+ 'env': [], | |
+ 'checksum_type': 'sha256', | |
+} | |
+ | |
+REPO_DEF_REQ_KEYS = ['name'] | |
+REPO_DEF_OPT_KEYS = ['checksum_type', 'feed', 'env'] | |
+REPO_DEF_CHECKSUM_TYPES = ['sha', 'sha256'] | |
diff --git a/juicer/common/Errors.py b/juicer/common/Errors.py | |
index f254eb0..b3ba10f 100644 | |
--- a/juicer/common/Errors.py | |
+++ b/juicer/common/Errors.py | |
@@ -1,6 +1,6 @@ | |
# -*- coding: utf-8 -*- | |
# Juicer - Administer Pulp and Release Carts | |
-# Copyright © 2012, Red Hat, Inc. | |
+# Copyright © 2012-2014, Red Hat, Inc. | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
@@ -46,3 +46,15 @@ class JuicerManifestError(JuicerError): | |
class JuicerRpmSignPluginError(JuicerError): | |
pass | |
+ | |
+class JuicerRepoExclusionError(JuicerError): | |
+ pass | |
+ | |
+class JuicerRepoDefError(JuicerError): | |
+ """ | |
+ Raised for invalid repo def files | |
+ """ | |
+ pass | |
+ | |
+class JuicerRepoInUndefinedEnvs(JuicerError): | |
+ pass | |
diff --git a/juicer/common/Repo.py b/juicer/common/Repo.py | |
new file mode 100644 | |
index 0000000..019e153 | |
--- /dev/null | |
+++ b/juicer/common/Repo.py | |
@@ -0,0 +1,177 @@ | |
+# -*- coding: utf-8 -*- | |
+# Juicer - Administer Pulp and Release Carts | |
+# Copyright © 2014, Red Hat, Inc. | |
+# | |
+# This program is free software: you can redistribute it and/or modify | |
+# it under the terms of the GNU General Public License as published by | |
+# the Free Software Foundation, either version 3 of the License, or | |
+# (at your option) any later version. | |
+# | |
+# This program is distributed in the hope that it will be useful, | |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
+# GNU General Public License for more details. | |
+# | |
+# You should have received a copy of the GNU General Public License | |
+# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
+ | |
+import json | |
+import juicer.utils.Log | |
+import juicer.utils | |
+ | |
+ | |
+class Repo(object): | |
+ """ | |
+ Internal representation of a repository object | |
+ """ | |
+ def __init__(self, repo_name, env=None, repo_def=None): | |
+ """ | |
+ `repo_name` - Name of this repo | |
+ `env` - Environment the repo lives in | |
+ `repo_def` - Repository definition as per juicer (see: docs/markdown/repo_syntax.md) | |
+ """ | |
+ juicer.utils.Log.log_debug("creating repo object for %s-%s" % (repo_name, env)) | |
+ self.spec = {} | |
+ self['env'] = env | |
+ self._parse_repo_def(repo_def) | |
+ juicer.utils.Log.log_debug("instantiated Repo object for %s-%s" % (repo_name, env)) | |
+ | |
+ def _parse_repo_def(self, repo_def): | |
+ raise NotImplementedError("Don't call the Repo object directly. Instead call a PulpRepo or JuicerRepo") | |
+ | |
+ def __setitem__(self, key, value): | |
+ self.spec[key] = value | |
+ | |
+ def __getitem__(self, item): | |
+ if item in self.spec: | |
+ return self.spec[item] | |
+ else: | |
+ raise KeyError(item) | |
+ | |
+ def __contains__(self, item): | |
+ return item in self.spec | |
+ | |
+ def __str__(self): | |
+ return juicer.utils.create_json_str(self.spec) | |
+ | |
+ def _repo_ds(self): | |
+ return self.spec | |
+ | |
+ def get(self, key, default=None): | |
+ if key in self.spec: | |
+ return self.spec[key] | |
+ else: | |
+ return default | |
+ | |
+ | |
+class JuicerRepo(Repo): | |
+ """ | |
+ Internal representation of a Juicer repository object | |
+ """ | |
+ def _parse_repo_def(self, repo_def): | |
+ juicer.utils.Log.log_debug("parsing juicer definition") | |
+ self['name'] = repo_def['name'] | |
+ defaults = juicer.common.Constants.REPO_DEF_DEFAULTS | |
+ for key in juicer.common.Constants.REPO_DEF_OPT_KEYS: | |
+ self[key] = repo_def.get(key, defaults[key]) | |
+ juicer.utils.Log.log_debug("Defined %s as %s" % (key, str(self[key]))) | |
+ juicer.utils.Log.log_debug("finished parsing juicer definition") | |
+ | |
+ | |
+class PulpRepo(Repo): | |
+ """ | |
+ Internal representation of a Pulp repository object | |
+ """ | |
+ def _parse_repo_def(self, repo_def): | |
+ juicer.utils.Log.log_debug("parsing pulp definition") | |
+ #juicer.utils.Log.log_debug(juicer.utils.create_json_str(repo_def, indent=4)) | |
+ self.spec = repo_def | |
+ self['name'] = repo_def['display_name'] | |
+ self['rpm_count'] = repo_def.get('content_unit_counts', {}).get('rpm', 0) | |
+ self['srpm_count'] = repo_def.get('content_unit_counts', {}).get('srpm', 0) | |
+ # There's no pretty way to write this that doesn't take up 10 lines of code... | |
+ # Grab the deeply nested key 'checksum_type', or return 'sha256' if it doesn't exist | |
+ self['checksum_type'] = repo_def.get('distributors', [{}])[0].get('config', {}).get('checksum_type', 'sha256') | |
+ | |
+ # Does this thing even have an importer defined? | |
+ self['feed'] = None | |
+ if 'importers' in self.spec: | |
+ # Assume we were intelligent any only defined one importer | |
+ importer = self.spec['importers'][0] | |
+ if 'config' in importer: | |
+ # Good, it is configured | |
+ if 'feed' in importer['config']: | |
+ # OK, it has a feed set, track it | |
+ self.spec['feed'] = importer['config']['feed'] | |
+ juicer.utils.Log.log_debug("finished parsing pulp definition") | |
+ | |
+ | |
+class RepoDiff(object): | |
+ """Calculate the difference of a juicer repo and a pulp repo.""" | |
+ def __init__(self, juicer_repo=None, pulp_repo=None): | |
+ if not type(juicer_repo) == juicer.common.Repo.JuicerRepo: | |
+ raise TypeError("juicer_repo option to RepoDiff is not a JuicerRepo") | |
+ | |
+ if not type(pulp_repo) == juicer.common.Repo.PulpRepo: | |
+ raise TypeError("pulp_repo option to RepoDiff is not a PulpRepo") | |
+ | |
+ self.j = juicer_repo | |
+ self.p = pulp_repo | |
+ self.distributor_diff = {} | |
+ self.distributor_diff['distributor_config'] = {} | |
+ self.importer_diff = {} | |
+ self.importer_diff['importer_config'] = {} | |
+ self._diff() | |
+ | |
+ def diff(self): | |
+ """Return importer/distributor diff specs""" | |
+ return { | |
+ 'distributor': self.distributor_diff, | |
+ 'importer': self.importer_diff | |
+ } | |
+ | |
+ def __str__(self): | |
+ return str({ | |
+ 'distributor': self.distributor_diff, | |
+ 'importer': self.importer_diff | |
+ }) | |
+ | |
+ def _diff(self): | |
+ """Calculates what you need to do to make a pulp repo match a juicer repo def""" | |
+ j_cs = self.j['checksum_type'] | |
+ j_feed = self.j['feed'] | |
+ | |
+ p_cs = self.p['checksum_type'] | |
+ p_feed = self.p['feed'] | |
+ | |
+ # checksum is a distributor property | |
+ # Is the pulp checksum wrong? | |
+ if not p_cs == j_cs: | |
+ juicer.utils.Log.log_debug("Pulp checksum_type does not match juicer") | |
+ self.distributor_diff['distributor_config']['checksum_type'] = j_cs | |
+ juicer.utils.Log.log_debug("distributor_config::checksum_type SHOULD BE %s" % j_cs) | |
+ | |
+ # feed is an importer property | |
+ if not p_feed == j_feed: | |
+ juicer.utils.Log.log_debug("Pulp feed does not match juicer") | |
+ self.importer_diff['importer_config']['feed'] = j_feed | |
+ juicer.utils.Log.log_debug("importer_config::feed SHOULD BE %s" % j_feed) | |
+ | |
+ | |
+# Custom encoder for Repo types so we can dump them with standard json tools | |
+class RepoEncoder(json.JSONEncoder): | |
+ def default(self, obj): | |
+ if isinstance(obj, Repo): | |
+ return obj._repo_ds() | |
+ elif isinstance(obj, RepoDiff): | |
+ return str(obj) | |
+ # Let the base class default method raise the TypeError | |
+ return json.JSONEncoder.default(self, obj) | |
+ | |
+# Custom encoder for RepoDiff types so we can dump them with standard json tools | |
+# class RepoDiffEncoder(json.JSONEncoder): | |
+# def default(self, obj): | |
+# if isinstance(obj, RepoDiff): | |
+# return str(obj) | |
+# # Let the base class default method raise the TypeError | |
+# return json.JSONEncoder.default(self, obj) | |
diff --git a/juicer/juicer/Parser.py b/juicer/juicer/Parser.py | |
index 105498d..89ec4d8 100644 | |
--- a/juicer/juicer/Parser.py | |
+++ b/juicer/juicer/Parser.py | |
@@ -23,291 +23,291 @@ import juicer.utils | |
class Parser(object): | |
def __init__(self): | |
- self.parser = argparse.ArgumentParser(\ | |
- description='Manage release carts') | |
+ self.parser = argparse.ArgumentParser( | |
+ description='Manage release carts') | |
juicer.juicer.parser = self.parser | |
self._default_start_in = juicer.utils.get_login_info()[1]['start_in'] | |
self._default_envs = juicer.utils.get_environments() | |
- self.parser.add_argument('-v', action='count', \ | |
- default=1, \ | |
- help='Increase the verbosity (up to 3x)') | |
+ self.parser.add_argument('-v', action='count', | |
+ default=1, | |
+ help='Increase the verbosity (up to 3x)') | |
- self.parser.add_argument('-V', '--version', action='version', \ | |
- version="juicer-%s" \ | |
- % juicer.utils.juicer_version()) | |
+ self.parser.add_argument('-V', '--version', action='version', | |
+ version="juicer-%s" | |
+ % juicer.utils.juicer_version()) | |
################################################################## | |
# Keep the different commands separate | |
- subparsers = self.parser.add_subparsers(title='Commands', \ | |
- dest='command', \ | |
- description='\'%(prog)s COMMAND -h\' for individual help topics') | |
+ subparsers = self.parser.add_subparsers(title='Commands', | |
+ dest='command', | |
+ description='\'%(prog)s COMMAND -h\' for individual help topics') | |
################################################################## | |
# Create the 'cart' sub-parser | |
- parser_cart = subparsers.add_parser('cart', \ | |
- help='Cart operations') | |
+ parser_cart = subparsers.add_parser('cart', | |
+ help='Cart operations') | |
subparser_cart = parser_cart.add_subparsers(dest='sub_command') | |
################################################################## | |
# Create the 'rpm' sub-parser | |
- parser_rpm = subparsers.add_parser('rpm', \ | |
- help='RPM operations') | |
+ parser_rpm = subparsers.add_parser('rpm', | |
+ help='RPM operations') | |
subparser_rpm = parser_rpm.add_subparsers(dest='sub_command') | |
################################################################## | |
# Create the 'repo' sub-parser | |
- parser_repo = subparsers.add_parser('repo', \ | |
- help='Repo operations') | |
+ parser_repo = subparsers.add_parser('repo', | |
+ help='Repo operations') | |
subparser_repo = parser_repo.add_subparsers(dest='sub_command') | |
################################################################## | |
# Create the 'cart create' sub-parser | |
- parser_cart_create = subparser_cart.add_parser('create', \ | |
- help='Create a cart with the items specified.', \ | |
- usage='%(prog)s CARTNAME [-f rpm-manifest] ... [-r REPONAME items ... [ -r REPONAME items ...]]') | |
+ parser_cart_create = subparser_cart.add_parser('create', | |
+ help='Create a cart with the items specified.', | |
+ usage='%(prog)s CARTNAME [-f rpm-manifest] ... [-r REPONAME items ... [ -r REPONAME items ...]]') | |
- parser_cart_create.add_argument('cartname', metavar='cart-name', \ | |
- help='Cart name') | |
+ parser_cart_create.add_argument('cartname', metavar='cart-name', | |
+ help='Cart name') | |
cgroup = parser_cart_create.add_mutually_exclusive_group(required=True) | |
- cgroup.add_argument('-r', metavar=('reponame', 'item'), \ | |
- action='append', \ | |
- nargs='+', \ | |
- help='Destination repo name') | |
+ cgroup.add_argument('-r', metavar=('reponame', 'item'), | |
+ action='append', | |
+ nargs='+', | |
+ help='Destination repo name') | |
- cgroup.add_argument('-f', metavar='rpm-manifest', \ | |
- action='append', \ | |
- help='RPM manifest for cart') | |
+ cgroup.add_argument('-f', metavar='rpm-manifest', | |
+ action='append', | |
+ help='RPM manifest for cart') | |
parser_cart_create.set_defaults(j=juicer.juicer.create) | |
################################################################## | |
# Create the 'edit' sub-parser | |
- # parser_edit = subparsers.add_parser('edit', \ | |
+ # parser_edit = subparsers.add_parser('edit', | |
# help='Interactively edit a release cart.') | |
- # parser_edit.add_argument('cart-name', metavar='cartname', \ | |
+ # parser_edit.add_argument('cart-name', metavar='cartname', | |
# help='The name of your release cart') | |
# parser_edit.set_defaults(j=juicer.juicer.edit) | |
################################################################## | |
# Create the 'cart show' sub-parser | |
- parser_cart_show = subparser_cart.add_parser('show', \ | |
- usage='%(prog)s CARTNAME [--in [environment [environment ...]]] [-h]', \ | |
- help='Print the contents of a cart.') | |
+ parser_cart_show = subparser_cart.add_parser('show', | |
+ usage='%(prog)s CARTNAME [--in [environment [environment ...]]] [-h]', | |
+ help='Print the contents of a cart.') | |
- parser_cart_show.add_argument('cartname', metavar='name', \ | |
- help='The name of your cart') | |
+ parser_cart_show.add_argument('cartname', metavar='name', | |
+ help='The name of your cart') | |
- parser_cart_show.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- default=juicer.utils.get_environments(), \ | |
- help='Only show carts pushed to the given environment.', \ | |
- dest='environment') | |
+ parser_cart_show.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ default=juicer.utils.get_environments(), | |
+ help='Only show carts pushed to the given environment.', | |
+ dest='environment') | |
parser_cart_show.set_defaults(j=juicer.juicer.show) | |
################################################################## | |
# Create the 'cart list' sub-parser | |
- parser_cart_list = subparser_cart.add_parser('list', \ | |
- help='List all of your carts.') | |
+ parser_cart_list = subparser_cart.add_parser('list', | |
+ help='List all of your carts.') | |
- parser_cart_list.add_argument('cart_glob', metavar='cart_glob', \ | |
- nargs='*', default=['*'], \ | |
- help='A pattern to match cart names against (default: *)') | |
+ parser_cart_list.add_argument('cart_glob', metavar='cart_glob', | |
+ nargs='*', default=['*'], | |
+ help='A pattern to match cart names against (default: *)') | |
parser_cart_list.set_defaults(j=juicer.juicer.list) | |
################################################################## | |
# Create the 'cart update' sub-parser | |
- parser_cart_update = subparser_cart.add_parser('update', \ | |
- help='Update a release cart with items.', \ | |
- usage='%(prog)s CARTNAME [-f rpm-manifest] ... [-r REPONAME items ... [-r REPONAME items...]]') | |
+ parser_cart_update = subparser_cart.add_parser('update', | |
+ help='Update a release cart with items.', | |
+ usage='%(prog)s CARTNAME [-f rpm-manifest] ... [-r REPONAME items ... [-r REPONAME items...]]') | |
- parser_cart_update.add_argument('cartname', metavar='cartname', \ | |
- help='The name of your release cart') | |
+ parser_cart_update.add_argument('cartname', metavar='cartname', | |
+ help='The name of your release cart') | |
- parser_cart_update.add_argument('-r', metavar=('reponame', 'item'), \ | |
- action='append', \ | |
- nargs='+', \ | |
- help='Destination repo name') | |
+ parser_cart_update.add_argument('-r', metavar=('reponame', 'item'), | |
+ action='append', | |
+ nargs='+', | |
+ help='Destination repo name') | |
- parser_cart_update.add_argument('-f', metavar='rpm-manifest', \ | |
- action='append', \ | |
- help='RPM manifest for cart') | |
+ parser_cart_update.add_argument('-f', metavar='rpm-manifest', | |
+ action='append', | |
+ help='RPM manifest for cart') | |
parser_cart_update.set_defaults(j=juicer.juicer.update) | |
################################################################## | |
# Create the 'cart pull' sub-parser | |
- parser_cart_pull = subparser_cart.add_parser('pull', \ | |
- help='Pull a release cart from remote.') | |
+ parser_cart_pull = subparser_cart.add_parser('pull', | |
+ help='Pull a release cart from remote.') | |
- parser_cart_pull.add_argument('cartname', metavar='cartname', \ | |
- help='The name of your release cart') | |
+ parser_cart_pull.add_argument('cartname', metavar='cartname', | |
+ help='The name of your release cart') | |
parser_cart_pull.set_defaults(j=juicer.juicer.pull) | |
################################################################## | |
# Create the 'create-like' sub-parser | |
- # parser_createlike = subparsers.add_parser('create-like', \ | |
+ # parser_createlike = subparsers.add_parser('create-like', | |
# help='Create a new cart based off an existing one.') | |
- # parser_createlike.add_argument('cart-name', metavar='cartname', \ | |
+ # parser_createlike.add_argument('cart-name', metavar='cartname', | |
# help='The name of your new release cart') | |
- # parser_createlike.add_argument('old-cart-name', \ | |
+ # parser_createlike.add_argument('old-cart-name', | |
# metavar='oldcartname', help='Cart to copy') | |
- # parser_createlike.add_argument('items', metavar='items', \ | |
- # nargs="+", \ | |
+ # parser_createlike.add_argument('items', metavar='items', | |
+ # nargs="+", | |
# help='Cart name') | |
# parser_createlike.set_defaults(j=juicer.juicer.createlike) | |
################################################################## | |
# Create the 'cart push' sub-parser | |
- parser_cart_push = subparser_cart.add_parser('push', \ | |
- help='Pushes/Updates a cart on the pulp server.', | |
- usage='%(prog)s CARTNAME [--in [environment [environment ...]]] [-h]') | |
+ parser_cart_push = subparser_cart.add_parser('push', | |
+ help='Pushes/Updates a cart on the pulp server.', | |
+ usage='%(prog)s CARTNAME [--in [environment [environment ...]]] [-h]') | |
- parser_cart_push.add_argument('cartname', metavar='cartname', \ | |
- help='The name of your new release cart') | |
+ parser_cart_push.add_argument('cartname', metavar='cartname', | |
+ help='The name of your new release cart') | |
- parser_cart_push.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- default=[self._default_start_in], \ | |
- help='The environments to push into.', \ | |
- dest='environment') | |
+ parser_cart_push.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ default=[self._default_start_in], | |
+ help='The environments to push into.', | |
+ dest='environment') | |
parser_cart_push.set_defaults(j=juicer.juicer.push) | |
################################################################## | |
# Create the 'rpm search' sub-parser | |
- parser_rpm_search = subparser_rpm.add_parser('search', \ | |
- help='Search for an RPM in pulp.', \ | |
- usage='%(prog)s rpmname [-r repo [repo]] [-c] [--in environment [environment]] [-h]') | |
+ parser_rpm_search = subparser_rpm.add_parser('search', | |
+ help='Search for an RPM in pulp.', | |
+ usage='%(prog)s rpmname [-r repo [repo]] [-c] [--in environment [environment]] [-h]') | |
- parser_rpm_search.add_argument('rpmname', metavar='rpmname', \ | |
- help='The name of the rpm(s) to search for.') | |
+ parser_rpm_search.add_argument('rpmname', metavar='rpmname', | |
+ help='The name of the rpm(s) to search for.') | |
- parser_rpm_search.add_argument('-r', nargs='*', metavar='repos', \ | |
- default=[], help='The repo(s) to limit search scope to.') | |
+ parser_rpm_search.add_argument('-r', nargs='*', metavar='repos', | |
+ default=[], help='The repo(s) to limit search scope to.') | |
- parser_rpm_search.add_argument('-c', '--carts', dest='carts', \ | |
- action='store_true', \ | |
- help="Search for the package in carts as well") | |
+ parser_rpm_search.add_argument('-c', '--carts', dest='carts', | |
+ action='store_true', | |
+ help="Search for the package in carts as well") | |
- parser_rpm_search.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- default=[self._default_start_in], \ | |
- help='The environments to limit search scope to.', \ | |
- dest='environment') | |
+ parser_rpm_search.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ default=[self._default_start_in], | |
+ help='The environments to limit search scope to.', | |
+ dest='environment') | |
parser_rpm_search.set_defaults(j=juicer.juicer.search) | |
################################################################## | |
# create the 'rpm upload' sub-parser | |
- parser_rpm_upload = subparser_rpm.add_parser('upload', \ | |
- help='Upload the items specified into repos.', \ | |
- usage='%(prog)s -r REPONAME items ... [ -r REPONAME items ...] [--in ENV ...]') | |
- | |
- parser_rpm_upload.add_argument('-r', metavar=('reponame', 'item'), \ | |
- action='append', \ | |
- nargs='+', \ | |
- required=True, \ | |
+ parser_rpm_upload = subparser_rpm.add_parser('upload', | |
+ help='Upload the items specified into repos.', | |
+ usage='%(prog)s -r REPONAME items ... [ -r REPONAME items ...] [--in ENV ...]') | |
+ | |
+ parser_rpm_upload.add_argument('-r', metavar=('reponame', 'item'), | |
+ action='append', | |
+ nargs='+', | |
+ required=True, | |
help='Destination repo name, items...') | |
- parser_rpm_upload.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- default=[self._default_start_in], \ | |
- help='The environments to upload into.', \ | |
- dest='environment') | |
+ parser_rpm_upload.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ default=[self._default_start_in], | |
+ help='The environments to upload into.', | |
+ dest='environment') | |
parser_rpm_upload.set_defaults(j=juicer.juicer.upload) | |
################################################################## | |
# create the 'hello' sub-parser | |
- parser_hello = subparsers.add_parser('hello', \ | |
- help='Test your connection to the pulp server', \ | |
- usage='%(prog)s hello [--in env ...]') | |
+ parser_hello = subparsers.add_parser('hello', | |
+ help='Test your connection to the pulp server', | |
+ usage='%(prog)s hello [--in env ...]') | |
- parser_hello.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- help='The environments to test the connection to.', \ | |
- default=self._default_envs, \ | |
- dest='environment') | |
+ parser_hello.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ help='The environments to test the connection to.', | |
+ default=self._default_envs, | |
+ dest='environment') | |
parser_hello.set_defaults(j=juicer.juicer.hello) | |
################################################################## | |
# create the 'cart promote' sub-parser | |
- parser_cart_promote = subparser_cart.add_parser('promote', \ | |
- help='Promote a cart to the next environment') | |
+ parser_cart_promote = subparser_cart.add_parser('promote', | |
+ help='Promote a cart to the next environment') | |
- parser_cart_promote.add_argument('cartname', metavar='cart', \ | |
- help='The name of the cart to promote') | |
+ parser_cart_promote.add_argument('cartname', metavar='cart', | |
+ help='The name of the cart to promote') | |
parser_cart_promote.set_defaults(j=juicer.juicer.promote) | |
################################################################## | |
# create the 'cart merge' sub-parser | |
- parser_cart_merge = subparser_cart.add_parser('merge', \ | |
- help='Merge the contents of two carts', \ | |
- usage='%(prog)s merge CART1 CART2 [CARTN ...]]] --into NEWCART') | |
+ parser_cart_merge = subparser_cart.add_parser('merge', | |
+ help='Merge the contents of two carts', | |
+ usage='%(prog)s merge CART1 CART2 [CARTN ...]]] --into NEWCART') | |
parser_cart_merge.add_argument('carts', nargs="+", | |
- metavar='carts', \ | |
- help='Two or more carts to merge') | |
+ metavar='carts', | |
+ help='Two or more carts to merge') | |
parser_cart_merge.add_argument('--into', '-i', | |
- metavar='new_cart_name', \ | |
- help='Name of resultant cart, defaults to updating CART1') | |
+ metavar='new_cart_name', | |
+ help='Name of resultant cart, defaults to updating CART1') | |
parser_cart_merge.set_defaults(j=juicer.juicer.merge) | |
################################################################## | |
# create the 'rpm delete' sub-parser | |
- parser_rpm_delete = subparser_rpm.add_parser('delete', \ | |
- help='Remove rpm(s) from repositories', \ | |
- usage='%(prog)s -r REPO-NAME ITEM ITEM ... --in [ENV ...]') | |
- | |
- parser_rpm_delete.add_argument('-r', metavar=('reponame', 'item'), \ | |
- required=True, \ | |
- action='append', \ | |
- nargs='+', \ | |
+ parser_rpm_delete = subparser_rpm.add_parser('delete', | |
+ help='Remove rpm(s) from repositories', | |
+ usage='%(prog)s -r REPO-NAME ITEM ITEM ... --in [ENV ...]') | |
+ | |
+ parser_rpm_delete.add_argument('-r', metavar=('reponame', 'item'), | |
+ required=True, | |
+ action='append', | |
+ nargs='+', | |
help='Target repo filename, filename...') | |
- parser_rpm_delete.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- help='The environments to test the connection to.', \ | |
- default=self._default_envs, \ | |
- dest='environment') | |
+ parser_rpm_delete.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ help='The environments to test the connection to.', | |
+ default=self._default_envs, | |
+ dest='environment') | |
parser_rpm_delete.set_defaults(j=juicer.juicer.delete_rpm) | |
################################################################## | |
# create the 'publish' sub-parser | |
- parser_repo_publish = subparser_repo.add_parser('publish', \ | |
- help='Publish a repository, this will regenerate metadata.', \ | |
- usage='%(prog)s publish REPO --in [ENV ...]') | |
- | |
- parser_repo_publish.add_argument('repo', metavar='reponame', \ | |
- help='Target repo to publish.') | |
- | |
- parser_repo_publish.add_argument('--in', nargs='*', \ | |
- metavar='environment', \ | |
- help='The environments to publish repository in.', \ | |
- default=self._default_envs, \ | |
- dest='environment') | |
+ parser_repo_publish = subparser_repo.add_parser('publish', | |
+ help='Publish a repository, this will regenerate metadata.', | |
+ usage='%(prog)s publish REPO --in [ENV ...]') | |
+ | |
+ parser_repo_publish.add_argument('repo', metavar='reponame', | |
+ help='Target repo to publish.') | |
+ | |
+ parser_repo_publish.add_argument('--in', nargs='*', | |
+ metavar='environment', | |
+ help='The environments to publish repository in.', | |
+ default=self._default_envs, | |
+ dest='environment') | |
parser_repo_publish.set_defaults(j=juicer.juicer.publish) | |
diff --git a/juicer/utils/ValidateRepoDef.py b/juicer/utils/ValidateRepoDef.py | |
new file mode 100644 | |
index 0000000..d301e06 | |
--- /dev/null | |
+++ b/juicer/utils/ValidateRepoDef.py | |
@@ -0,0 +1,131 @@ | |
+# -*- coding: utf-8 -*- | |
+# Juicer - Administer Pulp and Release Carts | |
+# Copyright © 2014, Red Hat, Inc. | |
+# | |
+# This program is free software: you can redistribute it and/or modify | |
+# it under the terms of the GNU General Public License as published by | |
+# the Free Software Foundation, either version 3 of the License, or | |
+# (at your option) any later version. | |
+# | |
+# This program is distributed in the hope that it will be useful, | |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
+# GNU General Public License for more details. | |
+# | |
+# You should have received a copy of the GNU General Public License | |
+# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
+ | |
+ | |
+""" | |
+Repository Definition file validation functions. | |
+ | |
+- Start by passing a document path to `validate_document`. | |
+ | |
+- Wrap in a try/except | |
+ | |
+- except for `JuicerRepoDefError` | |
+""" | |
+ | |
+ | |
+from juicer.common.Errors import * | |
+import json | |
+import juicer.utils | |
+import juicer.common.Constants | |
+import re | |
+import sys | |
+ | |
+valid_repo_name = re.compile('^([a-zA-Z0-9-_.]+)$') | |
+ | |
+def validate_document(document_path): | |
+ try: | |
+ defs = juicer.utils.read_json_document(document_path) | |
+ except ValueError, e: | |
+ raise JuicerRepoDefError("Could not validate: %s" % str(e)) | |
+ | |
+ if is_list(defs): | |
+ for repo_def in defs: | |
+ validate_definitions(defs) | |
+ else: | |
+ raise JuicerRepoDefError("Document is not a list") | |
+ | |
+ return defs | |
+ | |
+def is_list(ds): | |
+ return type(ds) == list | |
+ | |
+def is_dict(ds): | |
+ return type(ds) == dict | |
+ | |
+def is_valid_checksum(cs): | |
+ return cs in juicer.common.Constants.REPO_DEF_CHECKSUM_TYPES | |
+ | |
+def has_valid_keys(ds): | |
+ keys = ds.keys() | |
+ required_keys = juicer.common.Constants.REPO_DEF_REQ_KEYS | |
+ optional_keys = juicer.common.Constants.REPO_DEF_OPT_KEYS | |
+ if set(required_keys) & set(keys): | |
+ remaining_keys = set(keys) - set(required_keys) | |
+ if len(set(remaining_keys) - set(optional_keys)) > 0: | |
+ raise JuicerRepoDefError("Extra keys found: %s" % (', '.join(set(remaining_keys) - set(optional_keys)))) | |
+ | |
+ if 'env' in keys: | |
+ if not is_list(ds['env']): | |
+ raise JuicerRepoDefError("%s is not a list" % type(ds['env'])) | |
+ else: | |
+ pass | |
+ | |
+ if 'checksum_type' in keys: | |
+ if not is_valid_checksum(ds['checksum_type']): | |
+ raise JuicerRepoDefError("Invalid checksum_type in repo def: %s" % ds['checksum_type']) | |
+ else: | |
+ pass | |
+ | |
+ if 'feed' in keys: | |
+ if not is_string(ds['feed']): | |
+ raise JuicerRepoDefError("%s is not a string" % type(ds['feed'])) | |
+ else: | |
+ pass | |
+ | |
+ return True | |
+ else: | |
+ raise JuicerRepoDefError("Missing required keys: %s" % ', '.join(required_keys)) | |
+ | |
+def is_valid_repo_name(repo_name): | |
+ match = valid_repo_name.match(repo_name) | |
+ if match: | |
+ return True | |
+ else: | |
+ return False | |
+ | |
+def is_string(str): | |
+ return (type(str) == str) or (type(str) == unicode) | |
+ | |
+def validate_definitions(defs): | |
+ for definition in defs: | |
+ if is_dict(definition): | |
+ validate_def_keys(definition) | |
+ else: | |
+ raise JuicerRepoDefError("Repo definition is not a dictionary!") | |
+ | |
+def validate_def_keys(definition): | |
+ if has_valid_keys(definition): | |
+ if not is_valid_repo_name(definition['name']): | |
+ raise JuicerRepoDefError("Repo name is invalid: %s" % definition['name']) | |
+ else: | |
+ raise JuicerRepoDefError("Repo definition has invalid keys") | |
+ | |
+if __name__ == '__main__': | |
+ passes = 0 | |
+ failures = 0 | |
+ | |
+ for f in sys.argv[1:]: | |
+ try: | |
+ validate_document(f) | |
+ except JuicerRepoDefError, e: | |
+ failures += 1 | |
+ print "\033[00;31mFAIL:\033[0m %s: %s" % (f, str(e)) | |
+ else: | |
+ passes += 1 | |
+ print "\033[00;32mPASS:\033[0m %s" % f | |
+ | |
+ sys.exit(failures) | |
diff --git a/juicer/utils/__init__.py b/juicer/utils/__init__.py | |
index e9e4119..5dd377a 100644 | |
--- a/juicer/utils/__init__.py | |
+++ b/juicer/utils/__init__.py | |
@@ -26,6 +26,7 @@ import cStringIO | |
import fnmatch | |
import juicer.utils.Log | |
import juicer.utils.Remotes | |
+import juicer.common.Repo | |
import os | |
import os.path | |
import rpm | |
@@ -34,11 +35,11 @@ import sys | |
import requests | |
import shutil | |
import re | |
+import texttable | |
import urllib2 | |
import yaml | |
try: | |
import json | |
- json | |
except ImportError: | |
import simplejson as json | |
from pymongo import Connection as MongoClient | |
@@ -193,8 +194,7 @@ def get_environments(): | |
juicer.utils.Log.log_debug("Reading environment sections:") | |
environments = config.sections() | |
- | |
- juicer.utils.Log.log_notice("Read environment sections: %s", environments) | |
+ juicer.utils.Log.log_debug("Read environment sections: %s", ', '.join(environments)) | |
return environments | |
@@ -764,9 +764,59 @@ def find_latest(pkg_name, url='/content/units/rpm/search/'): | |
return pkg_info['version'], pkg_info['release'] | |
- | |
def juicer_version(): | |
""" | |
Duh, just print out what version of juicer you're running. | |
""" | |
return juicer.__version__ | |
+ | |
+def header(msg): | |
+ """ | |
+ Wrap `msg` in bars to create a header effect | |
+ """ | |
+ # Accounting for '| ' and ' |' | |
+ width = len(msg) + 4 | |
+ s = [] | |
+ s.append('-' * width) | |
+ s.append("| %s |" % msg) | |
+ s.append('-' * width) | |
+ return '\n'.join(s) | |
+ | |
+def table(rows): | |
+ t = texttable.Texttable() | |
+ t.add_rows(rows) | |
+ return t.draw() | |
+ | |
+ | |
+def unique_repo_def_envs(repo_def): | |
+ defined_envs = set() | |
+ for repo in repo_def: | |
+ defined_envs = defined_envs.union(repo['env']) | |
+ juicer.utils.Log.log_debug("envs for repo %s: %s", repo['name'], ", ".join(repo['env'])) | |
+ return defined_envs | |
+ | |
+def repo_exists_in_repo_list(repo, repo_list): | |
+ """`repo_def` - a Repo object representing a juicer repo def | |
+ | |
+ `repo_list` a list of repo names (sans environment-id), per the | |
+ data from juicer.admin.JuicerAdmin.list_repos | |
+ """ | |
+ return repo['name'] in repo_list | |
+ | |
+def repo_in_defined_envs(repo, all_envs): | |
+ """Raises exception if the repo references undefined environments""" | |
+ remaining_envs = set(repo['env']) - set(all_envs) | |
+ if set(repo['env']) - set(all_envs): | |
+ raise JuicerRepoInUndefinedEnvs("Repo def %s references undefined environments: %s" % | |
+ (repo['name'], ", ".join(list(remaining_envs)))) | |
+ else: | |
+ return True | |
+ | |
+def repo_def_matches_reality(juicer_def, pulp_def): | |
+ """Compare a juicer repo def with a given pulp definition. Compute and | |
+ return the update necessary to make `pulp_def` match `juicer_def`. | |
+ | |
+ `juicer_def` - A JuicerRepo() object representing a juicer repository | |
+ `pulp_def` - A PulpRepo() object representing a pulp repository | |
+ """ | |
+ return juicer.common.Repo.RepoDiff(juicer_repo=juicer_def, pulp_repo=pulp_def) | |
diff --git a/juicer/utils/texttable.py b/juicer/utils/texttable.py | |
new file mode 100644 | |
index 0000000..c817910 | |
--- /dev/null | |
+++ b/juicer/utils/texttable.py | |
@@ -0,0 +1,473 @@ | |
+#!/usr/bin/env python | |
+# | |
+# texttable - module for creating simple ASCII tables | |
+# Copyright (C) 2003-2009 Gerome Fournier <jefke(at)free.fr> | |
+# | |
+# This library is free software; you can redistribute it and/or | |
+# modify it under the terms of the GNU Lesser General Public | |
+# License as published by the Free Software Foundation; either | |
+# version 2.1 of the License, or (at your option) any later version. | |
+# | |
+# This library is distributed in the hope that it will be useful, | |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+# Lesser General Public License for more details. | |
+# | |
+# You should have received a copy of the GNU Lesser General Public | |
+# License along with this library; if not, write to the Free Software | |
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
+ | |
+"""module for creating simple ASCII tables | |
+ | |
+ | |
+Example: | |
+ | |
+ table = Texttable() | |
+ table.set_cols_align(["l", "r", "c"]) | |
+ table.set_cols_valign(["t", "m", "b"]) | |
+ table.add_rows([ ["Name", "Age", "Nickname"], | |
+ ["Mr\\nXavier\\nHuon", 32, "Xav'"], | |
+ ["Mr\\nBaptiste\\nClement", 1, "Baby"] ]) | |
+ print table.draw() | |
+ | |
+Result: | |
+ | |
+ +----------+-----+----------+ | |
+ | Name | Age | Nickname | | |
+ +==========+=====+==========+ | |
+ | Mr | | | | |
+ | Xavier | 32 | | | |
+ | Huon | | Xav' | | |
+ +----------+-----+----------+ | |
+ | Mr | | | | |
+ | Baptiste | 1 | | | |
+ | Clement | | Baby | | |
+ +----------+-----+----------+ | |
+""" | |
+ | |
+__all__ = ["Texttable", "ArraySizeError"] | |
+ | |
+__author__ = 'Gerome Fournier <jefke(at)free.fr>' | |
+__license__ = 'GPL' | |
+__version__ = '0.7' | |
+__revision__ = '$Id: texttable.py 128 2009-10-04 15:16:22Z jef $' | |
+__credits__ = """\ | |
+Jeff Kowalczyk: | |
+ - textwrap improved import | |
+ - comment concerning header output | |
+ | |
+Anonymous: | |
+ - add_rows method, for adding rows in one go | |
+ | |
+Sergey Simonenko: | |
+ - redefined len() function to deal with non-ASCII characters | |
+ | |
+""" | |
+ | |
+import sys | |
+import string | |
+ | |
+try: | |
+ if sys.version >= '2.3': | |
+ import textwrap | |
+ elif sys.version >= '2.2': | |
+ from optparse import textwrap | |
+ else: | |
+ from optik import textwrap | |
+except ImportError: | |
+ sys.stderr.write("Can't import textwrap module!\n") | |
+ raise | |
+ | |
+try: | |
+ True, False | |
+except NameError: | |
+ (True, False) = (1, 0) | |
+ | |
+def len(iterable): | |
+ """Redefining len here so it will be able to work with non-ASCII characters | |
+ """ | |
+ if not isinstance(iterable, str): | |
+ return iterable.__len__() | |
+ | |
+ try: | |
+ return len(unicode(iterable, 'utf')) | |
+ except: | |
+ return iterable.__len__() | |
+ | |
+class ArraySizeError(Exception): | |
+ """Exception raised when specified rows don't fit the required size | |
+ """ | |
+ | |
+ def __init__(self, msg): | |
+ self.msg = msg | |
+ Exception.__init__(self, msg, '') | |
+ | |
+ def __str__(self): | |
+ return self.msg | |
+ | |
+class Texttable: | |
+ | |
+ BORDER = 1 | |
+ HEADER = 1 << 1 | |
+ HLINES = 1 << 2 | |
+ VLINES = 1 << 3 | |
+ | |
+ def __init__(self, max_width=80): | |
+ """Constructor | |
+ | |
+ - max_width is an integer, specifying the maximum width of the table | |
+ - if set to 0, size is unlimited, therefore cells won't be wrapped | |
+ """ | |
+ | |
+ if max_width <= 0: | |
+ max_width = False | |
+ self._max_width = max_width | |
+ self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \ | |
+ Texttable.HEADER | |
+ self.set_chars(['-', '|', '+', '=']) | |
+ self.reset() | |
+ | |
+ def reset(self): | |
+ """Reset the instance | |
+ | |
+ - reset rows and header | |
+ """ | |
+ | |
+ self._hline_string = None | |
+ self._row_size = None | |
+ self._header = [] | |
+ self._rows = [] | |
+ | |
+ def header(self, array): | |
+ """Specify the header of the table | |
+ """ | |
+ | |
+ self._check_row_size(array) | |
+ self._header = map(str, array) | |
+ | |
+ def add_row(self, array): | |
+ """Add a row in the rows stack | |
+ | |
+ - cells can contain newlines and tabs | |
+ """ | |
+ | |
+ self._check_row_size(array) | |
+ self._rows.append(map(str, array)) | |
+ | |
+ def add_rows(self, rows, header=True): | |
+ """Add several rows in the rows stack | |
+ | |
+ - The 'rows' argument can be either an iterator returning arrays, | |
+ or a by-dimensional array | |
+ - 'header' specifies if the first row should be used as the header | |
+ of the table | |
+ """ | |
+ | |
+ # nb: don't use 'iter' on by-dimensional arrays, to get a | |
+ # usable code for python 2.1 | |
+ if header: | |
+ if hasattr(rows, '__iter__') and hasattr(rows, 'next'): | |
+ self.header(rows.next()) | |
+ else: | |
+ self.header(rows[0]) | |
+ rows = rows[1:] | |
+ for row in rows: | |
+ self.add_row(row) | |
+ | |
+ def set_chars(self, array): | |
+ """Set the characters used to draw lines between rows and columns | |
+ | |
+ - the array should contain 4 fields: | |
+ | |
+ [horizontal, vertical, corner, header] | |
+ | |
+ - default is set to: | |
+ | |
+ ['-', '|', '+', '='] | |
+ """ | |
+ | |
+ if len(array) != 4: | |
+ raise ArraySizeError, "array should contain 4 characters" | |
+ array = [ x[:1] for x in [ str(s) for s in array ] ] | |
+ (self._char_horiz, self._char_vert, | |
+ self._char_corner, self._char_header) = array | |
+ | |
+ def set_deco(self, deco): | |
+ """Set the table decoration | |
+ | |
+ - 'deco' can be a combinaison of: | |
+ | |
+ Texttable.BORDER: Border around the table | |
+ Texttable.HEADER: Horizontal line below the header | |
+ Texttable.HLINES: Horizontal lines between rows | |
+ Texttable.VLINES: Vertical lines between columns | |
+ | |
+ All of them are enabled by default | |
+ | |
+ - example: | |
+ | |
+ Texttable.BORDER | Texttable.HEADER | |
+ """ | |
+ | |
+ self._deco = deco | |
+ | |
+ def set_cols_align(self, array): | |
+ """Set the desired columns alignment | |
+ | |
+ - the elements of the array should be either "l", "c" or "r": | |
+ | |
+ * "l": column flushed left | |
+ * "c": column centered | |
+ * "r": column flushed right | |
+ """ | |
+ | |
+ self._check_row_size(array) | |
+ self._align = array | |
+ | |
+ def set_cols_valign(self, array): | |
+ """Set the desired columns vertical alignment | |
+ | |
+ - the elements of the array should be either "t", "m" or "b": | |
+ | |
+ * "t": column aligned on the top of the cell | |
+ * "m": column aligned on the middle of the cell | |
+ * "b": column aligned on the bottom of the cell | |
+ """ | |
+ | |
+ self._check_row_size(array) | |
+ self._valign = array | |
+ | |
+ def set_cols_width(self, array): | |
+ """Set the desired columns width | |
+ | |
+ - the elements of the array should be integers, specifying the | |
+ width of each column. For example: | |
+ | |
+ [10, 20, 5] | |
+ """ | |
+ | |
+ self._check_row_size(array) | |
+ try: | |
+ array = map(int, array) | |
+ if reduce(min, array) <= 0: | |
+ raise ValueError | |
+ except ValueError: | |
+ sys.stderr.write("Wrong argument in column width specification\n") | |
+ raise | |
+ self._width = array | |
+ | |
+ def draw(self): | |
+ """Draw the table | |
+ | |
+ - the table is returned as a whole string | |
+ """ | |
+ | |
+ if not self._header and not self._rows: | |
+ return | |
+ self._compute_cols_width() | |
+ self._check_align() | |
+ out = "" | |
+ if self._has_border(): | |
+ out += self._hline() | |
+ if self._header: | |
+ out += self._draw_line(self._header, isheader=True) | |
+ if self._has_header(): | |
+ out += self._hline_header() | |
+ length = 0 | |
+ for row in self._rows: | |
+ length += 1 | |
+ out += self._draw_line(row) | |
+ if self._has_hlines() and length < len(self._rows): | |
+ out += self._hline() | |
+ if self._has_border(): | |
+ out += self._hline() | |
+ return out[:-1] | |
+ | |
+ def _check_row_size(self, array): | |
+ """Check that the specified array fits the previous rows size | |
+ """ | |
+ | |
+ if not self._row_size: | |
+ self._row_size = len(array) | |
+ elif self._row_size != len(array): | |
+ raise ArraySizeError, "array should contain %d elements" \ | |
+ % self._row_size | |
+ | |
+ def _has_vlines(self): | |
+ """Return a boolean, if vlines are required or not | |
+ """ | |
+ | |
+ return self._deco & Texttable.VLINES > 0 | |
+ | |
+ def _has_hlines(self): | |
+ """Return a boolean, if hlines are required or not | |
+ """ | |
+ | |
+ return self._deco & Texttable.HLINES > 0 | |
+ | |
+ def _has_border(self): | |
+ """Return a boolean, if border is required or not | |
+ """ | |
+ | |
+ return self._deco & Texttable.BORDER > 0 | |
+ | |
+ def _has_header(self): | |
+ """Return a boolean, if header line is required or not | |
+ """ | |
+ | |
+ return self._deco & Texttable.HEADER > 0 | |
+ | |
+ def _hline_header(self): | |
+ """Print header's horizontal line | |
+ """ | |
+ | |
+ return self._build_hline(True) | |
+ | |
+ def _hline(self): | |
+ """Print an horizontal line | |
+ """ | |
+ | |
+ if not self._hline_string: | |
+ self._hline_string = self._build_hline() | |
+ return self._hline_string | |
+ | |
+ def _build_hline(self, is_header=False): | |
+ """Return a string used to separated rows or separate header from | |
+ rows | |
+ """ | |
+ horiz = self._char_horiz | |
+ if (is_header): | |
+ horiz = self._char_header | |
+ # compute cell separator | |
+ s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()], | |
+ horiz) | |
+ # build the line | |
+ l = string.join([horiz*n for n in self._width], s) | |
+ # add border if needed | |
+ if self._has_border(): | |
+ l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz, | |
+ self._char_corner) | |
+ else: | |
+ l += "\n" | |
+ return l | |
+ | |
+ def _len_cell(self, cell): | |
+ """Return the width of the cell | |
+ | |
+ Special characters are taken into account to return the width of the | |
+ cell, such like newlines and tabs | |
+ """ | |
+ | |
+ cell_lines = cell.split('\n') | |
+ maxi = 0 | |
+ for line in cell_lines: | |
+ length = 0 | |
+ parts = line.split('\t') | |
+ for part, i in zip(parts, range(1, len(parts) + 1)): | |
+ length = length + len(part) | |
+ if i < len(parts): | |
+ length = (length/8 + 1)*8 | |
+ maxi = max(maxi, length) | |
+ return maxi | |
+ | |
+ def _compute_cols_width(self): | |
+ """Return an array with the width of each column | |
+ | |
+ If a specific width has been specified, exit. If the total of the | |
+ columns width exceed the table desired width, another width will be | |
+ computed to fit, and cells will be wrapped. | |
+ """ | |
+ | |
+ if hasattr(self, "_width"): | |
+ return | |
+ maxi = [] | |
+ if self._header: | |
+ maxi = [ self._len_cell(x) for x in self._header ] | |
+ for row in self._rows: | |
+ for cell,i in zip(row, range(len(row))): | |
+ try: | |
+ maxi[i] = max(maxi[i], self._len_cell(cell)) | |
+ except (TypeError, IndexError): | |
+ maxi.append(self._len_cell(cell)) | |
+ items = len(maxi) | |
+ length = reduce(lambda x,y: x+y, maxi) | |
+ if self._max_width and length + items*3 + 1 > self._max_width: | |
+ maxi = [(self._max_width - items*3 -1) / items \ | |
+ for n in range(items)] | |
+ self._width = maxi | |
+ | |
+ def _check_align(self): | |
+ """Check if alignment has been specified, set default one if not | |
+ """ | |
+ | |
+ if not hasattr(self, "_align"): | |
+ self._align = ["l"]*self._row_size | |
+ if not hasattr(self, "_valign"): | |
+ self._valign = ["t"]*self._row_size | |
+ | |
+ def _draw_line(self, line, isheader=False): | |
+ """Draw a line | |
+ | |
+ Loop over a single cell length, over all the cells | |
+ """ | |
+ | |
+ line = self._splitit(line, isheader) | |
+ space = " " | |
+ out = "" | |
+ for i in range(len(line[0])): | |
+ if self._has_border(): | |
+ out += "%s " % self._char_vert | |
+ length = 0 | |
+ for cell, width, align in zip(line, self._width, self._align): | |
+ length += 1 | |
+ cell_line = cell[i] | |
+ fill = width - len(cell_line) | |
+ if isheader: | |
+ align = "c" | |
+ if align == "r": | |
+ out += "%s " % (fill * space + cell_line) | |
+ elif align == "c": | |
+ out += "%s " % (fill/2 * space + cell_line \ | |
+ + (fill/2 + fill%2) * space) | |
+ else: | |
+ out += "%s " % (cell_line + fill * space) | |
+ if length < len(line): | |
+ out += "%s " % [space, self._char_vert][self._has_vlines()] | |
+ out += "%s\n" % ['', self._char_vert][self._has_border()] | |
+ return out | |
+ | |
+ def _splitit(self, line, isheader): | |
+ """Split each element of line to fit the column width | |
+ | |
+ Each element is turned into a list, result of the wrapping of the | |
+ string to the desired width | |
+ """ | |
+ | |
+ line_wrapped = [] | |
+ for cell, width in zip(line, self._width): | |
+ array = [] | |
+ for c in cell.split('\n'): | |
+ array.extend(textwrap.wrap(unicode(c, 'utf'), width)) | |
+ line_wrapped.append(array) | |
+ max_cell_lines = reduce(max, map(len, line_wrapped)) | |
+ for cell, valign in zip(line_wrapped, self._valign): | |
+ if isheader: | |
+ valign = "t" | |
+ if valign == "m": | |
+ missing = max_cell_lines - len(cell) | |
+ cell[:0] = [""] * (missing / 2) | |
+ cell.extend([""] * (missing / 2 + missing % 2)) | |
+ elif valign == "b": | |
+ cell[:0] = [""] * (max_cell_lines - len(cell)) | |
+ else: | |
+ cell.extend([""] * (max_cell_lines - len(cell))) | |
+ return line_wrapped | |
+ | |
+if __name__ == '__main__': | |
+ table = Texttable() | |
+ table.set_cols_align(["l", "r", "c"]) | |
+ table.set_cols_valign(["t", "m", "b"]) | |
+ table.add_rows([ ["Name", "Age", "Nickname"], | |
+ ["Mr\nXavier\nHuon", 32, "Xav'"], | |
+ ["Mr\nBaptiste\nClement", 1, "Baby"] ]) | |
+ print table.draw() | |
diff --git a/setup.py.in b/setup.py.in | |
index 55ed36c..e8fa96b 100644 | |
--- a/setup.py.in | |
+++ b/setup.py.in | |
@@ -7,7 +7,7 @@ setup(name='juicer', | |
version='%VERSION%', | |
description='Administer Pulp and Release Carts', | |
maintainer='Tim Bielawa', | |
- maintainer_email='tim@redhat.com', | |
+ maintainer_email='tbielawa@redhat.com', | |
url='https://github.com/juicer/juicer', | |
license='GPLv3+', | |
package_dir={ 'juicer': 'juicer' }, | |
diff --git a/share/juicer/repo_def_example.json b/share/juicer/repo_def_example.json | |
new file mode 100644 | |
index 0000000..d417af4 | |
--- /dev/null | |
+++ b/share/juicer/repo_def_example.json | |
@@ -0,0 +1,14 @@ | |
+[ | |
+ { | |
+ "name": "repodef01", | |
+ "checksum_type": "sha", | |
+ "env": ["re", "qa", "stage", "prod"] | |
+ }, | |
+ { | |
+ "name": "repodef02" | |
+ }, | |
+ { | |
+ "name": "repodef03", | |
+ "env": ["re"] | |
+ } | |
+] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment