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.
@@ -0,0 +1,56 @@
1
+ module SSC
2
+ module Handler
3
+ class Build < Base
4
+
5
+ default_task :build
6
+
7
+ desc "build", "build an appliance"
8
+ require_appliance_id
9
+ method_option :image_type, :type => :string
10
+ def build
11
+ require_appliance_dir do |appliance, files|
12
+ if appliance.status.state != "ok"
13
+ raise Thor::Error, "Appliance is not OK. Please fix before building.\n#{appliance.status.issues.join("\n")}\n"
14
+ else
15
+ build = StudioApi::RunningBuild.new(:appliance_id => appliance.id, :image_type => options.image_type)
16
+ build.save
17
+ config_file= File.join(Dir.pwd, '.sscrc')
18
+ if File.exists?(config_file)
19
+ config= YAML::load(File.read(config_file))
20
+ config.merge!('latest_build_id' => build.id)
21
+ File.open(config_file, 'w') do |file|
22
+ file.write(config.to_yaml)
23
+ end
24
+ end
25
+ say "Build Started. Build id: #{build.id}"
26
+ end
27
+ end
28
+ end
29
+
30
+ desc "status", "find the build status of an appliance"
31
+ require_authorization
32
+ require_build_id
33
+ def status
34
+ build = StudioApi::Build.find options.build_id
35
+ additional_info=(build.state == 'finished' ? "" : " - #{build.percent}")
36
+ say "Build Status: #{build.state}" + additional_info
37
+ end
38
+
39
+ desc "list", "list builds (running or completed)"
40
+ require_appliance_id
41
+ method_option :running, :type => :boolean, :default => false
42
+ def list
43
+ builds= if options.running?
44
+ StudioApi::RunningBuild.find(:all, :params => {:appliance_id => options.appliance_id})
45
+ else
46
+ StudioApi::Build.find(:all, :params => {:appliance_id => options.appliance_id})
47
+ end
48
+ say "Build List:\n"
49
+ print_table([["id", "version", "state"]]+
50
+ builds.collect{|i| [i.id, "v#{i.version}", i.state]})
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end
data/lib/handlers/file.rb CHANGED
@@ -4,39 +4,34 @@ module SSC
4
4
  module Handler
5
5
  class OverlayFile < Base
6
6
 
7
+ include DirectoryManager
7
8
 
8
9
  no_tasks do
9
10
  cattr_reader :local_source
10
11
  @@local_source= 'files/'
11
12
  end
12
13
 
13
- # must be run in appliance directory
14
- # takes the following argument:
15
- # file_path => (relative positions("." and "..") allowed and ~ for home directory allowed)
16
- # takes the following options:
17
- # --path="/path/to/file_directory/" => optional (by default it is the path of the file on the local system)
18
- # --name="file_name" => optional (by default it is the name of the file on the local system)
19
- # --permissions="0766" => optional (default: 0755)
20
- # --owner="user" => optional (default: root)
21
- desc 'file create PATH', 'create a new overlay file'
14
+ desc 'file add PATH', 'create a new overlay file'
22
15
  require_appliance_id
23
16
  allow_remote_option
24
- method_option :path, :type => :string, :default => ''
25
- method_option :name, :type => :string, :default => ''
17
+ method_option :path, :type => :string, :default => ''
18
+ method_option :name, :type => :string, :default => ''
26
19
  method_option :permissions, :type => :string, :default => '0755'
27
- method_option :owner, :type => :string, :default => 'root'
28
- def create(path)
20
+ method_option :owner, :type => :string, :default => 'root'
21
+ method_option :group, :type => :string, :default => 'root'
22
+ def add(path)
29
23
  absolute_path= File.expand_path(path)
30
24
  optional_file_params= {:permissions => options.permissions,
31
- :owner => options.owner}
25
+ :group => options.group,
26
+ :owner => options.owner}
32
27
  file_dir, file_name= File.split(absolute_path)
33
28
  file_dir = options.path == '' ? file_dir : options.path
34
29
  file_name = options.name == '' ? file_name : options.name
30
+ file_params= ({:path => file_dir, :filename => file_name})
31
+ file_params.merge!(optional_file_params)
35
32
  id= nil
36
33
  if options.remote?
37
34
  require_appliance do |appliance|
38
- file_params= ({:path => file_dir, :filename => file_name})
39
- file_params.merge!(optional_file_params)
40
35
  File.open(absolute_path) do |file|
41
36
  file= StudioApi::File.upload(file, appliance.id, file_params)
42
37
  id= file.id.to_i
@@ -44,8 +39,32 @@ module SSC
44
39
  say "Overlay file saved. Id: #{id}"
45
40
  end
46
41
  end
47
- local_copy= initiate_file(file_dir, file_name, id)
48
- say "Created #{local_copy}"
42
+ if ApplianceDirectory.new.valid?
43
+ local_copy= FileListFile.new.initiate_file(absolute_path, file_params)
44
+ say "Created #{local_copy}"
45
+ end
46
+ end
47
+
48
+ desc 'file remove FILE_NAME', 'removes existing overlay file'
49
+ require_appliance_id
50
+ allow_remote_option
51
+ def remove(file_name)
52
+ @file_list= FileListFile.new
53
+ file_id= @file_list.is_uploaded?(file_name)
54
+ if options.remote? && file_id
55
+ begin
56
+ StudioApi::File.find(file_id).destroy
57
+ say "File '#{file_name}' removed"
58
+ rescue
59
+ raise Thor::Error, "Couldn't remove file #{file_name} (id: #{file_id}"
60
+ end
61
+ elsif options.remote? && !file_id
62
+ raise Thor::Error, "File '#{file_name}' not found"
63
+ else
64
+ @file_list.push('remove', {file_name => nil})
65
+ @file_list.save
66
+ say "File '#{file_name}' marked for removal"
67
+ end
49
68
  end
50
69
 
51
70
  desc 'file show FILE_NAME', 'show the contents of the file'
@@ -89,7 +108,7 @@ module SSC
89
108
  out= if options.remote? || file_list_empty?
90
109
  response= StudioApi::File.find(:all, :params => {:appliance_id => appliance.id})
91
110
  response.collect do |file|
92
- {file.filename => {"id" => id, "path" => file.path}}
111
+ {file.filename => {"id" => file.id, "path" => file.path}}
93
112
  end
94
113
  else
95
114
  list_local_files
@@ -30,6 +30,12 @@ module SSC
30
30
  :default => config["appliance_id"]
31
31
  end
32
32
 
33
+ def require_build_id
34
+ config= get_config
35
+ method_option :build_id, :type => :numeric, :required => true,
36
+ :default => config["latest_build_id"]
37
+ end
38
+
33
39
  def allow_remote_option
34
40
  method_option :remote, :type => :boolean, :default => false
35
41
  end
@@ -45,6 +51,9 @@ module SSC
45
51
 
46
52
  module InstanceMethods
47
53
 
54
+ include DirectoryManager
55
+
56
+
48
57
  # Establish connection to Suse Studio with username, password
49
58
  def connect(user, pass, connection_options)
50
59
  @connection= StudioApi::Connection.new(user, pass, self.class::API_URL, connection_options)
@@ -62,7 +71,13 @@ module SSC
62
71
  # Included for those methods that still return arrays for printing
63
72
  # Can be removed eventually
64
73
  # Still seems to be a nice way to format the text output of methods
65
- say array.join("\n"), color
74
+ if array.is_a?(Array)
75
+ array= array.collect {|i| yield(i)} if block_given?
76
+ say array.join("\n"), color
77
+ else
78
+ say "\n"
79
+ end
80
+ array
66
81
  end
67
82
 
68
83
  def require_appliance
@@ -72,6 +87,22 @@ module SSC
72
87
  raise "Unable to find the appliance"
73
88
  end
74
89
  end
90
+
91
+ class ApplianceDirectoryError < StandardError; end
92
+
93
+ def require_appliance_directory
94
+ if File.exist?('./.sscrc')
95
+ require_appliance do |appliance|
96
+ files= {
97
+ :package => PackageFile.new,
98
+ :repository => RepositoryFile.new,
99
+ :file_list => FileListFile.new }
100
+ yield(appliance, files)
101
+ end
102
+ else
103
+ raise ApplianceDirectoryError, 'Appliance directory not found'
104
+ end
105
+ end
75
106
  end
76
107
 
77
108
  end
@@ -47,7 +47,7 @@ module SSC
47
47
  require_appliance_id
48
48
  method_option :all_repos, :type => :boolean, :default => true
49
49
  def search(search_string)
50
- require_appliance_id(@options) do |appliance|
50
+ require_appliance do |appliance|
51
51
  params= {:all_repos => options.all_repos} if options.all_repos
52
52
  software= appliance.search_software(search_string, params)
53
53
  say_array software.collect do |software|
@@ -61,20 +61,20 @@ module SSC
61
61
  allow_remote_option
62
62
  method_option :build_id, :type => :numeric
63
63
  def list(type)
64
- say("installed | selected package only", :red) unless ['installed', 'selected'].include?(type)
65
- out= if options.remote? || no_local_list?
64
+ package_file= PackageFile.new
65
+ raise Thor::Error, "installed | selected package only" unless ['installed', 'selected'].include?(type)
66
+ out= if options.remote? || package_file.empty_list?
66
67
  require_appliance do |appliance|
67
68
  params= {:build_id => options.build_id} if options.build_id
68
69
  software= appliance.send("#{type}_software")
69
70
  formatted_list= software.collect do |package|
70
71
  version= package.version ? { "version" => package.version } : nil
71
- {package.name => version}
72
+ package_file.push('list', {package.name => version})
72
73
  end
73
- save(type, formatted_list)
74
- formatted_list
74
+ package_file.save
75
75
  end
76
76
  else
77
- read(type)
77
+ package_file.read
78
78
  end
79
79
  say out.to_yaml
80
80
  end
@@ -99,7 +99,9 @@ module SSC
99
99
  end
100
100
  end
101
101
  else
102
- save("add", [ name ])
102
+ package_file= PackageFile.new
103
+ package_file.push('add', name)
104
+ package_file.save
103
105
  say "#{name} marked for addition"
104
106
  end
105
107
  end
@@ -114,7 +116,9 @@ module SSC
114
116
  say "State: #{response['state']}"
115
117
  end
116
118
  else
117
- save("remove", [ name ])
119
+ package_file= PackageFile.new
120
+ package_file.push('remove', name)
121
+ package_file.save
118
122
  say "#{name} marked for removal"
119
123
  end
120
124
  end
@@ -129,7 +133,9 @@ module SSC
129
133
  response.collect{|key, val| "#{key}: #{val}"}
130
134
  end
131
135
  else
132
- save("ban", [ name ])
136
+ package_file= PackageFile.new
137
+ package_file.push('ban', name)
138
+ package_file.save
133
139
  say "#{name} marked to be banned"
134
140
  end
135
141
  end
@@ -144,7 +150,9 @@ module SSC
144
150
  response.collect{|key, val| "#{key}: #{val}"}
145
151
  end
146
152
  else
147
- save("unban", [ name ])
153
+ package_file= PackageFile.new
154
+ package_file.push('unban', name)
155
+ package_file.save
148
156
  say "#{name} marked to be unbanned"
149
157
  end
150
158
  end
@@ -48,18 +48,19 @@ module SSC
48
48
  require_appliance_id
49
49
  allow_remote_option
50
50
  def list
51
- list= if options.remote? || no_local_list?
51
+ repo_file= RepositoryFile.new
52
+ list= if options.remote? || repo_file.empty_list?
52
53
  require_appliance do |appliance|
53
54
  appliance.repositories.collect do |repo|
54
- { repo.id => { 'name' => repo.name,
55
- 'type' => repo.type,
56
- 'base_system' => repo.base_system}}
55
+ repo_file.push('list', { repo.id => { 'name' => repo.name,
56
+ 'type' => repo.type,
57
+ 'base_system' => repo.base_system}})
57
58
  end
59
+ repo_file.save
58
60
  end
59
61
  else
60
- read('list')
62
+ repo_file['list']
61
63
  end
62
- save('list', list) unless options.remote?
63
64
  say list.to_yaml
64
65
  end
65
66
 
@@ -73,7 +74,9 @@ module SSC
73
74
  say "Added"+( response.collect{|repos| repos.name} ).join(", ")
74
75
  end
75
76
  else
76
- save('add', repo_ids)
77
+ repo_file= RepositoryFile.new
78
+ repo_ids.each {|id| repo_file.push('add', id)}
79
+ repo_file.save
77
80
  say "Marked the following for addition #{repo_ids.join(", ")}"
78
81
  end
79
82
  end
@@ -88,19 +91,24 @@ module SSC
88
91
  say "Removed #{repo_ids.join(", ")}"
89
92
  end
90
93
  else
91
- save('remove', repo_ids)
94
+ repo_file= RepositoryFile.new
95
+ repo_ids.each {|id| repo_file.push('remove', id)}
96
+ repo_file.save
92
97
  say "Marked the following for removal #{repo_ids.join(", ")}"
93
98
  end
94
99
  end
95
100
 
96
101
  desc 'repository import URL NAME', 'import a 3rd party repository into appliance'
102
+ require_authorization
97
103
  allow_remote_option
98
104
  def import(url, name)
99
105
  if options.remote?
100
106
  repository= StudioApi::Repository.import(url, name)
101
107
  say "Added #{repository.name} at #{url}"
102
108
  else
103
- save("import", [{"name" => name, "url" => url}])
109
+ repo_file= RepositoryFile.new
110
+ repo_file.push('import', {"name" => name, "url" => url})
111
+ repo_file.save
104
112
  say "Marked #{name} for import"
105
113
  end
106
114
  end
data/lib/ssc.rb CHANGED
@@ -8,11 +8,134 @@ require 'handlers/all'
8
8
  require 'yaml'
9
9
 
10
10
  module SSC
11
- class Base < Thor
11
+ class Client < Handler::Base
12
+
13
+ include DirectoryManager
14
+
12
15
  register Handler::Appliance, :appliance, "appliance", "manage appliances"
13
16
  register Handler::Repository, :repository, "repository","manage repositories"
14
17
  register Handler::Package, :package, "package", "manage packages"
15
18
  register Handler::Template, :template, "template", "manage templates"
16
19
  register Handler::OverlayFile, :file, "file", "manage files"
