ssc 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +1 -1
- data/README.rdoc +83 -3
- data/VERSION +1 -1
- data/bin/ssc +1 -1
- data/lib/directory_manager.rb +107 -155
- data/lib/handlers/all.rb +8 -1
- data/lib/handlers/appliance.rb +16 -12
- data/lib/handlers/build.rb +56 -0
- data/lib/handlers/file.rb +38 -19
- data/lib/handlers/helper.rb +32 -1
- data/lib/handlers/package.rb +19 -11
- data/lib/handlers/repository.rb +17 -9
- data/lib/ssc.rb +124 -1
- data/ssc.gemspec +82 -0
- data/test/helper.rb +12 -1
- data/test/integration/test_appliance.rb +82 -0
- data/test/integration/test_file.rb +86 -0
- data/test/integration/test_package.rb +75 -0
- data/test/integration/test_repository.rb +78 -0
- data/test/unit/test_directory_manager.rb +100 -0
- metadata +12 -22
- data/test/handlers/test_appliance.rb +0 -20
- data/test/handlers/test_helper.rb +0 -45
- data/test/handlers/test_repository.rb +0 -20
- data/test/handlers/test_template.rb +0 -27
- data/test/test_argument_parser.rb +0 -55
- data/test/test_directory_manager.rb +0 -40
- data/test/test_ssc.rb +0 -39
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -4,15 +4,95 @@ This is the new version of the the Suse Studio command line client. Built as a p
|
|
4
4
|
|
5
5
|
== Installing ssc
|
6
6
|
|
7
|
+
=== The easy way
|
8
|
+
gem install ssc
|
9
|
+
|
7
10
|
=== Straight from the code
|
8
11
|
* Checkout the code
|
9
12
|
* In the checked out directory do `rake install`
|
10
13
|
|
11
|
-
|
12
|
-
|
14
|
+
== Using ssc
|
15
|
+
|
16
|
+
=== Command Listing
|
17
|
+
|
18
|
+
ssc checkout --appliance-id=N --password=PASSWORD --username=USERNAME # checkout the latest changes to an appliance
|
19
|
+
ssc commit --appliance-id=N --password=PASSWORD --username=USERNAME # commit changes to studio
|
20
|
+
ssc status --appliance-id=N --password=PASSWORD --username=USERNAME # show status of the appliance
|
21
|
+
|
22
|
+
ssc appliance # manage appliances
|
23
|
+
|
24
|
+
ssc appliance create APPLIANCE_NAME --password=PASSWORD --source-id=N --username=USERNAME # Create an appliance
|
25
|
+
ssc appliance destroy --appliance-id=N --password=PASSWORD --username=USERNAME # destroy the current appliance
|
26
|
+
ssc appliance info --appliance-id=N --password=PASSWORD --username=USERNAME # show details of a specific appliance
|
27
|
+
ssc appliance list --password=PASSWORD --username=USERNAME # list all appliances
|
28
|
+
ssc appliance status --appliance-id=N --password=PASSWORD --username=USERNAME # gives status of the appliance
|
29
|
+
|
30
|
+
ssc package # manage packages
|
31
|
+
|
32
|
+
ssc package add NAME --appliance-id=N --password=PASSWORD --username=USERNAME
|
33
|
+
# add a package to the appliance
|
34
|
+
ssc package ban NAME --appliance-id=N --password=PASSWORD --username=USERNAME
|
35
|
+
# ban a package from the appliance
|
36
|
+
ssc package list [selected|installed] --appliance-id=N --password=PASSWORD --username=USERNAME
|
37
|
+
# list all selected or installed packages
|
38
|
+
ssc package remove NAME --appliance-id=N --password=PASSWORD --username=USERNAME
|
39
|
+
# remove a package from the appliance
|
40
|
+
ssc package search SEARCH_STRING --appliance-id=N --password=PASSWORD --username=USERNAME
|
41
|
+
# search available packages and patterns
|
42
|
+
ssc package unban NAME --appliance-id=N --password=PASSWORD --username=USERNAME
|
43
|
+
# unban a package for the appliance
|
44
|
+
|
45
|
+
ssc repository # manage repositories
|
46
|
+
ssc repository add REPO_IDS --appliance-id=N --password=PASSWORD --username=USERNAME
|
47
|
+
# add existing repositories to the appliance
|
48
|
+
ssc repository import URL NAME --password=PASSWORD --username=USERNAME
|
49
|
+
# import a 3rd party repository into appliance
|
50
|
+
ssc repository list --appliance-id=N --password=PASSWORD --username=USERNAME
|
51
|
+
# list all repositories in a given appliance
|
52
|
+
ssc repository remove REPO_IDS --appliance-id=N --password=PASSWORD --username=USERNAME
|
53
|
+
# remove existing repositories from appliance
|
54
|
+
ssc repository search SEARCH_STRING --password=PASSWORD --username=USERNAME
|
55
|
+
# search all available repositories
|
56
|
+
|
57
|
+
|
58
|
+
ssc file # manage files
|
59
|
+
ssc file add PATH --appliance-id=N --password=PASSWORD --username=USERNAME # create a new overlay file
|
60
|
+
ssc file diff FILE_NAME --appliance-id=N --password=PASSWORD --username=USERNAME
|
61
|
+
# show the diff of the remote file and the local one
|
62
|
+
ssc file list --appliance-id=N --password=PASSWORD --username=USERNAME # show all overlay files
|
63
|
+
ssc file remove FILE_NAME --appliance-id=N --password=PASSWORD --username=USERNAME # removes existing overlay file
|
64
|
+
ssc file show FILE_NAME --appliance-id=N --password=PASSWORD --username=USERNAME # show the contents of the file
|
65
|
+
|
66
|
+
|
67
|
+
ssc build # manage builds
|
68
|
+
ssc build --appliance-id=N --password=PASSWORD --username=USERNAME # build an appliance
|
69
|
+
ssc list --appliance-id=N --password=PASSWORD --username=USERNAME # list builds (running or completed)
|
70
|
+
ssc status --build-id=N --password=PASSWORD --username=USERNAME # find the build status of an appliance
|
71
|
+
|
72
|
+
ssc template # manage templates
|
73
|
+
ssc template list SET_NAME --password=PASSWORD --username=USERNAME # show details of a particular template set
|
74
|
+
ssc template list_sets --password=PASSWORD --username=USERNAME # list all available template sets
|
75
|
+
|
76
|
+
ssc help [TASK] # Describe available tasks or one specific task
|
77
|
+
|
78
|
+
=== Examples
|
79
|
+
|
80
|
+
* Creating and modifying a new appliance:
|
81
|
+
$ ssc appliance create web_server --source-id=SOURCE_APPLIANCE_ID --username=USERNAME --password=PASSWORD
|
82
|
+
$ cd web_server
|
83
|
+
$ ssc package list
|
84
|
+
$ ssc package add apache
|
85
|
+
$ ssc file add /etc/apache2/apache2.conf
|
86
|
+
$ ssc commit
|
87
|
+
|
88
|
+
* Checking out an existing appliance and starting a build
|
89
|
+
$ ssc checkout --appliance-id 59836 --username=USERNAME --password=PASSWORD
|
90
|
+
$ cd APPLIANCE_NAME
|
91
|
+
$ ssc build
|
92
|
+
$ ssc build status
|
13
93
|
|
14
94
|
== Contributing to ssc
|
15
|
-
|
95
|
+
|
16
96
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
17
97
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
18
98
|
* Fork the project
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/bin/ssc
CHANGED
data/lib/directory_manager.rb
CHANGED
@@ -1,194 +1,146 @@
|
|
1
1
|
module SSC
|
2
2
|
module DirectoryManager
|
3
|
+
class LocalStorageFile
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
def create_appliance_directory(appliance_dir, username, password, appliance_id)
|
11
|
-
FileUtils.mkdir_p(appliance_dir)
|
12
|
-
FileUtils.mkdir_p(File.join(appliance_dir, 'files'))
|
13
|
-
FileUtils.touch(File.join(appliance_dir, 'repositories'))
|
14
|
-
FileUtils.touch(File.join(appliance_dir, 'software'))
|
15
|
-
FileUtils.touch(File.join(appliance_dir, 'files/.file_list'))
|
16
|
-
File.open(File.join(appliance_dir, '.sscrc'), 'w') do |file|
|
17
|
-
file.write("username: #{username}\n"+
|
18
|
-
"password: #{password}\n"+
|
19
|
-
"appliance_id: #{appliance_id}")
|
20
|
-
end
|
21
|
-
File.join(Dir.pwd, appliance_dir)
|
5
|
+
def initialize(file_name, path= nil)
|
6
|
+
path = path ? path : Dir.pwd
|
7
|
+
@location= File.join(path, file_name)
|
22
8
|
end
|
23
9
|
|
24
|
-
def
|
25
|
-
|
26
|
-
if appliance_directory_valid?(Dir.pwd)
|
27
|
-
file= File.join(Dir.pwd, local_source)
|
28
|
-
self.class.class_variable_set('@@local_source', file) if File.exist?(file)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def appliance_directory_valid?(dir)
|
35
|
-
config_file= File.join(dir, '.sscrc')
|
36
|
-
File.exist?(config_file) && File.read(config_file).match(/appliance_id:\ *\d+/)
|
10
|
+
def valid?
|
11
|
+
File.exist?(@location)
|
37
12
|
end
|
38
13
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
include Thor::Actions
|
43
|
-
|
44
|
-
# Save data to local storage file
|
45
|
-
# @param [String] section The section of the document that is to be saved
|
46
|
-
# @param [Array] list The data in Array format which will be merged with existing data
|
47
|
-
def save(section, list)
|
48
|
-
safe_get_source_file do |source|
|
49
|
-
parsed_file= YAML::load(File.read(source))
|
50
|
-
# YAML::load returns false if file is empty
|
51
|
-
parsed_file= {} unless parsed_file
|
52
|
-
final_list= list
|
53
|
-
if parsed_file[section]
|
54
|
-
current_list= parsed_file[section]
|
55
|
-
final_list= current_list | final_list
|
56
|
-
end
|
57
|
-
parsed_file[section]= final_list
|
58
|
-
File.open(source, 'w') {|f| f.write parsed_file.to_yaml}
|
59
|
-
end
|
14
|
+
def read
|
15
|
+
# default error is informative enough if the file is not found
|
16
|
+
@parsed_file = @parsed_file || YAML::load(File.read(@location)) || {}
|
60
17
|
end
|
61
18
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
safe_get_source_file do |source|
|
69
|
-
if section
|
70
|
-
parsed_file= YAML::load(File.read(source))
|
71
|
-
parsed_file[section]
|
72
|
-
else
|
73
|
-
File.read(source)
|
74
|
-
end
|
19
|
+
def [](section)
|
20
|
+
read
|
21
|
+
if @parsed_file[section]
|
22
|
+
@parsed_file[section]
|
23
|
+
else
|
24
|
+
[]
|
75
25
|
end
|
76
26
|
end
|
77
27
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
# It takes a block with one argument - the path of the source file
|
83
|
-
def safe_get_source_file
|
84
|
-
source= self.class.class_variable_get('@@local_source')
|
85
|
-
source= File.join(Dir.pwd, source)
|
86
|
-
if File.exist?(source)
|
87
|
-
yield source
|
28
|
+
def pop(section)
|
29
|
+
read
|
30
|
+
if @parsed_file[section].is_a?(Array)
|
31
|
+
@parsed_file[section].pop
|
88
32
|
else
|
89
|
-
|
33
|
+
nil
|
90
34
|
end
|
91
35
|
end
|
92
|
-
|
93
|
-
def
|
94
|
-
|
95
|
-
parsed_file
|
96
|
-
|
97
|
-
files= parsed_file["list"].select{|i| i.keys[0] == file_name}
|
98
|
-
if files.length < 1
|
99
|
-
raise ArgumentError, "file not found"
|
100
|
-
else
|
101
|
-
files[0][file_name]["id"]
|
102
|
-
end
|
36
|
+
|
37
|
+
def push(section, item)
|
38
|
+
read
|
39
|
+
if @parsed_file[section].is_a?(Array)
|
40
|
+
@parsed_file[section] |= [ item ]
|
103
41
|
else
|
104
|
-
|
42
|
+
@parsed_file[section] = [ item ]
|
105
43
|
end
|
44
|
+
item
|
106
45
|
end
|
107
46
|
|
108
|
-
def full_local_file_path(file)
|
109
|
-
full_path= File.join(self.class.class_variable_get('@@local_source'), file)
|
110
|
-
end
|
111
47
|
|
112
|
-
def
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
raise ArgumentError, "file not found"
|
118
|
-
end
|
48
|
+
def save
|
49
|
+
contents= @parsed_file.to_yaml
|
50
|
+
@parsed_file= nil
|
51
|
+
File.open(@location, 'w') {|f| f.write contents}
|
52
|
+
contents
|
119
53
|
end
|
120
54
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
def initiate_file(file_dir, file_name, id)
|
126
|
-
source_file= File.join(file_dir, file_name)
|
127
|
-
destination_file= full_local_file_path(file_name)
|
128
|
-
file_list= File.join(self.class.class_variable_get('@@local_source'), '.file_list')
|
129
|
-
if File.exist?(source_file)
|
130
|
-
FileUtils.cp(source_file, destination_file)
|
131
|
-
parsed_file= YAML::load(File.read(file_list)) || {}
|
132
|
-
File.open(file_list, 'w') do |f|
|
133
|
-
if id # if the file has been uploaded
|
134
|
-
parsed_file['list']= [] unless parsed_file['list']
|
135
|
-
parsed_file['list'] |= [{file_name => {
|
136
|
-
"id" => id,
|
137
|
-
"path" => file_dir}}]
|
138
|
-
else
|
139
|
-
parsed_file['add']= [] unless parsed_file['add']
|
140
|
-
|
141
|
-
parsed_file['add'] |= [{file_name => {
|
142
|
-
"path" => file_dir}}]
|
143
|
-
end
|
144
|
-
f.write(parsed_file.to_yaml)
|
145
|
-
end
|
146
|
-
destination_file
|
147
|
-
else
|
148
|
-
raise ArgumentError, "File does not exist"
|
149
|
-
end
|
55
|
+
def empty_list?
|
56
|
+
read
|
57
|
+
(!@parsed_file['list']) || (@parsed_file['list'] == []) || (@parsed_file['list'] == {})
|
150
58
|
end
|
59
|
+
end
|
151
60
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
parsed_file = {} unless parsed_file
|
156
|
-
parsed_file["list"]
|
61
|
+
class PackageFile < LocalStorageFile
|
62
|
+
def initialize(path= nil)
|
63
|
+
super("software", path)
|
157
64
|
end
|
65
|
+
end
|
158
66
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
YAML::load(file_list)
|
67
|
+
class RepositoryFile < LocalStorageFile
|
68
|
+
def initialize(path= nil)
|
69
|
+
super("repositories", path)
|
163
70
|
end
|
71
|
+
end
|
164
72
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
73
|
+
class FileListFile < LocalStorageFile
|
74
|
+
def initialize(path= nil)
|
75
|
+
super("files/.file_list", path)
|
76
|
+
end
|
77
|
+
|
78
|
+
def pop(section)
|
79
|
+
file_hash= super(section)
|
80
|
+
file_name= file_hash.keys[0]
|
81
|
+
file_hash= file_hash[file_name]
|
82
|
+
{ :name => file_name,
|
83
|
+
:full_path => File.join(file_hash["path"]),
|
84
|
+
:params => file_hash.slice("owner", "group", "permissions")}
|
85
|
+
end
|
86
|
+
|
87
|
+
def initiate_file(path, options)
|
88
|
+
raise "Unknown file #{path}" unless File.exists?(path)
|
89
|
+
file_path, file_name= File.split(path)
|
90
|
+
file_path ||= options[:path]
|
91
|
+
destination_path= File.join(File.split(@location)[0], file_name)
|
92
|
+
FileUtils.cp(path, destination_path)
|
93
|
+
if options[:id]
|
94
|
+
push("list", {file_name => {
|
95
|
+
"id" => options[:id],
|
96
|
+
"path" => file_path }})
|
97
|
+
else
|
98
|
+
file_params= options.slice(:permissions, :group, :owner).merge(:path => file_path)
|
99
|
+
push("add", {file_name => file_params})
|
174
100
|
end
|
101
|
+
destination_file
|
175
102
|
end
|
176
103
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
180
|
-
|
104
|
+
def is_uploaded?(file_name)
|
105
|
+
read
|
106
|
+
list= @parsed_file["list"].select{|i| i.keys[0] == file_name}
|
107
|
+
list.length > 0 ? list[0]["id"].to_i : nil
|
181
108
|
end
|
182
109
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
110
|
+
end
|
111
|
+
|
112
|
+
class ApplianceDirectory
|
113
|
+
attr_reader :path
|
114
|
+
attr_reader :files
|
115
|
+
|
116
|
+
def initialize(name= '', options = {})
|
117
|
+
@name= name
|
118
|
+
@path= File.join(Dir.pwd, name)
|
119
|
+
@files = if Dir.exist?(@path)
|
120
|
+
{:package => PackageFile.new(@path),
|
121
|
+
:repository => RepositoryFile.new(@path),
|
122
|
+
:file_list => FileListFile.new(@path)}
|
123
|
+
else
|
124
|
+
{}
|
125
|
+
end
|
126
|
+
@options= options
|
127
|
+
end
|
128
|
+
|
129
|
+
def create
|
130
|
+
FileUtils.mkdir_p(@name)
|
131
|
+
FileUtils.mkdir_p(File.join(@name, 'files'))
|
132
|
+
@files[:repository] = FileUtils.touch(File.join(@name, 'repositories'))[0]
|
133
|
+
@files[:package] = FileUtils.touch(File.join(@name, 'software'))[0]
|
134
|
+
@files[:file_list] = FileUtils.touch(File.join(@name, 'files/.file_list'))[0]
|
135
|
+
File.open(File.join(@name, '.sscrc'), 'w') do |file|
|
136
|
+
file.write(@options.stringify_keys.to_yaml)
|
189
137
|
end
|
138
|
+
File.join(Dir.pwd, @name)
|
190
139
|
end
|
191
140
|
|
141
|
+
def valid?
|
142
|
+
Dir.exists?(@path) && File.exists?(File.join(@path, '.sscrc'))
|
143
|
+
end
|
192
144
|
end
|
193
145
|
end
|
194
146
|
end
|
data/lib/handlers/all.rb
CHANGED
@@ -11,7 +11,6 @@ module SSC
|
|
11
11
|
class Base < Thor
|
12
12
|
|
13
13
|
include Helper
|
14
|
-
include DirectoryManager
|
15
14
|
|
16
15
|
API_URL= 'https://susestudio.com/api/v1/user'
|
17
16
|
|
@@ -22,10 +21,18 @@ module SSC
|
|
22
21
|
connect(options.username, options.password, optional_connection_options)
|
23
22
|
@not_local= true if options.remote?
|
24
23
|
end
|
24
|
+
|
25
|
+
no_tasks do
|
26
|
+
def say(*args)
|
27
|
+
super(*args)
|
28
|
+
args[0]
|
29
|
+
end
|
30
|
+
end
|
25
31
|
end
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
35
|
+
require 'build'
|
29
36
|
require 'appliance'
|
30
37
|
require 'repository'
|
31
38
|
require 'package'
|
data/lib/handlers/appliance.rb
CHANGED
@@ -11,18 +11,20 @@ module SSC
|
|
11
11
|
params= {:name => appliance_name}
|
12
12
|
params.merge!(:arch => options.arch) if options.arch
|
13
13
|
appliance= StudioApi::Appliance.clone(options.source_id, params)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
appliance_params= {
|
15
|
+
:username => options.username,
|
16
|
+
:password => options.password,
|
17
|
+
:appliance_id => appliance.id }
|
18
|
+
appliance_dir= ApplianceDirectory.new(appliance_name, appliance_params)
|
19
|
+
appliance_dir.create
|
20
|
+
say_array(["Created: ", appliance_dir.path] + appliance_dir.files.values)
|
19
21
|
end
|
20
22
|
|
21
23
|
desc "appliance list", "list all appliances"
|
22
24
|
require_authorization
|
23
25
|
def list
|
24
26
|
appliances= StudioApi::Appliance.find(:all)
|
25
|
-
print_table appliances.collect{|i| [i.id, i.name]}
|
27
|
+
print_table([["id", "name"]]+appliances.collect{|i| [i.id, i.name]})
|
26
28
|
end
|
27
29
|
|
28
30
|
desc "appliance info", "show details of a specific appliance"
|
@@ -37,12 +39,14 @@ module SSC
|
|
37
39
|
desc "appliance destroy", "destroy the current appliance (within appliance directory only)"
|
38
40
|
require_appliance_id
|
39
41
|
def destroy
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
require_appliance do |appliance|
|
43
|
+
if appliance.destroy.code_type == Net::HTTPOK
|
44
|
+
say 'Appliance Successfully Destroyed', :red
|
45
|
+
else
|
46
|
+
say_array ['There was a problem with destroying the appliance.',
|
47
|
+
'Make sure that you\'re in the appliance directory OR',
|
48
|
+
'Have provided the --appliance_id option']
|
49
|
+
end
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|