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 CHANGED
@@ -24,7 +24,7 @@ GEM
24
24
  activeresource (>= 2.3.8)
25
25
  xml-simple (>= 1.0.0)
26
26
  thor (0.14.6)
27
- xml-simple (1.1.0)
27
+ xml-simple (1.0.15)
28
28
 
29
29
  PLATFORMS
30
30
  ruby
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
- === Ocassionally released packaged gems
12
- * Download
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.2.0
1
+ 0.3.0
data/bin/ssc CHANGED
@@ -3,4 +3,4 @@
3
3
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
4
 
5
5
  require 'ssc'
6
- SSC::Base.start
6
+ SSC::Client.start
@@ -1,194 +1,146 @@
1
1
  module SSC
2
2
  module DirectoryManager
3
+ class LocalStorageFile
3
4
 
4
- def self.included(base)
5
- base.extend ClassMethods
6
- base.send :include, InstanceMethods
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 manage(local_source)
25
- self.class.class_variable_set('@@appliance_directory', Dir.pwd)
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
- end
40
-
41
- module InstanceMethods
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
- # Reads data from the local storage file
63
- # @param [String] section (optional) This is the top-level section
64
- # of the storage file that is to be read. It can be left blank to
65
- # return all sections of the file
66
- # @return [String] Either the whole file of the specified section
67
- def read(section = nil)
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
- private
79
-
80
-
81
- # Wrapper to check existence of source file and other sanity checks
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
- raise "Couldn't find the local source file" unless options.remote?
33
+ nil
90
34
  end
91
35
  end
92
-
93
- def find_file_id(file_name)
94
- file_list= File.join(self.class.class_variable_get('@@local_source'), '.file_list')
95
- parsed_file= YAML::load(File.read(file_list))
96
- if parsed_file["list"]
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
- raise ArgumentError, "file not found"
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 show_file(file)
113
- full_path= full_local_file_path(file)
114
- if File.exist?(full_path)
115
- File.read(full_path)
116
- else
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 find_diff(remote, local)
122
- `diff #{remote} #{local}`
123
- end
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
- def list_local_files
153
- source= self.class.class_variable_get('@@local_source')
154
- parsed_file= YAML::load File.read(File.join(source, '.file_list'))
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
- def parse_file_list
160
- source= self.class.class_variable_get('@@local_source')
161
- file_list= File.read(File.join(source, '.file_list'))
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
- def write_to_file(file, data)
166
- written= []
167
- existing_lines= file.readlines.collect{|i| i.strip}
168
- file.write("\n") if existing_lines.last != '' and existing_lines != []
169
- data.each do |line|
170
- unless existing_lines.include?( line )
171
- file.write(line+"\n")
172
- written << line
173
- end
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 file_list_empty?
178
- safe_get_source_file do |source|
179
- !YAML::load File.read(File.join(source, '.file_list'))
180
- end
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
- # Checks if the local source file has a list
184
- # @return [Boolean] true if there is no list
185
- def no_local_list?
186
- safe_get_source_file do |source|
187
- list= YAML::load(File.read source)
188
- !list || list == nil || list == {} || list == []
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'
@@ -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
- appliance_dir= self.class.create_appliance_directory(appliance_dir, options.username, options.password, appliance.id)
15
- say_array ["Created: ", appliance_dir,
16
- File.join(appliance_dir, 'files'),
17
- File.join(appliance_dir, 'repositories'),
18
- File.join(appliance_dir, 'software') ]
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
- if appliance.destroy.code_type == Net::HTTPOK
41
- say 'Appliance Successfully Destroyed', :red
42
- else
43
- say_array ['There was a problem with destroying the appliance.',
44
- 'Make sure that you\'re in the appliance directory OR',
45
- 'Have provided the --appliance_id option']
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