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

dotnet cli #2636

Open
sebastienros opened this Issue Nov 7, 2018 · 14 comments

Comments

Projects
None yet
4 participants
@sebastienros
Member

sebastienros commented Nov 7, 2018

We should have a dotnet global tool as the cli. This tool should work using REST APIs only. This means the Orchard Core application would need to be started already, which is normal and already supported by the dotnet sdk (we can build and start the application using the sdk, so anything would be done from the command line and from scripts).

The only issue is how would the CLI be extended by specific modules. After all some features will only be available with some features. And also authorization: should it be able to work remotely, how do we ensure the client has access to the instance (private key?).

Here is the Drupla CLI for reference: https://github.com/drush-ops/drush

@jrestall

This comment has been minimized.

Contributor

jrestall commented Nov 7, 2018

I like the simplicity of personal access tokens, but I also wonder how simple the OpenId flow could be or if it could be used behind the personal access tokens. @PinpointTownes

https://www.contentful.com/r/knowledgebase/personal-access-tokens/
https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/

@sebastienros

This comment has been minimized.

Member

sebastienros commented Nov 7, 2018

Funny you suggest it, I actually talked with @PinpointTownes about simpler ways to authenticate in client apps, and he suggested PATs. And also that it should have it's custom module. So I assume we could have a page to create, list, revoke any PAT. And an authentication handler that would look for these in a header and impersonate the user.

@PinpointTownes

This comment has been minimized.

Contributor

PinpointTownes commented Nov 7, 2018

Funny you suggest it, I actually talked with @PinpointTownes about simpler ways to authenticate in client apps, and he suggested PATs.

Well, I mentioned PATs (because it was the option corresponding to your criteria when we chatted), but I didn't say I suggested using them 😅

Personally, I'd likely use the authorization code flow, by launching the default browser and running a temporary HTTP server to catch the callback authorization response or registering a custom system URI scheme (probably less handy for a CLI tooling).

The device code flow might also be a good option. If you want to see it in action, give the Azure CLI tooling a try. The downside is that it's still not officially standardized (and thus not supported by OpenIddict).

@jrestall

This comment has been minimized.

Contributor

jrestall commented Nov 7, 2018

The Contentful CLI tooling uses the authorization code flow that opens a browser window and has you authenticate the app. I'm a little biased towards personal access tokens though since I need them to configure with Netlify to call back into Orchard when rebuilding my site.

https://github.com/contentful/contentful-cli

The only issue is how would the CLI be extended by specific modules. After all some features will only be available with some features.

So here's a crazy idea. Let's go all in on GraphQL. Since we can then do an introspection on the schema and dynamically build the list of supported CLI commands from the mutations that the modules provide.

  • CLI tooling would be generic so we don't need to constantly update it when adding new features.
  • CLI commands support exactly the enabled features on the instance of Orchard Core that it is pointed to.
  • GraphQL has so many benefits over REST. If we are providing querying over GraphQL, we shouldn't complicate things by also having JSON API's.
  • Users can use GraphiQL to discover the possible commands/mutations from within OC, rather than us providing docs or swagger.

So this is not something that has been done before and I'm only familiar with how we might do it as an npm package. Is dotnet global tool a must?

Here's some code that dynamically introspects a given schema from a remote URL and makes it available to call easily via the Delegate class. We would need to add in the schema to yargs code to be able to run graphql mutations as commands, but I think it should be doable.

(Ignore that it's an express app - was quickest way for me to test that the code worked)

const fetch = require('node-fetch')
const express = require('express');
const { Delegate } = require('graphql-binding/dist/Delegate')
const { HttpLink } = require('apollo-link-http')
const { setContext } = require('apollo-link-context');
const { introspectSchema, makeRemoteExecutableSchema, WrapQuery } = require('graphql-tools')

async function getExecutableSchema(link) {
  const schema = await introspectSchema(link);

  const executableSchema = makeRemoteExecutableSchema({
    schema,
    link,
  });

  return executableSchema
}

// Create the remote schema
const http = new HttpLink({ uri: 'http://orchardcoredomain/graphql', fetch });

const link = setContext((request, previousContext) => ({
  headers: {
    'Authorization': `Bearer 12345`,
  }
})).concat(http);

(async () => {
  const schema = await getExecutableSchema(link);
  
  // Create the `before` function
  const before = () => console.log(`Sending a request to the GraphQL API ...`)

  const delegate = new Delegate({ schema, before })

  const app = express();

  app.get('/', (req, res) => {

  // Testing Delegate
  const args = { name: `My Tenant`, recipeName: `Blog` }
  
  delegate.delegate(
    `mutation`,
    `createTenant`,
    args
  )
    .then(createTenantResult => res.send(JSON.stringify(createTenantResult)).sendStatus(200))
    .catch((e) => { console.error(e) })
  })
  
  const listener = app.listen(process.env.PORT, function() {
    console.log('Your app is listening on port ' + listener.address().port);
  });
})();
@sebastienros

This comment has been minimized.

Member

sebastienros commented Nov 7, 2018

I totally agree with all the things you said.

@PinpointTownes

This comment has been minimized.

Contributor

PinpointTownes commented Nov 7, 2018

The Contentful CLI tooling uses the authorization code flow that opens a browser window and has you authenticate the app.

Good to know! I'm not really surprised as it's certainly the most convenient approach (tho' it requires having a machine with a GUI, it's easier to use than the device code flow as you don't even have to copy/paste some user codes).

If we go with this approach, we can make it as simple as clicking on a "enable remote management" button in the admin UI (which would create an OpenID Connect client registration and some Orchard-specific scopes for the CLI tooling).

@sebastienros

This comment has been minimized.

Member

sebastienros commented Nov 7, 2018

How does it work when we use a CLI like in Docker, could we use a different flow, and type the creds in the console app instead?

@PinpointTownes

This comment has been minimized.

Contributor

PinpointTownes commented Nov 7, 2018

The password flow could be used for that, but if we want the "state of the art" option for headless machines (e.g a CLI executed via SSH or in a Docker container), we'll want to use the device code flow once it's standardized. It's what the Azure CLI tooling uses.

@PinpointTownes

This comment has been minimized.

Contributor

PinpointTownes commented Nov 7, 2018

image

@sebastienros

This comment has been minimized.

Member

sebastienros commented Nov 7, 2018

Interesting. Then how long is the authentication valid once the code has been registered on the web app? What does it store locally?

@PinpointTownes

This comment has been minimized.

Contributor

PinpointTownes commented Nov 7, 2018

Then how long is the authentication valid once the code has been registered on the web app?

It's completely implementation-specific and depends on the lifespan of the access token returned by the authorization server. If a refresh token is returned, the CLI can retrieve new access tokens so nothing prevents us from having a never expiring authentication logic.

What does it store locally?

It can store the access token/refresh token or keep everything in-memory, depending on your choices.

@sebastienros sebastienros added this to the rc milestone Nov 8, 2018

@jrestall

This comment has been minimized.

Contributor

jrestall commented Nov 10, 2018

I've started creating a node based CLI since the library support makes it a lot easier.

It's here currently but we can move it if you think it should be part of Orchard Core.
https://github.com/jrestall/orchardcore-cli

It will generate a CLI based on the graphql endpoint you give it, so when pointing to http://api.githunt.com/graphql it will generate the following options:

orchardcore -h

Welcome to Orchard Core CLI

Usage: orchardcore <cmd> [args]
Commands:
  orchardcore config                     Sets the CLI config
  orchardcore submitRepository           Submit a new repository, returns the
  <repoFullName>                            new submission
  orchardcore vote <repoFullName>        Vote on a repository submission,
  <type>                                    returns the submission that was
                                            voted on
  orchardcore submitComment              Comment on a repository, returns the
  <repoFullName> <commentContent>           new comment
Options:
  -h, --help     Show help                                             [boolean]
  -v, --version  Show version number                                   [boolean]

For more information, find the documentation at https://orchardcore.readthedocs.io/

It can show detailed documentation on specific mutations also:

orchardcore vote -h

Welcome to Orchard Core CLI

orchardcore.js vote <repoFullName> <type>

Vote on a repository submission, returns the submission that was voted on
Positionals:
  repoFullName  The full repository name from GitHub, e.g.
                "apollostack/GitHunt-API"                    [string] [required]
  type          The type of vote - UP, DOWN, or CANCEL
                           [string] [required] [choices: "UP", "DOWN", "CANCEL"]
Options:
  -h, --help     Show help                                             [boolean]
  -v, --version  Show version number                                   [boolean]
@jrestall

This comment has been minimized.

Contributor

jrestall commented Nov 11, 2018

I've added dotnet global tool support to the orchard-cli by using NodeServices. So now we support both nodejs and dotnet.

https://github.com/jrestall/orchardcore-cli/blob/master/Program.cs

To install & test:

dotnet pack
dotnet tool install --global --add-source ./nupkg orchardcore-cli

orchardcore config --host "https://api.githunt.com/graphql"
orchardcore -help
orchardcore vote -help
orchardcore vote myRepoName UP
@rserj

This comment has been minimized.

Contributor

rserj commented Nov 16, 2018

It looks like a good idea, as I understand Orchard instance must have
GraphQL and OpenId modules enabled before using Orchard CLI.
Probably we can prepare some recipe which will provide "Orchard core CLI support" by enabling all necessary modules and probably adding some default ClientIds, Client Secrets for b2b and b2c scenarios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment