ssc 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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