Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Go client library #141

Merged
merged 7 commits into from Mar 21, 2018
Merged

Provide Go client library #141

merged 7 commits into from Mar 21, 2018

Conversation

minwoox
Copy link
Member

@minwoox minwoox commented Mar 5, 2018

Motivation:
If we provide Go client library, more people can adapt Central Dogma.

Modifications:

  • Add Go client library

Result:

  • More customers

Todos:

  • Repackage the original sources
  • Adapt CLI using this library
  • Add the watcher

@minwoox minwoox force-pushed the go_library branch 3 times, most recently from a2f9202 to 6afa71c Compare March 7, 2018 10:32
@trustin trustin added this to the 0.22.0 milestone Mar 8, 2018
_ = &Change{Path: "/a.json", Type: "APPLY_JSON_PATCH", Content: content}

// TODO(minwoox) fix this to work
//if !reflect.DeepEqual(entry, want) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will you fix this in this PR or?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed~!!

{Path: "/b.txt", Type: "APPLY_TEXT_PATCH", Content: "--- /b.txt\n+++ /b.txt\n@@ -1,1 +1,1 @@\n-foo\n+bar"}}

// TODO(minwoox) fix this to work
//if !reflect.DeepEqual(changes, want) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

DefaultPort = 36462
DefaultPathPrefix = "api/v1/"
PathPrefixV0 = "api/v0/"
DefaultBaseURL = "http://localhost:36462/"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Just curious - can we construct this from DefaultScheme, DefaultHostName and DefaultPort?
  • Perhaps we could make all constants here except DefaultPort hidden from the public API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Thank you ~!
I thought I couldn't make it because strconv.Itoa(port) didn't work. But I found that I can simply do string(port).

return NewClient(baseURL, config.Client(context.Background()))
}

func NewClient(baseURL string, client *http.Client) (*Client, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which would be used more frequently? If it's NewClientWithCredential, we could rename NewClientWithCredential to NewClient and NewClient to NewClientWithHTTPClient?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Thank you~!

defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return false, fmt.Errorf("authenticaion check is failed (status: %s)", res.Status)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authentication failed (status: %s)


private final String refreshToken;

//TODO(minwoox) add scope when it needs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add scope if needed. ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a part of the response, there is a scope.

scope (optional) If the scope the user granted is identical to the scope the
app requested, this parameter is optional. If the granted scope is different
from the requested scope, such as if the user modified the scope, then
this parameter is required.

in here
But I don't think we need it right now, so just left a comment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually meant to update the comment to 'Add scope if needed.' rather than asking to add it in this PR. :-D

Copy link
Member Author

@minwoox minwoox Mar 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops~ got it.~! Thank you!


@JsonProperty("expires_in")
public long expiresIn() {
return expiresIn;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should use expires_at so that a client knows when exactly a token will be expired. Or is this a part of OAUTH2 specification?

Copy link
Member Author

@minwoox minwoox Mar 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is a part of the spec. :-)

return new UsernamePasswordToken(username, password);
}

return throwResponseException("invalid_request", "request was missing the username or password.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request must contain username and password.?

* {@link JsonNode}.
*/
public static HttpResponseException newHttpResponseException(HttpStatus status, JsonNode node) {
return HttpResponseException.of(newResponseWithJson(status, node));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should provide a way to specify an HttpStatus with a response object in Armeria?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was think about adding methods whose signatures are same as HttpResponse.of(...), but I was not sure which methods I needed to add to HttpResponseException, because there are a lot. :-)
To handle above case, I need a method like this.

public static HttpResponseException of(HttpStatus status, MediaType mediaType, byte[] content) {
...
}
or 
public static HttpResponseException of(HttpHeaders headers, byte[] content) {
...
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we should add those methods so that a user can send an error response conveniently. However, I guess a user will find it weird to throw an exception when sending a successful response.

Copy link
Member Author

@minwoox minwoox Mar 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. Do we need to check the status here if we add those methods?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess not. You know, some peculiar API uses 200 OK for error responses ;-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we talked about it in this morning. lol

HttpHeaderNames.AUTHORIZATION, "basic " + encoder.encodeToString(
(USERNAME + ':' + PASSWORD).getBytes(StandardCharsets.US_ASCII)),
HttpHeaderNames.CONTENT_TYPE, MediaType.FORM_DATA.toString()),
"grant_type=client_credentials",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be password?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the Golang oauth2 client, I thought it was "grant_type=client_credentials".
But I checked again the spec, and it actually does not require the grant_type.
So I will remove it :-)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I deleted this review comment but seems like I forgot. :-$

@minwoox minwoox force-pushed the go_library branch 5 times, most recently from 47c9d25 to 8c4065f Compare March 8, 2018 11:06
@trustin trustin modified the milestones: 0.22.0, 0.23.0 Mar 9, 2018
@minwoox minwoox force-pushed the go_library branch 3 times, most recently from ffb69f3 to 1903c0b Compare March 9, 2018 09:24
@codecov-io
Copy link

codecov-io commented Mar 9, 2018

Codecov Report

Merging #141 into master will decrease coverage by 0.01%.
The diff coverage is 87.09%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master    #141      +/-   ##
=========================================
- Coverage   63.51%   63.5%   -0.02%     
=========================================
  Files         273     273              
  Lines        9451    9474      +23     
  Branches     1065    1063       -2     
=========================================
+ Hits         6003    6016      +13     
- Misses       2825    2834       +9     
- Partials      623     624       +1
Impacted Files Coverage Δ
...raldogma/server/internal/api/ContentServiceV1.java 91.08% <100%> (-1.7%) ⬇️
...centraldogma/server/internal/api/DtoConverter.java 96.15% <100%> (+4.84%) ⬆️
...corp/centraldogma/internal/api/v1/AccessToken.java 100% <100%> (ø)
...inecorp/centraldogma/internal/api/v1/EntryDto.java 66.66% <100%> (-3.04%) ⬇️
...com/linecorp/centraldogma/server/CentralDogma.java 72.13% <100%> (-0.63%) ⬇️
...er/internal/admin/authentication/LoginService.java 73.01% <68.75%> (-4.26%) ⬇️
.../centraldogma/server/internal/api/HttpApiUtil.java 78.57% <83.33%> (-0.6%) ⬇️
...necorp/centraldogma/internal/api/v1/CommitDto.java 63.15% <88.88%> (-1.55%) ⬇️
...ternal/api/converter/HttpApiResponseConverter.java 63.63% <0%> (-9.1%) ⬇️
... and 4 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bc249fc...0f5485d. Read the comment docs.

PathPrefixV0 = "api/v0/"
DefaultBaseURL = DefaultScheme + "://" + DefaultHostName + ":36462/"

defaultPort = 36462
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually meant making all constants private, except DefaultPort. :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops~~ let me fix this~

} else {
buf = new(bytes.Buffer)
enc := json.NewEncoder(buf)
//enc.SetEscapeHTML(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is this necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's necessary. Forgot to bring it back. Thank you!

@@ -60,14 +71,13 @@ public LoginService(CentralDogmaSecurityManager securityManager, CommandExecutor
protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) throws Exception {
final CompletableFuture<HttpResponse> future = new CompletableFuture<>();
req.aggregate().thenAccept(aMsg -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we can use AggregatedHttpMessage on the parameter list. So, how about changing HttpRequest to AggregatedHttpMessage and removing the aggregation from here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Thank you~!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops I realized that this class inherits the AbstractHttpService and the method overrides, so I can not change :-)

@minwoox
Copy link
Member Author

minwoox commented Mar 13, 2018

As described in #59, we are going to remove Thrift :)

}

func (con *contentService) getFiles(ctx context.Context,
projectName, repoName, revision, pathPattern string) ([]*Entry, *http.Response, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any chance that user would care about http.Response? Adding response will make the API couple to http package.

Copy link
Member Author

@minwoox minwoox Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question~! This is the one that I barely made a decision.

Some go client libraries such as go-github that I referred, returns the *http.Response.
I was thinking that a user may want to take a look at the response when something unexpected happens.
And I made the constructor for client which takes the *http.Client as an argument. So I though t is was OK. If the user does not want it, the user can just ignore it with _.
But I think I can take that out as well. :-)
Do you think it is too tight with the http package?

Copy link
Contributor

@huydx huydx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave few nits comments.
One more question: as a user I want something like Watcher in java client, is it possible without Thrift or we need to make some REST API for polling?


if len(revision) != 0 {
u += "?revision=" + revision
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about use url#Values pacakge to format

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to do that, parse the string as url.URL and change it back to string when it invokes newRequest().
That's why I was reluctant to use the package, but probably it's better use it. :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I totally misunderstood the API. Thank you for letting me know that.

u := fmt.Sprintf("%vprojects/%v/repos/%v/commits/%v", defaultPathPrefix, projectName, repoName, from)

urlQuery := ""
if len(pathPattern) != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto (using url#Values instead)


type Push struct {
CommitMessage *CommitMessage `json:"commitMessage"`
Changes []Change `json:"changes"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about use []*Change instead for uniformity

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea.
Is there any convenient way to convert the []Change to []*Change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraird not :(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just changed the method parameter to take []*Change.
I realized that I don't have to take the argument as varargs because I need at least one Change.
Thank you~!


// CreateProject creates a project.
func (c *Client) CreateProject(ctx context.Context, name string) (*Project, *http.Response, error) {
return c.project.create(ctx, name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what is better, but compare between below 2 interfaces, I prefer former one. HDYT?

// expose service object
dogma.Project.Create(...)

vs

// current approach
dogma.CreateProject(...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way is fine to me. Just wanted to have same method name as in Java client library :-)

Copy link
Contributor

@huydx huydx Mar 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same method name as in Java client

this sound reasonable, so let's keep compability for emthod naming rule

import "golang.org/x/oauth2/clientcredentials"

func Client() {
config := clientcredentials.Config{ClientID: clientID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we support oauth only and not basic auth?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This authClient actually uses basic auth when it requests a token. But I think I need to change the example because this can confuse :-)

@minwoox
Copy link
Member Author

minwoox commented Mar 14, 2018

One more question: as a user I want something like Watcher in java client, is it possible without Thrift or we need to make some REST API for polling?

I am planning to make the Watcher in different PR. I'm still trying to figure out which is the best way to implement it. :-)

minwoox and others added 5 commits March 15, 2018 16:57
Motivation:
If we provide Go client library, more people can adapt Central Dogma.

Modifications:
- Add Go client library

Result:
- More customers

Todos:
- Repackage the original sources
- Adapt CLI using this library
- Adapt OAuth2 client
- Add the watcher
@minwoox
Copy link
Member Author

minwoox commented Mar 15, 2018

@huydx , Fixed the issues, please take a look.

// QueryType can be "identity" or "json_path". "identity" is used to retrieve the content as it is.
// "json_path" applies a series of JSON path to the content.
// See https://github.com/json-path/JsonPath/blob/master/README.md
QueryType string
Copy link
Contributor

@huydx huydx Mar 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I missed that at first round, do you think string is better than iota?
Using iota may need some more boilerplate code to generate equivalent string, but for users it's easier to use because they know that what type of QueryType is supported, and prevent typo as well.
In case you find it using iota will add too much boiler plate, how about define some constant for those strings?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iota should be much better~! Thank you~!

// Entry represents an entry in the repository.
type Entry struct {
Path string `json:"path"`
Type string `json:"type"` // can be JSON, TEXT or DIRECTORY
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with above comment about iota

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field is used to store the JSON response. If I use this as iota, I need a custom deserializer to convert the sting to corresponding iota number.
Can I implement the custom deserializer or is it better to use predefined string constants?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a user, I would prefer iota constant. IMHO it's fine to add deserialize function like func (o *Type) UnmarshalJSON(b []byte) error {}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I will try the way you suggest. Thang you!

Path string `json:"path"`

// can be UPSERT_JSON, UPSERT_TEXT, REMOVE, RENAME, APPLY_JSON_PATCH or APPLY_TEXT_PATCH
Type string `json:"type"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with above comment about iota

return newClientWithHTTPClient(normalizedURL.String(), config.Client(context.Background()))
}

// newClientWithHTTPClient returns a Central Dogma client withe the specified baseURL and client.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo withe

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks~!

}

// NewClient returns a Central Dogma client with the specified baseURL, clientID and clientSecret.
func NewClient(baseURL string, clientID, clientSecret string) (*Client, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about 2 separate constructors like

NewClient(baseUrl string) *Client, error {
}

NewSecureClient(baseURL string, clientID, clientSecret string) *Client, error {
}

And in inner implement we will check if clientID and clientSecret is empty, we will use secure API and opposite?
Not sure about the spec of CD server right now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having just one constructor is fine for now so that the client does not have to handle the authentication by itself. Let's add it when we think it really needs :-)

@huydx
Copy link
Contributor

huydx commented Mar 15, 2018

@minwoox added second round comments

@minwoox
Copy link
Member Author

minwoox commented Mar 16, 2018

@huydx Please take a look again~. I was trying to use some library like enumer to convert the iota values, but it seems like I coudln't not have different names for the enum. For example, we use UPSERT_JSON in the Central Dogma server, but
If I name the values as golang style like UpsertJSON, I cannot convert the value using that library.
So, I decide to just declare maps and arrays to convert that.

@huydx
Copy link
Contributor

huydx commented Mar 16, 2018

LGTM! Should I merge it @minwoox

@minwoox
Copy link
Member Author

minwoox commented Mar 16, 2018

Thanks @huydx. Let's wait a little bit in case @trustin, @hyangtack want to leave a comment.

Copy link
Collaborator

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM

@minwoox
Copy link
Member Author

minwoox commented Mar 19, 2018

I misunderstood the Oauth2 spec. I removed the client credentials part. :-)

// NewClient returns a Central Dogma client with the specified baseURL, clientID and clientSecret.
func NewClient(baseURL string, clientID, clientSecret string) (*Client, error) {
// NewClient returns a Central Dogma client with the specified baseURL, username and password.
func NewClient(baseURL string, username, password string) (*Client, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Just curious. username -> username string? Is the type unnecessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops~! Thank you for pointing it out. (baseURL, username, password string) is correct because all of the three parameters are string, I can just put one string at the end. (does not have to put type for every parameter, if they are same)

@minwoox minwoox merged commit df35d84 into line:master Mar 21, 2018
@minwoox
Copy link
Member Author

minwoox commented Mar 21, 2018

Thanks reviews~!

@minwoox minwoox deleted the go_library branch March 21, 2018 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants