OSDN Git Service

fdc18d11a534d6f92492fbe4645eb478995aea5e
[osdn-codes/osdn-cli.git] / lib / osdn / cli.rb
1 require "osdn/cli/version"
2 require "osdn/cli/runner"
3 require "osdn/cli/overrides"
4 require 'getoptlong'
5 require 'logger'
6 require 'pathname'
7 require 'yaml'
8 require 'fileutils'
9 require 'osdn-client'
10 require 'hashie'
11 require 'json'
12 require 'digest'
13
14 module OSDN
15   module CLI
16     @@client_id = "osdn-cli"
17     @@client_secret = "not-secret"
18     def client_id
19       @@client_id
20     end
21     def client_secret
22       @@client_secret
23     end
24
25     @@_show_progress = false
26     def _show_progress
27       @@_show_progress
28     end
29     def _show_progress=(v)
30       @@_show_progress = v
31     end
32
33     module_function :client_id, :client_secret,
34                     :_show_progress, :_show_progress=
35     
36     module Command
37       autoload :Login,     'osdn/cli/command/login'
38       autoload :Package,   'osdn/cli/command/package'
39       autoload :Release,   'osdn/cli/command/release'
40       autoload :Relfile,   'osdn/cli/command/relfile'
41       autoload :FrsMkdirs, 'osdn/cli/command/frs_mkdirs'
42       autoload :FrsUpload, 'osdn/cli/command/frs_upload'
43       
44       class Base
45         def initialize(logger)
46           @logger = logger
47           @credential = Hashie::Mash.new
48           @format = 'pretty'
49         end
50         attr_reader :logger
51         attr_accessor :credential, :format
52
53         def credential_path
54           Pathname.new(ENV['HOME']) + '.config/osdn/credential.yml'
55         end
56
57         def load_credential
58           begin
59             stat = credential_path.stat()
60             unless credential_path.owned?
61               logger.error "Invalid ownership of credential file #{credential_path}, skip loading."
62               return
63             end
64             unless (stat.mode & 0777).to_s(8) == "600"
65               logger.error "Invalid permission #{(stat.mode & 0777).to_s(8)} of credential file #{credential_path}, skip loading."
66               return
67             end
68           rescue Errno::ENOENT
69             return
70           end
71           logger.debug "Loading credentials from #{credential_path}"
72           @credential = Hashie::Mash.new(YAML.load_file(credential_path))
73           set_client_token
74         end
75
76         def write_credential
77           FileUtils.mkdir_p credential_path.dirname, verbose: false
78           cio = credential_path.open('w', 0600)
79           YAML.dump(credential.to_hash, cio)
80           cio.close
81         end
82
83         def update_token
84           logger.debug "Checking token expires..."
85           load_credential
86           unless credential.access_token
87             logger.fatal "You have no access token. Please login with '#{$0} login'."
88             return
89           end
90           if credential.expires_at > Time.now + 30
91             logger.debug "You have valid access token, skip to refresh."
92             return
93           end
94
95           logger.debug "Access token has been expired. Trying to refresh token..."
96           api = OSDNClient::DefaultApi.new
97           begin
98             set_credential api.token(CLI.client_id, CLI.client_secret, grant_type: 'refresh_token', refresh_token: credential.refresh_token)
99           rescue OSDNClient::ApiError => e
100             begin
101               err = JSON.parse(e.response_body)
102               logger.fatal err["error_description"]
103             rescue
104               logger.fatal "Failed to refresh access token."
105             end
106             logger.fatal "Please login again."
107             return
108           end
109           logger.debug "Access token is refreshed successfully."
110         end
111
112         def set_credential(token, update_expires = true)
113           token = Hashie::Mash.new(token.to_hash)
114           if update_expires
115             token.expires_at = Time.now + token.expires_in.to_i
116           end
117           token.scope = [*token.scope].join(' ').split(' ')
118           
119           credential.update token
120           write_credential
121           set_client_token
122         end
123
124         def set_client_token
125           if credential.access_token && !credential.access_token.empty?
126             OSDNClient.configure do |config|
127               config.access_token = credential.access_token
128             end
129           end
130         end
131
132         def load_variables(path = '.', recursive_merge = true)
133           vars = {}
134           path = Pathname.new(Dir.getwd) + path
135           cur_dir = Pathname.new('/')
136           if recursive_merge
137             path.each_filename do |d|
138               cur_dir = cur_dir + d
139               vf = cur_dir + '.osdn.vars'
140               vf.exist? or next
141               begin
142                 logger.debug "Load and merge variables from #{vf}"
143                 vars.update YAML.load_file(vf.to_s)
144               rescue => e
145                 logger.warn "Failed to load variables from #{vf}; #{e.message}"
146               end
147             end
148           else
149             begin
150               path = path+'.osdn.vars'
151               if path.exist?
152                 logger.debug "Load and merge variables from #{path}"
153                 vars.update YAML.load_file(path)
154               end
155             rescue => e
156               logger.warn "Failed to load variables from #{path}; #{e.message}"
157             end
158           end
159           logger.debug "Variables: #{vars.inspect}"
160           Hashie::Mash.new(vars)
161         end
162
163         def write_variables(vars, dir = nil)
164           path = Pathname.new(dir || '.') + '.osdn.vars'
165           logger.info "Save variables to #{path}"
166           vio = path.open('w')
167           YAML.dump(vars.to_hash, vio)
168           vio.close
169         end
170
171         def update_variables(dir, vars)
172           write_variables(load_variables(dir, false).deep_merge(vars), dir)
173         end
174
175         private
176         def hexdigest(klass, file)
177           fio = file.open
178           dig = klass.new
179           while buf = fio.read(1024*1024) and buf.length > 0
180             dig << buf
181           end
182           fio.close
183           dig.hexdigest
184         end
185         
186       end
187
188       class Ping < Base
189         def run
190           update_token
191           api = OSDNClient::DefaultApi.new
192           pp api.ping
193         end
194
195         def help
196           puts "#{$0} ping"
197         end
198
199         def self.description
200           "Test API request."
201         end
202       end
203         
204       class Vars < Base
205         def run
206           subcommand = ARGV.shift ||'show'
207           self.send subcommand
208         end
209
210         def show
211           name = ARGV.shift
212           if name
213             puts load_variables[name]
214           else
215             pp load_variables
216           end
217         end
218
219         def set
220           name, value = ARGV.shift, ARGV.shift
221           if !name || name.empty?
222             logger.fatal "Missing variable name"
223             help
224             exit
225           end
226           if !value || value.empty?
227             logger.fatal "Missing variable value"
228             help
229             exit
230           end
231           vars = load_variables('.', false)
232           vars[name] = value
233           write_variables vars
234         end
235
236         def help
237           puts "#{$0} vars show [name]            -- Show current variable"
238           puts "#{$0} vars set <name> <value>     -- Save variable to .osdn.vars"
239         end
240
241         def self.description
242           "Get/set request environment variable."
243         end
244       end
245     end
246   end
247 end