20
+ register Handler::Build, :build, "build", "manage builds"
21
+
22
+ desc "status", "show status of the appliance"
23
+ require_appliance_id
24
+ def status
25
+ require_appliance_directory do |appliance, files|
26
+ # Show appliance status
27
+ say "Appliance: id: #{appliance.id} | name: #{appliance.name}"
28
+ say "Status: #{appliance.status.state}"
29
+ say appliance.status.issues
30
+
31
+ # Show additions
32
+ say "\nAdditions : \n"
33
+ say "\nPackages : \n"
34
+ say_array files[:package]["add"]
35
+ say "\nRepositories : \n"
36
+ say_array files[:repository]["add"]
37
+ say "\nOverlay Files : \n"
38
+ say_array(files[:file_list]["add"]) {|i| i.keys[0]}
39
+
40
+ # Show removals
41
+ say "\nRemovals : \n"
42
+ say "\nPackages : \n"
43
+ say_array files[:package]["remove"]
44
+ say "\nRepositories : \n"
45
+ say_array files[:repository]["remove"]
46
+ say "\nOverlay Files :\n "
47
+ say_array(files[:file_list]["remove"]) {|i| i.keys[0]}
48
+
49
+ # Show banned
50
+ say "\nBanned Packages : \n"
51
+ say_array files[:package]["ban"]
52
+
53
+ # Show unbanned
54
+ say "\nUnBanned Packages : \n"
55
+ say_array files[:package]["unban"]
56
+ end
57
+ end
58
+
59
+ desc "checkout", "checkout the latest changes to an appliance"
60
+ require_appliance_id
61
+ def checkout
62
+ params= {:appliance_id => options.appliance_id,
63
+ :username => options.username,
64
+ :password => options.password}
65
+ require_appliance_directory do |appliance, files|
66
+ options= params.merge(:remote => true)
67
+ invoke "s_s_c:handler:package:list", ["installed"], options
68
+ invoke "s_s_c:handler:repository:list", [], options
69
+ invoke "s_s_c:handler:overlay_file:list", [], options
70
+ end
71
+ rescue ApplianceDirectoryError
72
+ require_appliance do |appliance|
73
+ ApplianceDirectory.new(appliance.name, params).create
74
+ Dir.chdir(appliance.name)
75
+ options= params.merge(:remote => true)
76
+ invoke "s_s_c:handler:package:list", ["installed"], options
77
+ invoke "s_s_c:handler:repository:list", [], options
78
+ invoke "s_s_c:handler:overlay_file:list", [], options
79
+ end
80
+ end
81
+
82
+ desc "commit", "commit changes to studio"
83
+ require_appliance_id
84
+ def commit
85
+ params= {:remote => true,
86
+ :appliance_id => options.appliance_id,
87
+ :username => options.username,
88
+ :password => options.password}
89
+ # Add, Remove, Ban and Unban Packages
90
+ package_file= PackageFile.new
91
+ ["add", "remove", "ban", "unban"].each do |action|
92
+ while package= package_file.pop(action)
93
+ invoke "s_s_c:handler:package:#{action}", [package], params
94
+ end
95
+ end
96
+ package_file.save
97
+
98
+ # Add or Remove Repositories
99
+ repository_file= RepositoryFile.new
100
+ ["add", "remove"].each do |action|
101
+ while repository= repository_file.pop(action)
102
+ invoke "s_s_c:handler:repository:#{action}", [repository], params
103
+ end
104
+ end
105
+ repository_file.save
106
+
107
+ # Add Overlay Files
108
+ file_list = FileListFile.new
109
+ while file= file_list.pop("add")
110
+ params= params.merge(file[:params])
111
+ invoke "s_s_c:handler:overlay_file:add", [file[:full_path]], params
112
+ end
113
+ # Remove Overlay Files
114
+ while file= file_list.pop("remove")
115
+ invoke "s_s_c:handler:overlay_file:remove", [file[:name]], params
116
+ end
117
+ file_list.save
118
+ end
119
+
120
+ class << self
121
+ def help(*args)
122
+ message= <<HELP_MESSAGE
123
+ Tasks:
124
+ ssc checkout # checkout the latest changes to an appliance
125
+ ssc commit # commit changes to studio
126
+ ssc status # show status of the appliance
127
+
128
+ ssc appliance # manage appliances
129
+ ssc build # manage builds
130
+ ssc file # manage files
131
+ ssc package # manage packages
132
+ ssc repository # manage repositories
133
+ ssc template # manage templates
134
+
135
+ ssc help [TASK] # Describe available tasks or one specific task
136
+ HELP_MESSAGE
137
+ puts message
138
+ end
139
+ end
17
140
  end
18
141
  end