github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/common/action.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Copyright 2016 Cloudbase Solutions 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package common 6 7 import ( 8 "github.com/juju/errors" 9 "github.com/juju/names" 10 11 "github.com/juju/juju/apiserver/params" 12 "github.com/juju/juju/state" 13 "github.com/juju/juju/state/watcher" 14 ) 15 16 // ParamsActionExecutionResultsToStateActionResults does exactly what 17 // the name implies. 18 func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) { 19 var status state.ActionStatus 20 switch arg.Status { 21 case params.ActionCancelled: 22 status = state.ActionCancelled 23 case params.ActionCompleted: 24 status = state.ActionCompleted 25 case params.ActionFailed: 26 status = state.ActionFailed 27 case params.ActionPending: 28 status = state.ActionPending 29 default: 30 return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status) 31 } 32 return state.ActionResults{ 33 Status: status, 34 Results: arg.Results, 35 Message: arg.Message, 36 }, nil 37 } 38 39 // TagToActionReceiver takes a tag string and tries to convert it to an 40 // ActionReceiver. It needs a findEntity function passed in that can search for the tags in state. 41 func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) { 42 return func(tag string) (state.ActionReceiver, error) { 43 receiverTag, err := names.ParseTag(tag) 44 if err != nil { 45 return nil, ErrBadId 46 } 47 entity, err := findEntity(receiverTag) 48 if err != nil { 49 return nil, ErrBadId 50 } 51 receiver, ok := entity.(state.ActionReceiver) 52 if !ok { 53 return nil, ErrBadId 54 } 55 return receiver, nil 56 } 57 } 58 59 // AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state 60 // and returns a function that can fetch an action from state by id and check the authorization. 61 func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) { 62 return func(tag string) (state.Action, error) { 63 actionTag, err := names.ParseActionTag(tag) 64 if err != nil { 65 return nil, errors.Trace(err) 66 } 67 action, err := getActionByTag(actionTag) 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 receiverTag, err := names.ActionReceiverTag(action.Receiver()) 72 if err != nil { 73 return nil, errors.Trace(err) 74 } 75 if !canAccess(receiverTag) { 76 return nil, ErrPerm 77 } 78 return action, nil 79 } 80 } 81 82 // BeginActions calls begin on every action passed in through args. 83 // It's a helper function currently used by the uniter and by machineactions 84 // It needs an actionFn that can fetch an action from state using it's id, that's usually created by AuthAndActionFromTagFn 85 func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults { 86 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))} 87 88 for i, arg := range args.Entities { 89 action, err := actionFn(arg.Tag) 90 if err != nil { 91 results.Results[i].Error = ServerError(err) 92 continue 93 } 94 95 _, err = action.Begin() 96 if err != nil { 97 results.Results[i].Error = ServerError(err) 98 continue 99 } 100 } 101 102 return results 103 } 104 105 // FinishActions saves the result of a completed Action. 106 // It's a helper function currently used by the uniter and by machineactions 107 // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn 108 func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults { 109 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))} 110 111 for i, arg := range args.Results { 112 action, err := actionFn(arg.ActionTag) 113 if err != nil { 114 results.Results[i].Error = ServerError(err) 115 continue 116 } 117 actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg) 118 if err != nil { 119 results.Results[i].Error = ServerError(err) 120 continue 121 } 122 123 _, err = action.Finish(actionResults) 124 if err != nil { 125 results.Results[i].Error = ServerError(err) 126 continue 127 } 128 } 129 130 return results 131 } 132 133 // Actions returns the Actions by Tags passed in and ensures that the receiver asking for 134 // them is the same one that has the action. 135 // It's a helper function currently used by the uniter and by machineactions. 136 // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn 137 func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults { 138 results := params.ActionResults{ 139 Results: make([]params.ActionResult, len(args.Entities)), 140 } 141 142 for i, arg := range args.Entities { 143 action, err := actionFn(arg.Tag) 144 if err != nil { 145 results.Results[i].Error = ServerError(err) 146 continue 147 } 148 if action.Status() != state.ActionPending { 149 results.Results[i].Error = ServerError(ErrActionNotAvailable) 150 continue 151 } 152 results.Results[i].Action = ¶ms.Action{ 153 Name: action.Name(), 154 Parameters: action.Parameters(), 155 } 156 } 157 158 return results 159 } 160 161 // WatchOneActionReceiverNotifications to create a watcher for one receiver. 162 // It needs a tagToActionReceiver function and a registerFunc to register 163 // resources. 164 // It's a helper function currently used by the uniter and by machineactions 165 func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r Resource) string) func(names.Tag) (params.StringsWatchResult, error) { 166 return func(tag names.Tag) (params.StringsWatchResult, error) { 167 nothing := params.StringsWatchResult{} 168 receiver, err := tagToActionReceiver(tag.String()) 169 if err != nil { 170 return nothing, err 171 } 172 watch := receiver.WatchActionNotifications() 173 174 if changes, ok := <-watch.Changes(); ok { 175 return params.StringsWatchResult{ 176 StringsWatcherId: registerFunc(watch), 177 Changes: changes, 178 }, nil 179 } 180 return nothing, watcher.EnsureErr(watch) 181 } 182 } 183 184 // WatchActionNotifications returns a StringsWatcher for observing incoming actions towards an actionreceiver. 185 // It's a helper function currently used by the uniter and by machineactions 186 // canAccess is passed in by the respective caller to provide authorization. 187 // watchOne is usually a function created by WatchOneActionReceiverNotifications 188 func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults { 189 result := params.StringsWatchResults{ 190 Results: make([]params.StringsWatchResult, len(args.Entities)), 191 } 192 193 for i, entity := range args.Entities { 194 tag, err := names.ActionReceiverFromTag(entity.Tag) 195 if err != nil { 196 result.Results[i].Error = ServerError(err) 197 continue 198 } 199 err = ErrPerm 200 if canAccess(tag) { 201 result.Results[i], err = watchOne(tag) 202 } 203 result.Results[i].Error = ServerError(err) 204 } 205 206 return result 207 } 208 209 // GetActionsFn declares the function type that returns a slice of 210 // state.Action and error, used to curry specific list functions. 211 type GetActionsFn func() ([]state.Action, error) 212 213 // ConvertActions takes a generic getActionsFn to obtain a slice 214 // of state.Action and then converts them to the API slice of 215 // params.ActionResult. 216 func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) { 217 items := []params.ActionResult{} 218 actions, err := fn() 219 if err != nil { 220 return items, err 221 } 222 for _, action := range actions { 223 if action == nil { 224 continue 225 } 226 items = append(items, MakeActionResult(ar.Tag(), action)) 227 } 228 return items, nil 229 } 230 231 // MakeActionResult does the actual type conversion from state.Action 232 // to params.ActionResult. 233 func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult { 234 output, message := action.Results() 235 return params.ActionResult{ 236 Action: ¶ms.Action{ 237 Receiver: actionReceiverTag.String(), 238 Tag: action.ActionTag().String(), 239 Name: action.Name(), 240 Parameters: action.Parameters(), 241 }, 242 Status: string(action.Status()), 243 Message: message, 244 Output: output, 245 Enqueued: action.Enqueued(), 246 Started: action.Started(), 247 Completed: action.Completed(), 248 } 249 }